import java.lang {
    JStringBuilder=StringBuilder,
    JCharacter=Character {
        toChars,
        charCount
    },
    IndexOutOfBoundsException
}

"""Builder utility for constructing [[strings|String]] by 
   incrementally appending strings or characters.
   
       value builder = StringBuilder();
       builder.append("hello");
       builder.appendCharacter(' ');
       builder.append("world");
       String hello = builder.string; //hello world"""
tagged("Strings")
shared native final class StringBuilder() 
        satisfies SearchableList<Character> &
                  Ranged<Integer,Character,String> &
                  IndexedCorrespondenceMutator<Character> { 
    
    "The number of characters in the current content, that 
     is, the [[size|String.size]] of the produced [[string]]."
    shared actual native Integer size;
    
    "Determines if the current content holds at least one
     character."
    shared actual native Boolean empty;
    
    shared actual native Integer? lastIndex;
    
    "The resulting string. If no characters have been
     appended, the empty string."
    shared actual native variable String string;
    
    "A copy of this `StringBuilder`, whose content is 
     initially the same as the current content of this
     instance."
    since("1.3.0")
    shared actual StringBuilder clone() {
        value clone = StringBuilder();
        clone.string = string;
        return clone;
    }
    
    shared actual native Iterator<Character> iterator();
    
    "Returns a string of the given [[length]] containing
     the characters beginning at the given [[index]]."
    deprecated ("use [[measure]]")
    since("1.1.0")
    shared 
    String substring(Integer index, Integer length)
            => measure(index, length);
    
    shared actual native
    Character? getFromFirst(Integer index);
    
    "Append the characters in the given [[string]]."
    shared native 
    StringBuilder append(String string);
    
    "Append the characters in the given [[strings]]."
    shared native 
    StringBuilder appendAll({String*} strings) {
        for (s in strings) {
            append(s);
        }
        return this;
    }
    
    "Prepend the characters in the given [[string]]."
    since("1.1.0")
    shared native 
    StringBuilder prepend(String string);
    
    "Prepend the characters in the given [[strings]]."
    since("1.1.0")
    shared native 
    StringBuilder prependAll({String*} strings) {
        for (s in strings) {
            prepend(s);
        }
        return this;
    }
    
    "Append the given [[character]]."
    shared native 
    StringBuilder appendCharacter(Character character);
    
    "Prepend the given [[character]]."
    since("1.1.0")
    shared native 
    StringBuilder prependCharacter(Character character);
    
    "Append a newline character."
    shared native 
    StringBuilder appendNewline() => appendCharacter('\n');
    
    "Append a space character."
    shared native 
    StringBuilder appendSpace() => appendCharacter(' ');
    
    "Remove all content and return to initial state."
    since("1.1.0")
    shared native 
    StringBuilder clear();
    
    "Set the character at the given index to the given
     [[character]]."
    since("1.3.0")
    shared actual void set(Integer index, Character character)
            => replace(index, 1, character.string);
    
    "Insert a [[string]] at the specified [[index]]."
    shared native 
    StringBuilder insert(Integer index, String string);
    
    "Insert a [[character]] at the specified [[index]]."
    shared native 
    StringBuilder insertCharacter
            (Integer index, Character character);
    
    "Replaces the specified [[number of characters|length]] 
     from the current content, starting at the specified 
     [[index]], with the given [[string]]. If [[length]] is 
     nonpositive, nothing is replaced, and the `string` is
     simply inserted at the specified `index`."
    since("1.1.0")
    shared native 
    StringBuilder replace
            (Integer index, Integer length, String string);
    
    "Deletes the specified [[number of characters|length]] 
     from the current content, starting at the specified 
     [[index]]. If [[length]] is nonpositive, nothing is 
     deleted."
    shared native 
    StringBuilder delete(Integer index, Integer length/*=1*/);
    
    "Deletes the specified [[number of characters|length]] 
     from the start of the string. If `length` is 
     nonpositive, nothing is deleted."
    since("1.1.0")
    shared native 
    StringBuilder deleteInitial(Integer length);
    
    "Deletes the specified [[number of characters|length]] 
     from the end of the string. If `length` is nonpositive, 
     nothing is deleted."
    since("1.1.0")
    shared native 
    StringBuilder deleteTerminal(Integer length);
    
    "Reverses the order of the current characters."
    since("1.1.0")
    shared native 
    StringBuilder reverseInPlace();
    
    "The first index at which the given 
     [[list of characters|sublist]] occurs as a sublist, 
     that is greater than or equal to the optional 
     [[starting index|from]]."
    shared actual native
    Integer? firstInclusion(List<Character> sublist,
        Integer from);
    
    "The last index at which the given 
     [[list of characters|sublist]] occurs as a sublist, 
     that falls within the range `0:size-from+1-sublist.size` 
     defined by the optional [[starting index|from]], 
     interpreted as a reverse index counting from the _end_
     of the list."
    shared actual native
    Integer? lastInclusion(List<Character> sublist,
        Integer from);
    
    "The first index at which the given [[character]] occurs, 
     that is greater than or equal to the optional 
     [[starting index|from]]."
    shared actual native
    Integer? firstOccurrence(Character character,
        Integer from, Integer length);
    
    "The last index at which the given [[character]] occurs, 
     that falls within the range `0:size-from` defined by 
     the optional [[starting index|from]], interpreted as a 
     reverse index counting from the _end_ of the list."
    shared actual native
    Integer? lastOccurrence(Character character,
        Integer from, Integer length);
    
    shared actual native
    {Integer*} inclusions(List<Character> sublist, 
        Integer from);
    
    shared actual native
    {Integer*} occurrences(Character character, 
        Integer from, Integer length);
    
    shared actual 
    Boolean occursAt(Integer index, Character character) 
            => if (exists ch = getFromFirst(index))
                then ch == character else false;
    
    shared actual 
    Boolean includesAt(Integer index, List<Character> sublist)
            => this[index:sublist.size] == sublist;
    
    shared actual native 
    String measure(Integer from, Integer length);
    
    shared actual 
    String initial(Integer length) 
            => measure(0, length);
    
    shared actual 
    String terminal(Integer length) 
            => measure(size-length, length);
    
    shared actual native String span(Integer from, Integer to);
    shared actual native String spanTo(Integer to);
    shared actual native String spanFrom(Integer from);
    
    shared actual native Boolean equals(Object that);
    shared actual native Integer hash;
}

