"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Primitive = void 0;
const _1 = require("./Class/$");
const Unit_1 = require("./Class/Unit");
const f_1 = require("./system/core/object/ForEachKeyValue/f");
class Primitive extends Unit_1.Unit {
    constructor({ i, o } = {}, opt = {}, system, id) {
        super({ i, o }, opt, system, id);
        this._i = {};
        this._o = {};
        this._active_i_count = 0;
        this._active_o_count = 0;
        this._i_start_count = 0;
        this._i_start = {};
        this._o_invalid_count = 0;
        this._o_invalid = {};
        this._i_invalid_count = 0;
        this._i_invalid = {};
        this._forwarding = false;
        this._backwarding = false;
        this._forwarding_empty = false;
        this._inputUnlisten = {};
        this._outputUnlisten = {};
        this.__buffer = [];
        this._setupInputs = (inputs) => {
            for (const name in inputs) {
                const input = inputs[name];
                const opt = this.getInputOpt(name);
                this._setupInput(name, input, opt);
            }
        };
        this._setupDataInput = (name, input) => {
            const dataListener = this._onDataInputData.bind(this, name);
            const dropListener = this._onDataInputDrop.bind(this, name);
            const invalidListener = this._onDataInputInvalid.bind(this, name);
            const startListener = this._onInputStart.bind(this, name);
            const endListener = this._onInputEnd.bind(this, name);
            input.addListener('_data', dataListener);
            input.addListener('data', dataListener);
            input.addListener('start', startListener);
            input.addListener('invalid', invalidListener);
            input.prependListener('drop', dropListener);
            input.prependListener('end', endListener);
            const unlisten = () => {
                input.removeListener('data', dataListener);
                input.removeListener('start', startListener);
                input.removeListener('invalid', invalidListener);
                input.removeListener('drop', dropListener);
                input.removeListener('end', endListener);
            };
            this._inputUnlisten[name] = unlisten;
        };
        this._setupRefInput = (name, input) => {
            const dataListener = this._onRefInputData.bind(this, name);
            const dropListener = this._onRefInputDrop.bind(this, name);
            const invalidListener = this._onRefInputInvalid.bind(this, name);
            input.addListener('data', dataListener);
            input.addListener('invalid', invalidListener);
            input.prependListener('drop', dropListener);
            const unlisten = () => {
                input.removeListener('data', dataListener);
                input.removeListener('invalid', invalidListener);
                input.removeListener('drop', dropListener);
            };
            this._inputUnlisten[name] = unlisten;
        };
        this._setupInput = (name, input, opt) => {
            const { ref } = opt;
            if (ref) {
                this._setupRefInput(name, input);
            }
            else {
                this._setupDataInput(name, input);
            }
        };
        this._setupOutputs = (outputs) => {
            for (let name in outputs) {
                const output = outputs[name];
                const opt = this.getOutputOpt(name);
                this._setupOutput(name, output, opt);
            }
        };
        this.__setupOutput = (name, output) => {
            const dataListener = this._onDataOutputData.bind(this, name);
            const dropListener = this._onDataOutputDrop.bind(this, name);
            const invalidListener = this._onOutputInvalid.bind(this, name);
            output.addListener('drop', dropListener);
            output.prependListener('data', dataListener);
            output.prependListener('invalid', invalidListener);
            const unlisten = () => {
                output.removeListener('data', dataListener);
                output.removeListener('drop', dropListener);
                output.removeListener('invalid', invalidListener);
            };
            this._outputUnlisten[name] = unlisten;
        };
        this._setupDataOutput = (name, output) => {
            this.__setupOutput(name, output);
            if (output.active()) {
                const data = output.peak();
                this._onDataOutputData(name, data);
            }
        };
        this._setupRefOutput = (name, output) => {
            this.__setupOutput(name, output);
            if (output.active()) {
                const data = output.peak();
                this._onRefOutputData(name, data);
            }
        };
        this._setupOutput = (name, output, opt) => {
            const { ref } = opt;
            if (ref) {
                this._setupRefOutput(name, output);
            }
            else {
                this._setupDataOutput(name, output);
            }
        };
        this._onDataInputData = (name, data) => {
            this._activateInput(name, data);
            if (!this._paused) {
                this.onDataInputData(name, data);
            }
            else {
                this.__buffer.push({
                    name,
                    type: 'input',
                    event: 'data',
                    data,
                    ref: false,
                });
            }
        };
        this._input_effemeral = {};
        this._onRefInputData = (name, data) => {
            this._activateInput(name, data);
            if (!this._paused) {
                this.__onRefInputData(name, data);
            }
            else {
                this.__buffer.push({
                    name,
                    type: 'input',
                    event: 'data',
                    data,
                    ref: true,
                });
            }
        };
        this.__onRefInputData = (name, data) => {
            if (data instanceof Function) {
                data = new data(this.__system);
                this._input_effemeral[name] = data;
                this._i[name] = data;
            }
            if (data instanceof _1.$) {
                data.register();
            }
            this.onRefInputData(name, data);
        };
        this._activateInput = (name, data) => {
            if (this._i[name] === undefined) {
                this._active_i_count++;
            }
            this._i[name] = data;
            if (this._i_invalid[name]) {
                delete this._i_invalid[name];
                this._i_invalid_count--;
            }
        };
        this._deactivateInput = (name) => {
            if (this._i[name] !== undefined) {
                delete this._i[name];
                this._active_i_count--;
            }
            if (this._i_invalid[name]) {
                delete this._i_invalid[name];
                this._i_invalid_count--;
            }
            if (this._i_start[name]) {
                delete this._i_start[name];
                this._i_start_count--;
            }
        };
        this._onDataInputDrop = (name, data) => {
            this._deactivateInput(name);
            if (!this._paused) {
                this.onDataInputDrop(name, data);
            }
            else {
                this.__buffer.push({ name, type: 'input', event: 'drop', ref: false });
            }
        };
        this._onRefInputDrop = (name, data) => {
            this._deactivateInput(name);
            if (!this._paused) {
                this.__onRefInputDrop(name, data);
            }
            else {
                this.__buffer.push({ name, type: 'input', event: 'drop', ref: true });
            }
        };
        this.__onRefInputDrop = (name, data) => {
            if (this._input_effemeral[name]) {
                const effemeral_unit = this._input_effemeral[name];
                delete this._input_effemeral[name];
                effemeral_unit.destroy();
            }
            this.onRefInputDrop(name, data);
        };
        this._onRefInputInvalid = (name) => {
            if (!this._i_invalid[name]) {
                this._i_invalid[name] = true;
                this._i_invalid_count++;
            }
            this.onRefInputInvalid(name);
        };
        this._activateOutput = (name) => {
            if (this._o[name] === undefined) {
                this._active_o_count++;
            }
            if (this._o_invalid[name]) {
                delete this._o_invalid[name];
                this._o_invalid_count--;
            }
        };
        this._deactivateOutput = (name) => {
            if (this._o[name] !== undefined) {
                this._active_o_count--;
                delete this._o[name];
            }
            if (this._o_invalid[name]) {
                delete this._o_invalid[name];
                this._o_invalid_count--;
            }
        };
        this.__onOutputData = (name, data) => {
            this._activateOutput(name);
            this._o[name] = data;
        };
        this._onDataOutputData = (name, data) => {
            this.__onOutputData(name, data);
            this.onDataOutputData(name, data);
        };
        this._onRefOutputData = (name, data) => {
            this.__onOutputData(name, data);
            this.onRefOutputData(name, data);
        };
        this._onDataOutputDrop = (name) => {
            this._deactivateOutput(name);
            if (!this._paused) {
                this.onDataOutputDrop(name);
            }
            else {
                this.__buffer.push({ name, type: 'output', event: 'drop', ref: false });
            }
        };
        this._onRefOutputDrop = (name) => {
            this._deactivateOutput(name);
            if (!this._paused) {
                this.onRefOutputDrop(name);
            }
            else {
                this.__buffer.push({ name, type: 'output', event: 'drop', ref: true });
            }
        };
        this._onOutputInvalid = (name) => {
            if (!this._o_invalid[name]) {
                this._o_invalid[name] = true;
                this._o_invalid_count++;
            }
            this.onOutputInvalid(name);
        };
        this._setupInputs(this._input);
        this._setupOutputs(this._output);
        this.addListener('set_input', this._onInputSet);
        this.addListener('set_output', this._onOutputSet);
        this.addListener('remove_input', this._onInputRemoved);
        this.addListener('remove_output', this._onOutputRemoved);
        this.addListener('rename_input', this._onInputRenamed);
        this.addListener('rename_output', this._onOutputRenamed);
        this.addListener('destroy', () => {
            (0, f_1.default)(this._input, (input, name) => {
                this._plunkInput(name, input);
            });
            (0, f_1.default)(this._output, (output, name) => {
                this._plunkOutput(name, output);
            });
        });
        this.addListener('reset', () => {
            this._backwarding = false;
            this._forwarding = false;
            this._forwarding_empty = false;
        });
        this.addListener('play', () => {
            while (this.__buffer.length > 0) {
                const { name, type, event, data } = this.__buffer.shift();
                if (type === 'input') {
                    const { ref } = this.getInputOpt(name);
                    if (event === 'data') {
                        if (ref) {
                            this.__onRefInputData(name, data);
                        }
                        else {
                            this.onDataInputData(name, data);
                        }
                    }
                    else {
                        if (ref) {
                            this.__onRefInputDrop(name, data);
                        }
                        else {
                            this.onDataInputDrop(name, data);
                        }
                    }
                }
                else {
                    if (event === 'drop') {
                        this.onDataOutputDrop(name);
                    }
                }
            }
        });
    }
    getActiveInputCount() {
        return this._active_i_count;
    }
    getActiveOutputCount() {
        return this._active_o_count;
    }
    getOutputData() {
        return this._o;
    }
    _plunkOutput(name, output) {
        const unlisten = this._outputUnlisten[name];
        unlisten && unlisten();
        delete this._outputUnlisten[name];
    }
    _plunkInput(name, input) {
        const unlisten = this._inputUnlisten[name];
        unlisten && unlisten();
        delete this._inputUnlisten[name];
    }
    _onInputSet(name, input, opt, propagate) {
        this._setupInput(name, input, opt);
        this.onInputSet(name, input, opt, propagate);
    }
    onInputSet(name, input, opt, propagate) {
        if (!input.empty()) {
            const data = input.peak();
            this._activateInput(name, data);
            if (propagate) {
                this._onInputStart(name);
                if (this.hasRefInputNamed(name)) {
                    this._onRefInputData(name, data);
                }
                else {
                    this._onDataInputData(name, data);
                }
            }
        }
    }
    _onOutputSet(name, output, opt, propagate) {
        this._setupOutput(name, output, opt);
        this.onOutputSet(name, output, opt, propagate);
    }
    onOutputSet(name, output, opt, propagate) { }
    _onInputRemoved(name, input) {
        this._plunkInput(name, input);
        if (input.active()) {
            this._deactivateInput(name);
        }
        this.onInputRemoved(name, input);
    }
    onInputRemoved(name, input) {
        // console.log(this.constructor.name, 'onInputRemoved', name)
    }
    _onOutputRemoved(name, output) {
        this._plunkOutput(name, output);
        if (!output.empty()) {
            this._deactivateOutput(name);
        }
        this.onOutputRemoved(name, output);
    }
    onOutputRemoved(name, output) {
        if (!output.empty()) {
            this._onDataOutputDrop(name);
        }
    }
    onDataInputData(name, data) { }
    onRefInputData(name, data) { }
    _onInputRenamed(name, newName) {
        // console.log('Primitive', '_onInputRenamed', name, newName)
        const input = this.getInput(newName);
        const opt = this.getInputOpt(newName);
        this._plunkInput(name, input);
        this._setupInput(newName, input, opt);
        this.onInputRenamed(name, newName, opt, opt);
    }
    _onOutputRenamed(name, newName) {
        // console.log('Primitive', '_onOutputRenamed', name, newName)
        const active = this._o[name] !== undefined;
        const output = this.getOutput(newName);
        const opt = this.getOutputOpt(newName);
        this._plunkOutput(name, output);
        this._setupOutput(newName, output, opt);
        if (active) {
            this._active_o_count--;
        }
        this.onOutputRenamed(name, newName, opt, opt);
    }
    onInputRenamed(name, newName, opt, newOpt) {
        //
    }
    onOutputRenamed(name, newName, opt, newOpt) {
        //
    }
    onDataInputDrop(name, data) { }
    onRefInputDrop(name, data) { }
    onDataOutputData(name, data) { }
    onDataOutputDrop(name) { }
    onRefOutputData(name, data) { }
    onRefOutputDrop(name) { }
    onOutputInvalid(name) { }
    _onInputEnd(name) {
        if (this._i_start[name]) {
            this._i_start[name] = false;
            this._i_start_count--;
        }
        this.onDataInputEnd(name);
    }
    onDataInputEnd(name) { }
    _onInputStart(name) {
        if (!this._i_start[name]) {
            this._i_start[name] = true;
            this._i_start_count++;
        }
        this.onDataInputStart(name);
    }
    onDataInputStart(name) { }
    _onDataInputInvalid(name) {
        if (!this._i_invalid[name]) {
            this._i_invalid[name] = true;
            this._i_invalid_count++;
        }
        this.onDataInputInvalid(name);
    }
    _start() {
        (0, f_1.default)(this._output, (output) => output.start());
    }
    _invalidate() {
        (0, f_1.default)(this._output, (output) => output.invalidate());
    }
    _end() {
        (0, f_1.default)(this._output, (output) => output.end());
    }
    _forward(name, data) {
        const output = this._output[name];
        this._forward_(output, data);
    }
    _forward_(output, data) {
        this._forwarding = true;
        output.push(data);
        this._forwarding = false;
    }
    _forward_all_empty() {
        this._forwarding_empty = true;
        (0, f_1.default)(this._output, (o) => o.take());
        this._forwarding_empty = false;
    }
    _forward_empty(name) {
        this._forwarding_empty = true;
        const output = this._output[name];
        output.take();
        this._forwarding_empty = false;
    }
    _backward_all() {
        this._backwarding = true;
        (0, f_1.default)(this._data_input, (i) => i.pull());
        this._backwarding = false;
    }
    _backward(name) {
        this._backwarding = true;
        const input = this._input[name];
        input.pull();
        this._backwarding = false;
    }
    onDataInputInvalid(name) { }
    onRefInputInvalid(name) { }
    snapshotSelf() {
        return Object.assign(Object.assign({}, super.snapshotSelf()), { 
            // __buffer: this.__buffer,
            _forwarding: this._forwarding, _backwarding: this._backwarding, _forwarding_empty: this._forwarding_empty });
    }
    restoreSelf(state) {
        const { __buffer, _forwarding, _backwarding, _forwarding_empty } = state, rest = __rest(state, ["__buffer", "_forwarding", "_backwarding", "_forwarding_empty"]);
        super.restoreSelf(rest);
        // this.__buffer = __buffer || []
        this._forwarding = _forwarding;
        this._backwarding = _backwarding;
        this._forwarding_empty = _forwarding_empty;
        this._i = {};
        this._o = {};
        this._active_i_count = 0;
        this._active_o_count = 0;
        this._i_start_count = 0;
        this._i_start = {};
        this._o_invalid_count = 0;
        this._o_invalid = {};
        this._i_invalid_count = 0;
        this._i_invalid = {};
        for (let name in this._input) {
            const pin = this._input[name];
            const data = pin.peak();
            this._i[name] = data;
            if (data !== undefined) {
                this._active_i_count++;
                this._i_start_count++;
                this._i_start[name] = true;
                if (pin.invalid()) {
                    this._i_invalid_count++;
                    this._i_invalid[name] = true;
                }
            }
        }
        for (let name in this._output) {
            const output = this._output[name];
            const data = output.peak();
            this._o[name] = data;
            if (data !== undefined) {
                this._active_o_count++;
                if (output.invalid()) {
                    this._o_invalid_count++;
                    this._o_invalid[name] = true;
                }
            }
        }
    }
}
exports.Primitive = Primitive;
//# sourceMappingURL=Primitive.js.map