import { systemVariable, ServiceStatusCodes, VarStatusCodes, systemError, Actions, ErrorCodes } from './DataModels/Types.js';
import { DataTree } from './DataModels/DataTree.js';
import { escape as escapeHtml } from 'html-escaper';
import { ErrorTray } from './DataModels/ErrorTray.js';
/**Organizzational class */
export class ServiceManager {
    constructor() {
        this.dataTree = new DataTree();
        this.errorTray = new ErrorTray("errortray");
        this.dataEngines = new Map();
        this.status = ServiceStatusCodes.Down;
        this._initPromise = new Promise((resolve) => {
            this._initResolve = resolve;
        });
        this._defaultEngine = null;
    }
    /**
     *
     * @param subsystemName
     * @param engine
     */
    AddEngine(engine) {
        let subsystemName = escapeHtml(engine.name);
        this.dataEngines.set(subsystemName, engine);
        engine.manager = this;
        if (this._defaultEngine === null)
            this._defaultEngine = engine;
    }
    SetDefaultEngine(engine) {
        if (!this.dataEngines.has(engine.name))
            this.AddEngine(engine);
        this._defaultEngine = engine;
    }
    GetEngine(engine_name) {
        if (typeof engine_name !== "string")
            throw Error("Engine Name must be a string");
        if (engine_name.toLocaleLowerCase() === "default")
            return this._defaultEngine;
        else
            return this.dataEngines.get(engine_name);
    }
    async Subscribe(engine_name, target) {
        if (typeof target.name !== "string" || typeof target.system !== "string")
            throw Error("CANNOT SUBSCRIBE variable " + target.name);
        await this.isInitialized();
        target.name = escapeHtml(target.name);
        target.system = escapeHtml(target.system);
        let engine = this.GetEngine(engine_name);
        if (engine) {
            if (this.dataTree.ExistVar(target)) {
                if (!engine.isVarSubscribed(target) && this.dataTree.GetVar(target).status !== VarStatusCodes.Pending) // var exist from localstorage but not yet subscribed
                    this.dataTree.UpdateStatus(target, VarStatusCodes.Pending);
            }
            else
                this.dataTree.Create(target);
            engine.RequestSubscription(target);
        }
        else {
            this.dataTree.Create(target);
            this.dataTree.UpdateStatus(target, VarStatusCodes.Error);
            this.CreateAndDispatchError(target.system, ErrorCodes.EngineNotExist, "", Actions.Subscribe);
            throw new Error(`Engine '${engine_name}' does not exist.`);
        }
    }
    async Unsubscribe(engine_name, target) {
        if (typeof target.name !== "string" || typeof target.system !== "string")
            throw Error("CANNOT UNSUBSCRIBE variable " + target.name);
        await this.isInitialized();
        let engine = this.GetEngine(engine_name);
        if (engine) {
            engine.RequestUnsubscription(target);
        }
        else {
            this.CreateAndDispatchError(target.system, ErrorCodes.EngineNotExist, "", Actions.Unsubscribe);
            throw new Error(`Engine '${engine_name}' does not exist.`);
        }
    }
    Update(data) {
        this.dataTree.Update(data);
    }
    async Read(engine_name, vars) {
        if (typeof vars !== "object")
            throw new TypeError("'system' must be a string and 'vars' an array of strings");
        await this.isInitialized();
        let engine = this.GetEngine(engine_name);
        if (engine) {
            let resp = await engine.Read(vars);
            // Maybe here we need something like STATUS OK - if was in error it is not clear that it would be subscribed
            // see issue https://github.com/JaS-HMI/jashmi/issues/2
            engine.UpdateVars(resp, VarStatusCodes.Subscribed, Actions.Read);
            return resp;
        }
        else {
            this.CreateAndDispatchError(engine_name, ErrorCodes.EngineNotExist, "", Actions.Read);
            throw new Error(`Engine '${engine_name}' does not exist.`);
        }
    }
    async Write(engine_name, vars, values) {
        if (typeof vars !== "object" ||
            typeof values !== "object")
            throw new TypeError("'system' must be a string and 'vars' and values cannot be null");
        await this.isInitialized();
        let engine = this.GetEngine(engine_name);
        if (engine) {
            let sys_vars = vars.map(v => { let x = new systemVariable(v); x.status = VarStatusCodes.Pending; return x; });
            this.dataTree.Update(sys_vars);
            let resp = await engine.Write(vars, values);
            // Maybe here we need something like STATUS OK - if was in error it is not clear that it would be subscribed
            // see issue https://github.com/JaS-HMI/jashmi/issues/2
            engine.UpdateVars(resp, VarStatusCodes.Subscribed, Actions.Write);
            return resp;
        }
        else {
            this.CreateAndDispatchError(engine_name, ErrorCodes.EngineNotExist, "", Actions.Write);
            throw new Error(`Engine '${engine_name}' does not exist.`);
        }
    }
    DispatchError(error) {
        this.errorTray.Create(error);
    }
    CreateAndDispatchError(system, code, target = "", action = "") {
        let error = new systemError(system, code, target, action);
        this.DispatchError(error);
    }
    async Init() {
        this.status = ServiceStatusCodes.Warming;
        let proms = [];
        Array.from(this.dataEngines.values()).forEach(engine => proms.push(engine._init()));
        await Promise.all(proms);
        this.status = ServiceStatusCodes.Ready;
        // signal that all the engines are added, can start 
        // adding variables to subscription list
        this._initResolve(true);
    }
    isInitialized() {
        return this._initPromise;
    }
}
// A bit ugly, but we must have a data instance that is shared 
// automatically between the ui-elements
export var Manager = new ServiceManager();