shared native("jvm") final class StringBuilder() 
        satisfies SearchableList<Character> &
                  Ranged<Integer,Character,String> &
                  IndexedCorrespondenceMutator<Character> {
    
    value builder = JStringBuilder();
    
    shared actual native("jvm") Integer size 
            => builder.codePointCount(0, builder.length());
    
    shared actual native("jvm") Boolean empty
            => builder.length() == 0;
    
    shared actual native("jvm") Integer? lastIndex 
            => if (builder.length() == 0)
            then null
            else builder.length() - 1;
    
    shared actual native("jvm") String string 
            => builder.string;
    
    native("jvm") assign string 
            => builder.replace(0, builder.length(), string);
    
    shared actual native("jvm") 
    Iterator<Character> iterator() {
        object stringBuilderIterator
                satisfies Iterator<Character> {
            variable Integer offset = 0;
            shared actual Character|Finished next() {
                if (offset < builder.length()) {
                    Integer codePoint = 
                            builder.codePointAt(offset);
                    offset += charCount(codePoint);
                    return codePoint.character;
                }
                else {
                    return finished;
                }
            }
        }
        return stringBuilderIterator;
    }
    
    shared actual native("jvm")
    Character? getFromFirst(Integer index) {
        try {
            return builder.codePointAt(startIndex(index))
                          .character;
        }
        catch (IndexOutOfBoundsException ioobe) {
            return null;
        }
    }
    
    shared native("jvm") 
    StringBuilder append(String string) {
        builder.append(string);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder prepend(String string) {
        builder.insert(0, string);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder appendCharacter(Character character) {
        builder.appendCodePoint(character.integer);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder prependCharacter(Character character) {
        builder.insert(0, toChars(character.integer));
        return this;
    }
    
    shared native("jvm") 
    StringBuilder clear() {
        builder.setLength(0);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder insert(Integer index, String string) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        builder.insert(startIndex(index), string);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder insertCharacter
            (Integer index, Character character) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        builder.insert(startIndex(index),
            toChars(character.integer));
        return this;
    }
    
    shared native("jvm") 
    StringBuilder replace
            (Integer index, Integer length, String string) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        "index+length must not be greater than size"
        assert (index+length<=size);
        Integer len = length<0 then 0 else length;
        Integer start = startIndex(index);
        Integer end = endIndex(start, len);
        builder.replace(start, end, string);
        return this;
    }
    
    shared native("jvm") 
    StringBuilder delete(Integer index, Integer length) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        "index+length must not be greater than size"
        assert (index+length<=size);
        if (length>0) {
            Integer start = startIndex(index);
            Integer end = endIndex(start, length);
            builder.delete(start, end);
        }
        return this;
    }
    
    shared native("jvm") 
    StringBuilder deleteInitial(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            builder.delete(0, startIndex(length));
        }
        return this;
    }
    
    shared native("jvm") 
    StringBuilder deleteTerminal(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            Integer start = startIndex(size - length);
            builder.delete(start, builder.length());
        }
        return this;
    }
    
    shared native("jvm") 
    StringBuilder reverseInPlace() {
        builder.reverse();
        return this;
    }
    
    shared actual native("jvm")
    Integer? firstInclusion(List<Character> sublist,
        Integer from) {
        try {
            value start 
                    = builder.offsetByCodePoints(0, 
                            from>0 then from else 0);
            value index 
                    = builder.indexOf(String(sublist), 
                            start);
            return index>=0 
                then from 
                    + builder.codePointCount(start, index);
        }
        catch (IndexOutOfBoundsException ioe) {
            return null;
        }
    }
    
    shared actual native("jvm")
    Integer? lastInclusion(List<Character> sublist,
        Integer from) {
        try {
            value start 
                    = builder.offsetByCodePoints(
                            builder.length(), 
                            (from>0 then -from else 0)
                                - sublist.size);
            value index 
                    = builder.lastIndexOf(String(sublist), 
                            start);
            return index>=0 
                then builder.codePointCount(0, index);
        }
        catch (IndexOutOfBoundsException ioe) {
            return null;
        }
    }
    
    shared actual native("jvm")
    Integer? firstOccurrence(Character character,
        Integer from, Integer length) {
        if (length<=0) { return null; }
        try {
            value start 
                    = builder.offsetByCodePoints(0, 
                            from>0 then from else 0);
            value index 
                    = builder.indexOf(character.string, 
                            start);
            if (index>=0) {
                value count  //TODO: wrong if from<0
                        = builder.codePointCount(start, index);
                return count < length
                    then from + count 
                    else null;
            }
            else {
                return null;
            }
        }
        catch (IndexOutOfBoundsException ioe) {
            return null;
        }
    }
    
    shared actual native("jvm")
    Integer? lastOccurrence(Character character,
        Integer from, Integer length) {
        if (length<=0) { return null; }
        try {
            value start 
                    = builder.offsetByCodePoints(
                            builder.length(), 
                            (from>0 then -from else 0) - 1);
            value index 
                    = builder.lastIndexOf(character.string, 
                            start);
            if (index>=0) {
                value count = //TODO: wrong if from<0
                        builder.codePointCount(index, start);
                return count < length 
                    then builder.codePointCount(0, index)
                    else null;
            }
            else {
                return null;
            }
        }
        catch (IndexOutOfBoundsException ioe) {
            return null;
        }
    }
    
    shared actual native("jvm")
    {Integer*} inclusions(List<Character> sublist, 
        Integer from)
            //TODO: optimize this!
            => string.inclusions(sublist, from);
    
    shared actual native("jvm")
    {Integer*} occurrences(Character character, 
        Integer from, Integer length)
            //TODO: optimize this!
            => string.occurrences(character, from, length);
    
    shared actual native("jvm")
    String measure(Integer from, Integer length) {
        value len = size;
        if (from >= len || length <= 0) {
            return "";
        }
        value resultLength 
                = if (from + length > len) 
                then len - from 
                else length;
        value start = startIndex(from);
        value end = endIndex(start, resultLength);
        return builder.substring(start, end);
    }
    
    shared actual native("jvm") 
    String span(Integer from, Integer to) {
        value len = size;
        if (len == 0) {
            return "";
        }
        value reverse = to < from;
        Integer _to;
        Integer _from;
        if (reverse) {
            _to = from;
            _from = to;
        }
        else {
            _to = to;
            _from = from;
        }
        if (_to < 0 || _from >= len) {
            return "";
        }
        value begin = _from < 0 then 0 else _from;
        value start = startIndex(begin);
        String result;
        if (_to >= len) {
            result = builder.substring(start);
        }
        else {
            value end = endIndex(start, _to+1 - begin);
            result = builder.substring(start, end);
        }
        return reverse then result.reversed else result;
    }
    
    shared actual native("jvm") 
    String spanFrom(Integer from) {
        if (from <= 0) {
            return string;
        }
        value len = size;
        if (len == 0 || from >= len) {
            return "";
        }
        return builder.substring(startIndex(from));
    }
    
    shared actual native("jvm") 
    String spanTo(Integer to) {
        value len = size;
        if (len == 0 || to < 0) {
            return "";
        }
        if (to >= len) {
            return string;
        }
        return builder.substring(0, startIndex(to+1));
    }
    
    shared actual native("jvm") Boolean equals(Object that) 
            => builder.equals(that);
    
    shared actual native("jvm") Integer hash 
            => builder.hash;
    
    Integer startIndex(Integer index) 
            => builder.offsetByCodePoints(0, index);
    
    Integer endIndex(Integer start, Integer length) 
            => builder.offsetByCodePoints(start, length);
    
}

