/**
 * Handles the customer journey interactions. Requires the following modules to be present on the page:
 * @param {Object} customerJourney - this particular module
 * @param {Object} links - the Avaya links.js file
 * @param {Object} avayaGlobal - utility functions, e.g. some String and JSON validation.
 */
(function (customerJourney, links, avayaGlobal) {
    "use strict";

    // UI module - passed in at setup
    var customerJourneyUI;

    var journeyElement = "web";

    var customerId = "";

    // also referred to as the workRequestId
    var contextId = null;

    // the topic can only contain the values [a-zA-Z0-9]. 
    // Groups of these characters may be split by the charcters ~(tilda), _(underscore) or .(period/full stop),
    // as long as the full string does not start or end with them.
    var topic = "Default_Topic";

    var routingStrategy = "Most Idle";

    // non-Oceana data. This will not be used in routing
    var nonRoutingData = {};

    // a default ServiceMap. Allows up to 6 services
    var serviceMap = {
        "1": {
            "attributes": {},
            "priority": "5"
        }
    };

    /**
     * ======================================
     * REQUEST FUNCTIONS
     * ======================================
     */

    /**
     * General-purpose function to handle the low level REST calls.
     * @param {String} url
     * @param {String} method - a HTTP method (e.g. GET, POST, PUT)
     * @param {Function} stateChangeFunction - a function that will be called when the readyState changes
     * @param {String} dataString - a JSON string containing the data. If not required, leave blank
     */
    function makeRestCall(url, method, stateChangeFunction, dataString) {
        var request = new XMLHttpRequest();
        request.open(method, url);
        request.setRequestHeader("Content-Type", "application/json");
        request.addEventListener("readystatechange", stateChangeFunction);
        request.send(dataString);
    }

    function createContext() {
        var json = {
            "data": nonRoutingData,
            "topic": topic,
            "schema": {
                "ServiceMap": serviceMap,
                "CustomerId": customerId,
                "Strategy": routingStrategy
            },
            "groupId": customerId,
            "contextId": contextId,
            "persistToEDM": true
        };
        var url = links.getOceanaCoreDataServicesUrl() + "context/schema?journeyElement=" + journeyElement;
        makeRestCall(url, "POST", createContextResponse, JSON.stringify(json));
    }

    function requestExistingContext() {
        var url = links.getOceanaCoreDataServicesUrl() + "context/" + contextId;
        makeRestCall(url, "GET", requestContextResponse, "");
    }

    function updateServiceMap() {
        var reqJson = {
            "data": nonRoutingData,
            "ServiceMap": serviceMap
        };
        console.log(reqJson);
        var url = links.getOceanaCoreDataServicesUrl() + 'update/serviceMap/' + contextId +
                '?journeyElement=' + journeyElement;
        makeRestCall(url, "PUT", genericUpdateResponse, JSON.stringify(reqJson));
    }

    function updateTopic(newTopic) {
        topic = sanitiseTopic(newTopic);
        var url = links.getCustomerManagementUrl() + "/oceana/data/update/topic/" + contextId + "?topic=" + topic;
        makeRestCall(url, "PUT", genericUpdateResponse, "");
    }

    /**
     * ======================================
     * RESPONSE FUNCTIONS
     * ======================================
     *
     * The genericUpdateResponse function is a general purpose one for when we simply want to confirm that an update request worked.
     * The createContextResponse function is specifically for requesting a new context.
     * The requestContextResponse function is used specifically when requesting an already existing context.
     * The parseCustomerIdResponse is used specifically when requesting a customer ID.
     * 
     * Declared as variables to avoid potential strict mode violations. Declaring using "function" results in a strict mode violation,
     * as the function has no owning object. See: https://stackoverflow.com/questions/6300937/strict-violation-using-this-keyword-and-revealing-module-pattern
     *
     */

    var genericUpdateResponse = function () {
        if (this.readyState === 4) {
            if (this.status === 0) {
                console.warn("Customer Journey: returned 0 when updating data. This may be caused by CORS issues or by blocking JavaScript");
            } else if (this.status === 200) {
                console.info("CustomerJourney: Updating data succeeded with status HTTP" + this.status + ' - ' + this.responseText);
            } else {
                console.warn('CustomerJourney: Updating data returned HTTP ' + this.status + ' - ' + this.responseText);
            }
        }
    };

    var createContextResponse = function () {
        if (this.readyState === 4) {
            if (this.status === 200) {
                var wrid = JSON.parse(this.response).data.contextId;
                console.info("CustomerJourney: Context created successfully. Context ID/WorkRequestId is : " + wrid);
                contextId = wrid;
                // save so we can use this over multiple pages
                sessionStorage.setItem("contextId", wrid);
            } else {
                console.warn('CustomerJourney: Creating Context returned HTTP ' + this.status + ' - ' + this.responseText);
            }
        }
    };

    var requestContextResponse = function () {
        if (this.readyState === 4) {
            if (this.status === 0) {
                console.warn("Customer Journey: returned 0 when requesting a context. This may be caused by CORS issues or by blocking JavaScript");
            } else if (this.status === 404) {
                console.warn("CustomerJourney: requesting a previous context returned HTTP 404. This context does not exist.");
            } else if (this.status === 200) {
                console.log(this.response);
                var json = JSON.parse(this.response);
                nonRoutingData = json.data;
                serviceMap = json.schema.serviceMap;
            }
        }
    };

    var parseCustomerIdResponse = function () {
        var response = this.response;
        if (this.readyState === 4) {
            console.log(response);
            var json = JSON.parse(response);
            if (this.status === 200) {
                console.log(json);
                customerId = json.customerId;
                customerJourneyUI.setSubscribeStatus("You have subscribed. Your customer ID is" + customerId);
                customerJourneyUI.closeSubscribePanel();
                sessionStorage.setItem("customerId", customerId);
            } else {
                var errorString = json.errors[0].type + ": " + json.errors[0].message;
                console.error("CustomerJourney: ", errorString);
                customerJourneyUI.setSubscribeStatus("Unable to subscribe. Please try again later");
            }
        }
    };

    /**
     * Remove invalid characters from the topic prior to sending. Will be validated on the server.
     * @param {String} topic
     * @returns {String} the sanitised string
     */
    function sanitiseTopic(topic) {
        return topic.replace(/[ #?@$\s]/g, "");
    }

    /**
     * ======================================
     * PUBLIC FUNCTIONS
     * ======================================
     *
     * These are outward facing, and may be called from your own code. The expected flow is as follows:
     * customerJourney.requestCustomerId("email", yourEmailHere);
     * customerJourney.setTopic("My_Example_Topic");
     * customerJourney.addAttribute("Language", "German", 1); // the third parameter is optional. You can repeat this line as often as you like.
     * customerJourney.setup();
     * customerJourney.addAttribute("Service", "Sales", 1); // this example assumes that you are waiting until the context has been created
     * customerJourney.updateSchema(); // actually update the schema.
     */


    /**
     * Request the customer ID.
     * @param {String} paramName - Can be email, phone or crmId
     * @param {String} paramValue - the Customer's email address, phone number or CRM ID
     */
    customerJourney.requestCustomerId = function (paramName, paramValue) {
        var url = links.getCustomerManagementUrl() + "/rest/customers/customerId?" + paramName + "=" + paramValue;
        makeRestCall(url, "GET", parseCustomerIdResponse, "");
    };

    /**
     * Request the Customer ID using social media details. Not present in prior releases.
     * @param {String} socialPlatform - the platform of the customer's specified account, e.g. Facebook
     * @param {String} socialHandle - the customer's social media handle
     */
    customerJourney.requestCustomerIdSocialMedia = function (socialPlatform, socialHandle) {
        var url = links.getCustomerManagementUrl() + "/rest/customers/customerId?socialPlatform=" + socialPlatform + "&socialHandle=" + socialHandle;
        makeRestCall(url, "GET", parseCustomerIdResponse, "");
    };

    /**
     * Set the UI module to avoid circular dependencies
     * @param {Object} cjui
     */
    customerJourney.setUI = function (cjui) {
        customerJourneyUI = cjui;
    };

    /**
     * General setup function. If we don't have a context ID (AKA work request ID) in sessionStorage,
     * create a new context. Otherwise, request the existing one.
     */
    customerJourney.setup = function () {
        var wrid = sessionStorage.getItem("contextId");
        if (wrid === null) {
            createContext();
        } else {
            console.log("Loaded context ID:", wrid, ". Requesting existing context");
            customerId = sessionStorage.getItem("customerId");
            contextId = wrid;
            requestExistingContext();
        }
    };

    /**
     * Set the topic for this contact.
     * @param {String} newTopic - new topic value. Must not be empty.
     */
    customerJourney.setTopic = function (newTopic) {
        if (!avayaGlobal.validateString(newTopic)) {
            console.error("Customer Journey: The topic must not be an empty String!");
            return;
        }

        // if we don't have a context ID, just set the topic here before creating the context.
        // otherwise, make a request to update it.
        if (contextId === null) {
            topic = sanitiseTopic(newTopic);
        } else {
            updateTopic(newTopic);
        }
    };

    /**
     * Overloaded version to allow a single attribute for backwards compatibility.
     * @param {String} newAttribute
     * @param {Number} serviceMapId
     */
    customerJourney.addAttribute = function (newAttribute, serviceMapId) {
        if (!avayaGlobal.validateString(newAttribute, true)) {
            console.error(newAttribute, "is not a valid attribute!");
            return;
        }
        var tmp = newAttribute.split(".");
        customerJourney.addRoutingAttribute(tmp[0], tmp[1], serviceMapId);
    };

    /**
     * Add a new routing attribute. Does *NOT* update the service map in OCDS; see the updateSchema function for that.
     * @param {String} attributeKey - identifies the attribute group (e.g. Language). Must not be empty.
     * @param {String} attributeValue - the new attribute value (e.g. Gaelic). Should not be empty.
     * @param {Number} serviceMapId - identifies the ServiceMap that this should take. Optional, and defaults to 1
     */
    customerJourney.addRoutingAttribute = function (attributeKey, attributeValue, serviceMapId) {
        if (!avayaGlobal.validateString(attributeKey, true) || !avayaGlobal.validateString(attributeValue, true)) {
            console.error("CustomerJourney: attributes cannot be empty!");
            return;
        }

        if (!avayaGlobal.validateNumber(serviceMapId)) {
            console.warn("CustomerJourney: invalid ServiceMap ID (" + serviceMapId + "). Setting to default (1)");
            serviceMapId = 1;
        }

        // Check if this attribute already exists
        var serviceId = serviceMapId.toString();
        var attributes = serviceMap[serviceId].attributes;
        var array = attributes[attributeKey];
        if (array === undefined) {
            array = [];
            attributes[attributeKey] = array;
        }

        var index = array.indexOf(attributeValue);
        if (index === -1) {
            array.push(attributeValue);
        }
        serviceMap[serviceId].attributes[attributeKey] = array;
        console.log("Added new attribute. Service map now:", serviceMap[serviceId]);
    };

    /**
     * Remove a new routing attribute. Does *NOT* update the service map in OCDS; see the updateSchema function for that.
     * @param {String} attributeKey - identifies the attribute group (e.g. Language). Must not be empty.
     * @param {String} attributeValue - the attribute value to remove (e.g. Gaelic). Must not be empty.
     * @param {Number} serviceMapId - identifies the ServiceMap that this should take. Defaults to 1
     */
    customerJourney.removeAttribute = function (attributeKey, attributeValue, serviceMapId) {
        if (!avayaGlobal.validateString(attributeKey) || !avayaGlobal.validateString(attributeValue)) {
            console.error("CustomerJourney: attributes cannot be empty!");
            return;
        }

        if (!avayaGlobal.validateNumber(serviceMapId, 1, 6)) {
            console.warn("CustomerJourney: invalid ServiceMap ID (" + serviceMapId + "). Setting to default (1)");
            serviceMapId = 1;
        }

        // Check if this attribute already exists
        var serviceId = serviceMapId.toString();
        var array = serviceMap[serviceId].attributes[attributeKey];
        var index = array.indexOf(attributeValue);
        console.log(array, index);
        if (index > -1) {
            if (array.length > 1) {
                array.splice(index, 1);
                console.log(array);
                serviceMap[serviceId].attributes[attributeKey] = array;
            } else {
                delete serviceMap[serviceId].attributes[attributeKey];
            }

            console.log("Service is now", serviceMap[serviceId]);
        }
    };

    /**
     * Manually set a specific ServiceMap or replace an existing one. May be more convenient than setting attributes and priority.
     * @param {ServiceMap} newService - the new service. Must not be empty.
     * @param {Number} serviceMapId - an optional parameter that defines the service map. Must be in the range 1-6; the default is 1.
     */
    customerJourney.setService = function (newService, serviceMapId) {
        if (!avayaGlobal.validateNumber(serviceMapId, 1, 6)) {
            console.warn("CustomerJourney: invalid ServiceMap ID (" + serviceMapId + "). Setting to default (1)");
            serviceMapId = 1;
        }

        if (avayaGlobal.isJsonEmpty(newService)) {
            console.error("CustomerJourney: ServiceMaps cannot be empty!");
            return;
        }

        var mapId = serviceMapId.toString();
        serviceMap[mapId] = serviceMap;
    };

    /**
     * Update the schema in OCDS.
     */
    customerJourney.updateSchema = function () {
        updateServiceMap();
    };

    /**
     * Debugging function to see the current context. Note: this may not entirely match the context in OCDS, depending on whether the schema has been updated.
     * @return {JSON}
     */
    customerJourney.getContext = function () {
        return {
            "data": nonRoutingData,
            "topic": topic,
            "schema": {
                "ServiceMap": serviceMap,
                "CustomerId": customerId,
                "Strategy": routingStrategy
            },
            "groupId": customerId,
            "contextId": contextId,
            "persistToEDM": true
        };
    };

    /**
     * Set the context ID. This is an optional step before creating the context.
     * @param {String} newContextId
     */
    customerJourney.setContextId = function (newContextId) {
        contextId = newContextId;
    };

    /**
     * Set the Customer ID, as an alternative to making a REST call.
     * @param {String} newCustomerId
     */
    customerJourney.setCustomerId = function (newCustomerId) {
        customerId = newCustomerId;
    };

    /**
     * Define how you wish this contact to be routed to an agent. Acceptable values are "Most Idle" or "FIFO" (First In, First Out)
     * @param {String} newStrategy
     */
    customerJourney.setRoutingStrategy = function (newStrategy) {
        if (newStrategy === "Most Idle" || newStrategy === "FIFO") {
            routingStrategy = newStrategy;
        } else {
            console.error("Customer Journey: the routing strategy can only be [Most Idle] or [FIFO]!");
            return;
        }

    };

    /**
     * Set the routing priority. Must be between 1 and 10 (inclusive of both). 
     * Will be sent up as a String, but it is easier to validate as a number.
     * @param {Number} newPriority
     * @param {Number} serviceMapId - identifies the ServiceMap. Optional, but must be in the range 1-6 (inclusive of both)
     */
    customerJourney.setPriority = function (newPriority, serviceMapId) {
        if (!avayaGlobal.validateNumber(newPriority, 1, 10)) {
            console.warn('Customer Journey: Priority (' + newPriority + ') is invalid! Resetting to default (5)');
            newPriority = 5;
        }

        if (!avayaGlobal.validateNumber(serviceMapId, 1, 6)) {
            console.warn('Customer Journey: Service Map (' + serviceMapId + ') is invalid! Resetting to default (1)');
            serviceMapId = 1;
        }

        serviceMap[serviceMapId.toString()].priority = newPriority.toString();
    };

    /**
     * Add non-routing data.
     * @param {String} key - must not be blank.
     * @param {Object} value - can be any String, JSON, Array or other object. 
     */
    customerJourney.addNonRoutingData = function (key, value) {
        if (!avayaGlobal.validateString(key)) {
            console.warn("Keys must be Strings!");
            return;
        }

        nonRoutingData[key] = value;
    };

    /**
     * Clear everything to start afresh.
     */
    customerJourney.reset = function () {
        console.info("Clearing customer ID and context ID");
        contextId = "";
        customerId = "";
        topic = "Default_Topic";
        routingStrategy = "Most Idle";
        sessionStorage.removeItem("contextId");
        sessionStorage.removeItem("customerId");
        nonRoutingData = {};
        serviceMap = {
            "1": {
                "attributes": {},
                "priority": "5"
            }
        };
    };

    return customerJourney;

})(window.customerJourney = window.customerJourney || {}, window.links, window.avayaGlobal);