import List from '../../core/collection/list/List.js';
import { noop } from "../../core/function/noop.js";
import ObservedString from "../../core/model/ObservedString.js";
import { SimpleLifecycle, SimpleLifecycleStateType } from "../../core/process/SimpleLifecycle.js";

import ConfirmationDialogView from '../../view/elements/ConfirmationDialogView.js';
import EmptyView from "../../view/elements/EmptyView.js";
import SimpleWindowView from '../../view/elements/SimpleWindow.js';
import { DomTooltipManager } from "../../view/tooltip/DomTooltipManager.js";
import { GMLEngine } from "../../view/tooltip/gml/GMLEngine.js";
import { TooltipManager } from "../../view/tooltip/TooltipManager.js";

import View from '../../view/View.js';
import AnimationTrack from "../animation/keyed2/AnimationTrack.js";
import AnimationTrackPlayback from "../animation/keyed2/AnimationTrackPlayback.js";
import { playTrackRealTime } from "../animation/playTrackRealTime.js";
import TransitionFunctions from "../animation/TransitionFunctions.js";
import { SerializationMetadata } from "../ecs/components/SerializationMetadata.js";

import Entity from '../ecs/Entity.js';

import GUIElement from '../ecs/gui/GUIElement.js';
import ViewportPosition from '../ecs/gui/position/ViewportPosition.js';
import Ticker from "../simulation/Ticker.js";
import { CursorType } from "./cursor/CursorType.js";
import { SceneGUIContext } from "./scene/SceneGUIContext.js";


class GUIEngine {
    windows = new List();

    /**
     *
     * @type {EntityManager|null}
     */
    entityManager = null;

    /**
     *
     * @type {Engine}
     */
    engine = null;

    /**
     *
     * @type {WeakMap<Scene, SceneGUIContext>}
     */
    sceneContexts = new WeakMap();

    /**
     *
     * @type {TooltipManager}
     */
    tooltips = new TooltipManager();

    /**
     *
     * @type {DomTooltipManager}
     */
    viewTooltips = new DomTooltipManager(this.tooltips);


    /**
     *
     * @type {Ticker}
     */
    ticker = new Ticker();

    view = new EmptyView({
        classList: ['gui-engine-root'],
        css: {
            position: "absolute",
            left: "0",
            top: "0"
        }
    });

    /**
     *
     * @type {GMLEngine}
     */
    gml = new GMLEngine();

    /**
     *
     * @type {ObservedString}
     */
    cursor = new ObservedString(CursorType.Normal);


    /**
     *
     * @type {Localization|null}
     */
    localization = null;

    constructor() {

        this.ticker.onTick.add(d => {


            let ctx = null;
            try {
                ctx = this.getActiveSceneContext();
            } catch (e) {
                //skip
            }

            if (ctx !== null) {
                ctx.tick(d);
            }
        });

    }

    /**
     * @param {boolean} closeable
     * @param {View} content
     * @param {string} title
     * @param {View} [wrapper]
     * @returns {Entity}
     */
    openWindow({closeable, content, title, wrapper}) {
        const entityBuilder = new Entity();

        function closeAction() {
            entityBuilder.destroy();
        }

        const windowView = new SimpleWindowView(content, {
            closeAction,
            title,
            closeable
        });

        entityBuilder.add(new ViewportPosition());

        let vElement;
        if (wrapper !== undefined) {
            vElement = wrapper;
            wrapper.addChild(windowView);
        } else {
            vElement = windowView;
        }

        const guiElement = GUIElement.fromView(vElement);
        entityBuilder.add(guiElement)
            .add(SerializationMetadata.Transient);

        const dataset = this.entityManager.dataset;

        animateView(windowView, dataset);

        entityBuilder.build(dataset);

        return entityBuilder;
    }

