"""A collection in which every element has a unique 
   non-negative integer index. The elements of a nonempty 
   list are indexed starting with `0` at the [[first]] 
   element of the list, and ending with the index 
   [[lastIndex]] at the [[last]] element of the list.
   
   - For any nonempty list, `lastIndex==size-1`. 
   - For an empty list, `size==0` and the `lastIndex` is 
     `null`.
   
   Thus, the range of indexes of the list is formed by the 
   expression `0:list.size`.
   
   A `List` is a [[Collection]] of its elements, and a 
   [[Correspondence]] from indexes to elements.
   
   Every list has a well-defined and stable iteration order.
   An [[iterator]] of a nonempty list is required to return 
   the elements of the list in order of increasing index, 
   beginning with the element at index `0`, and ending with
   the element at index `lastIndex`. Thus, every iterator of 
   an immutable list produces exactly the same elements in 
   exactly the same order.
   
   Direct access to a list element by index produces a value 
   of optional type. The following idiom may be used instead 
   of upfront bounds-checking, as long as the list element 
   type is a non-`null` type:
   
       if (exists char = "hello world"[index]) { 
           //do something with char
       }
       else {
           //out of bounds
       }
   
   When an algorithm guarantees that a list contains a given 
   index, the following idiom may be used:
   
       assert (exists char = "hello world"[index]);
       //do something with char
   
   To iterate the indexes of a `List`, use the following
   idiom:
   
       for (i->char in "hello world".indexed) { ... }
   
   [[Strings|String]], [[sequences|Sequential]], 
   [[tuples|Tuple]], and [[arrays|Array]] are all `List`s,
   and are all of fixed length. Variable-length mutable
   `List`s are also possible."""
see (`interface Sequence`, 
     `interface Empty`, 
     `class Array`)
