// this is our global websocket, used to communicate from/to Stream Deck software
// and some info about our plugin, as sent by Stream Deck software
var websocket = null,
    uuid = null,
    actionInfo = {},
    settings = {},
    globalSettings = {},
    isQT = navigator.appVersion.includes('QtWebEngine'); // 'oninput'; // change this, if you want interactive elements act on any change, or while they're modified

const websiteAction = 'tf.meow.remote.website';

function connectSocket (
    inPort,
    inUUID,
    inMessageType,
    inApplicationInfo,
    inActionInfo
) {
    connectElgatoStreamDeckSocket(
        inPort,
        inUUID,
        inMessageType,
        inApplicationInfo,
        inActionInfo
    );
}

function connectElgatoStreamDeckSocket (inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
    uuid = inUUID;
    // please note: the incoming arguments are of type STRING, so
    // in case of the inActionInfo, we must parse it into JSON first
    actionInfo = JSON.parse(inActionInfo); // cache the info
    inInfo = JSON.parse(inInfo);
    websocket = new WebSocket('ws://localhost:' + inPort);

    /** Since the PI doesn't have access to your OS native settings
     * Stream Deck sends some color settings to PI
     * We use these to adjust some styles (e.g. highlight-colors for checkboxes)
     */
    addDynamicStyles(inInfo.colors, 'connectElgatoStreamDeckSocket');

    /** let's see, if we have some settings */
    settings = getPropFromString(actionInfo, 'payload.settings');
    console.log(settings, actionInfo);
    initPropertyInspector(5);

    // if connection was established, the websocket sends
    // an 'onopen' event, where we need to register our PI
    websocket.onopen = function () {
        var json = {
            event: inRegisterEvent,
            uuid: inUUID
        };

        websocket.send(JSON.stringify(json));
    };

    websocket.onmessage = function (evt) {
        // Received message from Stream Deck
        let jsonObj = JSON.parse(evt.data);

        let event = jsonObj['event'];

        console.log('Got event', event);

        switch (event) {
            case 'didReceiveGlobalSettings':
                didReceiveGlobalSettings(jsonObj);
                break;
        }
    };
}

function initPropertyInspector(initDelay) {
    const action = actionInfo['action'];

    $('[data-action="' + action + '"]').removeClass('hidden');

    Object.keys(settings).forEach(function (item) {
        let $item = $('#' + item),
            value = settings[item];

        switch ($item.attr('type')) {
            case 'checkbox':
                let itemVal = $item.attr('value');

                if (itemVal == 'false' || itemVal == 'true') {
                    itemVal = (/^true$/i).test(itemVal);
                }

                if (itemVal === value) {
                    $item.prop('checked', true);
                }
                break;
            default:
                $item.val(value);
        }
    });

    $('input').each(function() {
        let $this = $(this),
            id = $this.attr('id');

        let $item = $this.closest('.sdpi-item');

        $this.on('change', function() {
            const type = $this.attr('type');

            let val = $this.val();

            switch (type) {
                case 'checkbox':
                    // If unchecked, unset the setting
                    if (!this.checked) {
                        removeSetting(id);
                        return;
                    }

                    if (val == 'false' || val == 'true') {
                        val = (/^true$/i).test(val);
                    }
                    break;
                case 'file':
                    const info = $item.find('.sdpi-file-info');

                    if (info) {
                        const s = decodeURIComponent($this.val().replace(/^C:\\fakepath\\/, '')).split('/').pop();

                        info.text(s.length > 28
                            ? s.substr(0, 10)
                            + '...'
                            + s.substr(s.length - 10, s.length)
                            : s);
                    }
                    break;
            }

            updateSetting(id, val);
        });
    });
}

if (!isQT) {
    document.addEventListener('DOMContentLoaded', function () {
        initPropertyInspector(100);
    });
}

/** Stream Deck software passes system-highlight color information
 * to Property Inspector. Here we 'inject' the CSS styles into the DOM
 * when we receive this information. */