    /**
     *
     * @param {View} content
     * @param {string} title
     * @param {number} priority
     * @returns {SimpleLifecycle}
     */
    createModal({content, title, priority = 0}) {
        const entityManager = this.entityManager;

        const self = this;
        let window = null;
        let overlay = null;


        function destroy() {
            window.destroy();
            overlay.destroy();
        }

        function makeOverlay() {
            const overlay = new View();
            overlay.el = document.createElement('div');
            overlay.el.classList.add('ui-modal-overlay');
            //make overlay dismiss modal
            overlay.el.addEventListener('click', function (event) {
                event.stopPropagation();
                lifecycle.makeDestroyed();
            });

            const builder = new Entity();

            builder.add(SerializationMetadata.Transient);
            builder.add(GUIElement.fromView(overlay));
            return builder;
        }

        function build() {
            overlay = makeOverlay();

            overlay.build(entityManager.dataset);

            const view = content;

            const vModalContainer = new EmptyView({classList: ['ui-modal-window-container']});

            window = self.openWindow({
                title: title,
                content: view,
                closeable: false,
                wrapper: vModalContainer
            });

            const windowGuiElement = window.getComponent(GUIElement);
            windowGuiElement.anchor.set(0.5, 0.5);

            window.removeComponent(ViewportPosition);

        }

        const lifecycle = new SimpleLifecycle({priority});

        lifecycle.sm.addEventHandlerStateEntry(SimpleLifecycleStateType.Active, build);
        lifecycle.sm.addEventHandlerStateExit(SimpleLifecycleStateType.Active, destroy);

        this.getActiveSceneContext().modals.add(lifecycle);

        return lifecycle;
    }

    /**
     *
     * @param {string} title
     * @param {View} content
     * @param {ObservedBoolean|ReactiveExpression} [confirmationEnabled]
     * @returns {Promise<any>}
     */
    createModalConfirmation({title, content, confirmationEnabled}) {

        const self = this;

        let lifecycle = null;


        const result = new Promise(function (resolve, reject) {
            //make view

            let resolved = false;

            function clear() {
                lifecycle.makeDestroyed();
            }

            function callbackYes() {
                resolved = true;
                clear();
                resolve();
            }

            function callbackNo() {
                resolved = true;
                clear();
                reject();
            }

            const view = new ConfirmationDialogView(content,
                [{
                    name: "yes",
                    displayName: self.localization.getString("system_confirmation_confirm"),
                    callback: callbackYes,
                    enabled: confirmationEnabled
                }, {
                    name: "no",
                    displayName: self.localization.getString("system_confirmation_cancel"),
                    callback: callbackNo
                }]
            );

            lifecycle = self.createModal({
                content: view,
                title: title
            });

            lifecycle.sm.addEventHandlerStateEntry(SimpleLifecycleStateType.Destroyed, function () {
                if (!resolved) {
                    //if destroyed without resolution, reject the promise
                    reject();
                }
            });
        });


        return result;
    }

    /**
     * @param {string} text
     * @param {string} title
     * @returns {Promise} will be resolved or rejected based on user choice
     */
    confirmTextDialog({text, title}) {
        const content = createTextView(text);

        return this.createModalConfirmation({
            title,
            content: content
        });
    }

    /**
     *
     * @param {string} text
     * @param {string} title
     * @returns {Promise}
     */
    createTextAlert({text, title}) {
        const content = createTextView(text);
        return this.createAlert({
            content,
            title
        });
    }

    /**
     *
     * @param {View} content
     * @param {string} title
     * @param {View[]} [marks]
     * @param {number} priority
     * @param {function(SimpleLifecycle)} [lifecycleHook]
     * @returns {Promise}
     */
    createAlert(
        {
            content,
            title,
            marks = [],
            priority = 0,
            lifecycleHook = noop
        }
    ) {
        /**
         *
         * @type {SimpleLifecycle|null}
         */
        let lifecycle = null;

        function clear() {
            lifecycle.makeDestroyed();
        }

        const localization = this.localization;

        const view = new ConfirmationDialogView(content,
            [{
                name: "ok",
                displayName: localization.getString("system_confirmation_continue"),
                callback: clear
            }]
        );

        if (marks.length > 0) {
            const vMarks = new EmptyView({classList: ['marks']});

            marks.forEach(vMarks.addChild, vMarks);

            view.addChild(vMarks);
        }


        lifecycle = this.createModal({
            content: view,
            title,
            priority
        });

        const result = new Promise(function (resolve, reject) {
            lifecycle.sm.addEventHandlerStateEntry(SimpleLifecycleStateType.Destroyed, resolve);
        });

        lifecycleHook(lifecycle);

        return result;
    }

