import List from "../../core/collection/list/List.js";
import { Reference } from "../reference/v2/Reference.js";
import Signal from "../../core/events/signal/Signal.js";
import { ProcessState } from "../../core/process/ProcessState.js";
import { assert } from "../../core/assert.js";

const TRANSIENT_STATES = [
    ProcessState.Finalizing,
    ProcessState.Initializing,
    ProcessState.Starting,
    ProcessState.Stopping
];

/**
 *
 * @param {EnginePlugin} plugin
 * @param {Engine} engine
 * @returns {Promise}
 */
function navigate_to_initialized(plugin, engine) {

    const s = plugin.getState().getValue();

    if (s === ProcessState.Initialized || s === ProcessState.Stopped) {
        return Promise.resolve();
    }

    if (s === ProcessState.New || s === ProcessState.Finalized) {

        plugin.initialize(engine);

        return Promise.resolve();

    } else if (s === ProcessState.Running) {

        return plugin.shutdown();

    } else {
        return Promise.reject(`Unsupported initial state '${s}'`);
    }

}

/**
 *
 * @param {EnginePlugin} plugin
 * @param {Engine} engine
 * @returns {Promise}
 */
function navigate_to_finalized(plugin, engine) {

    const s = plugin.getState().getValue();

    if (s === ProcessState.New || s === ProcessState.Finalized) {
        return Promise.resolve();
    }

    if (s === ProcessState.Running) {
        return navigate_to_initialized(plugin, engine).then(() => {
            return plugin.finalize();
        });
    } else if (s === ProcessState.Stopped || s === ProcessState.Initialized) {
        plugin.finalize();

        return Promise.resolve();
    } else {
        return Promise.reject(`Unsupported initial state '${s}'`);
    }

}

/**
 *
 * @param {EnginePlugin} plugin
 * @param {Engine} engine
 * @returns {Promise}
 */
function navigate_to_running(plugin, engine) {

    const s = plugin.getState().getValue();

    if (s === ProcessState.Running) {
        return Promise.resolve();
    }

    if (s === ProcessState.Initialized || s === ProcessState.Stopped) {
        return plugin.startup();
    } else if (s === ProcessState.New || s === ProcessState.Finalized) {
        return navigate_to_initialized(plugin, engine).then(() => {
            return plugin.startup();
        });
    } else {
        return Promise.reject(`Unsupported initial state '${s}'`);
    }
}

export class PluginReferenceContext {
    /**
     *
     * @param {EnginePlugin} instance
     * @param {Class<T>} klass
     */
    constructor(instance, klass) {

        /**
         *
         * @type {Class<T>}
         * @private
         */
        this.__klass = klass;

        /**
         *
         * @type {EnginePlugin}
         * @private
         */
        this.__instance = instance;

        /**
         *
         * @type {List<Reference> }
         * @private
         */
        this.__references = new List();

        /**
         *
         * @type {List<Reference<EnginePlugin>>}
         */
        this.dependency_references = new List();

        this.on = {
            lastReleased: new Signal()
        };

        /**
         *
         * @type {Promise<void>}
         * @private
         */
        this.__transition = Promise.resolve();
    }

    /**
     * Returns promise to the latest transition, useful to synchronization
     * @example await ctx.synchronize();
     * @returns {Promise<void>}
     */
    synchronize() {
        return this.__transition;
    }

    /**
     *
     * @param {ProcessState} target_state
     * @param {Engine} engine
     * @returns {Promise<unknown>}
     */
    transition(target_state, engine) {

        assert.defined(engine, 'engine');
        assert.notNull(engine, 'engine');
        assert.enum(target_state, ProcessState, 'target_state');

        if (TRANSIENT_STATES.includes(target_state)) {
            return Promise.reject(`Requested state '${target_state}' is transient. Only steady states are allowed`);
        }

        const plugin = this.__instance;

        const promise = this.__transition.then(() => {
            return new Promise((resolve, reject) => {

                const state = plugin.getState().getValue();

                if (state === target_state) {
                    return resolve();
                }

                if (target_state === ProcessState.Initialized) {
                    return navigate_to_initialized(plugin, engine)
                        .then(resolve, reject);
                } else if (target_state === ProcessState.Running) {
                    return navigate_to_running(plugin, engine)
                        .then(resolve, reject);
                } else if (target_state === ProcessState.Finalized) {
                    return navigate_to_finalized(plugin, engine)
                        .then(resolve, reject);
                } else {
                    reject(`Unsupported target state '${target_state}'`);
                }

            });

        });

        this.__transition = promise;

        return promise;

    }

    async dispose() {
        // shutdown the plugin
        const plugin = this.__instance;
        const state = plugin.getState();

        if (state.getValue() === ProcessState.Running) {
            await plugin.shutdown();
        }

        if (state.getValue() === ProcessState.Stopped || state.getValue() === ProcessState.Initialized) {
            await plugin.finalize();
        }

        // release references of dependencies
        this.dependency_references.forEach(r => r.release());
    }


    /**
     *
     * @param {Reference} ref
     * @private
     */
    __handle_reference_released(ref) {


        const removed = this.__references.removeOneOf(ref);

        if (removed && this.__references.isEmpty()) {
            // last reference removed
            this.on.lastReleased.send2(this, ref);
        }
    }

    getKlass() {
        return this.__klass;
    }

    /**
     *
     * @returns {Reference}
     */
    getReference() {
        const r = new Reference();

        r.onReleased.add(this.__handle_reference_released, this);
        r.bind(this.__instance);

        this.__references.add(r);

        return r;
    }

    /**
     *
     * @returns {number}
     */
    get referenceCount() {
        return this.__references.length;
    }

    /**
     *
     * @returns {EnginePlugin}
     */
    get instance() {
        return this.__instance;
    }
}