function addDynamicStyles (clrs, fromWhere) {
    const node = document.getElementById('#sdpi-dynamic-styles') || document.createElement('style');
    if (!clrs.mouseDownColor) clrs.mouseDownColor = fadeColor(clrs.highlightColor, -100);
    const clr = clrs.highlightColor.slice(0, 7);
    const clr1 = fadeColor(clr, 100);
    const clr2 = fadeColor(clr, 60);
    const metersActiveColor = fadeColor(clr, -60);

    node.setAttribute('id', 'sdpi-dynamic-styles');
    node.innerHTML = `

    input[type="radio"]:checked + label span,
    input[type="checkbox"]:checked + label span {
        background-color: ${clrs.highlightColor};
    }

    input[type="radio"]:active:checked + label span,
    input[type="radio"]:active + label span,
    input[type="checkbox"]:active:checked + label span,
    input[type="checkbox"]:active + label span {
      background-color: ${clrs.mouseDownColor};
    }

    input[type="radio"]:active + label span,
    input[type="checkbox"]:active + label span {
      background-color: ${clrs.buttonPressedBorderColor};
    }

    td.selected,
    td.selected:hover,
    li.selected:hover,
    li.selected {
      color: white;
      background-color: ${clrs.highlightColor};
    }

    .sdpi-file-label > label:active,
    .sdpi-file-label.file:active,
    label.sdpi-file-label:active,
    label.sdpi-file-info:active,
    input[type="file"]::-webkit-file-upload-button:active,
    button:active {
      background-color: ${clrs.buttonPressedBackgroundColor};
      color: ${clrs.buttonPressedTextColor};
      border-color: ${clrs.buttonPressedBorderColor};
    }

    ::-webkit-progress-value,
    meter::-webkit-meter-optimum-value {
        background: linear-gradient(${clr2}, ${clr1} 20%, ${clr} 45%, ${clr} 55%, ${clr2})
    }

    ::-webkit-progress-value:active,
    meter::-webkit-meter-optimum-value:active {
        background: linear-gradient(${clr}, ${clr2} 20%, ${metersActiveColor} 45%, ${metersActiveColor} 55%, ${clr})
    }
    `;
    document.body.appendChild(node);
};

/** UTILITIES */

/** get a JSON property from a (dot-separated) string
 * Works on nested JSON, e.g.:
 * jsn = {
 *      propA: 1,
 *      propB: 2,
 *      propC: {
 *          subA: 3,
 *          subB: {
 *             testA: 5,
 *             testB: 'Hello'
 *          }
 *      }
 *  }
 *  getPropFromString(jsn,'propC.subB.testB') will return 'Hello';
 */
const getPropFromString = (jsn, str, sep = '.') => {
    const arr = str.split(sep);
    return arr.reduce((obj, key) =>
        (obj && obj.hasOwnProperty(key)) ? obj[key] : undefined, jsn);
};

/*
    Quick utility to lighten or darken a color (doesn't take color-drifting, etc. into account)
    Usage:
    fadeColor('#061261', 100); // will lighten the color
    fadeColor('#200867'), -100); // will darken the color
*/
function fadeColor (col, amt) {
    const min = Math.min, max = Math.max;
    const num = parseInt(col.replace(/#/g, ''), 16);
    const r = min(255, max((num >> 16) + amt, 0));
    const g = min(255, max((num & 0x0000FF) + amt, 0));
    const b = min(255, max(((num >> 8) & 0x00FF) + amt, 0));
    return '#' + (g | (b << 8) | (r << 16)).toString(16).padStart(6, 0);
}

function updateSetting(setting, value) {
    if (!settings) {
        settings = {};
    }

    settings[setting] = value;

    setSettings(settings);
}

function removeSetting(setting) {
    if (!settings) {
        settings = {};
    }

    delete settings[setting];

    setSettings(settings);
}

function setSettings(settings) {
    let json = {
        "event": "setSettings",
        "context": uuid,
        "payload": settings
    };

    if (websocket) {
        websocket.send(JSON.stringify(json));
    }
}

function updateGlobalSetting(id, val) {
    globalSettings[id] = val;

    setGlobalSettings(globalSettings);
}

function getGlobalSettings() {
    let json = {
        "event": "getGlobalSettings",
        "context": uuid
    };

    if (websocket) {
        websocket.send(JSON.stringify(json));
    }
}

function setGlobalSettings(settings) {
    let json = {
        "event": "setGlobalSettings",
        "context": uuid,
        "payload": settings
    };

    if (websocket) {
        websocket.send(JSON.stringify(json));
    }
}

function didReceiveGlobalSettings(obj) {
    globalSettings = getPropFromString(obj, 'payload.settings');

    // Load defaults for fields not set
    Object.keys(globalSettings).forEach(function(item) {
        if (!(item in settings)) {
            $('#' + item).val(globalSettings[item]);
        }
    });
}

function isAction(action) {
    return actionInfo['action'] === action;
}