/*last modified: 2024-03-20*/
/*jslint single: true, long:true, for:true, unordered:true*/
/*global ScenesCommon, pako, fromByteArray*/

const ScenesCommon = (function () {
    const DURATION_FOREVER = 60000;
    const BLACK_COLOR = {r: 0, g: 0, b: 0};
    const RAINBOW_COLORS = [
        {r: 255, g: 0, b: 0}, // red
        {r: 255, g: 165, b: 0}, // orange
        {r: 255, g: 255, b: 0}, // yellow
        {r: 0, g: 128, b: 0}, // green
        {r: 0, g: 0, b: 255}, // blue
        {r: 75, g: 0, b: 130}, // indigo
        {r: 238, g: 130, b: 238}  // violet
    ];
    const RAINBOW_COLORS_LOOP = RAINBOW_COLORS.concat(RAINBOW_COLORS[0]);
    const MAX_SETS = 720;
    const DYNAMISM_SLOW = 0.3;
    const DYNAMISM_MEDIUM = 0.6;
    const DYNAMISM_FAST = 1.0;

    function createSceneData(sceneID, numLeds, name) {
        return {
            name: name, //jslint-ignore-line
            ledCount: numLeds,
            idStr: sceneID,
            sceneDatas: []

        };
    }

    function createSceneDataSet(numLeds, duration) {
        return {
            duration: duration, //jslint-ignore-line
            rgbArray: [],
            ledCount: numLeds
        };
    }

    // base64 encode and deflate compression
    function sceneRequestWithMetadata(params) {

        var sceneData = params.sceneData;

        var hasSceneData = sceneData !== undefined;
        var jsonString;
        var setData;
        var data;

        var lights = {
            id: params.sceneID
        };

        if (params.lightsOn !== undefined) {
            lights.on = params.lightsOn;
        }

        if (params.brightness !== undefined) {
            lights.brightness = params.brightness;
        }

        if (hasSceneData) {
            setData = [];

            sceneData.sceneDatas.forEach(function (sceneDataSet) {
                var sceneDataSetNew = {
                    rgbRaw: fromByteArray(sceneDataSet.rgbArray),
                    duration: parseInt(sceneDataSet.duration)
                };
                setData.push(sceneDataSetNew);
            });

            lights.sceneSet = setData;
            lights.name = sceneData.name;
        } else if (params.sceneName !== undefined) {
            lights.name = params.sceneName;
        }

        if (params.metaData !== undefined) {
            lights.metaData = params.metaData;
        }

        data = {
            numberOfLights: 1,
            lights: [lights]
        };

        jsonString = JSON.stringify(data);

        if (hasSceneData) {
            // Compress data
            return pako.deflate(jsonString);
        }

        return jsonString;
    }

    // deflate/compress base64 encode request
    function deflatedSceneRequest(sceneData, lightsOn, brightness, metaData) {
        if (sceneData.idStr.length > 36) {
            throw new Error("The identifier of the scene is too long (max. 36 chars)");
        }
        
        var params = {
            "sceneData": sceneData,
            "sceneID": sceneData.idStr,
            "lightsOn": lightsOn,
            "brightness": brightness,
            "metaData": metaData
        };

        return sceneRequestWithMetadata(params);
    }

    function toggleSceneOnRequest(sceneID, lightsOn) {
        var params = {
            "sceneID": sceneID,
            "lightsOn": lightsOn
        };

        return sceneRequestWithMetadata(params);
    }

    function changeBrightnessRequest(sceneID, brightness) {
        var params = {
            "sceneID": sceneID,
            "brightness": brightness
        };

        return sceneRequestWithMetadata(params);
    }

    function changeSceneNameRequest(sceneID, sceneName) {
        var params = {
            "sceneID": sceneID,
            "sceneName": sceneName
        };

        return sceneRequestWithMetadata(params);
    }

    function colorFromHexString(str) {
        return {
            r: parseInt(str.substr(1, 2), 16),
            g: parseInt(str.substr(3, 2), 16),
            b: parseInt(str.substr(5, 2), 16)
        };
    }

    function pushColor(arr, color) {
        arr.push(color.r);
        arr.push(color.g);
        arr.push(color.b);
    }

    function valueOrDefaultFromParameters(parameters, config, name) {
        var parameter = configForParameterName(config, name);
        var result = parameters[name];
        
        if (result === undefined) {
            return parameter.default;
        }
        
        if (parameter.type === "int" || parameter.type === "int_enum") {
            return parseInt(result);
        }
        
        return result;
    }

    function configForParameterName(config, name) {
        return config.find(function (param) {
            return param.name === name;
        });
    }

    function alterArrayForMinMaxParameterName(arr, config, name) {
        const max = parseInt(configForParameterName(config, name).max);
        const min = parseInt(configForParameterName(config, name).min);
        var i = 0;

        if (arr.length > max) {
            return arr.slice(0, max);
        }

        while (arr.length < min) {
            arr.push(arr[i]);
            i += 1;
        }

        return arr;
    }

    function generateAIColorFromParameters(parameters, config) {
        const paramColors = valueOrDefaultFromParameters(parameters, config, "colors");
        const colorWeights = valueOrDefaultFromParameters(parameters, config, "color_weights");
        var i = 0;
        var len = paramColors.length;

        var colors = [];

        for (i = 0; i < len; i += 1) {
            colors.push({
                colorString: paramColors[i],
                weight: colorWeights[i]
            });
        }

        return colors;
    }

    function gradientColors(start, end, count) {
        var alpha = 0.0;
        var colors = [];
        var i = 0;

        for (i = 0; i < count; i += 1) {
            alpha += (1.0 / count);

            colors.push({
                r: Math.round(end.r * alpha + (1 - alpha) * start.r),
                g: Math.round(end.g * alpha + (1 - alpha) * start.g),
                b: Math.round(end.b * alpha + (1 - alpha) * start.b)
            });
        }

        return colors;
    }

    function addRainbowColorsToDataSet(sceneDataSet, numLeds, offset) {
        var numInterpolatedColors = Math.ceil(numLeds / RAINBOW_COLORS_LOOP.length);
        var i = 0;
        var index;
        var colorIndex;
        var nextColorIndex;
        var rainbowColor;
        var nextColor;
        var interpolation;
        var color;

        for (i = 0; i < numLeds; i += 1) {
            index = (i + offset) % numLeds;
            colorIndex = Math.floor(index / numInterpolatedColors);
            nextColorIndex = (colorIndex + 1) % RAINBOW_COLORS_LOOP.length;

            rainbowColor = RAINBOW_COLORS_LOOP[colorIndex];
            nextColor = RAINBOW_COLORS_LOOP[nextColorIndex];

            interpolation = (index % numInterpolatedColors) / numInterpolatedColors;
            color = {
                r: Math.round(rainbowColor.r + interpolation * (nextColor.r - rainbowColor.r)),
                g: Math.round(rainbowColor.g + interpolation * (nextColor.g - rainbowColor.g)),
                b: Math.round(rainbowColor.b + interpolation * (nextColor.b - rainbowColor.b))
            };
            pushColor(sceneDataSet.rgbArray, color);
        }
    }

    function colorEqual(color1, color2) {
        return (
            color1.r === color2.r &&
            color1.g === color2.g &&
            color1.b === color2.b
        );
    }

    function randomColor() {
        return {
            r: Math.round(Math.random() * 255),
            g: Math.round(Math.random() * 255),
            b: Math.round(Math.random() * 255)
        };
    }

    function hexString(colorInt) {
        return colorInt.toString(16).toUpperCase().padStart(2, "0");
    }

    function hexColors(colors) {
        var result = [];

        colors.forEach(function (color) {
            result.push('#' + hexString(color.r) + hexString(color.g) + hexString(color.b));
        });

        return result;
    }

    return {
        "BLACK_COLOR": BLACK_COLOR,
        "RAINBOW_COLORS": RAINBOW_COLORS,
        "DURATION_FOREVER": DURATION_FOREVER,
        "MAX_SETS": MAX_SETS,
        "DYNAMISM_SLOW": DYNAMISM_SLOW,
        "DYNAMISM_MEDIUM": DYNAMISM_MEDIUM,
        "DYNAMISM_FAST": DYNAMISM_FAST,
        "createSceneData": createSceneData,
        "createSceneDataSet": createSceneDataSet,
        "deflatedSceneRequest": deflatedSceneRequest,
        "toggleSceneOnRequest": toggleSceneOnRequest,
        "changeBrightnessRequest": changeBrightnessRequest,
        "changeSceneNameRequest": changeSceneNameRequest,
        "colorFromHexString": colorFromHexString,
        "pushColor": pushColor,
        "valueOrDefaultFromParameters": valueOrDefaultFromParameters,
        "configForParameterName": configForParameterName,
        "alterArrayForMinMaxParameterName": alterArrayForMinMaxParameterName,
        "generateAIColorFromParameters": generateAIColorFromParameters,
        "gradientColors": gradientColors,
        "addRainbowColorsToDataSet": addRainbowColorsToDataSet,
        "colorEqual": colorEqual,
        "randomColor": randomColor,
        "hexColors": hexColors
    };
}());

// returns deflated/compressed base64 encoded request
function deflatedSceneRequest(sceneData, lightsOn, brightness, metaData) {
    return ScenesCommon.deflatedSceneRequest(sceneData, lightsOn, brightness, metaData);
}

// returns plain JSON request
function toggleSceneOnRequest(sceneID, lightsOn) {
    return ScenesCommon.toggleSceneOnRequest(sceneID, lightsOn);
}

function changeBrightnessRequest(sceneID, brightness) {
    return ScenesCommon.changeBrightnessRequest(sceneID, brightness);
}

function changeSceneNameRequest(sceneID, sceneName) {
    return ScenesCommon.changeSceneNameRequest(sceneID, sceneName);
}
