import { array_set_diff } from "../../../core/collection/array/array_set_diff.js";
import List from "../../../core/collection/list/List.js";
import Vector2 from "../../../core/geom/Vector2.js";
import SVG from "../../SVG.js";

import View from "../../View.js";
import EmptyView from "../EmptyView.js";
import { makeDonut } from "./makeDonut.js";
import RadialMenuElement from "./RadialMenuElement.js";

const PI2 = Math.PI * 2;

const DEFAULT_BACKGROUND_COLOR = "rgba(0,0,0,0.6)";

class RadialMenu extends View {
    /**
     *
     * @param {RadialMenuElementDefinition[]} items
     * @param {number} [padding=0]
     * @param {number} [outerRadius=150]
     * @param {number} [innerRadius=50]
     * @param {number} [focusWidth]
     * @param {number} [backdropInnerRadius]
     * @param {number} [backdropOuterRadius]
     * @param {string} [backgroundColor] CSSColor string
     * @param {string[]} [classList]
     * @constructor
     */
    constructor(items, {
        padding = 0,
        outerRadius = 150,
        innerRadius = 50,
        focusWidth = 20,
        backdropInnerRadius,
        backdropOuterRadius,
        backgroundColor = DEFAULT_BACKGROUND_COLOR,
        classList = []
    }) {
        super();

        /**
         *
         * @type {List<RadialMenuElement>}
         */
        this.selected = new List();

        /**
         *
         * @type {Array.<RadialMenuElement>}
         */
        this.elements = [];

        /**
         *
         * @type {number}
         */
        this.firstElementOffset = 0;

        /**
         *
         * @type {number}
         */
        this.padding = padding;

        /**
         *
         * @type {number}
         */
        this.outerRadius = outerRadius;
        /**
         *
         * @type {number}
         */
        this.innerRadius = innerRadius;

        if (backdropInnerRadius === undefined) {
            backdropInnerRadius = innerRadius;
        }

        if (backdropOuterRadius === undefined) {
            backdropOuterRadius = outerRadius + 10;
        }

        /**
         * @type {number}
         */
        this.backdropInnerRadius = backdropInnerRadius;
        /**
         * @type {number}
         */
        this.backdropOuterRadius = backdropOuterRadius;


        /**
         *
         * @type {number}
         */
        this.focusOuterRadius = this.outerRadius + focusWidth;
        /**
         *
         * @type {number}
         */
        this.focusInnerRadius = this.innerRadius;

        /**
         *
         * @type {number}
         */
        this.width = this.focusOuterRadius * 2;
        /**
         *
         * @type {number}
         */
        this.height = this.focusOuterRadius * 2;

        const el = this.el = document.createElement("div");
        el.classList.add("ui-radial-menu");
        classList.forEach(c => this.addClass(c));

        el.style.position = "absolute";
        el.style.overflow = "visible";

        this.size.set(this.width, this.height);

        const svgElDonut = SVG.createElement("svg");
        svgElDonut.classList.add("backdrop");
        svgElDonut.style.overflow = "visible";
        svgElDonut.setAttribute("width", this.width);
        svgElDonut.setAttribute("height", this.height);

        //line to give feedback
        const elLine = SVG.createElement("line");
        elLine.classList.add("pointer-line");
        elLine.style.stroke = "rgba(255,255,255,0.6)";
        elLine.style.strokeWidth = "5";
        elLine.style.strokeLinecap = "round";

        el.appendChild(svgElDonut);

        const elDonut = makeDonut(this.backdropOuterRadius, this.backdropInnerRadius, this.focusOuterRadius, this.focusOuterRadius);
        elDonut.setAttribute("fill", backgroundColor);
        svgElDonut.appendChild(elDonut);

        const vElementContainer = new EmptyView({ classList: ["elements"] });
        vElementContainer.position.set(this.focusOuterRadius, this.focusOuterRadius);
        this.addChild(vElementContainer);

        this.vElementContainer = vElementContainer;

        function moveLineEnd(x, y) {
            elLine.setAttribute("x2", vElementContainer.position.x + x);
            elLine.setAttribute("y2", vElementContainer.position.y + y);
        }

        this.linePosition = new Vector2(0, 0);
        this.linePosition.onChanged.add(moveLineEnd);

        elLine.setAttribute("x1", vElementContainer.position.x);
        elLine.setAttribute("y1", vElementContainer.position.y);

        //initialize line to 0 length in the middle
        moveLineEnd(0, 0);

        this.init(items);

        const vLineContainer = new EmptyView({ classList: ["pointer-line"] });
        const elSvgLine = SVG.createElement("svg");
        vLineContainer.el.appendChild(elSvgLine);
        elSvgLine.appendChild(elLine);
        this.addChild(vLineContainer);
    }

