/*
Project Name: SPIKE Prime Web Interface
File name: ServiceDock_SystemLink.js
Author: Jeremy Jung
Last update: 7/19/20
Description: HTML Element definition for <service-systemlink> to be used in ServiceDocks
Credits/inspirations:
History:
    Created by Jeremy on 7/16/20
LICENSE: MIT
(C) Tufts Center for Engineering Education and Outreach (CEEO)
*/

// import { Service_SystemLink } from "./Service_SystemLink.js";

class servicesystemlink extends HTMLElement {   

    constructor () {

        super();

        this.active = false; // whether the service was activated
        this.service = new Service_SystemLink(); // instantiate a service object ( one object per button )
        this.proceed = false; // if there are credentials input

        // Create a shadow root
        var shadow = this.attachShadow({ mode: 'open' });

        /* wrapper definition and CSS */
        var wrapper = document.createElement('div');
        wrapper.setAttribute('class', 'wrapper');
        wrapper.setAttribute("style", "width: 50px; height: 50px; position: relative; margin-top: 10px;")
        
        /* ServiceDock button definition and CSS */
        
        var button = document.createElement("button");
        button.setAttribute("id", "sl_button");
        button.setAttribute("class", "SD_button");
        
        /* CSS */
        //var imageRelPath = "./modules/views/systemlinkIcon.png" // relative to the document in which a servicesystemlink is created ( NOT this file )
        var length = 50; // for width and height of button
        var backgroundColor = "#A2E1EF" // background color of the button
        var buttonStyle = "width:" + length + "px; height:" + length + "px; background:" + "url('data:image/png;base64,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')" + "no-repeat; background-size: 50px 50px; background-color:" + backgroundColor 
                + "; border: none; background-position: center; cursor: pointer; border-radius: 10px; position: relative; margin: 4px 0px; "
        button.setAttribute("style", buttonStyle);

        /* status circle definition and CSS */

        this.status = document.createElement("div");
        this.status.setAttribute("class", "status");
        
        /* CSS */
        var length = 20; // for width and height of circle
        var statusBackgroundColor = "red" // default background color of service (inactive color)
        var posLeft = 30;
        var posTop = 20;
        var statusStyle = "border-radius: 50%; height:" + length + "px; width:" + length + "px; background-color:" + statusBackgroundColor +
         "; position: relative; left:" + posLeft + "px; top:" + posTop + "px;";
        this.status.setAttribute("style", statusStyle);

        /* event listeners */

        button.addEventListener("mouseleave", function (event) {
            button.style.backgroundColor = "#A2E1EF";
            button.style.color = "#000000";
        });

        button.addEventListener("mouseenter", function(event){
            button.style.backgroundColor = "#FFFFFF";
            button.style.color = "#000000";
        })

        this.addEventListener("click", async function() {

            if ( !this.active ) {
                this.popUpBox();
            }

            // check active flag so once activated, the service doesnt reinit
            if ( !this.active && this.proceed) {
                
                console.log("%cTuftsCEEO ", "color: #3ba336;", "Activating SystemLink Service");
                
                var initSuccessful = await this.service.init(this.APIKey);
                
                if (initSuccessful) {
                    this.active = true;
                    this.status.style.backgroundColor = "green";
                }

            }

        });

        shadow.appendChild(wrapper);
        button.appendChild(this.status);
        wrapper.appendChild(button);
    }

    /* Ask user for API credentials */
    popUpBox() {
        var APIKeyExists = true;
        // if apikey was not given in attributes
        if (this.getAttribute("apikey") == undefined || this.getAttribute("apikey") == "") {
            var APIKeyResult = prompt("Please enter your System Link Cloud API Key:");
            
            // APIkey 
            if (APIKeyResult == null || APIKeyResult == "") {
                console.log("%cTuftsCEEO ", "color: #3ba336;", "You inserted no API key");
                APIKeyExists = false;
            }
            else {
                this.APIKey = APIKeyResult;
            }
        }
        else {
            var APIKeyResult = this.getAttribute("apikey");
            this.APIKey = APIKeyResult;
        }
        

        if ( APIKeyExists ) {
            this.proceed = true;
        }
    }

