import {MapUtils} from './MapUtils';
import {SetUtils} from './SetUtils';
import {CoreType} from './Type';

export class ArrayUtils {
	static min<T>(array: Array<T>): T {
		let min = array[0];
		for (let element of array) {
			if (element < min) {
				min = element;
			}
		}
		return min;
	}
	static max<T>(array: Array<T>): T {
		let min = array[0];
		for (let element of array) {
			if (element > min) {
				min = element;
			}
		}
		return min;
	}
	static sum(array: number[]): number {
		let sum = 0;
		for (let element of array) {
			sum += element;
		}
		return sum;
	}
	static compact<T>(array: Array<T | null | undefined>): Array<T> {
		const newArray: Array<T> = [];

		for (let elem of array) {
			if (elem != null) {
				newArray.push(elem);
			}
		}

		return newArray;
	}
	static uniq<T>(array: Array<T>): Array<T> {
		const tmpSet: Set<T> = new Set();

		for (let elem of array) {
			tmpSet.add(elem);
		}

		const newArray: Array<T> = new Array(tmpSet.size);
		let i = 0;
		tmpSet.forEach((elem) => {
			newArray[i] = elem;
			i++;
		});

		return newArray;
	}
	static chunk<T extends number | string>(array: Array<T>, chunkSize: number): Array<Array<T>> {
		const newArray: Array<Array<T>> = [];

		let newSubArray: Array<T> = [];
		newArray.push(newSubArray);
		for (let i = 0; i < array.length; i++) {
			if (newSubArray.length == chunkSize) {
				newSubArray = [];
				newArray.push(newSubArray);
			}
			newSubArray.push(array[i]);
		}

		return newArray;
	}
	static union<T extends number | string>(array0: Array<T>, array1: Array<T>): Array<T> {
		const newArray: Array<T> = [];
		const unionSet = SetUtils.union(this.toSet(array0), this.toSet(array1));
		unionSet.forEach((val) => newArray.push(val));

		return newArray;
	}
	static intersection<T extends number | string>(array0: Array<T>, array1: Array<T>): Array<T> {
		const newArray: Array<T> = [];
		const intersectionSet = SetUtils.intersection(this.toSet(array0), this.toSet(array1));
		intersectionSet.forEach((val) => newArray.push(val));

		return newArray;
	}
	static difference<T extends number | string>(array0: Array<T>, array1: Array<T>): Array<T> {
		const newArray: Array<T> = [];
		const differenceSet = SetUtils.difference(this.toSet(array0), this.toSet(array1));
		differenceSet.forEach((val) => newArray.push(val));

		return newArray;
	}
	static toSet<T extends number | string>(array: Array<T>): Set<T> {
		const set: Set<T> = new Set();
		for (let elem of array) {
			set.add(elem);
		}
		return set;
	}
	static isEqual<T extends number | string>(array0: Array<T>, array1: Array<T>): boolean {
		if (array0.length != array1.length) {
			return false;
		}
		const count = array0.length;
		for (let i = 0; i < count; i++) {
			if (array0[i] != array1[i]) {
				return false;
			}
		}
		return true;
	}
	static sortBy<T, K extends number | string>(array: Array<T>, callback: (e: T) => K): Array<T> {
		if (array.length == 0) {
			return [];
		}
		const elementsByValue: Map<K, T[]> = new Map();
		const valuesSet: Set<K> = new Set();
		for (let elem of array) {
			const value: K = callback(elem);
			valuesSet.add(value);
			MapUtils.push_on_array_at_entry(elementsByValue, value, elem);
		}
		const values: K[] = new Array(valuesSet.size);
		let i = 0;
		valuesSet.forEach((value) => {
			values[i] = value;
			i++;
		});

		// sort differently if the callback return value is a string or a number
		if (CoreType.isString(values[0])) {
			values.sort();
		} else {
			values.sort((a, b) => (a as number) - (b as number));
		}

		const sorted_elements: Array<T> = new Array(array.length);
		i = 0;
		for (let value of values) {
			const elements_for_value = elementsByValue.get(value);
			if (elements_for_value) {
				for (let element of elements_for_value) {
					sorted_elements[i] = element;
					i++;
				}
			}
		}
		return sorted_elements;
	}

	static range(start: number, end?: number, step: number = 1): number[] {
		if (end == null) {
			end = start;
			start = 0;
		}
		const length = Math.floor((end - start) / step);
		const array: number[] = new Array(length);

		for (let i = 0; i < array.length; i++) {
			array[i] = start + i * step;
		}
		return array;
	}
}
