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

import Iterator from '../iterator'
import OrderedSet from '../ordered-set'

class SegmentIterator implements Iterator<{ segment: number } & OrderedSet.IndexRange> {
    private segmentRange = OrderedSet.IndexRange();
    private setRange = OrderedSet.IndexRange();
    private value = { segment: 0, start: 0, end: 0 };
    private last: number = 0;

    [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set, this.start, this.end); };
    done: boolean = false;

    next() {
        const value = this.move();
        return { value, done: this.done }
    }

    move() {
        this.done = this.segmentRange.end <= this.segmentRange.start;
        while (!this.done) {
            if (!this.updateValue()) {
                this.updateSegmentRange();
            } else {
                this.value.segment = this.segmentRange.start++;
                break;
            }
        }
        return this.value;
    }

    private getSegmentIndex(value: number) {
        if (value >= this.last) return -1;
        return OrderedSet.getPredIndex(this.segments, value - 1);
    }

    private updateValue() {
        const segmentEnd = OrderedSet.getAt(this.segments, this.segmentRange.start + 1);
        const setEnd = OrderedSet.getPredIndexInRange(this.set, segmentEnd, this.setRange);
        this.value.start = this.setRange.start;
        this.value.end = setEnd;
        this.setRange.start = setEnd;
        return setEnd > this.value.start;
    }

    private updateSegmentRange() {
        const min = OrderedSet.getAt(this.set, this.setRange.start), max = OrderedSet.getAt(this.set, this.setRange.end - 1);
        this.segmentRange.start = this.getSegmentIndex(min);
        this.segmentRange.end = this.getSegmentIndex(max) + 1;
        this.done = this.segmentRange.end <= this.segmentRange.start;
    }

    constructor(private segments: OrderedSet, private set: OrderedSet, private start: number, private end: number) {
        this.last = OrderedSet.max(segments);
        this.setRange.start = start;
        this.setRange.end = end;
        this.updateSegmentRange();
    }
}

function createIterator(segments: OrderedSet, set: OrderedSet, range?: OrderedSet.IndexRange) {
    const start = !!range ? range.start : 0;
    const end = !!range ? range.end : OrderedSet.size(set);
    return new SegmentIterator(segments, set, start, end);
}

export default createIterator