"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Primitive = void 0;
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_active = new Set();
        this._i_active = new Set();
        this._i_start = new Set();
        this._o_invalid = new Set();
        this._i_invalid = new Set();
        this._forwarding = false;
        this._backwarding = false;
        this._forwarding_empty = false;
        this._inputUnlisten = {};
        this._outputUnlisten = {};
        this.__buffer = [];
        this._replaying = false;
        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('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, backpropagation) => {
            if (backpropagation) {
                return;
            }
            if (!this._paused) {
                this._activateInput(name, data);
                this.onDataInputData(name, data);
            }
            else {
                this.__buffer.push({
                    name,
                    type: 'input',
                    event: 'data',
                    data,
                    ref: false,
                });
            }
        };
        this._onRefInputData = (name, data, backpropagation) => {
            if (backpropagation) {
                return;
            }
            if (!this._paused) {
                this._activateInput(name, data);
                this.__onRefInputData(name, data);
            }
            else {
                this.__buffer.push({
                    name,
                    type: 'input',
                    event: 'data',
                    data,
                    ref: true,
                });
            }
        };
        this.__onRefInputData = (name, data) => {
            this.onRefInputData(name, data);
        };
        this._activateInput = (name, data) => {
            this._i_active.add(name);
            this._i[name] = data;
            this._i_invalid.delete(name);
        };
        this._deactivateInput = (name) => {
            delete this._i[name];
            this._i_active.delete(name);
            this._i_invalid.delete(name);
            this._i_start.delete(name);
        };
        this._onDataInputDrop = (name, data) => {
            if (!this._paused) {
                this._deactivateInput(name);
                this.onDataInputDrop(name, data);
            }
            else {
                this.__buffer.push({ name, type: 'input', event: 'drop', ref: false });
            }
        };
        this._onRefInputDrop = (name, data) => {
            if (!this._paused) {
                this._deactivateInput(name);
                this.__onRefInputDrop(name, data);
            }
            else {
                this.__buffer.push({ name, type: 'input', event: 'drop', ref: true });
            }
        };
        this.__onRefInputDrop = (name, data) => {
            this.onRefInputDrop(name, data);
        };
        this._onRefInputInvalid = (name) => {
            this._i_invalid.add(name);
            this.onRefInputInvalid(name);
        };
        this._activateOutput = (name) => {
            this._o_active.add(name);
            this._o_invalid.delete(name);
        };
        this._deactivateOutput = (name) => {
            this._o_active.delete(name);
            this._o_invalid.delete(name);
        };
        this.__onOutputData = (name, data) => {
            this._activateOutput(name);
        };
        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) => {
            this._o_invalid.add(name);
            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.__buffer = [];
        });
        this.addListener('play', () => {
            while (this.__buffer.length > 0) {
                this._replaying = true;
                const { name, type, event, data } = this.__buffer.shift();
                if (!this.__buffer.length) {
                    this._replaying = false;
                }
                if (type === 'input') {
                    const { ref } = this.getInputOpt(name);
                    if (event === 'data') {
                        this._activateInput(name, data);
                        if (ref) {
                            this.__onRefInputData(name, data);
                        }
                        else {
                            this.onDataInputData(name, data);
                        }
                    }
                    else if (event === 'drop') {
                        this._deactivateInput(name);
                        if (ref) {
                            this.__onRefInputDrop(name, data);
                        }
                        else {
                            this.onDataInputDrop(name, data);
                        }
                    }
                    else if (event === 'invalid') {
                        if (ref) {
                            this._onRefInputInvalid(name);
                        }
                        else {
                            this._onDataInputInvalid(name);
                        }
                    }
                    else if (event === 'start') {
                        this._onInputStart(name);
                    }
                }
            }
        });
    }
    getActiveInputCount() {
        return this._i_active.size;
    }
    getActiveOutputCount() {
        return this._o_active.size;
    }
    _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];
        delete this._i[name];
        this._i_active.delete(name);
        this._i_start.delete(name);
        this._i_invalid.delete(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();
            if (this._paused) {
                this.__buffer.push({
                    name,
                    type: 'input',
                    event: 'data',
                    data,
                    ref: opt.ref,
                });
                return;
            }
            this._activateInput(name, data);
            if (propagate) {
                this._onInputStart(name);
                if (this.hasRefInputNamed(name)) {
                    this._onRefInputData(name, data, false);
                }
                else {
                    this._onDataInputData(name, data, false);
                }
            }
        }
    }
    _onOutputSet(name, output, opt, propagate) {
        this._setupOutput(name, output, opt);
        this.onOutputSet(name, output, opt, propagate);
    }
    onOutputSet(name, output, opt, propagate) { }
    _onInputRemoved(name, input, propagate) {
        this._plunkInput(name, input);
        if (input.active()) {
            this._deactivateInput(name);
        }
        this.onInputRemoved(name, input, propagate);
    }
    onInputRemoved(name, input, propagate) {
        // console.log(this.constructor.name, 'onInputRemoved', name)
    }
    _onOutputRemoved(name, output, propagate) {
        this._plunkOutput(name, output);
        if (!output.empty()) {
            this._deactivateOutput(name);
        }
        this.onOutputRemoved(name, output, propagate);
    }
    onOutputRemoved(name, output, propagate) {
        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);
        const data = this._i[name];
        const active = this._i_active.has(name);
        const start = this._i_start.has(name);
        const invalid = this._i_invalid.has(name);
        this._plunkInput(name, input);
        this._setupInput(newName, input, opt);
        this._i[newName] = data;
        if (start) {
            this._i_start.add(newName);
        }
        if (active) {
            this._i_active.add(newName);
        }
        if (invalid) {
            this._i_invalid.add(newName);
        }
        this.onInputRenamed(name, newName, opt, opt);
    }
    _onOutputRenamed(name, newName) {
        // console.log('Primitive', '_onOutputRenamed', name, newName)
        const output = this.getOutput(newName);
        const opt = this.getOutputOpt(newName);
        this._plunkOutput(name, output);
        this._setupOutput(newName, output, opt);
        this._o_active.delete(name);
        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) {
        this._i_start.delete(name);
        this.onDataInputEnd(name);
    }
    onDataInputEnd(name) { }
    _onInputStart(name) {
        if (this._paused) {
            this.__buffer.push({
                name,
                type: 'input',
                event: 'start',
                data: undefined,
                ref: false,
            });
            return;
        }
        this._i_start.add(name);
        this.onDataInputStart(name);
    }
    onDataInputStart(name) { }
    _onDataInputInvalid(name) {
        if (this._paused) {
            this.__buffer.push({
                name,
                type: 'input',
                event: 'invalid',
                data: undefined,
                ref: false,
            });
            return;
        }
        this._i_invalid.add(name);
        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, name) => {
            if (this._replaying) {
                this._deactivateInput(name);
                return;
            }
            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 {
            ...super.snapshotSelf(),
            // __buffer: this.__buffer,
            _forwarding: this._forwarding,
            _backwarding: this._backwarding,
            _forwarding_empty: this._forwarding_empty,
        };
    }
    restoreSelf(state) {
        const { __buffer, _forwarding, _backwarding, _forwarding_empty, ...rest } = state;
        super.restoreSelf(rest);
        // this.__buffer = __buffer || []
        this._forwarding = _forwarding;
        this._backwarding = _backwarding;
        this._forwarding_empty = _forwarding_empty;
        this._i = {};
        for (let name in this._input) {
            const input = this._input[name];
            const data = input.peak();
            this._i[name] = data;
            if (data !== undefined) {
                this._i_active.add(name);
                this._i_start.add(name);
                if (input.invalid()) {
                    this._i_invalid.add(name);
                }
            }
        }
        for (let name in this._output) {
            const output = this._output[name];
            const data = output.peak();
            if (data !== undefined) {
                this._o_active.add(name);
                if (output.invalid()) {
                    this._o_invalid.add(name);
                }
            }
        }
    }
}
exports.Primitive = Primitive;
//# sourceMappingURL=Primitive.js.map