"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.Functional = void 0;
const Primitive_1 = require("../../Primitive");
const f_1 = require("../../system/core/object/ForEachKeyValue/f");
const object_1 = require("../../util/object");
class Functional extends Primitive_1.Primitive {
    constructor({ i, o } = {}, opt = {}, system, id) {
        super({ i, o }, opt, system, id);
        this._looping = false;
        this._on_data_output_drop = (name) => {
            this._backward_if_ready();
            this._forward_if_ready();
        };
        this._done = (data, err = null) => {
            if (err) {
                this._forwarding = true;
                this.err(err);
                this._forwarding = false;
                this._forward_all_empty();
            }
            else {
                if (data !== undefined) {
                    const output_empty = (0, object_1.filterObj)(this._output, (o) => o.empty());
                    const output_not_empty = (0, object_1.filterObj)(this._output, (o) => !o.empty());
                    const push_or_pull = (output, name) => {
                        const o = data[name];
                        if (o === undefined) {
                            output.pull();
                        }
                        else {
                            output.push(o);
                        }
                    };
                    this._forwarding = true;
                    (0, f_1.default)(output_empty, push_or_pull);
                    (0, f_1.default)(output_not_empty, push_or_pull);
                    this._forwarding = false;
                }
            }
            this._backward_if_ready();
        };
        this.addListener('take_err', () => {
            if (this._looping) {
                this._backward_if_ready();
            }
        });
        this.addListener('take_caught_err', () => {
            if (!this.hasErr()) {
                this._backward_if_ready();
            }
        });
    }
    _on_input_data(name) {
        if (this._i[name] !== undefined && this._active_i_count === this._i_count) {
            this._looping = false;
        }
        this._forward_if_ready();
    }
    _on_input_drop(name, data) {
        if (this._active_i_count === this._i_count - 1) {
            if (!this._backwarding && !this._forwarding_empty) {
                this._looping = false;
                if (this._catchErr) {
                    this.takeCaughtErr();
                }
                else {
                    this.takeErr();
                }
                this.d(name, data);
                this._forward_all_empty();
            }
        }
    }
    _on_input_invalid(name) {
        if (this._i_invalid_count === 1) {
            this.i(name);
            this._invalidate();
        }
    }
    onDataInputData(name) {
        this._on_input_data(name);
    }
    onDataInputDrop(name, data) {
        this._on_input_drop(name, data);
    }
    onDataInputStart(name) {
        if (this._i_start_count === this._i_count) {
            this._start();
        }
    }
    onDataInputInvalid(name) {
        this._on_input_invalid(name);
    }
    onDataInputEnd(name) {
        if (this._i_start_count === this._i_count - 1) {
            this._end();
        }
    }
    onRefInputData(name) {
        this._on_input_data(name);
    }
    onRefInputDrop(name, data) {
        this._on_input_drop(name, data);
    }
    onRefInputInvalid(name) {
        this._on_input_invalid(name);
    }
    onDataOutputDrop(name) {
        this._on_data_output_drop(name);
    }
    f(i, done) { }
    i(name) { }
    d(name, data) { }
    _backward_if_ready() {
        if (!this._forwarding_empty &&
            !this._forwarding &&
            !this._err &&
            !this._caughtErr &&
            this._active_o_count === 0) {
            this._looping = false;
            this._backward_all();
            // without setTimeout an infinite loop will turn into a stack overflow
            // setTimeout(() => {
            this._forward_if_ready();
            // }, 0)
            // this.__system.tick(this._forward_if_ready.bind(this))
        }
    }
    _forward_if_ready() {
        // console.log('Functional', '_forward_if_ready')
        while (this._active_o_count - this._o_invalid_count === 0 &&
            this._active_i_count - this._i_invalid_count === this._i_count &&
            !this._forwarding &&
            !this._backwarding &&
            !this._looping // prevent async infinite while loop
        ) {
            this._looping = true;
            if (this._err !== null) {
                this._forwarding_empty = true;
                this.takeErr();
                this._forwarding_empty = false;
            }
            this.f(this._i, this._done);
        }
    }
    snapshotSelf() {
        return Object.assign(Object.assign({}, super.snapshotSelf()), { _looping: this._looping });
    }
    restoreSelf(state) {
        const { _looping } = state, rest = __rest(state, ["_looping"]);
        super.restoreSelf(rest);
        this._looping = _looping;
    }
}
exports.Functional = Functional;
//# sourceMappingURL=index.js.map