import {IO, runIO} from "jabz/io";
import {Monad, monad} from "jabz/monad";
import {Future, fromPromise, sinkFuture} from "./future";
import {Behavior, at} from "./behavior";
import {Stream} from "./stream";
@monad
export abstract class Now implements Monad {
// Impurely run the now computation
abstract run(): A;
of(b: B): Now {
return new OfNow(b);
}
static of(b: B): Now {
return new OfNow(b);
}
chain(f: (a: A) => Now): Now {
return new ChainNow(this, f);
}
static multi = false;
multi = false;
// Definitions below are inserted by Jabz
flatten: () => Now;
map: (f: (a: A) => B) => Now;
mapTo: (b: B) => Now;
ap: (a: Now<(a: A) => B>) => Now;
lift: (f: Function, ...ms: any[]) => Now;
}
class OfNow extends Now {
constructor(private value: A) {
super();
}
run() {
return this.value;
}
}
class ChainNow extends Now {
constructor(private first: Now, private f: (a: any) => Now) {
super();
}
run() {
return this.f(this.first.run()).run();
}
}
class AsyncNow extends Now> {
constructor(private comp: IO) {
super();
}
run(): Future {
return fromPromise(runIO(this.comp));
}
}
export function async(comp: IO): Now> {
return new AsyncNow(comp);
}
class SampleNow extends Now {
constructor(private b: Behavior) {
super();
}
run(): A {
return at(this.b);
}
}
export function sample(b: Behavior): Now {
return new SampleNow(b);
}
class PerformIOStream extends Stream {
constructor(s: Stream>) {
super();
s.addListener(this);
}
push(io: IO): void {
runIO(io).then((a: A) => this.child.push(a));
}
}
class PerformStreamNow extends Now> {
constructor(private s: Stream>) {
super();
}
run(): Stream {
return new PerformIOStream(this.s);
}
}
export function performStream(s: Stream>): Now> {
return new PerformStreamNow(s);
}
class PerformIOStreamLatest extends Stream {
constructor(s: Stream>) {
super();
s.addListener(this);
}
next: number = 0;
newest: number = 0;
running: number = 0;
push(io: IO): void {
const time = ++this.next;
this.running++;
runIO(io).then((a: A) => {
this.running--;
if (time > this.newest) {
if (this.running === 0) {
this.next = 0;
this.newest = 0;
} else {
this.newest = time;
}
this.child.push(a);
}
});
}
}
class PerformStreamNowLatest extends Now> {
constructor(private s: Stream>) {
super();
}
run(): Stream {
return new PerformIOStreamLatest(this.s);
}
}
export function performStreamLatest(s: Stream>): Now> {
return new PerformStreamNowLatest(s);
}
class PerformIOStreamOrdered extends Stream {
constructor(s: Stream>) {
super();
s.addListener(this);
}
nextId: number = 0;
next: number = 0;
buffer: {value:A}[] = []; // Object-wrapper to support a result as undefined
push(io: IO): void {
const id = this.nextId++;
runIO(io).then((a: A) => {
if (id === this.next) {
this.buffer[0] = {value: a}
this.pushFromBuffer();
} else {
this.buffer[id - this.next] = {value: a};
}
});
}
pushFromBuffer(): void {
while (this.buffer[0] !== undefined) {
const {value} = this.buffer.shift();
this.child.push(value);
this.next++;
}
}
}
class PerformStreamNowOrdered extends Now> {
constructor(private s: Stream>) {
super();
}
run(): Stream {
return new PerformIOStreamOrdered(this.s);
}
}
export function performStreamOrdered(s: Stream>): Now> {
return new PerformStreamNowOrdered(s);
}
function run(now: Now): A {
return now.run();
}
class PlanNow extends Now> {
constructor(private future: Future>) {
super();
}
run(): Future {
return this.future.map(run);
}
}
export function plan(future: Future>): Now> {
return new PlanNow(future);
}
export function runNow(now: Now>): Promise {
return new Promise((resolve, reject) => {
now.run().subscribe(resolve);
});
}