import { assert } from "../../core/assert.js";
import List from "../../core/collection/list/List.js";
import AABB2 from "../../core/geom/2d/aabb/AABB2.js";
import ConcurrentExecutor from "../../core/process/executor/ConcurrentExecutor.js";
import Task from "../../core/process/task/Task.js";
import { TaskSignal } from "../../core/process/task/TaskSignal.js";
import { frameThrottle } from "../../engine/graphics/FrameThrottle.js";
import { PointerDevice } from "../../engine/input/devices/PointerDevice.js";
import EmptyView from "../elements/EmptyView.js";
import { GMLEngine } from "./gml/GMLEngine.js";
import TooltipView from "./TooltipView.js";

export class TooltipManager {

    constructor() {
        /**
         * @private
         * @type {GMLEngine}
         */
        this.gml = null;

        /**
         *
         * @type {View|null}
         */
        this.contextView = new EmptyView();

        this.contextView.addClass('ui-tooltip-manager-context');


        /**
         *
         * @type {PointerDevice}
         */
        this.pointer = null;


        this.updateTask = new Task({
            name: "Update Tooltip Cursor",
            cycleFunction: () => {
                const position = this.pointer.position;

                this.update(position.x, position.y);

                return TaskSignal.Yield;
            }
        });

        this.executor = new ConcurrentExecutor(500, 0.00001);

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

    /**
     *
     * @returns {GMLEngine}
     */
    getGML() {
        return this.gml;
    }

    /**
     *
     * @param {GMLEngine} gml
     * @param {PointerDevice} pointer
     */
    initialize(gml, pointer) {
        this.gml = gml;

        /**
         *
         * @type {PointerDevice}
         */
        this.pointer = pointer;


        this.signalBindings = [];
    }

    startup() {

        return new Promise((resolve, reject) => {

            if (this.contextView === null) {
                throw new Error('ContextView not set; probably not initialized');
            }

            this.signalBindings.forEach(b => b.link());

            this.executor.run(this.updateTask);

            this.gml.startup().then(resolve, reject);
        });
    }

    shutdown() {
        const self = this;

        return new Promise(function (resolve, reject) {
            self.signalBindings.forEach(b => b.unlink());

            self.executor.removeTask(self.updateTask);

            resolve();
        });
    }

    update() {
        //TODO do update stuff

        /*

        this.tips.forEach(t => {
            this.positionTip(t.view);
        });

         */
    }

    /**
     * @private
     * @returns {TooltipView}
     * @param {VisualTip} tip
     */
    buildTipView(tip) {
        const gml = this.getGML();

        gml.pushState();
        gml.setTooltipsEnabled(false);

        //build tip content
        const factoryProduct = tip.factory.call(tip.factoryContext);

        gml.popState();

        assert.defined(factoryProduct, "factory product");
        assert.notNull(factoryProduct, "factory product");

        let contentView;

        if (typeof factoryProduct === "object") {
            //expecting factory product to be a view, passing it directly

            contentView = factoryProduct;

        } else {
            //treat as a normal string, compile
            const code = factoryProduct.toString();

            //build content view
            contentView = this.gml.compile(code);
        }

        const view = new TooltipView(tip, contentView);

        tip.view = view;

        const layout = frameThrottle(() => {
            this.positionTip(view);
        });

        view.bindSignal(tip.target.position.onChanged, layout);
        view.bindSignal(tip.target.size.onChanged, layout);

        return view;
    }

    /**
     *
     * @param {VisualTip} tip
     */
    add(tip) {
        assert.defined(tip, 'tip');
        assert.notNull(tip, 'tip');

        const contextView = this.contextView;

        //build tip view
        const tooltipView = this.buildTipView(tip);

        //make transparent
        tooltipView.css({
            opacity: 0
        });

        requestAnimationFrame(() => {
            this.positionTip(tooltipView);

            tooltipView.css({
                opacity: 1
            });
        });

        contextView.addChild(tooltipView);

        //remember tip
        this.tips.add(tip);
    }

    /**
     *
     * @param {VisualTip} tip
     * @returns {boolean}
     */
    contains(tip) {
        return this.tips.contains(tip);
    }

    /**
     *
     * @param {VisualTip} tip
     */
    remove(tip) {
        const removed = this.tips.removeOneOf(tip);

        if (removed) {
            this.contextView.removeChild(tip.view);

            //clear out the view
            tip.view = null;
        }
    }

    /**
     *
     * @param {TooltipView} tipView
     */
    positionTip(tipView) {
        if (tipView === null) {
            return;
        }

        const contextView = this.contextView;

        const bounds = new AABB2(0, 0, contextView.size.x, contextView.size.y);

        tipView.layout(bounds);
    }


}
