/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the Source EULA. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

export interface IteratorDefinedResult<T> {
	readonly done: false;
	readonly value: T;
}
export interface IteratorUndefinedResult {
	readonly done: true;
	readonly value: undefined;
}
export const FIN: IteratorUndefinedResult = { done: true, value: undefined };
export type IteratorResult<T> = IteratorDefinedResult<T> | IteratorUndefinedResult;

export interface Iterator<T> {
	next(): IteratorResult<T>;
}

export module Iterator {
	const _empty: Iterator<any> = {
		next() {
			return FIN;
		}
	};

	export function empty<T>(): Iterator<T> {
		return _empty;
	}

	export function fromArray<T>(array: T[], index = 0, length = array.length): Iterator<T> {
		return {
			next(): IteratorResult<T> {
				if (index >= length) {
					return FIN;
				}

				return { done: false, value: array[index++] };
			}
		};
	}

	export function from<T>(elements: Iterator<T> | T[] | undefined): Iterator<T> {
		if (!elements) {
			return Iterator.empty();
		} else if (Array.isArray(elements)) {
			return Iterator.fromArray(elements);
		} else {
			return elements;
		}
	}

	export function map<T, R>(iterator: Iterator<T>, fn: (t: T) => R): Iterator<R> {
		return {
			next() {
				const element = iterator.next();
				if (element.done) {
					return FIN;
				} else {
					return { done: false, value: fn(element.value) };
				}
			}
		};
	}

	export function filter<T>(iterator: Iterator<T>, fn: (t: T) => boolean): Iterator<T> {
		return {
			next() {
				while (true) {
					const element = iterator.next();
					if (element.done) {
						return FIN;
					}
					if (fn(element.value)) {
						return { done: false, value: element.value };
					}
				}
			}
		};
	}

	export function forEach<T>(iterator: Iterator<T>, fn: (t: T) => void): void {
		for (let next = iterator.next(); !next.done; next = iterator.next()) {
			fn(next.value);
		}
	}

	export function collect<T>(iterator: Iterator<T>): T[] {
		const result: T[] = [];
		forEach(iterator, value => result.push(value));
		return result;
	}
}

export type ISequence<T> = Iterator<T> | T[];

export function getSequenceIterator<T>(arg: Iterator<T> | T[]): Iterator<T> {
	if (Array.isArray(arg)) {
		return Iterator.fromArray(arg);
	} else {
		return arg;
	}
}

export interface INextIterator<T> {
	next(): T | null;
}

export class ArrayIterator<T> implements INextIterator<T> {

	private items: T[];
	protected start: number;
	protected end: number;
	protected index: number;

	constructor(items: T[], start: number = 0, end: number = items.length, index = start - 1) {
		this.items = items;
		this.start = start;
		this.end = end;
		this.index = index;
	}

	public first(): T | null {
		this.index = this.start;
		return this.current();
	}

	public next(): T | null {
		this.index = Math.min(this.index + 1, this.end);
		return this.current();
	}

	protected current(): T | null {
		if (this.index === this.start - 1 || this.index === this.end) {
			return null;
		}

		return this.items[this.index];
	}
}

export class ArrayNavigator<T> extends ArrayIterator<T> implements INavigator<T> {

	constructor(items: T[], start: number = 0, end: number = items.length, index = start - 1) {
		super(items, start, end, index);
	}

	public current(): T | null {
		return super.current();
	}

	public previous(): T | null {
		this.index = Math.max(this.index - 1, this.start - 1);
		return this.current();
	}

	public first(): T | null {
		this.index = this.start;
		return this.current();
	}

	public last(): T | null {
		this.index = this.end - 1;
		return this.current();
	}

	public parent(): T | null {
		return null;
	}
}

export class MappedIterator<T, R> implements INextIterator<R> {

	constructor(protected iterator: INextIterator<T>, protected fn: (item: T | null) => R) {
		// noop
	}

	next() { return this.fn(this.iterator.next()); }
}

export interface INavigator<T> extends INextIterator<T> {
	current(): T | null;
	previous(): T | null;
	parent(): T | null;
	first(): T | null;
	last(): T | null;
	next(): T | null;
}

export class MappedNavigator<T, R> extends MappedIterator<T, R> implements INavigator<R> {

	constructor(protected navigator: INavigator<T>, fn: (item: T) => R) {
		super(navigator, fn);
	}

	current() { return this.fn(this.navigator.current()); }
	previous() { return this.fn(this.navigator.previous()); }
	parent() { return this.fn(this.navigator.parent()); }
	first() { return this.fn(this.navigator.first()); }
	last() { return this.fn(this.navigator.last()); }
	next() { return this.fn(this.navigator.next()); }
}
