/**
 * Represents a single line of text (i.e., with no newlines) and allows new
 * characters to be inserted at any point in middle.
 */
public class TextLine {

  // RI: prefix != null and suffix != null and
  //     0 <= prefixLen < prefix.length and
  //     0 <= suffixLen < suffix.length
  // AF(this) = prefix[0..prefixLen-1] + reverse(suffix[0..suffixLen-1])
  private char[] prefix, suffix;
  private int prefixLen, suffixLen;

  /** @effects makes this an empty line */
  public TextLine() {
    prefix = new char[0];
    suffix = new char[0];
  }

  /** @returns length of this */
  public int getLength() { return prefixLen + suffixLen; }

  /** @returns this (as a string) */
  public String getText() {
    StringBuilder buf = new StringBuilder();
    buf.append(prefix, 0, prefixLen);
    // Inv: buf = prefix[0..prefixLen-1] + reverse(suffix[i+1..suffixLen-1])
    for (int i = suffixLen - 1; i >= 0; i--)
      buf.append(suffix[i]);
    return buf.toString();
  }

  /** @requires 0 <= col <= length
    * @modifies this
    * @effects this_post = this_pre[0..col-1] + ch + this_pre[col..] */
  public void insert(int col, char ch) {
    moveSplitTo(col);

    prefix = ensureSpace(prefix, prefixLen + 1);
    prefix[prefixLen] = ch;
    prefixLen += 1;

    checkRep();
  }

  /** @requires 0 <= col < length
    * @modifies this
    * @effects this_post = this_pre[0..col-1] + this_pre[col+1..] */
  public void remove(int col) {
    moveSplitTo(col);

    suffixLen--;

    checkRep();
  }

  /** @requires 0 <= col <= length
    * @modifies this
    * @effects this is unchanged and prefixLen = col */
  private void moveSplitTo(int col) {
    if (prefixLen > col) {
      suffix = ensureSpace(suffix, prefixLen + suffixLen - col);
      do {  // Inv: this is unchanged
        suffix[suffixLen++] = prefix[--prefixLen];
      } while (prefixLen > col);

    } else if (prefixLen < col) {
      prefix = ensureSpace(prefix, col);
      do {  // Inv: this is unchanged
        prefix[prefixLen++] = suffix[--suffixLen];
      } while (prefixLen < col);

    } else {
      // split is already in the right place
    }
  }

  /** @returns an array containing the same chars plus any padding necessary to
    *    make the length at least len */
  private static char[] ensureSpace(char[] chars, int len) {
    if (len <= chars.length) {
      return chars;
    } else {
      char[] newChars = new char[Math.max(len, 2 * chars.length)];
      System.arraycopy(chars, 0, newChars, 0, chars.length);
      return newChars;
    }
  }

  /** Checks that RI holds. */
  private void checkRep() {
    assert prefix != null;
    assert suffix != null;
    assert 0 <= prefixLen && prefixLen < prefix.length;
    assert 0 <= suffixLen && suffixLen < suffix.length;
  }
}