"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const vm = require("vm");
const domain = require("domain");
const rxjs_1 = require("rxjs");
const core_1 = require("@babel/core");
const core_2 = require("@ha4us/core");
const sandbox_class_1 = require("./sandbox.class");
var ScriptEventType;
(function (ScriptEventType) {
    ScriptEventType["Error"] = "error";
    ScriptEventType["Log"] = "log";
    ScriptEventType["State"] = "state";
})(ScriptEventType = exports.ScriptEventType || (exports.ScriptEventType = {}));
class ScriptEvent {
    constructor(name, type, data) {
        this.name = name;
        this.type = type;
        this.data = data;
    }
}
exports.ScriptEvent = ScriptEvent;
class LogEvent {
    constructor(severity, message, attachments) {
        this.severity = severity;
        this.message = message;
        this.attachments = attachments;
    }
}
exports.LogEvent = LogEvent;
class Ha4usScript {
    constructor(scriptObject, opts) {
        this.opts = opts;
        this.autostart = false;
        this._status = 'stopped';
        this.status$ = new rxjs_1.Subject();
        this.log$ = new rxjs_1.Subject();
        this.name = scriptObject.topic;
        this.topic = core_2.MqttUtil.slice(this.name, 1);
        this.path = opts.$args.scriptsDir || process.cwd();
        this.$log = opts.$log;
        this._source = scriptObject.native.source;
        this.autostart =
            scriptObject.native.autostart ||
                typeof scriptObject.native.autostart === 'undefined';
    }
    set source(val) {
        this._source = val;
    }
    set status(val) {
        this._status = val;
        this.status$.next(val);
    }
    get status() {
        return this._status;
    }
    log(severity, msg, attachments) {
        this.log$.next(new LogEvent(severity, msg, attachments));
    }
    prepareStack(e, match) {
        const lines = e.stack.split('\n');
        const stack = [];
        stack.push(e.message);
        for (const line of lines) {
            stack.push(line);
            if (line.match(match)) {
                // stack.splice(-1, 1)
                break;
            }
        }
        return stack.join('\n');
    }
    init() {
        return __awaiter(this, void 0, void 0, function* () {
            this.status = 'stopped';
            this.sandbox = new sandbox_class_1.Sandbox(this);
            this.domain = domain.create();
            this.enterDomain();
            this.$log.debug(`creating sandbox for ${this.name}`);
            this.domain.on('error', e => {
                this.$log.warn(`catched domain error ${e.message} in ${this.name}`);
                this.log$.next(new LogEvent('error', this.prepareStack(e, /domain:$/)));
            });
            return this;
        });
    }
    transpile(source) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                core_1.transform(source, {
                    presets: [
                        [
                            '@babel/env',
                            {
                                targets: {
                                    node: 'current',
                                },
                            },
                        ],
                    ],
                }, (err, res) => {
                    if (err) {
                        reject(err);
                    }
                    else {
                        this.$log.debug(`${this.name} transpiled`);
                        resolve(res.code);
                    }
                });
            });
        });
    }
    compile() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._source) {
                throw new core_2.Ha4usError(500, `source not available`);
            }
            try {
                const transpiled = yield this.transpile(this._source);
                const extSource = `((async ()=>{${transpiled}})())`;
                this.script = new vm.Script(extSource, {
                    filename: this.name,
                    timeout: 1000,
                });
                this.log('info', `script successfully compiled`);
                this.$log.debug(`${this.name} compiled`);
                return this;
            }
            catch (e) {
                this.$log.warn(`compilation of ${this.name} failed ${e.message}`);
                this.status = 'error';
                this.log('error', this.prepareStack(e, /at Ha4usScript.compile|at Parser.raise/));
                throw e;
            }
        });
    }
    enterDomain() {
        if (process.domain !== this.domain) {
            this.$log.debug(`${this.name} entering script domain`);
            this.domain.enter();
        }
    }
    start() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.status === 'running') {
                return this;
            }
            if (!this.script) {
                throw new core_2.Ha4usError(500, `script is not compiled`);
            }
            this.$log.debug('Starting script');
            const context = vm.createContext(this.sandbox.sandbox);
            return this.script
                .runInContext(context)
                .then((val) => {
                this.result = val;
                if (!this.sandbox._onDestroy) {
                    this.log('info', `script has no $onDestroy function`);
                }
                this.log('info', `script finished with result ${val}`);
                this.status = 'running';
                return this;
            })
                .catch(e => {
                this.log('error', this.prepareStack(e, /Script.runInContext/));
                this.status = 'error';
                throw new core_2.Ha4usError(500, 'error script execution', e);
            });
        });
    }
    stop() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.status !== 'running') {
                return this;
            }
            this.$log.debug('Stopping script');
            this.sandbox.stop$.next();
            try {
                if (this.sandbox._onDestroy) {
                    this.sandbox._onDestroy();
                }
            }
            catch (e) {
                this.log('error', this.prepareStack(e, /at ContextifyScript.Script.runInContext/));
            }
            finally {
                this.status = 'stopped';
            }
            return this;
        });
    }
    restart() {
        return __awaiter(this, void 0, void 0, function* () {
            this.$log.debug('Restarting script');
            yield this.stop();
            if (this.autostart === true) {
                yield this.start();
            }
            return this;
        });
    }
    destroy() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.stop();
            this.log('info', `script destroyed`);
            this.log$.complete();
            this.status$.complete();
            if (this.domain) {
                this.domain.removeAllListeners();
                this.domain.exit();
            }
            return this;
        });
    }
    toHa4usObject() {
        return {
            role: core_2.Ha4usRole.Script,
            native: {
                source: this._source,
            },
        };
    }
}
exports.Ha4usScript = Ha4usScript;
//# sourceMappingURL=ha4us-script.js.map