/**
 * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

/**
 * "Idiomatic" usage:
 *
 * const it = ...;
 * while (it.hasNext) { const v = it.move(); ... }
 */
interface Iterator<T> {
    readonly hasNext: boolean,
    move(): T
}

class ArrayIteratorImpl<T> implements Iterator<T> {
    private xs: ArrayLike<T> = [];
    private index: number = -1;
    private length: number = 0;
    private lastValue: T;

    hasNext: boolean = false;

    move() {
        ++this.index;
        this.lastValue = this.xs[this.index];
        this.hasNext = this.index < this.length - 1;
        return this.lastValue;
    }

    constructor(xs: ArrayLike<T>) {
        this.length = xs.length;
        this.hasNext = xs.length > 0;
        this.xs = xs;
        this.index = -1;
        // try to avoid deoptimization with undefined values
        this.lastValue = xs.length > 0 ? xs[0] : void 0 as any;
    }
}

class RangeIteratorImpl implements Iterator<number> {
    private value: number = 0;
    hasNext: boolean = false;

    move() {
        ++this.value;
        this.hasNext = this.value < this.max;
        return this.value;
    }

    constructor(min: number, private max: number) {
        this.value = min - 1;
        this.hasNext = max >= min;
    }
}

class ValueIterator<T> implements Iterator<T> {
    hasNext = true;
    move() { this.hasNext = false; return this.value; }
    constructor(private value: T) { }
}

class MapIteratorImpl<T, R> implements Iterator<R> {
    hasNext: boolean = false;

    move() {
        const v = this.f(this.base.move());
        this.hasNext = this.base.hasNext;
        return v;
    }

    constructor(private base: Iterator<T>, private f: (v: T) => R) {
        this.hasNext = base.hasNext;
    }
}

class FilterIteratorImpl<T> implements Iterator<T> {
    private next: T;
    hasNext: boolean;

    move() {
        const ret = this.next;
        this.hasNext = this.findNext();
        return ret;
    }

    private findNext() {
        while (this.base.hasNext) {
            this.next = this.base.move();
            if (this.p(this.next)) return true;
        }
        return false;
    }

    constructor(private base: Iterator<T>, private p: (v: T) => boolean) {
        this.hasNext = this.findNext();
    }
}

namespace Iterator {
    export const Empty: Iterator<any> = new RangeIteratorImpl(0, -1);
    export function Array<T>(xs: ArrayLike<T>): Iterator<T> { return new ArrayIteratorImpl<T>(xs); }
    export function Value<T>(value: T): Iterator<T> { return new ValueIterator(value); }
    export function Range(min: number, max: number): Iterator<number> { return new RangeIteratorImpl(min, max); }
    export function map<T, R>(base: Iterator<T>, f: (v: T) => R): Iterator<R> { return new MapIteratorImpl(base, f); }
    export function filter<T>(base: Iterator<T>, p: (v: T) => boolean): Iterator<T> { return new FilterIteratorImpl(base, p); }

    // Iterate until first truthy value is returned.
    export function forEach<T, Ctx>(it: Iterator<T>, f: (v: T, ctx: Ctx) => any, ctx: Ctx): Ctx {
        while (it.hasNext) {
            const c = f(it.move(), ctx);
            if (c) return ctx;
        }
        return ctx;
    }
}

export default Iterator