    render() {
        this.elements.forEach(function (el) {
            el.render();
        });
    }

    link() {
        super.link();
        this.render();
    }

    computeTotalShareValue() {
        return this.elements.reduce(function (prev, element) {
            return prev + element.description.share;
        }, 0);
    }

    /**
     *
     * @param {RadialMenuElementDefinition[]} items
     */
    init(items) {
        const self = this;

        const n = items.length;
        const padding = n > 1 ? this.padding : 0;

        this.elements = [];

        for (let i = 0; i < n; i++) {
            /**
             *
             * @type {RadialMenuElementDefinition}
             */
            const item = items[i];

            if (!item.isRadialMenuElementDefinition) {
                throw new Error(`Supplied element is not RadialMenuElementDefinition`);
            }

            item.outerRadius = self.outerRadius;
            item.innerRadius = self.innerRadius;
            item.padding = padding;

            const element = new RadialMenuElement(item);

            this.elements.push(element);

            this.vElementContainer.addChild(element);
        }
    }

    autoLayout() {

        const numElements = this.elements.length;

        if (numElements === 1) {
            const first = this.elements[0];

            first.description.share = 0.33;
        } else {

            //normalize share values
            this.normalizeElementShares();
        }

        if (numElements > 0) {
            //set first element position so that it appears at the top
            const firstElement = this.elements[0];

            firstElement.description.offset = -(0.25 + firstElement.description.share / 2);
        }

        this.updatePositions();
    }

    normalizeElementShares() {
        const shareValue = this.computeTotalShareValue();
        this.elements.forEach(function (element) {
            element.description.share /= shareValue;
        });
    }

    normalizeOffsetsSequentially() {
        const elements = this.elements;

        if (elements.length === 0) {
            return;
        }
        const firstElement = elements[0];

        let offset = this.firstElementOffset + firstElement.description.offset;

        elements.forEach(function (element) {
            const share = element.description.share;
            element.description.offset = offset;
            offset += share;
        });
    }

    updatePositions() {
        this.normalizeOffsetsSequentially();
        //render
        this.elements.forEach(function (element, index) {
            element.render();
        });
    }

    /**
     *
     * @param {RadialMenuElement} el
     * @param {boolean} flag
     */
    setElementSelection(el, flag) {
        const selected = this.selected;

        const index = selected.indexOf(el);

        if (flag && index < 0) {
            // should be selected but is not

            selected.add(el);

            el.outerRadius = this.focusOuterRadius;
            el.innerRadius = this.focusInnerRadius;

            const onSelected = el.description.onSelected;
            if (typeof onSelected === "function") {
                // invoke callback
                onSelected();
            }

        } else if (!flag && index >= 0) {
            // should not be selected, but currently is
            selected.remove(index);

            el.outerRadius = this.outerRadius;
            el.innerRadius = this.innerRadius;

            const onDeSelected = el.description.onDeSelected;
            if (typeof onDeSelected === "function") {
                // invoke callback
                onDeSelected();
            }

        }
    }

    resetElementSelection() {
        const self = this;

        const selected = this.selected.asArray().slice();

        for (const el of selected) {
            self.setElementSelection(el, false);
        }
    }

    /**
     *
     * @param {number} angle
     */
    selectByAngle(angle) {
        const na = 1 - angle / PI2;

        //pick element that fits the angle
        const elements = this.elements;
        const elementCount = elements.length;

        const selected = [];

        for (let i = 0; i < elementCount; i++) {
            const el = elements[i];

            /**
             *
             * @type {RadialMenuElementDefinition}
             */
            const description = el.description;

            //normalize offset
            let no = description.offset;

            while (no < 0) {
                no += 1;
            }

            let s0 = no;
            let s1 = no + description.share;


            if ((s0 <= na && s1 > na) || (s1 > 1 && (s1 % 1) > na)) {
                selected.push(el);
            }
        }

        const diff = array_set_diff(this.selected.asArray(), selected);

        const removals = diff.uniqueA;
        const additions = diff.uniqueB;

        removals.forEach(el => this.setElementSelection(el, false));
        additions.forEach(el => this.setElementSelection(el, true));
    }

    runSelected() {
        this.selected.forEach(function (el) {
            const action = el.description.action;
            if (typeof action === "function") {
                action();
            }
        });
    }
}


export default RadialMenu;
