class DateEntriesIter<T> implements IterableIterator<[Date, T]> {

    public constructor(protected internalMapIter: IterableIterator<[number, T]>) { }

    public next(): IteratorResult<[Date, T]> {
        let t = this.internalMapIter.next();
        return {
            done: t.done,
            value: [new Date(t.value[0]), t.value[1]]
        }
    }

    public [Symbol.iterator](): IterableIterator<[Date, T]> {
        return this;
    }
}

class DateIter<T> implements IterableIterator<Date> {

    public constructor(protected internalMapIter: IterableIterator<number>) { }

    public next(): IteratorResult<Date> {
        let t = this.internalMapIter.next();
        return {
            done: t.done,
            value: new Date(t.value)
        }
    }

    public [Symbol.iterator](): IterableIterator<Date> {
        return this;
    }
}

export default class DateMap<T> {
    private internalMap: Map<number, T> = new Map();

    public clear(): void {
        this.internalMap.clear();
    }

    public delete(key: Date): boolean {
        if (key !== undefined) {
            return this.internalMap.delete(key.getTime());
        }
    }

    public forEach(callbackfn: (value: T, key: Date, map: DateMap<T>) => void, thisArg?: any): void {
        let self: DateMap<T> = this;
        this.internalMap.forEach((value: T, key: number, map: Map<number, T>) => {
            callbackfn(value, new Date(key), self);
        }, this);
    }

    public get(key: Date): T | undefined {
        if (key !== undefined) {
            return this.internalMap.get(key.getTime());
        } else {
            return undefined;
        }
    }

    public has(key: Date): boolean {
        if (key !== undefined) {
            return this.internalMap.has(key.getTime());
        } else {
            return undefined;
        }
    }

    public set(key: Date, value: T): this {
        if (key !== undefined) {
            this.internalMap.set(key.getTime(), value);
        }
        return this;
    }

    public size(): number {
        return this.internalMap.size;
    }

    public entries(): IterableIterator<[Date, T]> {
        return new DateEntriesIter<T>(this.internalMap.entries());
    }

    public keys(): IterableIterator<Date> {
        return new DateIter(this.internalMap.keys());
    }

    public values(): IterableIterator<T> {
        return this.internalMap.values();
    }

    /** returns the date range (min/max dates) of this date map */
    public dateRange(): Date[] {
        return [new Date(Math.min(...this.internalMap.keys())), new Date(Math.max(...this.internalMap.keys()))];
    }

    public [Symbol.toStringTag]: string;
}

export class DateSet extends DateMap<Date> {
    public add(value: Date | IterableIterator<Date>): this {
        if (value instanceof Date) {
            this.set(value, value);
        } else {
            // add all values
            for (let date of value) {
                this.set(date, date);
            }
        }

        return this;
    }
}