import dat from 'dat.gui'
import View from "../../view/View.js";
import { OptionGroup } from "./OptionGroup.js";

function isOption(c) {
    return typeof c.read === "function";
}

/**
 * DAT.GUI sets styles directly on the element internally, to prevent this - we use this hack that clears styles every frame while view is visible
 * @param {View} view
 */
function clearGUIStyles(view) {

    let clearStyleFlag = false;

    function clearStyle() {
        view.el.removeAttribute('style');
    }

    function runClearStyle() {
        clearStyle();

        if (clearStyleFlag) {
            requestAnimationFrame(runClearStyle);
        }
    }

    view.on.linked.add(function () {
        clearStyleFlag = true;

        runClearStyle();
    });

    view.on.unlinked.add(function () {
        clearStyleFlag = false;
    });
}

class OptionsView extends View {
    /**
     *
     * @constructor
     * @param {OptionGroup} options
     * @param {Localization} localization
     * @param {string[][]} [inclusions]
     */
    constructor({ options, localization, inclusions }) {
        super();

        if (inclusions === undefined) {
            //no inclusions specified, include everything
            inclusions = [];

            options.traverseOptions(op => {
                inclusions.push(op.computePath());
            });
        }

        const gui = new dat.GUI({
            autoPlace: false,
            resizable: false,
            closed: false
        });

        /**
         *
         * @param {Option|OptionGroup} option
         * @returns {String[]}
         */
        function getPathFor(option) {
            return option.computePath();
        }

        /**
         *
         * @param {Option|OptionGroup} option
         */
        function getNameFor(option) {
            const pathString = getPathFor(option).join('.');

            const localizationKey = 'system_option.' + pathString;

            return localization.getString(localizationKey);
        }

        const controls = new Map();

        function updateLocalization() {
            controls.forEach((control, option) => {
                const optionName = getNameFor(option);
                if (option.isOptionGroup) {
                    control.name = optionName;
                } else {
                    control.name(optionName);
                }
            });
        }

        this.on.linked.add(updateLocalization);
        this.bindSignal(localization.locale.onChanged, updateLocalization);


        const self = this;

        /**
         *
         * @param {Option} option
         * @param rootFolder
         */
        function makeOption(option, rootFolder) {
            const op = {
                v: null
            };

            let control;

            Object.defineProperty(op, "v", {
                get: function () {
                    return option.read();
                },
                set: function (v) {
                    option.write(v);
                }
            });

            try {
                control = rootFolder.add(op, "v", option.settings.values);
            } catch (e) {
                console.error("Failed to add option controller", option, e);
                return;
            }

            if (typeof option.settings.min === "number") {
                control = control.min(option.settings.min);
            }
            if (typeof option.settings.max === "number") {
                control = control.max(option.settings.max);
            }

            control.name(getNameFor(option));

            controls.set(option, control);

            self.bindSignal(option.on.written, (v) => {
                control.updateDisplay();
            });
        }

        /**
         *
         * @param {Option|OptionGroup} op
         */
        function isOptionIncluded(op) {
            const n = inclusions.length;

            const path = op.computePath();
            const pathLength = path.length;

            main: for (let i = 0; i < n; i++) {
                const inclusion = inclusions[i];

                const inclusionLength = inclusion.length;

                if (pathLength > inclusionLength) {
                    continue;
                }

                for (let j = 0; j < pathLength; j++) {
                    const pathPart = path[j];
                    const inclusionPart = inclusion[j];

                    if (pathPart !== inclusionPart) {
                        continue main;
                    }
                }

                //path is inside the inclusion
                return true;
            }

            return false;
        }

        /**
         *
         * @param {OptionGroup} group
         * @param {GUI} rootFolder
         */
        function makeGroup(group, rootFolder) {

            group.children.forEach((c) => {

                if (!isOptionIncluded(c)) {
                    return;
                }

                if (isOption(c)) {
                    makeOption(c, rootFolder);
                } else {
                    const folder = rootFolder.addFolder(c.id);

                    folder.name = getNameFor(c);

                    controls.set(c, folder);

                    makeGroup(c, folder);
                }
            });
        }

        makeGroup(options, gui);


        this.el = gui.domElement;

        clearGUIStyles(this);


        //update all controls on linkage
        this.on.linked.add(() => {
            controls.forEach(control => control.updateDisplay());
        }, this);
    }
}


export default OptionsView;