    /* for Service's API credentials */

    static get observedAttributes() {
        return ["apikey"];
    }

    get apikey() {
        return this.getAttribute("apikey");
    }

    set apikey(val) {
        // console.log("%cTuftsCEEO ", "color: #3ba336;", val);
        if ( val ) {
            this.setAttribute("apikey", val);
        }
        else {
            this.removeAttribute("apikey");
        }
    }

    attributeChangedCallback (name, oldValue, newValue) {
        console.log("%cTuftsCEEO ", "color: #3ba336;", "new value of apikey: ", newValue);
        this.APIKey = newValue;
    }

    /* get the Service_SystemLink object */
    getService() {
        return this.service;
    }

    /* get whether the ServiceDock button was clicked */
    getClicked() {
        return this.active;
    }

    // initialize the service (is not used in this class but available for use publicly)
    async init() {
        var initSuccess = await this.service.init(this.APIKey);
        if (initSuccess) {
            this.status.style.backgroundColor = "green";
            this.active = true;
            return true;
        }
        else {
            return false;
        }
    }

}

// when defining custom element, the name must have at least one - dash 
window.customElements.define('service-systemlink', servicesystemlink);

/*
Project Name: SPIKE Prime Web Interface
File name: Service_SystemLink.js
Author: Jeremy Jung
Last update: 8/04/20
Description: SystemLink Service Library (OOP)
History:
    Created by Jeremy on 7/15/20
LICENSE: MIT
(C) Tufts Center for Engineering Education and Outreach (CEEO)
*/

/**
 * 
 * @class Service_SystemLink
 * @example
 * // assuming you declared <service-systemlink> with the id, "service_systemlink"
 * var mySL = document.getElemenyById("service_systemlink").getService();
 * mySL.setAttribute("apikey", "YOUR API KEY");
 * mySL.init();
 */
