/* ---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. 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 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 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 class MappedNavigator<T, R> extends MappedIterator<T, R> implements INavigator<R> {
	constructor(protected navigator: INavigator<T>, fn: (item: T | null) => 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()) }
}