import { assert } from "../core/assert.js";
import { computeSystemName } from "./ecs/computeSystemName.js";

export class EngineConfiguration {

    /**
     * @private
     * @type {System[]}
     */
    systems = [];

    /**
     * @private
     * @type {Class<EnginePlugin>[]}
     */
    plugins = [];

    /**
     * Static database
     * Can contain tables of structured data that the application often works with.
     * Engine startup will load all tables ensuring that data is ready once startup finishes
     * Use sparingly, this is not suitable for very large amounts of data as all the data will be in memory and deserialization will stall engine startup
     * Example: inventory item table, monster table
     * @private
     * @readonly
     * @type {StaticKnowledgeDataTableDescriptor[]}
     */
    knowledge = [];

    /**
     *
     * @type {Map<string,AssetLoader<any>>}
     */
    loaders = new Map();

    /**
     *
     * @param {string} type
     * @returns {boolean}
     */
    hasLoader(type) {
        return this.loaders.has(type);
    }

    /**
     * @template T
     * @param {string} type
     * @param {AssetLoader<T>} loader
     */
    addLoader(type, loader) {
        if (this.loaders.has(type)) {
            return false;
        }

        this.loaders.set(type, loader);

        return true;
    }

    /**
     * @template T
     * @param {string} type
     * @returns {AssetLoader<T>}
     */
    removeLoader(type) {
        const assetLoader = this.loaders.get(type);

        if (assetLoader !== undefined) {
            this.loaders.delete(type);
        }

        return assetLoader;
    }

    /**
     *
     * @param {Class<EnginePlugin>} plugin
     * @returns {boolean}
     */
    addPlugin(plugin) {
        assert.defined(plugin, 'plugin');

        this.plugins.push(plugin);

        return true;
    }

    /**
     *
     * @param {System} system
     * @returns {boolean}
     */
    addSystem(system) {
        assert.defined(system, 'system');
        assert.notNull(system, 'system');
        assert.equal(system.isSystem, true, "system.isSystem !== true");

        this.systems.push(system);

        return true;
    }

    /**
     * @param {System} systems
     */
    addManySystems(...systems) {
        assert.defined(systems, "systems");
        assert.isArray(systems, "systems");

        systems.forEach(s => this.addSystem(s));
    }

    /**
     *
     * @param {StaticKnowledgeDataTableDescriptor} table
     * @returns {boolean}
     */
    addDataTable(table) {
        assert.defined(table,'table');
        assert.notNull(table,'table');

        this.knowledge.push(table);

        return true;
    }

    /**
     *
     * @param {Engine} engine
     */
    async apply(engine) {
        // asset loaders
        const loader_promises = [];

        this.loaders.forEach((loader, type) => {
            loader_promises.push(engine.assetManager.registerLoader(type, loader));
        });

        await Promise.all(loader_promises);

        // Knowledge tables

        const knowledgeTableCount = this.knowledge.length;
        for (let i = 0; i < knowledgeTableCount; i++) {
            const tableDescriptor = this.knowledge[i];

            engine.staticKnowledge.add(tableDescriptor.id, tableDescriptor.source, tableDescriptor.table);
        }

        // Plugins

        const plugins = engine.plugins;

        const config_plugins = this.plugins;
        const pluginCount = config_plugins.length;
        for (let i = 0; i < pluginCount; i++) {
            const enginePlugin = config_plugins[i];

            await plugins.acquire(enginePlugin);
        }

        console.log(`Loaded ${pluginCount} plugins`);

        // Systems

        const em = engine.entityManager;

        const systemCount = this.systems.length;
        for (let i = 0; i < systemCount; i++) {
            const system = this.systems[i];

            if (engine.entityManager.hasSystem(system.constructor)) {
                // system already registered
                console.warn(`system '${computeSystemName(system)}' already registered`);
                continue;
            }

            await em.addSystem(system);
        }


    }
}
