doc "Abstract supertype of containers whose elements may be 
     iterated. An iterable container need not be finite, but
     its elements must at least be countable. There may not
     be a well-defined iteration order, and so the order of
     iterated elements may not be stable.
     
     An instance of `Iterable` may be iterated using a `for`
     loop:
     
         for (c in \"hello world\") { ... }
     
     `Iterable` and its subtypes define various operations
     that return other iterable objects. Such operations 
     come in two flavors:
     
     - _Lazy_ operations return a \"view\" of the receiving
       iterable object. If the underlying iterable object is
       mutable, then changes to the underlying object will
       be reflected in the resulting view. Lazy operations
       are usually efficient, avoiding memory allocation or
       iteration of the receiving iterable object.
       
     - _Eager_ operations return an immutable object. If the
       receiving iterable object is mutable, changes to this
       object will not be reflected in the resulting 
       immutable object. Eager operations are often 
       expensive, involving memory allocation and iteration
       of the receiving iterable object.
     
     Lazy operations are preferred, because they can be 
     efficiently chained. For example:
     
         string.filter((Character c) c.letter).map((Character c) c.uppercased)
     
     is much less expensive than:
     
         string.select((Character c) c.letter).collect((Character c) c.uppercased)
     
     Furthermore, it is always easy to produce a new 
     immutable iterable object given the view produced by a
     lazy operation. For example:
     
         { string.filter((Character c) c.letter).map((Character c) c.uppercased)... }
     
     Lazy operations normally return an instance of 
     `Iterable` or `Map`.
     
     However, there are certain scenarios where an eager 
     operation is more useful, more convenient, or no more 
     expensive than a lazy operation, including:
     
     - sorting operations, which are eager by nature,
     - operations which preserve emptiness/nonemptiness of
       the receiving iterable object.
     
     Eager operations normally return a sequence."
see (Collection)
by "Gavin"
shared interface Iterable<out Element> 
        satisfies ContainerWithFirstElement<Element,Nothing> {

    doc "An iterator for the elements belonging to this 
         container."
    shared formal Iterator<Element> iterator;

    doc "Determines if the iterable object is empty, that is
         to say, if the iterator returns no elements."
    shared actual default Boolean empty {
        return is Finished iterator.next();
    }

    doc "The first element returned by the iterator, if any.
         This should produce the same value as
         `ordered.iterator.head`."
    shared actual default Element? first {
        if (is Element first = iterator.next()) {
            return first;
        }
        else {
            return null;
        }
    }

    doc "The last element returned by the iterator, if any.
         Iterables are potentially infinite, so calling this
         might never return; also, this implementation will
         iterate through all the elements, which might be
         very time-consuming."
    shared actual default Element? last {
        variable Element? e := null;
        for (x in this) {
            e := x;
        }
        return e;
    }

    doc "Returns an iterable object containing all but the 
         first element of this container."
    shared default Iterable<Element> rest {
        return skipping(1);
    }

    doc "A sequence containing the elements returned by the
         iterator."
    shared default Element[] sequence {
        return { this... };
    }

    doc "An `Iterable` containing the results of applying
         the given mapping to the elements of to this 
         container."
    see (collect)
    shared default Iterable<Result> map<Result>(
            doc "The mapping to apply to the elements."
            Result collecting(Element elem)) {
        return elements(for (elem in this) collecting(elem));
    }

    doc "An `Iterable` containing the elements of this 
         container that satisfy the given predicate."
    see (select)
    shared default Iterable<Element> filter(
            doc "The predicate the elements must satisfy."
            Boolean selecting(Element elem)) {
        return elements(for (elem in this) if (selecting(elem)) elem);
    }

    doc "The result of applying the accumulating function to 
         each element of this container in turn." 
    shared default Result fold<Result>(Result initial,
            doc "The accumulating function that accepts an
                 intermediate result, and the next element."
            Result accumulating(Result partial, Element elem)) {
        variable value r := initial;
        for (e in this) {
            r := accumulating(r, e);
        }
        return r;
    }

    doc "The first element which satisfies the given 
         predicate, if any, or `null` otherwise."
    shared default Element? find(
            doc "The predicate the element must satisfy."
            Boolean selecting(Element elem)) {
        for (e in this) {
            if (selecting(e)) {
                return e;
            }
        }
        return null;
    }

    doc "The last element which satisfies the given
         predicate, if any, or `null` otherwise."
    shared default Element? findLast(
            doc "The predicate the element must satisfy."
            Boolean selecting(Element elem)) {
        variable Element? last := null;
        for (e in this) {
            if (selecting(e)) {
                last := e;
            }
        }
        return last;
    }

    doc "A sequence containing the elements of this
         container, sorted according to a function 
         imposing a partial order upon the elements.
         
         For convenience, the functions `byIncreasing()` 
         and `byDecreasing()` produce a suitable 
         comparison function:
         
             \"Hello World!\".sort(byIncreasing((Character c) c.lowercased))
         
         This operation is eager by nature."
    see (byIncreasing, byDecreasing)
    shared default Element[] sort(
            doc "The function comparing pairs of elements."
            Comparison? comparing(Element x, Element y)) { throw; }

    doc "A sequence containing the results of applying the
         given mapping to the elements of this container. An 
         eager counterpart to `map()`."
    see (map)
    shared default Result[] collect<Result>(
            doc "The transformation applied to the elements."
            Result collecting(Element element)) {
        return map(collecting).sequence;
    }

    doc "A sequence containing the elements of this 
         container that satisfy the given predicate. An 
         eager counterpart to `filter()`."
    see (filter)
    shared default Element[] select(
            doc "The predicate the elements must satisfy."
            Boolean selecting(Element element)) {
        return filter(selecting).sequence;
    }

    doc "Return `true` if at least one element satisfies the
         predicate function."
    shared default Boolean any(
            doc "The predicate that at least one element 
                 must satisfy."
            Boolean selecting(Element e)) {
        for (e in this) {
            if (selecting(e)) {
                return true;
            }
        }
        return false;
    }

    doc "Return `true` if all elements satisfy the predicate
         function."
    shared default Boolean every(
            doc "The predicate that all elements must 
                 satisfy."
            Boolean selecting(Element e)) {
        for (e in this) {
            if (!selecting(e)) {
                return false;
            }
        }
        return true;
    }
    
    doc "Produce an `Iterable` containing the elements of
         this iterable object, after skipping the first 
         `skip` elements. If this iterable object does not 
         contain more elements than the specified number of 
         elements, the `Iterable` contains no elements."
    shared default Iterable<Element> skipping(Integer skip) {
        if (skip <= 0) { return this; }
        object iterable satisfies Iterable<Element> {
            shared actual Iterator<Element> iterator {
                value iterator = outer.iterator;
                variable value i:=0;
                while (i++<skip && !is Finished iterator.next()) {}
                return iterator;
            }
        }
        return iterable;
    }

    doc "Produce an `Iterable` containing the first `take`
         elements of this iterable object. If the specified 
         number of elements is larger than the number of 
         elements of this iterable object, the `Iterable` 
         contains the same elements as this iterable object."
    shared default Iterable<Element> taking(Integer take) {
        if (take <= 0) { return {}; }
        object iterable satisfies Iterable<Element> {
            shared actual Iterator<Element> iterator {
                value outerIterable { return outer; }
                object iterator satisfies Iterator<Element> {
                    value iter = outerIterable.iterator;
                    variable value i:=0;
                    actual shared Element|Finished next() {
                        return ++i>take then exhausted 
                                else iter.next();
                    }
                }
                return iterator;
            }
        }
        return iterable;
    }
    
    doc "Produce an `Iterable` containing every `step`th 
         element of this iterable object. If the step size 
         is `1`, the `Iterable` contains the same elements 
         as this iterable object. The step size must be 
         greater than zero. The expression
         
             (0..10).by(3)
         
         results in an iterable object with the elements
         `0`, `3`, `6`, and `9` in that order."
    throws (Exception, "if the given step size is nonpositive, 
                        i.e. `step<1`") //TODO: better exception type
    shared default Iterable<Element> by(Integer step) {
        if (step <= 0) {
            throw Exception("step size must be greater than zero");
        }
        else if (step == 1) {
            return this;
        } 
        else {
            object iterable satisfies Iterable<Element> {
                shared actual Iterator<Element> iterator {
                    value outerIterable { return outer; }
                    object iterator satisfies Iterator<Element> {
                        value iter = outerIterable.iterator;
                        actual shared Element|Finished next() {
                            value next = iter.next();
                            variable value i:=0;
                            while (++i<step && !is Finished iterator.next()) {}
                            return next;
                        }
                    }
                    return iterator;
                }
            }
            return iterable;
        }
    }

    doc "Return the number of elements in this `Iterable` 
         that satisfy the predicate function."
    shared default Integer count(
            doc "The predicate satisfied by the elements to
                 be counted."
            Boolean selecting(Element element)) {
        variable value count:=0;
        for (elem in this) {
            if (is Object elem) {
                if (selecting(elem)) {
                    count++;
                }
            }
        }
        return count;
    }

    doc "The non-null elements of this `Iterable`, in their
         original order. For null elements of the original 
         `Iterable`, there is no entry in the resulting 
         iterable object."
    shared default Iterable<Element&Object> coalesced {
        return elements { for (e in this) if (exists e) e };
    }

    doc "All entries of form `index->element` where `index` 
         is the position at which `element` occurs, for every
         non-null element of this `Iterable`, ordered by
         increasing `index`. For a null element at a given
         position in the original `Iterable`, there is no 
         entry with the corresponding index in the resulting 
         iterable object. The expression 
         
             { \"hello\", null, \"world\" }.indexed
             
         results in an iterable object with the entries
         `0->\"hello\"` and `2->\"world\"`."
    shared default Iterable<Entry<Integer,Element&Object>> indexed {
            object iterable satisfies Iterable<Entry<Integer,Element&Object>?> {
                shared actual Iterator<Entry<Integer,Element&Object>?> iterator {
                    value outerIterable { return outer; }
                    object iterator satisfies Iterator<Entry<Integer,Element&Object>?> {
                        value iter = outerIterable.iterator;
                        variable value i:=0;
                        actual shared Entry<Integer,Element&Object>?|Finished next() {
                            value next = iter.next();
                            if (is Element next) {
                                if (exists next) {
                                    return i++->next;
                                }
                                else {
                                    i++;
                                    return null;
                                }
                            }
                            else {
                                return exhausted;
                            }
                        }
                    }
                    return iterator;
                }
            }
            return iterable.coalesced;
    }

    doc "The elements of this iterable object, in their
         original order, followed by the elements of the 
         given iterable object also in their original
         order."
    shared default Iterable<Element|Other> chain<Other>(
            Iterable<Other> other) {
        object chained satisfies Iterable<Element|Other> {
            shared actual Iterator<Element|Other> iterator {
                return ChainedIterator(outer, other);
            }
        }
        return chained;
    }

    doc "Creates a Map that contains this `Iterable`'s
         elements, grouped in `Sequence`s under the
         keys provided by the grouping function."
    shared default Map<Grouping,Sequence<Element>> group<Grouping>(
                doc "A function that must return the key under
                     which to group the specified element."
                Grouping grouping(Element elem))
            given Grouping satisfies Object {
        throw;
    }

}