# Source: Behavior.js

    "use strict";
    const Future_1 = require("./Future");
    const F = require("./Future");
    /**
     * A behavior is a value that changes over time. Conceptually it can
     * be though of as a function from time to a value. I.e. `type
     * Behavior<A> = (t: Time) => A`.
     */
    class Behavior {
        constructor() {
            this.cbListeners = [];
            // The consumers that depends on this behavior
            this.listeners = [];
            this.of = of;
        }
        publish(b) {
            this.last = b;
            let l = this.cbListeners.length;
            for (let i = 0; i < l; i++) {
                this.cbListeners[i](b);
            }
            l = this.listeners.length;
            for (let i = 0; i < l; i++) {
                this.listeners[i].push(b, this);
            }
        }
        ;
        set def(b) {
            b.cbListeners.push(...this.cbListeners);
            b.listeners.push(...this.listeners);
            this.cbListeners = b.cbListeners;
            this.listeners = b.listeners;
        }
        map(fn) {
            const newB = new MapBehavior(this, fn);
            this.listeners.push(newB);
            return newB;
        }
        chain(fn) {
            const newB = new ChainBehavior(this, fn);
            this.listeners.push(newB);
            return newB;
        }
        addListener(listener) {
            this.listeners.push(listener);
        }
        unlisten(listener) {
            // The indexOf here is O(n), where n is the number of listeners,
            // if using a linked list it should be possible to perform the
            // unsubscribe operation in constant time.
            const l = this.listeners;
            const idx = l.indexOf(listener);
            if (idx !== -1) {
                // if the subscriber is not at the end of the list we overwrite
                // it with the element at the end of the list
                if (idx !== l.length - 1) {
                    l[idx] = l[l.length - 1];
                }
                l.length--; // remove the last element of the list
            }
        }
    }
    Behavior.of = of;
    exports.Behavior = Behavior;
    function of(val) {
        return new ConstantBehavior(val);
    }
    exports.of = of;
    // Impure function that gets the current value of a behavior.
    function at(b) {
        return b.pushing === true ? b.last : b.pull();
    }
    exports.at = at;
    class ConstantBehavior extends Behavior {
        constructor(last) {
            super();
            this.last = last;
            this.pushing = true;
        }
        push() {
            throw new Error("Cannot push a value to a constant behavior");
        }
        pull() {
            return this.last;
        }
    }
    class MapBehavior extends Behavior {
        constructor(parent, fn) {
            super();
            this.parent = parent;
            this.fn = fn;
            this.pushing = parent.pushing;
            if (parent.pushing) {
                this.last = fn(at(parent));
            }
        }
        push(a) {
            this.pushing = true;
            this.last = this.fn(a);
            this.publish(this.last);
        }
        pull() {
            return this.fn(at(this.parent));
        }
    }
    class ChainBehavior extends Behavior {
        constructor(outer, fn) {
            super();
            this.outer = outer;
            this.fn = fn;
            this.innerB = this.fn(at(this.outer));
            this.pushing = outer.pushing && this.innerB.pushing;
            this.innerB.listeners.push(this);
            this.last = at(this.innerB);
        }
        push(a, changed) {
            if (changed === this.outer) {
                this.innerB.unlisten(this);
                const newInner = this.fn(at(this.outer));
                newInner.addListener(this);
                this.innerB = newInner;
            }
            this.last = at(this.innerB);
            this.publish(this.last);
        }
        pull() {
            return at(this.fn(at(this.outer)));
        }
    }
    class FunctionBehavior extends Behavior {
        constructor(fn) {
            super();
            this.fn = fn;
            this.pushing = false;
        }
        push(v) {
            throw new Error("Cannot push to a FunctionBehavior");
        }
        pull() {
            return this.fn();
        }
    }
    /** @private */
    class ApBehavior extends Behavior {
        constructor(fn, val) {
            super();
            this.fn = fn;
            this.val = val;
            this.pushing = fn.pushing && val.pushing;
            this.last = at(fn)(at(val));
        }
        push() {
            const fn = at(this.fn);
            const val = at(this.val);
            this.last = fn(val);
            this.publish(this.last);
        }
        pull() {
            return at(this.fn)(at(this.val));
        }
    }
    class SinkBehavior extends Behavior {
        constructor(last) {
            super();
            this.last = last;
            this.pushing = true;
        }
        push(v) {
            this.last = v;
            this.publish(v);
        }
        pull() {
            return this.last;
        }
    }
    /**
     * A placeholder behavior is a behavior without any value. It is used
     * to do value recursion in `./framework.ts`.
     * @private
     */
    class PlaceholderBehavior extends Behavior {
        constructor() {
            super();
            this.pushing = false;
        }
        push(v) {
            this.last = v;
            this.publish(v);
        }
        pull() {
            return this.last;
        }
        replaceWith(b) {
            this.source = b;
            b.addListener(this);
            this.pushing = b.pushing;
            if (b.pushing === true) {
                this.push(at(b));
            }
        }
    }
    exports.PlaceholderBehavior = PlaceholderBehavior;
    function placeholder() {
        return new PlaceholderBehavior();
    }
    exports.placeholder = placeholder;
    class WhenBehavior extends Behavior {
        constructor(parent) {
            super();
            this.parent = parent;
            this.pushing = true;
            parent.addListener(this);
            this.push(at(parent));
        }
        push(val) {
            if (val === true) {
                this.last = Future_1.Future.of({});
            }
            else {
                this.last = new Future_1.BehaviorFuture(this.parent);
            }
        }
        pull() {
            return this.last;
        }
    }
    /**
     * Take a behavior `b` of booleans and return a behavior that for time
     * `t` contains a future that occurs when `b` is true after `t`.
     * @param b - A boolean valued behavior.
     */
    function when(b) {
        return new WhenBehavior(b);
    }
    exports.when = when;
    // FIXME: This can probably be made less ugly.
    class SnapshotBehavior extends Behavior {
        constructor(parent, future) {
            super();
            this.parent = parent;
            if (future.occured === true) {
                // Future has occurred at some point in the past
                this.afterFuture = true;
                this.pushing = parent.pushing;
                parent.addListener(this);
                this.last = Future_1.Future.of(at(parent));
            }
            else {
                this.afterFuture = false;
                this.pushing = true;
                this.last = F.sink();
                future.listen(this);
            }
        }
        push(val) {
            if (this.afterFuture === false) {
                // The push is coming from the Future, it has just occurred.
                this.afterFuture = true;
                this.last.resolve(at(this.parent));
                this.parent.addListener(this);
            }
            else {
                // We are recieving an update from `parent` after `future` has
                // occurred.
                this.last = Future_1.Future.of(val);
            }
        }
        pull() {
            return this.last;
        }
    }
    function snapshot(behavior, future) {
        return new SnapshotBehavior(behavior, future);
    }
    exports.snapshot = snapshot;
    class SwitcherBehavior extends Behavior {
        constructor(behavior, next) {
            super();
            this.behavior = behavior;
            this.last = at(behavior);
            behavior.addListener(this);
            // FIXME: Using `bind` is hardly optimal for performance.
            next.subscribe(this.doSwitch.bind(this));
        }
        push(val) {
            this.last = val;
            this.publish(val);
        }
        pull() {
            return this.last;
        }
        doSwitch(newBehavior) {
            this.behavior.unlisten(this);
            newBehavior.addListener(this);
            this.push(at(newBehavior));
        }
    }
    /**
     * From an initial behavior and a future of a behavior `switcher`
     * creates a new behavior that acts exactly like `initial` until
     * `next` occurs after which it acts like the behavior it contains.
     */
    function switcher(init, next) {
        return new SwitcherBehavior(init, next);
    }
    exports.switcher = switcher;
    class StepperBehavior extends Behavior {
        constructor(initial, steps) {
            super();
            this.steps = steps;
            this.pushing = true;
            this.last = initial;
            steps.eventListeners.push(this);
        }
        push(val) {
            this.last = val;
            this.publish(val);
        }
        pull() {
            throw new Error("Cannot pull from StepperBehavior");
        }
    }
    /**
     * Creates a Behavior whose value is the last occurrence in the stream.
     * @param initial the initial value that the behavior has
     * @param steps the stream that will change the value of the behavior
     */
    function stepper(initial, steps) {
        return new StepperBehavior(initial, steps);
    }
    exports.stepper = stepper;
    // Creates a pull Behavior from a continous function
    function fromFunction(fn) {
        return new FunctionBehavior(fn);
    }
    exports.fromFunction = fromFunction;
    function sink(initialValue) {
        return new SinkBehavior(initialValue);
    }
    exports.sink = sink;
    function subscribe(fn, b) {
        b.cbListeners.push(fn);
    }
    exports.subscribe = subscribe;
    function publish(a, b) {
        b.publish(a);
    }
    exports.publish = publish;
    function map(fn, b) {
        return b.map(fn);
    }
    exports.map = map;
    function ap(fnB, valB) {
        const newB = new ApBehavior(fnB, valB);
        fnB.listeners.push(newB);
        valB.listeners.push(newB);
        return newB;
    }
    exports.ap = ap;
    function isBehavior(b) {
        return b instanceof Behavior;
    }
    exports.isBehavior = isBehavior;
    //# sourceMappingURL=Behavior.js.map