/*
    MAP ITERATOR
*/

class MapIterator<It, Elem, Trf> {
    iter: 'It [Iterable<'It, 'Elem>];
    f: 'Elem => 'Trf;
}

fn<It, Elem, Trf> iterator(it: MapIterator<'It, 'Elem, 'Trf>) -> MapIterator<'It, 'Elem, 'Trf> {
    return *it;
}

fn<It, Elem, Trf> next(it: @MapIterator<'It, 'Elem, 'Trf>) -> 'Trf {
    let fnc = it.f();
    return fnc(it.iter().next());
}

fn<It, Elem, Trf> is_consumed(it: @MapIterator<'It, 'Elem, 'Trf>) -> Bool {
    return it.iter().is_consumed();
}

fn<Cont, It, Elem, Trf> map(it: 'Cont [Iterable<'It, 'Elem>], f: 'Elem => 'Trf) -> MapIterator<'It, 'Elem, 'Trf> {
    return MapIterator(iterator(move(it)), move(f));
}

implement<It, Elem, Trf> Iterable<MapIterator<'It, 'Elem, 'Trf>, 'Trf> for MapIterator<'It, 'Elem, 'Trf>;

/*
    FILTER ITERATOR
*/

class FilterIterator<It, Elem> {
    iter: 'It [Iterable<'It, 'Elem>];
    f: 'Elem => Bool;
    curr: 'Elem;
    consumed: Bool;
}

fn<It, Elem> advance_till_true(it: @FilterIterator<'It, 'Elem>) {
    let fnc = it.f();
    let iter_i = it.iter();

    while !iter_i.is_consumed() {
        it.curr() := iter_i.next();

        if fnc(*it.curr()) {
            return;
        }
    }
    
    it.consumed() := true;
}

fn<It, Elem> iterator(it: FilterIterator<'It, 'Elem>) -> FilterIterator<'It, 'Elem> {
    return *it;
}

fn<It, Elem> next(it: @FilterIterator<'It, 'Elem>) -> 'Elem {    
    let res = *it.curr();

    if it.iter().is_consumed() {
        it.consumed() := true;
    }

    it.advance_till_true();

    return *res;
}

fn<It, Elem> is_consumed(it: @FilterIterator<'It, 'Elem>) -> Bool {
    return it.iter().is_consumed() && it.consumed();
}

fn<Cont, It, Elem> filter(it: 'Cont [Iterable<'It, 'Elem>], f: 'Elem => Bool) -> FilterIterator<'It, 'Elem> {
    let iter_i = iterator(move(it));
    let res = FilterIterator(move(iter_i), *f, iter_i.next(), false);

    if !f(*res.curr()) {
        res.advance_till_true();
    }

    return *res;
}

implement<It, Elem> Iterable<FilterIterator<'It, 'Elem>, 'Elem> for FilterIterator<'It, 'Elem>;

// Terminal operators

fn<It> sum(it: 'It [Iterable<'It, Int>]) -> Int {
    let res = 0;

    for i in move(it) {
        res := res + i;
    }

    return move(res);
}

fn<It> sum(it: 'It [Iterable<'It, Float>]) -> Float {
    let res = 0;

    for i in *it {
        res := res + i;
    }

    return *res;
}

fn<It, Elem> reduce(it: 'It [Iterable<'It, 'Elem>], op: ('Elem, 'Elem) => 'Elem, start: 'Elem) -> 'Elem {
    let res = move(start);

    for i in *it {
        res := op(*res, *i);
    }

    return *res;
}

fn<It> count(it: 'It [Iterable<'It, Int>]) -> Int {
    let res = 0;

    for i in move(it) {
        res.inc();
    }

    return move(res);
}