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

"""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 List<Character> {
    
    "The number characters in the current content, that is, 
     the [[size|String.size]] of the produced [[string]]."
    shared actual native Integer size;
    
    shared actual native Integer? lastIndex;
    
    "The resulting string. If no characters have been
     appended, the empty string."
    shared actual native String string;
    
    shared actual native Iterator<Character> iterator();
    
    "Returns a string of the given [[length]] containing
     the characters beginning at the given [[index]]."
    shared native 
    String substring(Integer index, Integer 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]]."
    shared native 
    StringBuilder prepend(String string);
    
    "Prepend the characters in the given [[strings]]."
    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]]."
    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."
    shared native 
    StringBuilder clear();
    
    "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."
    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."
    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."
    shared native 
    StringBuilder deleteTerminal(Integer length);
    
    "Reverses the order of the current characters."
    shared native 
    StringBuilder reverseInPlace();
    
    shared actual native Boolean equals(Object that);
    shared actual native Integer hash;
}

shared native("jvm") final class StringBuilder() 
        satisfies List<Character> {
    
    value builder = JStringBuilder();
    
    shared actual native("jvm") Integer size 
            => builder.codePointCount(0, builder.length());
    
    shared actual native("jvm") Integer? lastIndex 
            => if (builder.length() == 0)
            then null
            else builder.length() - 1;
    
    shared actual native("jvm") String string 
            => builder.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 native("jvm") 
    String substring(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);
            return builder.substring(start, end);
        }
        else {
            return "";
        }
    }
    
    shared actual native("jvm")
    Character? getFromFirst(Integer index) 
            => if (index<0 || index>size)
            then null
            else builder.codePointAt(startIndex(index)).character;
    
    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);
        if (!string.empty) {
            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") 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 List<Character> {
    
    variable String str = "";
    
    shared actual native("js") Integer size => str.size;
    
    shared actual native("js") Integer? lastIndex 
            => if (str.size == 0)
            then null
            else str.size - 1;
    
    shared actual native("js") String string => str;
    
    shared actual native("js") 
    Iterator<Character> iterator() 
            => str.iterator();
    
    shared native("js") 
    String substring(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);
        return if (length>0) then str[index..length] else "";
    }
    
    shared actual native("js")
    Character? getFromFirst(Integer index) 
            => if (index<0 || index>size) 
            then null 
            else str.getFromFirst(index);
    
    shared native("js") 
    StringBuilder append(String string) {
        str = str + string;
        return this;
    }
    
    shared native("js") 
    StringBuilder prepend(String string) {
        str = string + str;
        return this;
    }
    
    shared native("js") 
    StringBuilder appendCharacter(Character character) {
        str = str + character.string;
        return this;
    }
    
    shared native("js") 
    StringBuilder prependCharacter(Character character) {
        str = character.string + str;
        return this;
    }
    
    shared native("js") 
    StringBuilder clear() {
        str = "";
        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);
        str = str[0:index] + string + str[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);
        if (!string.empty) {
            str = str[0:index] + string + str[index+length...];
        }
        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) {
            str = str[0:index] + str[index+length...];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder deleteInitial(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            str = str[length...];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder deleteTerminal(Integer length) {
        "length must not be greater than size"
        assert (length<=size);
        if (length>0) {
            str = str[0:size-length];
        }
        return this;
    }
    
    shared native("js") 
    StringBuilder reverseInPlace() {
        str = str.reversed;
        return this;
    }
    
    shared actual native("js") Boolean equals(Object that) 
            => str.equals(that);
    
    shared actual native("js") Integer hash => str.hash;
}