shared native("js") final class StringBuilder() 
        satisfies SearchableList<Character> &
                  Ranged<Integer,Character,String> &
                  IndexedCorrespondenceMutator<Character> {
    
    shared actual native("js") variable String string = "";
        
    shared actual native("js") Integer size => string.size;
    
    shared actual native("js") Boolean empty => string.empty;
    
    shared actual native("js") Integer? lastIndex 
            => if (string.size == 0)
            then null
            else string.size - 1;
    
    shared actual native("js") 
    Iterator<Character> iterator() 
            => string.iterator();
    
    shared actual native("js")
    String measure(Integer from, Integer length)
            => string[from:length];
    
    shared actual native("js")
    String span(Integer from, Integer to) => string[from:to];
    
    shared actual native("js")
    String spanTo(Integer to) => string[...to];
    
    shared actual native("js")
    String spanFrom(Integer from) => string[from...];
    
    shared actual native("js")
    Character? getFromFirst(Integer index) 
            => string.getFromFirst(index);
    
    shared native("js") 
    StringBuilder append(String string) {
        this.string = this.string + string;
        return this;
    }
    
    shared native("js") 
    StringBuilder prepend(String string) {
        this.string = string + this.string;
        return this;
    }
    
    shared native("js") 
    StringBuilder appendCharacter(Character character) {
        string = string + character.string;
        return this;
    }
    
    shared native("js") 
    StringBuilder prependCharacter(Character character) {
        string = character.string + string;
        return this;
    }
    
    shared native("js") 
    StringBuilder clear() {
        string = "";
        return this;
    }
    
    shared native("js") 
    StringBuilder insert(Integer index, String string) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        this.string 
                = this.string[0:index] 
                + string 
                + this.string[index...];
        return this;
    }
    
    shared native("js") 
    StringBuilder insertCharacter
            (Integer index, Character character) 
            => insert(index, character.string);
    
    shared native("js") 
    StringBuilder replace
            (Integer index, Integer length, String string) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        "index+length must not be greater than size"
        assert (index+length<=size);
        value len = length<0 then 0 else length;
        this.string 
                = this.string[0:index] 
                + string + 
                this.string[index+len...];
        return this;
    }
    
    shared native("js") 
    StringBuilder delete(Integer index, Integer length) {
        "index must not be negative"
        assert (index>=0);
        "index must not be greater than size"
        assert (index<=size);
        "index+length must not be greater than size"
        assert (index+length<=size);
        if (length>0) {
            string = string[0:index] 
                   + string[index+length...];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder deleteInitial(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            string = string[length...];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder deleteTerminal(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            string = string[0:size-length];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder reverseInPlace() {
        string = string.reversed;
        return this;
    }
    
    shared actual native("js")
    Integer? firstInclusion(List<Character> sublist,
        Integer from) 
            => string.firstInclusion(sublist, from);
    
    shared actual native("js")
    Integer? lastInclusion(List<Character> sublist,
        Integer from) 
            => string.lastInclusion(sublist, from);
    
    shared actual native("js")
    Integer? firstOccurrence(Character character,
        Integer from, Integer length) 
            => string.firstOccurrence(character, from, length);
    
    shared actual native("js")
    Integer? lastOccurrence(Character character,
        Integer from, Integer length) 
            => string.lastOccurrence(character, from, length);
    
    shared actual native("js")
    {Integer*} inclusions(List<Character> sublist, 
        Integer from)
            => string.inclusions(sublist, from);
    
    shared actual native("js")
    {Integer*} occurrences(Character character, 
        Integer from, Integer length)
            => string.occurrences(character, from, length);
    
    shared actual native("js") Boolean equals(Object that) 
            => string.equals(that);
    
    shared actual native("js") Integer hash => string.hash;
}