"An implementation of `List` that wraps an `Iterable` of
 elements. All operations on this `List` are performed on 
 the `Iterable`."
by ("Enrique Zamudio")
shared class LazyList<out Element>({Element*} elems)
        satisfies List<Element> {
    
    shared actual Integer? lastIndex {
        value size = elems.size;
        return size > 0 then size-1 else null;
    }
    
    shared actual Element? get(Integer index) {
        if (index == 0) {
            return elems.first;
        } 
        else {
            return elems.skipping(index).first;
        }
    }
    
    shared actual List<Element> rest => LazyList(elems.rest);
    
    shared actual Iterator<Element> iterator() => 
            elems.iterator();
    
    "Returns a `List` with the elements of this `List` 
     in reverse order. This operation will create copy 
     the elements to a new `List`, so changes to the 
     original `Iterable` will no longer be reflected in 
     the new `List`."
    shared actual List<Element> reversed =>
            elems.sequence.reversed;
    
    shared actual List<Element> clone => this;
    
    shared actual List<Element> span
            (Integer from, Integer to) {
        if (to < 0 && from < 0) {
            return [];
        }
        Integer toIndex = largest(to,0);
        Integer fromIndex = largest(from,0);
        if (toIndex >= fromIndex) {
            value els = fromIndex > 0 
                    then elems.skipping(fromIndex)
                    else elems;
            return LazyList(els.taking(toIndex-fromIndex+1));
        } 
        else {
            //reversed
            value seq = toIndex>0 
                    then elems.skipping(toIndex)
                    else elems;
            return seq.taking(fromIndex-toIndex+1)
                    .sequence.reversed;
        }
    }
    
    shared actual List<Element> spanTo(Integer to) =>
            to<0 then {} else LazyList(elems.taking(to+1));
    
    shared actual List<Element> spanFrom(Integer from) =>
            from > 0 then LazyList(elems.skipping(from))
                    else this;
    
    shared actual List<Element> segment
            (Integer from, Integer length) {
        if (length > 0) {
            value els = from > 0 then elems.skipping(from)
                    else elems;
            return LazyList(els.taking(length));
        } 
        else {
            return [];
        }
    }
    
    shared actual default Boolean equals(Object that) {
        if (is List<Anything> that) {
            value size = elems.size;
            if (that.size==size) {
                for (i in 0..size-1) {
                    value x = this[i];
                    value y = that[i];
                    if (exists x) {
                        if (exists y) {
                            if (x!=y) {
                                return false;
                            }
                        }
                        else {
                            return false;
                        }
                    }
                    else if (exists y) {
                        return false;
                    }
                }
                else {
                    return true;
                }
            }
        }
        return false;
    }
    
    shared actual default Integer hash {
        variable value hash = 1;
        for (elem in elems) {
            hash *= 31;
            if (exists elem) {
                hash += elem.hash;
            }
        }
        return hash;
    }
    
    shared default actual Element? findLast(
            Boolean selecting(Element elem)) =>
                    elems.findLast(selecting);
    
    shared actual default Element? first => elems.first;
    
    shared actual default Element? last => elems.last;
    
}