    /**
     *
     * @param {Scene} scene
     * @return {SceneGUIContext}
     */
    obtainSceneContext(scene) {

        let context = this.sceneContexts.get(scene);

        if (context === undefined) {
            context = new SceneGUIContext();

            context.initialize(scene);
            context.startup();

            this.sceneContexts.set(scene, context);
        }

        return context;
    }

    /**
     * @returns {SceneGUIContext|null}
     */
    getActiveSceneContext() {
        const engine = this.engine;

        if (engine === null) {
            throw new Error(`Engine is not set`);
        }

        const sm = engine.sceneManager;

        const scene = sm.current_scene;

        if (scene === null) {
            return null;
        }

        return this.obtainSceneContext(scene);
    }

    /**
     * Invoked when locale is updated
     * @private
     */
    __update_localization() {
        // write locale to the view class so that CSS can be modifier on per-locale basis
        this.view.removeClassesByPattern(/locale-/)

        this.view.addClass(`locale-${this.localization.locale.getValue()}`);
    }

    /**
     *
     * @param {Engine} engine
     */
    startup(engine) {
        this.engine = engine;
        this.entityManager = engine.entityManager;

        const self = this;

        /**
         *
         * @type {Localization}
         */
        const localization = engine.localization;

        this.gml.initialize(engine.staticKnowledge, localization);

        this.tooltips.initialize(this.gml, engine.devices.pointer);

        //attach tooltips to GML
        this.gml.tooltips = this.viewTooltips;

        this.view.addChild(this.tooltips.contextView);

        engine.gameView.addChild(this.view);

        engine.gameView.size.process(function (x, y) {
            self.view.size.set(x, y);

            self.tooltips.contextView.size.set(x, y);
        });

        this.ticker.start();

        this.localization = localization;


        //register cursor propagation
        this.cursor.process(function (newValue, oldValue) {
            function className(cursorName) {
                return `cursor-${cursorName}`;
            }

            const classList = engine.graphics.domElement.classList;

            if (typeof oldValue === 'string') {
                classList.remove(className(oldValue));
            }

            if (typeof newValue === 'string') {
                classList.add(className(newValue));
            }
        });

        // subscribe to localization changes
        this.localization.locale.onChanged.add(this.__update_localization, this);
        this.__update_localization();

        return Promise.all([
            this.tooltips.startup()
        ]);
    }

    shutdown() {
        this.windows.reset();
        this.entityManager = null;

        const pTooltips = this.tooltips.shutdown()

        // unsubscribe from localization changes
        this.localization.locale.onChanged.remove(this.__update_localization, this);

        return Promise.all([
            pTooltips
        ]);
    }
}

/**
 *
 * @param {View} view
 * @param {EntityComponentDataset} ecd
 */
function animateView(view, ecd) {

    const animationTrack = new AnimationTrack(["alpha", "scale"]);
    animationTrack.addKey(0, [0, 0.95]);
    animationTrack.addKey(0.2, [1, 1]);

    animationTrack.addTransition(0, TransitionFunctions.Linear);

    const playback = new AnimationTrackPlayback(animationTrack, function (alpha, scale) {
        this.el.style.opacity = alpha;
        this.scale.set(scale, scale);
    }, view);

    //force view status to initial key of animation
    playback.update();

    playTrackRealTime(playback, ecd);
}

/**
 *
 * @param {string} text
 * @return {View}
 */
function createTextView(text) {
    const content = new View();
    content.el = document.createElement('div');
    content.el.classList.add('text');
    content.el.innerText = text;

    content.size.set(300, 100);
    return content;
}

export default GUIEngine;
