"use strict";
/**
 * A future is a thing that occurs at some point in time with a value.
 * It can be understood as a pair consisting of the time the future
 * occurs and its associated value. It is quite like a JavaScript
 * promise.
 */
class Future {
    constructor() {
        this.listeners = [];
    }
    listen(o) {
        if (this.occured !== true) {
            this.listeners.push(o);
        }
        else {
            o.push(this.value);
        }
    }
    subscribe(f) {
        new Subscribtion(f, this);
    }
    resolve(val) {
        this.occured = true;
        this.value = val;
        const listeners = this.listeners;
        for (let i = 0, l = listeners.length; i < l; ++i) {
            listeners[i].push(val);
        }
    }
    // A future is a functor, when the future occurs we can feed is't
    // result through the mapping function
    map(f) {
        return new MapFuture(f, this);
    }
    mapTo(b) {
        return new MapToFuture(b, this);
    }
    // A future is an applicative. `of` gives a future that has always
    // occured at all points in time.
    static of(b) {
        return new PureFuture(b);
    }
    of(b) {
        return new PureFuture(b);
    }
    static lift(f, ...args) {
        return f.length === 1 ? new MapFuture(f, args[0])
            : new LiftFuture(f, args);
    }
    // A future is a monad. Once the first future occurs `chain` passes
    // its value through the chain function and the future it returns is
    // the one returned by `chain`.
    chain(f) {
        return new ChainFuture(f, this);
    }
}
exports.Future = Future;
class MapFuture extends Future {
    constructor(f, parent) {
        super();
        this.f = f;
        this.parent = parent;
        parent.listen(this);
    }
    push(val) {
        this.resolve(this.f(val));
    }
}
class MapToFuture extends Future {
    constructor(value, parent) {
        super();
        this.value = value;
        this.parent = parent;
        parent.listen(this);
    }
    push(_) {
        this.resolve(this.value);
    }
}
class PureFuture extends Future {
    constructor(value) {
        super();
        this.value = value;
        this.occured = true;
    }
    push(_) {
        throw new Error("A PureFuture should never be pushed to.");
    }
}
class LiftFuture extends Future {
    constructor(f, futures) {
        super();
        this.f = f;
        this.futures = futures;
        this.delivered = 0;
        const l = this.dependencies = futures.length;
        for (let i = 0; i < l; ++i) {
            futures[i].listen(this);
        }
    }
    push(_) {
        const l = this.dependencies;
        if (++this.delivered === l) {
            // All the dependencies have occurred.
            for (let i = 0; i < l; ++i) {
                this.futures[i] = this.futures[i].value;
            }
            this.resolve(this.f.apply(undefined, this.futures));
        }
    }
}
class ChainFuture extends Future {
    constructor(f, parent) {
        super();
        this.f = f;
        this.parent = parent;
        this.parentOccurred = false;
        parent.listen(this);
    }
    push(val) {
        if (this.parentOccurred === false) {
            // The first future occured. We can now call `f` with it's value
            // and listen to the future it returns.
            this.parentOccurred = true;
            const newFuture = this.f(val);
            newFuture.listen(this);
        }
        else {
            this.resolve(val);
        }
    }
}
// I Sink is a producer that one can imperatively resolve.
class Sink extends Future {
    push(val) {
        throw new Error("A sink should never be pushed to.");
    }
}
function sink() {
    return new Sink();
}
exports.sink = sink;
// A subscribtion is a consumer that performs a side
class Subscribtion {
    constructor(f, parent) {
        this.f = f;
        this.parent = parent;
        parent.listen(this);
    }
    push(a) {
        this.f(a); // let `f` perform its side-effect.
    }
}
function fromPromise(p) {
    const future = sink();
    p.then(future.resolve.bind(future));
    return future;
}
exports.fromPromise = fromPromise;
/**
 * Create a future from a pushing behavior. The future occurs when the
 * behavior pushes it's next value. Constructing a BehaviorFuture is
 * impure and should not be done direcly.
 * @private
 */
class BehaviorFuture extends Future {
    constructor(b) {
        super();
        this.b = b;
        b.addListener(this);
    }
    push(a) {
        this.b.unlisten(this);
        this.resolve(a);
    }
}
exports.BehaviorFuture = BehaviorFuture;
exports.of = Future.of;
exports.lift = Future.lift;
//# sourceMappingURL=Future.js.map