function Service_SystemLink() {

    //////////////////////////////////////////
    //                                      //
    //          Global Variables            //
    //                                      //
    //////////////////////////////////////////

    /* private members */

    let tagsInfo = {}; // contains real-time information of the tags in the cloud

    let APIKey = "API KEY";

    let serviceActive = false; // set to true when service goes through init

    let pollInterval = 1000;

    var funcAtInit = undefined; // function to call after init

    //////////////////////////////////////////
    //                                      //
    //           Public Functions           //
    //                                      //
    //////////////////////////////////////////

    /** initialize SystemLink_Service
     * <p> Starts polling the System Link cloud </p>
     * <p> <em> this function needs to be executed after executeAfterInit but before all other public functions </em> </p>
     * 
     * @public
     * @param {string} APIKeyInput SYstemlink APIkey
     * @param {integer} pollIntervalInput interval at which to get tags from the cloud in MILISECONDS. Default value is 1000 ms.
     * @returns {boolean} True if service was successsfully initialized, false otherwise
     * @example
     * var SystemLinkElement = document.getElemenyById("service_systemlink");
     * var mySL = SystemLinkElement.getService();
     * mySL.init("APIKEY", 1000); // initialize SystemLink Service with a poll interval of 10 ms
     * 
     */
    async function init(APIKeyInput, pollIntervalInput) {

        // if an APIKey was specified
        if (APIKeyInput !== undefined) {
            APIKey = APIKeyInput;
        }

        var response = await checkAPIKey(APIKey);

        // if response from checkAPIKey is valid
        if (response) {

            if (pollIntervalInput !== undefined) {
                pollInterval = await pollIntervalInput;
            }

            // initialize the tagsInfo global variable
            updateTagsInfo(function () {

                serviceActive = true;

                // call funcAtInit if defined
                if (funcAtInit !== undefined) {

                    funcAtInit();
                }
            });

            return true;
        }
        else {
            return false;
        }
    }

    /** Get the callback function to execute after service is initialized
     * <p> <em> This function needs to be executed before calling init() </em> </p>
     * 
     * @public
     * @param {function} callback function to execute after initialization
     * @example
     * mySL.executeAfterInit( function () {
     *     var tagsInfo = mySL.getTagsInfo();
     * })
     */
    function executeAfterInit(callback) {
        // Assigns global variable funcAtInit a pointer to callback function
        funcAtInit = callback;
    }

    /** Return the tagsInfo global variable
     * 
     * @public
     * @returns basic information about currently existing tags in the cloud
     * @example
     * var tagsInfo = mySL.getTagsInfo();
     * var astringValue = tagsInfo["astring"]["value"];
     * var astringType = tagsInfo["astring"]["type"];
     */
    function getTagsInfo() {
        return tagsInfo;
    }

    /** Change the current value of a tag on SystemLink cloud. 
     * 
     * @private
     * @param {string} name name of tag to update
     * @param {any} value new value's data type must match the Tag's data type.
     * @param {function} callback function to execute after tag is updated
     * @example
     * // set a string type Value of a Tag and display
     * mySL.setTagValue("message", "hello there", function () {
     *    let messageValue = mySL.getTagValue("message");
     *    console.log("message: ", messageValue); // display the updated value
     * })
     * // set value of a boolean Tag
     * mySL.setTagValue("aBoolean", true);
     *
     * // set value of an integer Tag
     * mySL.setTagValue("anInteger", 10);
     *
     * // set value of a double Tag
     * mySL.setTagValue("aDouble", 5.2);
     */
    function setTagValue(tagName, newValue, callback) {
        // changes the value of a tag on the cloud
        setTagValueStrict(tagName, newValue, callback);
    }

    /** Change the current value of a tag on SystemLink cloud with strict data types. Values will be implicitly converted
     * <br>
     * NotStrict property indicates that the data type of the Value supplied will be implicitly converted. For example, allowing for setting an INT tag's value with a string, "123" or a STRING tag's value with
     * a number. This method exists for convenience but please avoid using it extensively as it can lead to unpredictable outcomes.
     * @public
     * @param {any} tagName 
     * @param {any} newValue 
     * @param {any} callback 
     * @example
     * // set a string type Value of a Tag and display
     * mySL.setTagValueNotStrict("message", 123, function () {
     *    let messageValue = mySL.getTagValue("message");
     *    console.log("message: ", messageValue); // display the updated value, which will be 123.
     * })
     * // set value of a boolean Tag
     * mySL.setTagValueNotStrict("aBoolean", true);
     *
     * // set value of an integer Tag
     * mySL.setTagValueNotStrict("anInteger", 10);
     * mySL.setTagValueNotStrict("anInteger", "10");
     *
     * // set value of a double Tag
     * mySL.setTagValueNotStrict("aDouble", 5.2);
     * mySL.setTagValueNotStrict("aDouble", "5.2");
     */
    function setTagValueNotStrict(tagName, newValue, callback) {
        // changes the value of a tag on the cloud
        changeValue(tagName, newValue, false, function (valueChanged) {
            if (valueChanged) {
                // wait for changed value to be retrieved
                setTimeout(function () {
                    if (typeof callback === 'function') {
                        callback();
                    }
                }, 1000)
            }
        });
    }

    /** Change the current value of a tag on SystemLink cloud with strict data types. There will be no implicit data type conversions. E.g. Updating tags of INT type will only work with javascript number.
     * 
     * @public
     * @param {any} name name of tag to update
     * @param {any} value value to update tag to
     * @param {any} callback function to execute after tag is updated
    * @example
     * // set a string type Value of a Tag and display
     * mySL.setTagValueStrict("message", "hello there", function () {
     *    let messageValue = mySL.getTagValue("message");
     *    console.log("message: ", messageValue); // display the updated value
     * })
     * // set value of a boolean Tag
     * mySL.setTagValueStrict("aBoolean", true);
     *
     * // set value of an integer Tag
     * mySL.setTagValueStrict("anInteger", 10);
     *
     * // set value of a double Tag
     * mySL.setTagValueStrict("aDouble", 5.2);
     */
    function setTagValueStrict(tagName, newValue, callback) {
        // changes the value of a tag on the cloud
        changeValue(tagName, newValue, true, function (valueChanged) {
            if (valueChanged) {
                // wait for changed value to be retrieved
                setTimeout(function () {
                    if (typeof callback === 'function') {
                        callback();
                    }
                }, 1000)
            }
        });
    }


    /** Get the current value of a tag on SystemLink cloud
     * 
     * @public
     * @param {string} tagName 
     * @returns {any} current value of tag
     * @example
     * messageValue = mySL.getTagValue("message");
     * console.log("message: ", messageValue);
     */
    function getTagValue(tagName) {

        var currentValue = tagsInfo[tagName].value;

        return currentValue;
    }

    /** Get whether the Service was initialized or not
     * 
     * @public
     * @returns {boolean} whether Service was initialized or not
     * @example
     * if (mySL.isActive() === true)
     *     // do something if SystemLink Service is active
     */
    function isActive() {
        return serviceActive;
    }

    /** Change the APIKey
     * @ignore
     * @param {string} APIKeyInput 
     */
    function setAPIKey(APIKeyInput) {
        // changes the global variable APIKey
        APIKey = APIKeyInput;
    }
    
    /** Create a new tag. The type of new tag is determined by the javascript data type of tagValue. 
     * @public
     * @param {string} tagName name of tag to create
     * @param {any} tagValue value to assign the tag after creation
     * @param {function} callback optional callback
     * @example
     * mySL.createTag("message", "hi", function () {
     *      mySL.setTagValueStrict("message", "bye"); // change the value of 'message' from "hi" to "bye"
     * })
     */
    function createTag(tagName, tagValue, callback) {
        
        // get the SystemLink formatted data type of tag
        var valueType = getValueType(tagValue);

        // create a tag with the name and data type. If tag exists, it still returns successful response
        createNewTagHelper(tagName, valueType, function (newTagCreated) {
            
            // after tag is created, assign a value to it
            changeValue(tagName, tagValue, false, function (newTagValueAssigned) {

                // execute callback if successful
                if (newTagCreated) {
                    if (newTagValueAssigned) {
                        // wait for changed value to be retrieved
                        setTimeout( function() {
                            if (typeof callback == 'function') {
                                callback();
                            }
                        }, 1000)
                    }
                }
            })
        })
    }

    /** Delete tag
     * 
     * @public
     * @param {string} tagName name of tag to delete
     * @param {function} callback optional callback
     * @example
     * mySL.deleteTag("message", function () {
     *      let tagsInfo = mySL.getTagsInfo();
     *      console.log("tagsInfo: ", tagsInfo); // tags information will now not contain the 'message' tag
     * })
     */
    function deleteTag(tagName, callback) {
        // delete the tag on System Link cloud
        deleteTagHelper(tagName, function (tagDeleted) {
            if ( tagDeleted ) {
                typeof callback === 'function' && callback();
            }
        });
    }

    //////////////////////////////////////////
    //                                      //
    //          Private Functions           //
    //                                      //
    //////////////////////////////////////////

    /** sleep function
     * 
     * @private
     * @param {integer} ms 
     * @returns {Promise}
     */
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    /** Check if Systemlink API key is valid for use
     * 
     * @private
     * @param {string} APIKeyInput 
     * @returns {Promise} resolve(true) or reject(error)
     */
    async function checkAPIKey(APIKeyInput) {
        return new Promise(async function (resolve, reject) {
            var apiKeyAuthURL = "https://api.systemlinkcloud.com/niauth/v1/auth";

            var request = await sendXMLHTTPRequest("GET", apiKeyAuthURL, APIKeyInput)

            request.onload = function () {

                var response = JSON.parse(request.response);

                if (response.error) {
                    reject(new Error("Error at apikey auth:", response));
                }
                else {
                    console.log("%cTuftsCEEO ", "color: #3ba336;", "APIkey is valid")
                    resolve(true)
                }

            }

            request.onerror = function () {
                var response = JSON.parse(request.response);
                // console.log("Error at apikey auth:", request.response);
                reject(new Error("Error at apikey auth:", response));
            }
        })
    }

    /** Assign list of tags existing in the cloud to {tagPaths} global variable
     * 
     * @private
     * @param {function} callback 
     */
    async function updateTagsInfo(callback) {

        // get the tags the first time before running callback
        getTagsInfoFromCloud(function (collectedTagsInfo) {

            // if the collectedTagsInfo is defined and not boolean false
            if (collectedTagsInfo) {
                tagsInfo = collectedTagsInfo;
            }

            // after tagsInfo is initialized, begin the interval to update it
            setInterval(async function () {

                getTagsInfoFromCloud(function (collectedTagsInfo) {

                    // if the object is defined and not boolean false
                    if (collectedTagsInfo) {
                        tagsInfo = collectedTagsInfo;
                    }
                });

            }, pollInterval)

            // run the callback of updateTagsInfo inside init()
            callback();

        });

    }

    /** Get the info of a tag in the cloud
     * 
     * @private
     * @param {function} callback 
     */
    async function getTagsInfoFromCloud(callback) {

        // make a new promise
        new Promise(async function (resolve, reject) {

            var collectedTagsInfo = {}; // to return

            var getMultipleTagsURL = "https://api.systemlinkcloud.com/nitag/v2/tags-with-values";

            // send request to SystemLink API
            var request = await sendXMLHTTPRequest("GET", getMultipleTagsURL, APIKey);

            // when transaction is complete, parse response and update return value (collectedTagsInfo)
            request.onload = async function () {

                // parse response (string) into JSON object
                var responseJSON = JSON.parse(this.response)

                var tagsInfoArray = responseJSON.tagsWithValues;

                // get total number of tags
                var tagsAmount = responseJSON.totalCount;

                for (var i = 0; i < tagsAmount; i++) {
                    // parse information of the tags

                    try {
                        var value = tagsInfoArray[i].current.value.value;
                        var valueType = tagsInfoArray[i].current.value.type;
                        var tagName = tagsInfoArray[i].tag.path;

                        var valueToAdd = await getValueFromType(valueType, value);

                        // store tag information
                        var pathInfo = {};
                        pathInfo["value"] = valueToAdd;
                        pathInfo["type"] = valueType;

                        // add a tag info to the return object
                        collectedTagsInfo[tagName] = pathInfo;

                    }
                    // when value is not yet assigned to tag
                    catch (e) {
                        var value = null
                        var valueType = tagsInfoArray[i].tag.type;
                        var tagName = tagsInfoArray[i].tag.path;

                        // store tag information
                        var pathInfo = {};
                        pathInfo["value"] = value;
                        pathInfo["type"] = valueType;

                        // add a tag info to the return object
                        collectedTagsInfo[tagName] = pathInfo;
                    }
                }

                resolve(collectedTagsInfo)

            }
            request.onerror = function () {

                console.log("%cTuftsCEEO ", "color: #3ba336;", this.response);

                reject(false);

            }
        }).then(
            // success handler 
            function (resolve) {
                //run callback with resolve object
                callback(resolve);
            },
            // failure handler
            function (reject) {
                // run calllback with reject object
                callback(reject);
            }
        )
    }

    /** Send PUT request to SL cloud API and change the value of a tag
     * This function will receive a newValue of any kind of type. Before the POST request is sent,
     * the SL data type of the tag to convert must be found, and newValue must be in string format
     * @private
     * @param {string} tagPath string of the name of the tag
     * @param {any} newValue value to assign tag
     * @param {function} callback
     */
    async function changeValue(tagPath, newValue, strict, callback) {
        new Promise(async function (resolve, reject) {

            var URL = "https://api.systemlinkcloud.com/nitag/v2/tags/" + tagPath + "/values/current";

            // assume newValue is already in correct datatype and just give the data type in SystemLink format
            //var valueType = getValueType(newValue);
            var valueType;
            var newValueStringified;

            // if Tag to change does not yet exist (possibly due to it being created very recently)
            if (tagsInfo[tagPath] === undefined) {
                // refer to newValue's JS type to deduce Tag's data type
                valueType = getValueTypeStrict(newValue);
            }
            // Tag to change exists; find the SL data type of tag from locally stored tagsInfo
            else {
                if (strict === true) {
                    /* strict changeValue. So no implicit data type conversions. All newValue's types need to match the tag's type */

                    expectedValueType = tagsInfo[tagPath].type;
                    inputValueType = getValueTypeStrict(newValue);
                    // console.log("%cTuftsCEEO ", "color: #3ba336;", expectedValueType, " vs ", inputValueType);
                    if (inputValueType !== expectedValueType) {
                        console.error("%cTuftsCEEO ", "color: #3ba336;", "Could not update value of tag on SystemLink Cloud. The given value is not of the data type defined for the tag in the database");
                        throw new Error("Could not update value of tag on SystemLink Cloud.The given value is not of the data type defined for the tag in the database");
                    }
                    else {
                        valueType = tagsInfo[tagPath].type;
                    }
                }
                else {
                    valueType = tagsInfo[tagPath].type;
                }
            }
            
            newValueStringified = changeToString(newValue);

            var data = { "value": { "type": valueType, "value": newValueStringified } };
            var requestBody = data;

            var request = await sendXMLHTTPRequest("PUT", URL, APIKey, requestBody);

            request.onload = function () {
                resolve(true);
            }

            request.onerror = function () {
                reject(false);
            }

            // catch error
            request.onreadystatechange = function () {
                if (this.readyState === XMLHttpRequest.DONE && (this.status != 200) ) {
                    console.log("%cTuftsCEEO ", "color: #3ba336;", this.status + " Error at changeValue: ", this.response)
                }
            }


        }).then(
            // success handler
            function (resolve) {
                callback(resolve);
            },
            function (reject) {
                callback(reject);
            }
        )
    }

    /** Send PUT request to SL cloud API and change the value of a tag
     * 
     * @private
     * @param {string} tagPath name of the tag
     * @param {string} tagType SystemLink format data type of tag
     * @param {function} callback 
     */
    async function createNewTagHelper(tagPath, tagType, callback) {
        new Promise(async function (resolve, reject) {

            var URL = "https://api.systemlinkcloud.com/nitag/v2/tags/";

            var data = { "type": tagType, "properties": {}, "path": tagPath, "keywords": [], "collectAggregates": false };

            var requestBody = data;

            var request = await sendXMLHTTPRequest("POST", URL, APIKey, requestBody);

            request.onload = function () {
                resolve(true);
            }

            request.onerror = function () {
                console.log("%cTuftsCEEO ", "color: #3ba336;", "Error at createNewTagHelper", request.response);
                reject(false);
            }

            // catch error
            request.onreadystatechange = function () {
                if (this.readyState === XMLHttpRequest.DONE && (this.status != 200 && this.status != 201)) {
                    console.log("%cTuftsCEEO ", "color: #3ba336;", this.status + " Error at createNewTagHelper: ", this.response)
                }
            }

        }).then(
            // success handler
            function (resolve) {
                callback(resolve)
            },
            // error handler
            function (reject) {
                callback(reject)
            }
        )
    }

    /** Delete the tag on the System Link cloud
     * 
     * @private
     * @param {string} tagName 
     * @param {function} callback 
     */
    async function deleteTagHelper ( tagName, callback ) {
        new Promise(async function (resolve, reject) {

            var URL = "https://api.systemlinkcloud.com/nitag/v2/tags/" + tagName;

            var request = await sendXMLHTTPRequest("DELETE", URL, APIKey);

            request.onload = function () {
                resolve(true);
            }
            
            request.onerror = function () {
                console.log("%cTuftsCEEO ", "color: #3ba336;", "Error at deleteTagHelper", request.response);
                reject(false);
            }

            // catch error
            request.onreadystatechange = function () {
                if (this.readyState === XMLHttpRequest.DONE && this.status != 200) {
                    console.log("%cTuftsCEEO ", "color: #3ba336;", this.status + " Error at deleteTagHelper: ", this.response)
                }
            }

        }).then(
            // success handler
            function (resolve) {
                callback(resolve)
            },
            // error handler
            function (reject) {
                callback(reject)
            }
        )
    }

    /** Helper function for sending XMLHTTPRequests
     * 
     * @private
     * @param {string} method 
     * @param {string} URL 
     * @param {string} APIKeyInput 
     * @param {object} body 
     * @returns {object} XMLHttpRequest
     */
    async function sendXMLHTTPRequest(method, URL, APIKeyInput, body) {
        var request = new XMLHttpRequest();
        request.open(method, URL, true);

        //Send the proper header information along with the request
        request.setRequestHeader("x-ni-api-key", APIKeyInput);

        if (body === undefined) {
            request.setRequestHeader("Accept", "application/json");
            
            request.send();
        }
        else {
            request.setRequestHeader("Content-type", "application/json");
            var requestBody = JSON.stringify(body);
            try {
                request.send(requestBody);
            } catch (e) {
                console.log("%cTuftsCEEO ", "color: #3ba336;", "error sending request:", request.response);
            }
        }

        return request;
    }

    /** Helper function for getting data types in systemlink format
     * 
     * @private
     * @param {any} new_value the variable containing the new value of a tag
     * @returns {any} data type of tag
     */
    function getValueType(new_value) {

        //if the value is not a number
        if (isNaN(new_value)) {
            //if the value is a boolean
            if (new_value === "true" || new_value === "false") {
                return "BOOLEAN";
            }
            //if the value is a string
            return "STRING";
        }
        //value is a number
        else {
            //if value is an integer
            if (Number.isInteger(parseFloat(new_value))) {
                return "INT"
            }
            //if value is a double
            else {
                return "DOUBLE"
            }
        }
    }

    /**
     * @private
     * @param {any} new_value 
     * @returns {string} data type of tag 
     */
    function getValueTypeStrict(new_value) {
        //if the value is a boolean
        if (typeof new_value === "boolean") {
            return "BOOLEAN";
        }
        else if (typeof new_value === "string") {
            return "STRING";
        }
        else if (typeof new_value === "number") {
            if (Number.isInteger(parseFloat(new_value))) {
                return "INT"
            }
            //if value is a double
            else {
                return "DOUBLE"
            }
        }
    }


    /** stringify newValue
     * Note: for POST request
     * @private
     * @param {any} newValue 
     * @returns {string} newValue stringified
     */
    function changeToString(newValue) {
        var newValueConverted;

        // already a string
        if (typeof newValue == "string") {
            newValueConverted = newValue;
        }
        else {
            newValueConverted = JSON.stringify(newValue);
        }

        return newValueConverted;
    }

    /** Helper function for converting values to correct type based on data type
     * 
     * @private
     * @param {string} valueType data type of value in systemlink format
     * @param {string} value value to convert
     * @returns {any} converted value
     */
    function getValueFromType(valueType, value) {
        if (valueType == "BOOLEAN") {
            if (value == "true") {
                return true;
            }
            else {
                return false;
            }
        }
        else if (valueType == "STRING") {
            return value;
        }
        else if (valueType == "INT" || valueType == "DOUBLE") {
            return parseFloat(value);
        }
        return value;
    }

    /* public members */
    return {
        init: init,
        getTagsInfo: getTagsInfo,
        setTagValueNotStrict: setTagValueNotStrict,
        setTagValueStrict: setTagValueStrict,
        getTagValue: getTagValue,
        executeAfterInit: executeAfterInit,
        setAPIKey: setAPIKey,
        isActive: isActive,
        createTag: createTag,
        deleteTag: deleteTag
    }
}