"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;
class ScanBehavior extends Behavior {
    constructor(initial, fn, source) {
        super();
        this.fn = fn;
        this.source = source;
        this.pushing = true;
        this.last = initial;
        source.eventListeners.push(this);
    }
    push(val) {
        this.last = this.fn(val, this.last);
        this.publish(this.last);
    }
    pull() {
        throw new Error("Cannot pull from Scan");
    }
}
function scan(fn, init, source) {
    return fromFunction(() => new ScanBehavior(init, fn, source));
}
exports.scan = scan;
// Creates a pull Behavior from an impure 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