import EmptyView from "../../../view/elements/EmptyView.js";
import View from "../../../view/View.js";
import { System } from '../System.js';
import GUIElement, { GUIElementFlag } from './GUIElement.js';
import { GUIElementEvent } from "./GUIElementEvent.js";

/**
 * @this {el:GUIElement, system:GUIElementSystem, entity: number}
 * @param {boolean} v
 * @ignore
 */
function handleComponentVisibilityChange(v) {
    /**
     * @type {GUIElementSystem}
     */
    const system = this.system;
    /**
     * @type {GUIElement}
     */
    const component = this.el;

    /**
     * @type {number}
     */
    const entity = this.entity;

    if (v) {
        system.attachComponent(component, entity);
    } else {
        system.detachComponent(component, entity);
    }
}

export const EVENT_VIEW_INSTANTIATED = 'component.gui-element.view.instantiated';

class GUIElementSystem extends System {

    view = new EmptyView({
        classList: ["gui-system-root"],
        css: {

            position: "absolute",
            top: 0,
            left: 0,
            zIndex: 100,
            width: "inherit",
            height: "inherit",
            userSelect: "none",
            pointerEvents: "none"
        }
    });

    dependencies = [GUIElement];

    /**
     *
     * @type {Object<View>}
     */
    groups = {};

    /**
     *
     * @param {View} containerView
     * @param {Engine} engine
     * @constructor
     */
    constructor(containerView, engine) {
        if (containerView === undefined) {
            throw new Error(`mandatory parameter containerView is undefined`);
        }

        if (containerView === null) {
            throw new Error(`mandatory parameter containerView is null`);
        }

        if (!(containerView instanceof View)) {
            throw new TypeError(`mandatory parameter containerView is not an instance of View`);
        }

        if (engine === undefined) {
            throw new Error('mandatory parameter engine is undefined');
        }


        super();
        this.containerView = containerView;


        /**
         * @type {ModuleRegistry}
         */
        this.classRegistry = engine.moduleRegistry;

        /**
         *
         * @type {Engine}
         */
        this.engine = engine;

    }

    async startup(em) {
        this.entityManager = em;
        this.containerView.addChild(this.view);
    }

    async shutdown(em) {
        this.containerView.removeChild(this.view);
    }

    /**
     *
     * @param {GUIElement} component
     * @param {number} entity
     */
    attachComponent(component, entity) {

        const ecd = this.entityManager.dataset;

        //initialize view
        if (component.getFlag(GUIElementFlag.Managed) && !component.getFlag(GUIElementFlag.Initialized)) {
            //managed, but not initialized

            if (component.view.isViewEntity) {
                /**
                 *
                 * @type {ViewEntity}
                 */
                const view = component.view;

                try {
                    view.initialize(component.parameters, entity, ecd, this.engine);
                } catch (e) {
                    console.error('Failed to initialize GUIElement View:', e);
                }

                ecd.sendEvent(entity, GUIElementEvent.Initialized);

                component.setFlag(GUIElementFlag.Initialized);

            } else {
                console.warn(`component.view is not an instance of ViewEntity, cannot initialize`, component.view);
            }

        }

        let parent;

        const componentGroupId = component.group;
        if (componentGroupId !== null) {
            //component specifies a named group

            const group = this.groups[componentGroupId];

            if (group !== undefined) {
                parent = group;
            } else {
                // Group doesn't exist, create it
                parent = new EmptyView({
                    classList: [`gui-element-system-group-${componentGroupId}`]
                });

                this.view.addChild(parent);

                this.groups[componentGroupId] = parent;
            }
        } else {
            parent = this.view;
        }

        parent.addChild(component.view);


        ecd.sendEvent(entity, GUIElementEvent.Attached);
    }

    /**
     *
     * @param {GUIElement} component
     * @param {number} entity
     */
    detachComponent(component, entity) {

        const componentGroupId = component.group;

        if (componentGroupId !== null) {
            //component specifies a named group

            const group = this.groups[componentGroupId];

            if (group !== undefined) {
                group.removeChild(component.view);
            } else {
                console.error(`Named group '${componentGroupId}' was not found, failed to detach component properly`, component);
            }
        } else {
            this.view.removeChild(component.view);
        }

    }

    /**
     *
     * @param {GUIElement} component
     * @param entity
     */
    link(component, entity) {
        if (component.view === null) {
            //component view is not initialized

            if (!component.getFlag(GUIElementFlag.Managed)) {
                console.error(`Element for entity '${entity}' does not have a view. Cannot create a view because Element is not Managed`);
            } else {

                /**
                 *
                 * @type {Class<ViewEntity>}
                 */
                const ViewKlass = this.classRegistry.get(component.klass);

                if (ViewKlass === undefined) {
                    console.error(`Class '${component.klass}' not found in registry. Failed to instanciate view for entity '${entity}'. Using empty view`);

                    component.view = new EmptyView();
                } else {

                    /**
                     *
                     * @type {ViewEntity}
                     */
                    const view = new ViewKlass();


                    component.view = view;
                }

                const ecd = this.entityManager.dataset;

                ecd.sendEvent(entity, EVENT_VIEW_INSTANTIATED, entity);
            }
        }


        if (component.visible.getValue()) {
            this.attachComponent(component, entity);
        }

        component.visible.onChanged.add(handleComponentVisibilityChange, {
            el: component,
            system: this,
            entity
        });
    }

    /**
     *
     * @param {GUIElement} component
     * @param entity
     */
    unlink(component, entity) {
        component.visible.onChanged.remove(handleComponentVisibilityChange);

        if (component.visible.getValue()) {
            this.detachComponent(component, entity);
        }

        if (component.getFlag(GUIElementFlag.Managed) && component.getFlag(GUIElementFlag.Initialized)) {
            //managed and initialized, need to finalize
            component.view.finalize();

            //clear initialization flag
            component.clearFlag(GUIElementFlag.Initialized);
        }
    }
}

export default GUIElementSystem;