tagged("Collections")
shared interface List<out Element=Anything>
        satisfies Collection<Element> &
                  Correspondence<Integer,Element> &
                  Ranged<Integer,Element,List<Element>> {
    
    "The first element of this `List`, if any."
    shared actual default Element? first => getFromFirst(0);
    
    "The last element of this `List`, if any."
    shared actual default Element? last => getFromLast(0);
    
    "Returns the element of this list with the given 
     [[index]] if the index refers to an element of this
     list, that is, if `0<=index<=list.lastIndex`, or `null` 
     otherwise. The first element of the list has index `0`,
     and the last element has index [[lastIndex]]."
    shared actual Element? get(Integer index) 
            => getFromFirst(index);
    
    "Returns the element of this list with the given 
     [[index]] if the index refers to an element of this
     list, that is, if `0<=index<=list.lastIndex`, or `null` 
     otherwise. The first element of the list has index `0`, 
     and the last element has index [[lastIndex]]."
    see (`function getFromLast`)
    shared actual formal Element? getFromFirst(Integer index);
    
    "Returns the element of this list with the given 
     [[index]], where the list is indexed from the _end_ of 
     the list instead of from the start, if the index refers
     to an element of this list, or `null` otherwise. The
     last element of the list has index `0`, and the first
     element has index [[lastIndex]]."
    since("1.1.0")
    shared default Element? getFromLast(Integer index)
            => getFromFirst(size-1-index);
    
    Element getElement(Integer index) {
        if (exists element = getFromFirst(index)) { 
            return element;
        }
        else {
            assert (is Element null);
            return null; 
        }
    }
    
    "The index of the last element of the list, or `null` if 
     the list is empty. Always `size>0 then size-1`."
    see (`value List.size`)
    shared formal Integer? lastIndex;
    
    "The number of elements in this list, always
     `1 + (lastIndex else -1)`."
    see (`value List.lastIndex`)
    shared actual default Integer size 
            => 1 + (lastIndex else -1);
    
    "Determines if the given index refers to an element of 
     this list, that is, if `0<=index<=list.lastIndex`."
    shared actual default Boolean defines(Integer index) 
            => 0 <= index < size;
    
    "Determines if this list contains the given value.
     Returns `true` for every element of this list."
    shared actual default Boolean contains(Object element) {
        for (index in 0:size) {
            if (exists elem = getFromFirst(index), 
                    elem==element) {
                return true;
            }
        }
        else {
            return false;
        }
    }
    
    "The rest of the list, without the first element.
     
     This is a lazy operation returning a view of this list."
    shared actual default List<Element> rest => Rest(1);
    
    "A list containing all indexes of this list.
     
     This is a lazy operation returning a view of this list."
    see (`function indexes`)
    shared actual default List<Integer> keys => Indexes();
    
    "A list containing the elements of this list in reverse 
     order to the order in which they occur in this list. 
     For every `index` of a reversed `list`:
     
         list.reversed[index]==list[size-1-index]
     
     This is a lazy operation returning a view of this list."
    shared default List<Element> reversed => Reversed();
    
    "A shallow copy of this list, that is, a list with the
     same elements as this list, which do not change if the
     elements of this list change."
    shared actual default List<Element> clone() 
            => sequence();
    
    shared actual default Iterator<Element> iterator() {
        if (size>0) {
            return object
                    satisfies Iterator<Element> {
                variable Integer index = 0;
                value size = outer.size;
                next() => index>=size
                    then finished
                    else getElement(index++);
                string => outer.string + ".iterator()";
            };
        }
        else {
            return emptyIterator;
        }
    }
    
    "Two `List`s are considered equal iff they have the 
     same `size` and _entry sets_. The entry set of a list 
     `list` is the set of elements of `list.indexed`. This 
     definition is equivalent to the more intuitive notion 
     that two lists are equal iff they have the same `size` 
     and for every index either:
     
     - the lists both have the element `null`, or
     - the lists both have a non-null element, and the
       two elements are equal.
     
     As a special exception, a [[String]] is not equal to 
     any list which is not also a [[String]]."
    shared actual default Boolean equals(Object that) {
        if (is String that) {
            return false;
        }
        if (is List<> that) {
            if (that.size==size) {
                for (index in 0:size) {
                    value x = getFromFirst(index);
                    value y = that.getFromFirst(index);
                    if (exists x) {
                        if (exists y) {
                            if (x!=y) {
                                return false;
                            }
                        }
                        else {
                            return false;
                        }
                    }
                    else if (exists y) {
                        return false;
                    }
                }
                else {
                    return true;
                }
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
    
    shared actual default Integer hash {
        variable value hash = 1;
        for (elem in this) {
            hash *= 31;
            if (exists elem) {
                hash += elem.hash;
            }
        }
        return hash;
    }
    
    shared actual default
    Boolean shorterThan(Integer length) => size<length;
    
    shared actual default 
    Boolean longerThan(Integer length) => size>length;
    
    "A list containing the elements of this list repeated 
     the [[given number of times|times]], or an empty list
     if `times<=0`. For every `index` of a repeated `list`:
     
         list.repeat(n)[index]==list[index%n]
     
     This is a lazy operation returning a view of this list."
    shared actual default 
    List<Element> repeat(Integer times) => Repeat(times);
    
    shared default actual 
    Element? find(
            Boolean selecting(Element&Object elem)) {
        variable value index = 0;
        while (index<size) {
            if (exists elem = getFromFirst(index++)) {
                if (selecting(elem)) {
                    return elem;
                }
            }
        }
        return null;
    }
    
    shared default actual 
    Element? findLast(
            Boolean selecting(Element&Object elem)) {
        variable value index = size-1;
        while (index >= 0) {
            if (exists elem = getFromFirst(index--), 
                    selecting(elem)) {
                return elem;
            }
        }
        return null;
    }
    
    "A sublist of this list, starting at the element with
     the given [[index|from]].
     
     This is a lazy operation, returning a view of this list."
    see (`function skip`)
    since("1.1.0")
    shared default 
    List<Element> sublistFrom(Integer from) 
            => from<=0 then this else Rest(from); 
    
    "A sublist of this list, ending at the element with the 
     given [[index|to]].
     
     This is a lazy operation, returning a view of this list."
    see (`function take`,
        `function initial`)
    since("1.1.0")
    shared default 
    List<Element> sublistTo(Integer to) 
            => to<0 then [] else Sublist(to);
    
    "A sublist of this list, starting at the element with
     index [[from]], ending at the element with the index 
     [[to]].
     
     This is a lazy operation, returning a view of this list."
    since("1.1.0")
    shared default 
    List<Element> sublist(Integer from, Integer to) 
            => sublistTo(to).sublistFrom(from);
    
    "Return a list formed by patching the given [[list]] 
     in place of a segment of this list identified by the
     given [[starting index|from]] and [[length]].
     
     This is a lazy operations, returning a view over this 
     list and the given list.
     
     Four special cases are interesting:
     
     - If `length==0`, the patched list has the given values 
       \"inserted\" into this list at the given index `from`.
     - If the given `list` is empty, the patched list has 
       the measure of this list identified by `from:length` 
       \"deleted\".
     - If `from==size`, the patched list is formed by
       appending the given list.
     - If `from==0`, the patched list is formed by 
       prepending the given list.
     
     For example:
     
     - `(-2..2).patch([], 1, 3)` produces the list `{-2, 2}`,
     - `[-2, 2].patch(-1..1, 1)` produces the list 
       `{-2, -1, 0, 1, 2}`, and
     - `0:3.patch(2..0)` produces the list 
       `{0, 1, 2, 2, 1, 0}`.
     
     Finally, to patch a single element, leaving the `size`
     of the list unchanged, explicitly specify `length==1`:
     
     - `[0, 1, 0, 1].patch([-1], 2, 1)` produces the list
       `{0, 1, -1, 1}`.
     
     If `length<0`, or if `from` is outside the range 
     `0..size`, return this list."
    since("1.1.0")
    shared default 
    List<Element|Other> patch<Other>(
        "The list of new elements."
        List<Other> list,
        "The index at which the elements will occur, and
         the start index of the segment to replace."
        Integer from=size,
        "The length of the segment to replace." 
        Integer length=0)
            => length>=0 && 0<=from<=size
            then Patch(list, from, length)
            else this;
    
    "Determine if the given [[list|sublist]] occurs at the 
     start of this list."
    see (`function endsWith`)
    shared default 
    Boolean startsWith(List<> sublist) 
            => !shorterThan(sublist.size) 
            && everyPair<Element,Anything>(
                (first, second)
                    => if (exists first, exists second)
                        then first==second
                        else first exists == second exists, 
                this, 
                sublist);
    
    "Determine if the given [[list|sublist]] occurs at the 
     end of this list."
    see (`function startsWith`)
    shared default 
    Boolean endsWith(List<> sublist)
            => !shorterThan(sublist.size) 
            && everyPair<Element,Anything>(
                (first, second)
                    => if (exists first, exists second)
                        then first==second
                        else first exists == second exists, 
                skip(size-sublist.size), 
                sublist);
    
    "The indexes in this list for which the element is not
     null and satisfies the given 
     [[predicate function|selecting]]."
    see (`function locations`)
    since("1.1.0")
    shared default 
    {Integer*} indexesWhere(
        "The predicate function the indexed elements must 
         satisfy."
        Boolean selecting(Element&Object element)) 
            => { for (index in 0:size) 
                    if (exists element=getFromFirst(index), 
                            selecting(element)) 
                        index };
    
    "The first index in this list for which the element is
     not null and satisfies the given 
     [[predicate function|selecting]]."
    see (`function locate`)
    since("1.1.0")
    shared default 
    Integer? firstIndexWhere(
        "The predicate function the indexed elements must 
         satisfy."
        Boolean selecting(Element&Object element)) {
        variable value index = 0;
        while (index<size) {
            if (exists element=getFromFirst(index), 
                selecting(element)) {
                return index;
            }
            index++;
        }
        return null;
    }
    
    "The last index in this list for which the element is
     not null and satisfies the given 
     [[predicate function|selecting]]."
    see (`function locateLast`)
    since("1.1.0")
    shared default 
    Integer? lastIndexWhere(
        "The predicate function the indexed elements must 
         satisfy."
        Boolean selecting(Element&Object element)) {
        variable value index = size;
        while (index>0) {
            index--;
            if (exists element=getFromFirst(index), 
                    selecting(element)) {
                return index;
            }
        }
        return null;
    }
    
    "Trim the elements satisfying the given [[predicate 
     function|trimming]], along with any null elements, from 
     the start and end of this list, returning a list no 
     longer than this list.
     
     This is an eager operation."
    shared default 
    List<Element> trim(
        "The predicate function that the trimmed elements 
         satisfy."
        Boolean trimming(Element&Object elem)) {
        if (size>0) {
            value end = size-1;
            variable Integer from=-1;
            variable Integer to=-1;
            for (index in 0..end) {
                if (exists elem=getFromFirst(index),
                        !trimming(elem)) {
                    from = index;
                    break;
                }
            }
            else {
                return [];
            }
            for (index in end..0) {
                if (exists elem=getFromFirst(index),
                        !trimming(elem)) {
                    to = index;
                    break;
                }
            }
            else {
                return [];
            }
            return this[from..to];
        }
        else {
            return [];
        }
    }
    
    "Trim the elements satisfying the given [[predicate 
     function|trimming]], along with any null elements, from
     the start of this list, returning a list no longer than 
     this list.
     
     This is an eager operation."
    shared default 
    List<Element> trimLeading(
        "The predicate function that the trimmed elements 
         satisfy."
        Boolean trimming(Element&Object elem)) {
        if (size>0) {
            value end = size-1;
            for (index in 0..end) {
                if (exists elem=getFromFirst(index),
                        !trimming(elem)) {
                    return this[index..end];
                }
            }
        }
        return [];
    }
    
    "Trim the elements satisfying the given [[predicate 
     function|trimming]], along with any null elements, from 
     the end of this list, returning a list no longer than 
     this list.
     
     This is an eager operation."
    shared default 
    List<Element> trimTrailing(
        "The predicate function that the trimmed elements 
         satisfy."
        Boolean trimming(Element&Object elem)) {
        if (size>0) {
            value end = size-1;
            for (index in end..0) {
                if (exists elem=getFromFirst(index),
                        !trimming(elem)) {
                    return this[0..index];
                }
            }
        }
        return [];
    }
    
    "Return two lists, the first containing the elements
     that occur before the given [[index]], the second with
     the elements that occur after the given `index`. If the
     given `index` is outside the range of indexes of this
     list, one of the returned lists will be empty.
     
     For any `list`, and for any integer `index`:
     
         list.slice(index) == [list[...index-1], list[index...]]
     
     This is an eager operation."
    since("1.1.0")
    shared default 
    List<Element>[2] slice(Integer index)
            => [this[...index-1], this[index...]];
    
    "Select the first elements of this list, returning a 
     list no longer than the given length. If this list is 
     shorter than the given length, return this list. 
     Otherwise return a list of the given length. If 
     `length<=0` return an empty list.
     
     For any `list`, and for any integer `length`:
     
         list.initial(length) == list[...length-1] == list[0:length]
     
     This is an eager operation."
    see (`function terminal`, 
         `function sublistTo`,
         `function take`)
    shared default 
    List<Element> initial(Integer length)
            => this[...length-1];
    
    "Select the last elements of the list, returning a list 
     no longer than the given length. If this list is 
     shorter than the given length, return this list. 
     Otherwise return a list of the given length.
     
     For any `list`, and for any integer `length`:
     
         list.terminal(length) == list[size-length...]
     
     This is an eager operation."
    see (`function initial`)
    shared default 
    List<Element> terminal(Integer length) 
            => this[size-length...];
    
    shared actual default 
    List<Element> span(Integer from, Integer to) {
        if (size>0) {
            value end = size-1;
            if (from <= to) {
                return 
                    if (to >= 0 && from <= end) 
                    then ArraySequence(Array(sublist(from,to)))
                    else [];
            }
            else {
                return 
                    if (from >= 0 && to <= end) 
                    then ArraySequence(Array(sublist(to,from).reversed))
                    else [];
            }
        }
        else {
            return [];
        }
    }
    
    shared actual default 
    List<Element> spanFrom(Integer from) {
        if (from<=0) {
            return clone();
        }
        else if (from<size) {
            return ArraySequence(Array(sublistFrom(from)));
        }
        else {
            return [];
        }
    }
    
    shared actual default 
    List<Element> spanTo(Integer to) {
        if (to>=size-1) {
            return clone();
        }
        else if (to>=0) {
            return ArraySequence(Array(sublistTo(to)));
        }
        else {
            return [];
        }
    }

    shared actual default
    List<Element> measure(Integer from, Integer length)
        => if (length > 0)
        then span(from, from + length - 1)
        else [];

    "A sequence containing the results of applying the given 
     mapping to the elements of this list."
    shared default actual 
    Result[] collect<Result>(
        "The transformation applied to the elements."
        Result collecting(Element element)) {
        if (empty) {
            return [];
        }
        else {
            object list
                    extends Object() 
                    satisfies List<Result> {
                lastIndex => outer.lastIndex;
                size = outer.size;
                getFromFirst(Integer index)
                        => if (0<=index<size) 
                        then collecting(outer.getElement(index))
                        else null;
            }
            return ArraySequence(Array(list));
        }
    }
    
    class Indexes()
            extends Object()
            satisfies List<Integer> {
        
        lastIndex => outer.lastIndex;
        
        getFromFirst(Integer index)
                => defines(index) then index;
        
        clone() => 0:size;
        
        measure(Integer from, Integer length)
                => clone()[from:length];
        
        span(Integer from, Integer to)
                => clone()[from..to];
        
        spanFrom(Integer from) 
                => clone()[from...];
        
        spanTo(Integer to) 
                => clone()[...to];
        
        string => if (exists endIndex=lastIndex)
            then "{ 0, ... , ``endIndex`` }"
            else "{}";
        
        iterator() 
                => object satisfies Iterator<Integer> {
                variable value i=0;
                next() => i<size then i++ else finished;
                string => "``outer.string``.iterator()";
            };
        
    }
    
    class Rest(Integer from)
            extends Object()
            satisfies List<Element> {
        
        assert (from>=0);
        
        sublistFrom(Integer from)
                => if (from>0) 
                then outer.Rest(from+this.from) 
                else this;
        
        getFromFirst(Integer index) 
                => if (index<0)
                then null 
                else outer.getFromFirst(index+from);
        
        lastIndex 
                => let (size = outer.size-from) 
                    (size>0 then size-1);
        
        measure(Integer from, Integer length) 
                => outer[from+this.from:length];
        
        span(Integer from, Integer to) 
                => outer[from+this.from..to+this.from];
        
        spanFrom(Integer from) 
                => outer[from+this.from...];
        
        spanTo(Integer to) 
                => outer[this.from..to+this.from];
        
        clone() => outer.clone().Rest(from);
        
        iterator() 
                => let (o = outer)
            object satisfies Iterator<Element> {
                variable value i=0;
                next() => if (i < outer.size)
                    then o.getElement(from + i++)
                    else finished;
                string => "``outer.string``.iterator()";
            };
        
    }
    
    class Sublist(Integer to)
            extends Object()
            satisfies List<Element> {
        
        assert (to>=0);
        
        sublistTo(Integer to) 
                => if (to<0) then [] 
                else if (to<this.to) then outer.Sublist(to) 
                else this;
        
        getFromFirst(Integer index)
                => if (0<=index<=to)
                then outer.getFromFirst(index)
                else null;
        
        lastIndex 
                => let (endIndex = outer.size-1) 
                    (endIndex>=0 then
                        (endIndex<to then endIndex else to));
        
        measure(Integer from, Integer length) 
                => from+length-1>to 
                    then outer[from:to] 
                    else outer[from:length];
        
        span(Integer from, Integer to) 
                => to>this.to
                    then outer[from..this.to] 
                    else outer[from..to];
        
        spanFrom(Integer from) 
                => outer[from..to];
        
        spanTo(Integer to)
                => to>this.to
                    then outer[...this.to] 
                    else outer[...to];
        
        clone() => outer.clone().Sublist(to);
        
        iterator() 
                => let (iter = outer.iterator()) 
            object satisfies Iterator<Element> {
                variable value i=0;
                next() => i++>to 
                    then finished 
                    else iter.next();
                string => "``outer.string``.iterator()";
            };
        
    }
            
    class Repeat(Integer times)
            extends Object()
            satisfies List<Element> {
        
        size => outer.size*times;
        
        lastIndex 
                => let (size = this.size) 
                    (size>0 then size-1);
        
        getFromFirst(Integer index) 
                => let (size = outer.size) 
                    if (index<size*times) 
                        then outer.getFromFirst(index%size)
                        else null;
        
        clone() => outer.clone().Repeat(times);
        
        iterator() => CycledIterator(outer,times);
        
        string => "(``outer.string``).repeat(``times``)";
        
    }
    
    class Patch<Other>(List<Other> list, 
        Integer from, Integer length)
            extends Object()
            satisfies List<Element|Other> {
        
        assert (length>=0);
        assert (0<=from<=outer.size);

        value exactLength => smallest(length, outer.size-from);
        
        size => outer.size+list.size-exactLength;
        
        lastIndex 
                => let (size = this.size) 
                    (size>0 then size-1);
        
        getFromFirst(Integer index) 
                => if (index<from) then
                    outer.getFromFirst(index)
                else if (index-from<list.size) then
                    list.getFromFirst(index-from)
                else
                    outer.getFromFirst(index-list.size+exactLength);
        
        clone() => outer.clone().Patch(list.clone(),from,exactLength);
        
        iterator() 
                => let (iter = outer.iterator(), 
                        patchIter = list.iterator()) 
            object satisfies Iterator<Element|Other> {
                variable value index = -1;
                shared actual Element|Other|Finished next() {
                    if (++index==from) {
                        for (skip in 0:exactLength) {
                            iter.next();
                        }
                    }
                    return if (0<=index-from<list.size)
                        then patchIter.next()
                        else iter.next();
                }
                string => "``outer.string``.iterator()";
            };
        
    }
    
    class Reversed()
            extends Object()
            satisfies List<Element> {
        
        lastIndex => outer.lastIndex;
        size => outer.size;
        first => outer.last;
        last => outer.first;
        
        reversed => outer;
        
        getFromFirst(Integer index)
                => if (size>0)
                then outer.getFromFirst(size-1-index)
                else null;
        
        measure(Integer from, Integer length)
                => if (size>0 && length>0)
                then let (start = size-1-from) 
                    outer[start..start-length+1]
                else [];
        
        span(Integer from, Integer to) 
                => outer[to..from];
        
        spanFrom(Integer from) 
                => let (endIndex = size-1) 
                    if (endIndex>=0, from<=endIndex) 
                        then outer[endIndex-from..0]
                        else [];
        
        spanTo(Integer to)
                => let (endIndex = size-1) 
                    if (endIndex>=0 && to>=0)
                        then outer[endIndex..endIndex-to]
                        else [];
        
        clone() => outer.clone().reversed;
        
        iterator() 
                => let (outerList = outer) 
            object satisfies Iterator<Element> {
                variable value index=outerList.size-1;
                next() => index<0 
                    then finished 
                    else outerList.getElement(index--);
                string => "``outer.string``.iterator()";
            };
        
    }
    
    "Produces a list with the same indexes as this list. For 
     every index, the element is the result of applying the 
     given [[transformation|List.mapElements.mapping]] 
     function to its associated element in this list. This 
     is a lazy operation, returning a view of this list."
    since("1.3.0")
    shared default 
    List<Result> mapElements<Result>(
        "The function that transforms an index/item pair of
         this list, producing the element of the resulting 
         list."
        Result mapping(Integer index, Element item)) 
            => object
            extends Object()
            satisfies List<Result> {
        
        shared actual Result? getFromFirst(Integer index) {
            if (0 <= index < size) {
                if (exists element 
                        = outer.getFromFirst(index)) {
                    return mapping(index, element);
                }
                else {
                    assert (is Element null);
                    return mapping(index, null);
                }
            }
            else {
                return null;
            }
        }
        
        iterator() 
                => let (it = outer.iterator())
                object satisfies Iterator<Result> {
                    variable value index = 0;
                    next() => if (!is Finished element 
                                    = it.next()) 
                                then mapping(index++, element)
                                else finished;
                };
        
        lastIndex => outer.lastIndex;
        size => outer.size;
        
        clone() => outer.clone().mapElements(mapping);
        
    };
    
}