"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Unit = exports.DEFAULT_PIN_OPT = void 0;
const _1 = require("../$");
const SELF_1 = require("../../constant/SELF");
const DuplicatedInputFoundError_1 = require("../../exception/DuplicatedInputFoundError");
const DuplicatedOutputFoundError_1 = require("../../exception/DuplicatedOutputFoundError");
const InputNotFoundError_1 = require("../../exception/InputNotFoundError");
const InvalidArgumentType_1 = require("../../exception/InvalidArgumentType");
const OutputNotFoundError_1 = require("../../exception/OutputNotFoundError");
const NOOP_1 = require("../../NOOP");
const Pin_1 = require("../../Pin");
const stringify_1 = require("../../spec/stringify");
const f_1 = __importDefault(require("../../system/core/object/ForEachKeyValue/f"));
const f_2 = require("../../system/f/object/Keys/f");
const array_1 = require("../../util/array");
const object_1 = require("../../util/object");
const toPinMap = (names, opts, system) => {
    return names.reduce((acc, name) => ({
        ...acc,
        [name]: new Pin_1.Pin(opts[name] ?? {}, system),
    }), {});
};
exports.DEFAULT_PIN_OPT = {
    ref: false,
};
class Unit extends _1.$ {
    constructor({ i = [], o = [] }, opt = {}, system, id) {
        super(system);
        this.__ = ['U'];
        this.lazy = false;
        this._parent = null;
        this._input = {};
        this._output = {};
        this._data_input = {};
        this._data_output = {};
        this._ref_input = {};
        this._ref_output = {};
        this._i_opt = {};
        this._o_opt = {};
        this._i_name_set = new Set();
        this._o_name_set = new Set();
        this._d_i_name = new Set();
        this._d_o_name = new Set();
        this._r_i_name = new Set();
        this._r_o_name = new Set();
        this._o_count = 0;
        this._i_count = 0;
        this._d_i_count = 0;
        this._d_o_count = 0;
        this._r_i_count = 0;
        this._r_o_count = 0;
        this._err = null;
        this.ref = null;
        this._paused = true;
        this._memRemoveDataInput = (name) => {
            this._d_i_count--;
            this._d_i_name.delete(name);
            delete this._data_input[name];
        };
        this._memRemoveRefInput = (name) => {
            this._r_i_count--;
            this._r_i_name.delete(name);
            delete this._ref_input[name];
        };
        this._memAddRefOutput = (name, output) => {
            this._r_o_count++;
            this._r_o_name.add(name);
            this._memSetRefOutput(name, output);
        };
        this._memSetRefOutput = (name, output) => {
            this._ref_output[name] = output;
        };
        this._memAddDataOutput = (name, output) => {
            this._d_o_count++;
            this._d_o_name.add(name);
            this._memSetDataOutput(name, output);
        };
        this._memSetDataOutput = (name, output) => {
            this._data_output[name] = output;
        };
        this._memRemoveRefPin = (type, name) => {
            if (type === 'input') {
                this._memRemoveRefInput(name);
            }
            else {
                this._memRemoveRefOutput(name);
            }
        };
        this._memRemoveRefOutput = (name) => {
            this._r_o_count--;
            this._r_o_name.delete(name);
            delete this._ref_output[name];
        };
        this._memRemoveDataOutput = (name) => {
            this._d_o_count--;
            this._d_o_name.delete(name);
            delete this._data_output[name];
        };
        this._catcherCallback = [];
        this._catcherDoneCount = 0;
        this._catcherDone = [];
        this._catchErr = false;
        this._caughtErr = null;
        const { input, output } = opt;
        const inputMap = toPinMap(i, opt.input ?? {}, this.__system);
        const outputMap = toPinMap(o, opt.output ?? {}, this.__system);
        this.setInputs(inputMap, input);
        this.setOutputs(outputMap, output);
        this._selfInput = new Pin_1.Pin({ data: this, constant: false, ref: true }, this.__system);
        this._selfOutput = new Pin_1.Pin({ data: this, constant: false, ref: true }, this.__system);
        this._selfOutput.addListener('drop', () => {
            throw new Error('self output cannot be dropped');
        });
        this.id = id;
    }
    isElement() {
        return false;
    }
    isPinIgnored(type, pinId) {
        const pin = this.getPin(type, pinId);
        const ignored = pin.ignored();
        return ignored;
    }
    isPinConstant(type, pinId) {
        const pin = this.getPin(type, pinId);
        const constant = pin.constant();
        return constant;
    }
    isPinRef(type, pinId) {
        const input = type === 'input';
        if (!input && pinId === SELF_1.SELF) {
            return true;
        }
        const ref = (input && !!this._ref_input[pinId]) ||
            (!input && !!this._ref_output[pinId]);
        return ref;
    }
    setParent(parent, ...extra) {
        this._parent = parent;
        this.emit('parent', this._parent);
    }
    removeParent() {
        this._parent = null;
        this.emit('parent', null);
    }
    setPinIgnored(type, pinId, ignored) {
        if (type === 'input') {
            this.setInputIgnored(pinId, ignored);
        }
        else {
            this.setOutputIgnored(pinId, ignored);
        }
    }
    setPinConstant(type, pinId, constant) {
        this.getPin(type, pinId).constant(constant);
        this.emit('set_pin_constant', type, pinId, constant);
    }
    _memSetPinOptData(type, pinId) {
        if (type === 'input') {
            this._memSetInputData(pinId);
        }
        else {
            this._memSetOutputData(pinId);
        }
    }
    _memSetInputData(pinId) {
        const input = this._input[pinId];
        this._memRemoveRefInput(pinId);
        this._memAddDataInput(pinId, input);
    }
    _memSetOutputData(pinId) {
        const output = this._output[pinId];
        this._memRemoveRefOutput(pinId);
        this._memAddDataOutput(pinId, output);
    }
    _memSetPinRef(type, pinId) {
        if (type === 'input') {
            this._memSetInputRef(pinId);
        }
        else {
            this._memSetOutputRef(pinId);
        }
    }
    _memSetInputRef(pinId) {
        const input = this._input[pinId];
        this._memRemoveDataInput(pinId);
        this._memAddRefInput(pinId, input);
    }
    _memSetOutputRef(pinId) {
        const output = this._output[pinId];
        this._memRemoveDataOutput(pinId);
        this._memAddRefOutput(pinId, output);
    }
    setPinRef(type, pinId, ref) {
        if (ref) {
            if (this.hasRefPinNamed(type, pinId)) {
                return;
            }
            else {
                this._memSetPinRef(type, pinId);
            }
        }
        else {
            if (this.hasPinNamed(type, pinId)) {
                return;
            }
            else {
                this._memSetPinOptData(type, pinId);
            }
        }
        this.emit('set_pin_ref', type, pinId, ref);
    }
    setInputRef(pinId, ref) {
        this.setPinRef('input', pinId, ref);
    }
    setOutputRef(pinId, ref) {
        this.setPinRef('output', pinId, ref);
    }
    setInputIgnored(pinId, ignore) {
        const input = this.getInput(pinId);
        if (this.hasRefInputNamed(pinId)) {
            return;
        }
        // return input.ignored(ignore)
    }
    setOutputIgnored(pinId, ignore) {
        const output = this.getOutput(pinId);
        return output.ignored(ignore);
    }
    setInputs(inputs, opts = {}) {
        for (const name in inputs) {
            const input = inputs[name];
            const opt = opts[name];
            this.setInput(name, input, opt);
        }
    }
    setPin(type, pinId, pin, opt = exports.DEFAULT_PIN_OPT, propagate = true) {
        if (type === 'input') {
            return this.setInput(pinId, pin, opt, propagate);
        }
        else {
            return this.setOutput(pinId, pin, opt, propagate);
        }
    }
    setInput(pinId, input, opt = exports.DEFAULT_PIN_OPT, propagate = true, hold = false) {
        this._setInput(pinId, input, opt, propagate);
        return this.react('set_input', pinId, input, opt, propagate, hold);
    }
    react(...args) {
        const [event, ...rest] = args;
        const emitArgs = rest.slice(0, -1);
        const hold = rest[rest.length - 1];
        const emit = () => {
            // @ts-ignore
            this.emit(event, ...emitArgs);
        };
        if (hold) {
            return emit;
        }
        emit();
        return NOOP_1.NOOP;
    }
    _setInput(pinId, input, opt = exports.DEFAULT_PIN_OPT, propagate) {
        if (this.hasInputNamed(pinId)) {
            this.removeInput(pinId, propagate);
        }
        this._i_count++;
        this._i_name_set.add(pinId);
        this._input[pinId] = input;
        this._i_opt[pinId] = opt;
        const { ref } = opt;
        if (ref) {
            this._memAddRefInput(pinId, input);
        }
        else {
            this._memAddDataInput(pinId, input);
        }
    }
    _memAddDataInput(name, input) {
        this._d_i_count++;
        this._d_i_name.add(name);
        this._data_input[name] = input;
    }
    _memAddRefInput(name, input) {
        this._r_i_count++;
        this._r_i_name.add(name);
        this._ref_input[name] = input;
    }
    _validateInputName(name) {
        if (typeof name === 'string') {
            if (!this.hasInputNamed(name)) {
                throw new InputNotFoundError_1.InputNotFoundError(name);
            }
        }
        else {
            throw new InvalidArgumentType_1.InvalidArgumentType('name should be a string');
        }
    }
    _validateOutputName(name) {
        if (typeof name === 'string') {
            if (!this.hasOutputNamed(name)) {
                throw new OutputNotFoundError_1.OutputNotFoundError(name);
            }
        }
        else {
            throw new InvalidArgumentType_1.InvalidArgumentType('name should be a string');
        }
    }
    addPin(type, name, pin, opt, propagate) {
        if (type === 'input') {
            this.addInput(name, pin, opt, propagate);
        }
        else {
            this.addOutput(name, pin, opt, propagate);
        }
    }
    addInput(name, input, opt = exports.DEFAULT_PIN_OPT, propagate) {
        if (this.hasInputNamed(name)) {
            throw new DuplicatedInputFoundError_1.DuplicatedInputFoundError(name);
        }
        this.setInput(name, input, opt, propagate);
    }
    removeInput(name, propagate = false) {
        if (!this.hasInputNamed(name)) {
            throw new InputNotFoundError_1.InputNotFoundError(name);
        }
        const input = this._input[name];
        this.emit('before_remove_input', name, input);
        this._i_count--;
        this._i_name_set.delete(name);
        const opt = this._i_opt[name];
        const { ref } = opt;
        if (ref) {
            this._memRemoveRefInput(name);
        }
        else {
            this._memRemoveDataInput(name);
        }
        delete this._i_opt[name];
        delete this._input[name];
        input.destroy();
        this.emit('remove_input', name, input, propagate);
    }
    setOutputs(outputs, opts = {}) {
        for (let name in outputs) {
            const output = outputs[name];
            const opt = opts[name];
            this.setOutput(name, output, opt);
        }
    }
    setOutput(name, output, opt = exports.DEFAULT_PIN_OPT, propagate = true, hold = false) {
        this._setOutput(name, output, opt, propagate);
        return this.react('set_output', name, output, opt, propagate, hold);
    }
    _setOutput(name, output, opt = exports.DEFAULT_PIN_OPT, propagate) {
        if (this.hasOutputNamed(name)) {
            this.removeOutput(name, propagate);
        }
        if (name === SELF_1.SELF) {
            this._selfOutput = output;
        }
        this._o_count++;
        this._o_name_set.add(name);
        this._o_opt[name] = opt;
        const { ref } = opt;
        if (ref) {
            this._memAddRefOutput(name, output);
        }
        else {
            this._memAddDataOutput(name, output);
        }
        this._output[name] = output;
    }
    addOutput(name, output, opt = exports.DEFAULT_PIN_OPT, propagate) {
        if (this.hasOutputNamed(name)) {
            throw new DuplicatedOutputFoundError_1.DuplicatedOutputFoundError(name);
        }
        this.setOutput(name, output, opt, propagate);
    }
    removeOutput(name, propagate) {
        if (!this.hasOutputNamed(name)) {
            throw new OutputNotFoundError_1.OutputNotFoundError(name);
        }
        const output = this._output[name];
        this.emit('before_remove_output', name, output);
        this._o_count--;
        this._o_name_set.delete(name);
        const opt = this._o_opt[name];
        const { ref } = opt;
        if (ref) {
            this._memRemoveRefOutput(name);
        }
        else {
            this._memRemoveDataOutput(name);
        }
        delete this._o_opt[name];
        delete this._output[name];
        output.destroy();
        this.emit('remove_output', name, output, propagate);
    }
    removePin(type, name, propagate) {
        if (type === 'input') {
            this.removeInput(name, propagate);
        }
        else {
            this.removeOutput(name, propagate);
        }
    }
    getPin(type, pinId) {
        if (type === 'input') {
            return this.getInput(pinId);
        }
        else {
            return this.getOutput(pinId);
        }
    }
    getInputs() {
        return this._input;
    }
    getDataInputs() {
        return this._data_input;
    }
    getRefInputs() {
        return this._ref_input;
    }
    getInput(name) {
        if (name === SELF_1.SELF) {
            return this._selfInput;
        }
        this._validateInputName(name);
        return this._input[name];
    }
    getOutputs() {
        return this._output;
    }
    getDataOutputs() {
        return this._data_output;
    }
    getRefOutputs() {
        return this._ref_output;
    }
    getOutput(name) {
        if (name === SELF_1.SELF) {
            return this._selfOutput;
        }
        this._validateOutputName(name);
        return this._output[name];
    }
    push(name, data) {
        this.pushInput(name, data);
    }
    pushInput(name, data) {
        this._validateInputName(name);
        this.getInput(name).push(data);
    }
    pushAllInput(data) {
        (0, f_1.default)(data, (value, name) => this.pushInput(name, value));
    }
    pushOutput(name, data) {
        this.getOutput(name).push(data);
    }
    pushAllOutput(data) {
        (0, f_1.default)(data, (value, name) => this.pushOutput(name, value));
    }
    pushAll(data) {
        this.pushAllInput(data);
    }
    pullInput(name) {
        return this.getInput(name).pull();
    }
    pullOutput(name) {
        return this.getOutput(name).pull();
    }
    pullPin(type, name) {
        return (type === 'input'
            ? this.pullInput(name)
            : this.pullOutput(name));
    }
    takePin(type, name) {
        return (type === 'input'
            ? this.takeInput(name)
            : this.takeOutput(name));
    }
    takeInput(name) {
        this._validateInputName(name);
        return this.getInput(name).take();
    }
    takeOutput(name) {
        return this.getOutput(name).take();
    }
    take(name) {
        return this.takeOutput(name);
    }
    takeAll() {
        return (0, object_1.mapObjVK)(this._output, (output) => output.take());
    }
    peakInput(name) {
        return this.getInput(name).peak();
    }
    peakOutput(name) {
        return this.getOutput(name).peak();
    }
    peak(name) {
        return this.peakOutput(name);
    }
    peakAllOutput() {
        return (0, object_1.mapObjVK)(this._output, (pin) => pin.peak());
    }
    peakAll() {
        return this.peakAllOutput();
    }
    hasPinNamed(type, name) {
        if (type === 'input') {
            return this.hasInputNamed(name);
        }
        else {
            return this.hasOutputNamed(name);
        }
    }
    hasInputNamed(name) {
        return this._input[name] !== undefined;
    }
    hasOutputNamed(name) {
        return this._output[name] !== undefined;
    }
    hasDataPinNamed(type, name) {
        if (type === 'input') {
            return this.hasDataInputNamed(name);
        }
        else {
            return this.hasDataOutputNamed(name);
        }
    }
    hasDataInputNamed(name) {
        return this._d_i_name.has(name);
    }
    hasDataOutputNamed(name) {
        return this._d_o_name.has(name);
    }
    hasRefPinNamed(type, name) {
        if (type === 'input') {
            return this.hasRefInputNamed(name);
        }
        else {
            return this.hasRefOutputNamed(name);
        }
    }
    hasRefInputNamed(name) {
        return this._r_i_name.has(name);
    }
    renamePin(type, name, newName) {
        if (type === 'input') {
            this.renameInput(name, newName);
        }
        else {
            this.renameOutput(name, newName);
        }
    }
    renameInput(name, newName, propagate = true) {
        // console.log('renameInput', name, newName, propagate)
        if (!this.hasInputNamed(name)) {
            throw new InputNotFoundError_1.InputNotFoundError(name);
        }
        const input = this._input[name];
        const opt = this._i_opt[name];
        this._i_name_set.delete(name);
        this._i_name_set.add(newName);
        delete this._input[name];
        delete this._i_opt[name];
        this._input[newName] = input;
        this._i_opt[newName] = opt;
        const { ref } = opt;
        if (ref) {
            this._r_i_name.delete(name);
            this._r_i_name.add(newName);
            delete this._ref_input[name];
            this._ref_input[newName] = input;
        }
        else {
            this._d_i_name.delete(name);
            this._d_i_name.add(newName);
            delete this._data_input[name];
            this._data_input[newName] = input;
        }
        this.emit('rename_input', name, newName);
    }
    renameOutput(name, newName, propagate = true) {
        if (!this.hasOutputNamed(name)) {
            throw new InputNotFoundError_1.InputNotFoundError(name);
        }
        const output = this._output[name];
        const opt = this._o_opt[name];
        this._o_name_set.delete(name);
        this._o_name_set.add(newName);
        delete this._output[name];
        this._output[newName] = output;
        delete this._o_opt[name];
        this._o_opt[newName] = opt;
        const { ref } = opt;
        if (ref) {
            this._r_o_name.delete(name);
            this._r_o_name.add(newName);
            delete this._ref_output[name];
            this._ref_output[newName] = output;
        }
        else {
            this._d_o_name.delete(name);
            this._d_o_name.add(newName);
            delete this._data_output[name];
            this._data_output[newName] = output;
        }
        this.emit('rename_output', name, newName);
    }
    getInputOpt(name) {
        return this._i_opt[name];
    }
    getOutputOpt(name) {
        return this._o_opt[name];
    }
    hasRefOutputNamed(name) {
        return this._r_o_name.has(name);
    }
    getInputCount() {
        return this._i_count;
    }
    getOutputCount() {
        return this._o_count;
    }
    getInputNames() {
        return (0, f_2.keys)(this._input);
    }
    getOutputNames() {
        return (0, f_2.keys)(this._output);
    }
    setPinData(type, pinId, data, propagate = true) {
        const pin = this.getPin(type, pinId);
        pin.push(data, false, propagate);
        this.emit('set_pin_data', type, pinId, data);
    }
    removePinData(type, pinId, propagate = true) {
        const pin = this.getPin(type, pinId);
        pin.take(propagate);
    }
    setInputConstant(pinId, constant) {
        const input = this.getInput(pinId);
        input.constant(constant);
    }
    setOutputConstant(pinId, constant) {
        const output = this.getOutput(pinId);
        output.constant(constant);
    }
    getCatchErr() {
        return this._catchErr;
    }
    caughtErr() {
        return this._caughtErr;
    }
    catch(callback) {
        (0, array_1.push)(this._catcherCallback, callback);
        (0, array_1.push)(this._catcherDone, false);
        this._catchErr = true;
        const err = this._err;
        if (err) {
            this._err = null;
            this._caughtErr = err;
            this.emit('catch_err', err);
        }
        const _check_all_done = () => {
            if (this._catcherDoneCount === this._catcherCallback.length) {
                _all_done();
            }
        };
        const _all_done = () => {
            const caughtErr = this._caughtErr;
            this._caughtErr = null;
            this._catcherDoneCount = 0;
            this._catcherDone.fill(false);
            this.emit('take_caught_err', caughtErr);
        };
        const unlisten = () => {
            const i = this._catcherCallback.indexOf(callback);
            if (i > -1) {
                (0, array_1.pull)(this._catcherCallback, callback);
                const done = this._catcherDone[i];
                (0, array_1.removeAt)(this._catcherDone, i);
                if (this._catcherCallback.length === 0) {
                    const err = this._caughtErr;
                    this._catchErr = false;
                    if (err !== null) {
                        this._caughtErr = null;
                        this.err(err);
                        this.emit('take_caught_err', err);
                    }
                }
                if (!done) {
                    _check_all_done();
                }
            }
            else {
                throw new Error('unregistered Catcher cannot call unlisten');
            }
        };
        const done = () => {
            if (this._caughtErr) {
                const i = this._catcherCallback.indexOf(callback);
                if (i > -1) {
                    const done = this._catcherDone[i];
                    if (done) {
                        throw new Error('catcher cannot be done twice');
                    }
                    else {
                        this._catcherDone[i] = true;
                        this._catcherDoneCount++;
                        _check_all_done();
                    }
                }
                else {
                    throw new Error('unregistered Catcher cannot call done');
                }
            }
            else {
                // throw new Error("catcher cannot call done when there's no caught error")
            }
        };
        return { unlisten, done };
    }
    getConfig() {
        return this._opt;
    }
    reset() {
        this.takeErr();
        this.emit('reset');
        for (const name of this._d_i_name) {
            const input = this.getInput(name);
            if (input.constant() && input.active()) {
                input.push(input.peak());
            }
        }
    }
    pause() {
        if (!this._paused) {
            this._paused = true;
            this.emit('pause');
        }
    }
    play() {
        if (this._paused) {
            this._paused = false;
            this.emit('play');
        }
    }
    paused() {
        return this._paused;
    }
    getSelfPin() {
        return this._selfOutput;
    }
    err(err) {
        // console.log('Unit', 'err', err)
        // everything is alright when there is no err
        if (err) {
            if (err instanceof Error) {
                err = err.message;
            }
            if (this._catchErr) {
                this._caughtErr = err;
                for (const callback of this._catcherCallback) {
                    callback(err);
                }
            }
            else {
                // exceptions in unit are isolated to the unit
                // they do not kill the application
                // they are expected though to stop the unit from functioning
                this._err = err;
                this.emit('err', err);
            }
        }
        return this._err;
    }
    hasErr() {
        return this._err !== null;
    }
    getErr() {
        return this._err;
    }
    takeErr() {
        const err = this._err;
        if (err) {
            this._err = null;
            this.emit('take_err', err);
        }
        return err;
    }
    takeCaughtErr() {
        const err = this._caughtErr;
        if (err) {
            this._caughtErr = null;
            for (const callback of this._catcherCallback) {
                callback(null);
            }
            this.emit('take_caught_err', err);
        }
        return err;
    }
    destroy() {
        super.destroy();
        for (const name of this._i_name_set) {
            const pin = this.getInput(name);
            pin.destroy();
        }
        for (const name of this._o_name_set) {
            const pin = this.getOutput(name);
            pin.destroy();
        }
        this._selfInput.destroy();
        this._selfOutput.destroy();
    }
    getPinData(type, pinId) {
        const pin = this.getPin(type, pinId);
        const data = pin.peak();
        return data;
    }
    getPinsData() {
        const data = {
            input: {},
            output: {},
        };
        (0, f_1.default)(this._input, (input, inputId) => {
            data.input[inputId] = input.peak();
        });
        (0, f_1.default)(this._output, (output, outputId) => {
            data.output[outputId] = output.peak();
        });
        return data;
    }
    getInputData() {
        const data = {};
        (0, f_1.default)(this._input, (input, inputId) => {
            if (!input.empty()) {
                const datum = input.peak();
                data[inputId] = datum;
            }
        });
        return data;
    }
    getRefInputData() {
        const data = {};
        (0, f_1.default)(this._ref_input, (input, inputId) => {
            if (!input.empty()) {
                let datum = input.peak();
                data[inputId] = datum;
            }
        });
        return data;
    }
    getSpec() {
        return this.__system.getSpec(this.id);
    }
    getUnitBundleSpec({ deep = false, system = false, state = false, error = false, } = {}) {
        const memory = this.snapshot({ deep, state });
        const input = (0, object_1.mapObjVK)(this._input, (input) => {
            const ignored = input.ignored();
            const constant = input.constant();
            const _data = input.peak();
            const data = constant && _data !== undefined ? (0, stringify_1.stringify)(_data) : undefined;
            return {
                ignored,
                constant,
                data,
            };
        });
        const output = (0, object_1.mapObjVK)(this._output, (output) => {
            const ignored = output.ignored();
            const constant = output.constant();
            const data = undefined;
            return {
                ignored,
                constant,
                data,
            };
        });
        const specs = {};
        const { id } = this;
        if (system) {
            specs[id] = this.__system.specs[id];
        }
        const bundle = {
            unit: { id, memory, input, output },
            specs,
        };
        if (error) {
            const err = this.getErr();
            if (err) {
                bundle.unit.err = err;
            }
        }
        return bundle;
    }
    snapshotSelf(opt = {}) {
        if (opt.error && this.hasErr()) {
            return { err: this._err };
        }
        return undefined;
    }
    snapshotInput(pinId) {
        return this.getInput(pinId).snapshot();
    }
    snapshotInputs() {
        const state = {};
        for (const name of this._i_name_set) {
            state[name] = this.snapshotInput(name);
        }
        return state;
    }
    snapshotOutput(pinId) {
        return this.getOutput(pinId).snapshot();
    }
    snapshotOutputs() {
        const state = {};
        for (const name of this._o_name_set) {
            state[name] = this.snapshotOutput(name);
        }
        return state;
    }
    snapshot(opt = { deep: true }) {
        const { deep, state } = opt;
        if (!deep && !state) {
            return undefined;
        }
        return {
            input: (deep && this.snapshotInputs()) || undefined,
            output: (deep && this.snapshotOutputs()) || undefined,
            memory: ((deep || state) && this.snapshotSelf(opt)) || undefined,
        };
    }
    restoreSelf(state) {
        return;
    }
    restoreInputs(state) {
        for (const name in state) {
            const memory = state[name];
            this.restoreInput(name, memory);
        }
    }
    restoreInput(pinId, state) {
        this.getInput(pinId).restore(state);
    }
    restoreOutputs(state) {
        for (const name in state) {
            const memory = state[name];
            this.restoreOutput(name, memory);
        }
    }
    restoreOutput(pinId, state) {
        this.getOutput(pinId).restore(state);
    }
    restore(state) {
        const { input, output, memory } = state;
        this.restoreInputs(input);
        this.restoreOutputs(output);
        this.restoreSelf(memory ?? {});
        this.emit('restore');
    }
}
exports.Unit = Unit;
//# sourceMappingURL=index.js.map