import Entity from "./Entity.js";

/**
 *
 * @param {Object} template
 * @param {Object} [seed]
 */
function populateJsonTemplate(template, seed) {
    if (seed === undefined) {
        return template;
    } else if (template === null) {
        return template;
    } else {

        const templateType = typeof template;

        if (templateType === "string") {
            if (template.startsWith('$')) {
                const varName = template.slice(1);

                return seed[varName];
            } else {
                return template;
            }
        } else if (templateType === "object") {

            if (Array.isArray(template)) {
                //populate array
                const l = template.length;

                const result = [];

                for (let i = 0; i < l; i++) {
                    result[i] = populateJsonTemplate(template[i], seed);
                }

                return result;
            } else {
                const result = {};

                for (const propertyName in template) {
                    const templateValue = template[propertyName];

                    result[propertyName] = populateJsonTemplate(templateValue, seed);
                }

                return result;
            }

        } else {
            return template;
        }

    }
}

/**
 * Template for instantiating entities
 * @class
 */
export class EntityBlueprint {
    /**
     * @private
     * @type {Map<Class, Object>}
     */
    #components = new Map();

    /**
     *
     * @param {Array} components
     * @returns {EntityBlueprint}
     */
    static from(components = []) {
        const r = new EntityBlueprint();

        for (let i = 0; i < components.length; i++) {
            const component = components[i];

            r.add(component);
        }

        return r;
    }

    /**
     *
     * @param {Object} component
     */
    add(component) {
        const constructor = Object.getPrototypeOf(component).constructor;

        this.addJSON(constructor, component.toJSON());
    }

    clear() {
        this.#components.clear();
    }

    /**
     * @template T
     * @param {Class<T>} klass
     * @param {Object} json
     */
    addJSON(klass, json) {
        this.#components.set(klass, json);
    }

    /**
     *
     * @param {Object} json
     * @param {ModuleRegistry} registry
     */
    fromJSON(json, registry) {
        const component_class_names = Object.keys(json);

        const component_count = component_class_names.length;

        for (let i = 0; i < component_count; i++) {
            const componentClassName = component_class_names[i];

            const ComponentClass = registry.get(componentClassName);

            const component_data = json[componentClassName];

            this.addJSON(ComponentClass, component_data);
        }
    }

    /**
     * @param {Object} json
     * @param {ModuleRegistry} registry
     */
    static fromJSON(json, registry) {
        const r = new EntityBlueprint();

        r.fromJSON(json, registry);

        return r;
    }


    /**
     * @param {object} [templateSeed]
     * @return {Entity}
     */
    build(templateSeed) {
        const eb = new Entity();

        this.#components.forEach((template, ComponentClass) => {
            const component = new ComponentClass();

            const json = populateJsonTemplate(template, templateSeed);

            component.fromJSON(json);

            eb.add(component);
        });

        return eb;
    }
}
