/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2010, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.collection
package mutable

import java.lang.{ StringBuilder => JavaStringBuilder }
import annotation.migration

/** A builder for mutable sequence of characters.  This class provides an API
 *  mostly compatible with java.lang.StringBuilder, except where there are conflicts
 *  with the Scala collections API (such as the `reverse` method.)
 *
 *  @author Stephane Micheloud
 *  @author Martin Odersky
 *  @version 2.8
 *  @since   2.7
 */
@serializable
@SerialVersionUID(0 - 8525408645367278351L)
final class StringBuilder(private val underlying: JavaStringBuilder)
      extends Builder[Char, String]
         with java.lang.CharSequence
         with IndexedSeq[Char] 
         with IndexedSeqOptimized[Char, IndexedSeq[Char]] {
  
  /** Constructs a string builder initialized with String initValue
   *  and with additional Char capacity initCapacity.
   */
  def this(initCapacity: Int, initValue: String) =
    this(new JavaStringBuilder(initValue.length + initCapacity) append initValue)

  /** Constructs a string builder with no characters in it and an 
   *  initial capacity of 16 characters.
   */
  def this() = this(16, "")

  /** Constructs a string builder with no characters in it and an 
   *  initial capacity specified by the capacity argument. 
   *
   *  @param  capacity  the initial capacity.
   *  @throws NegativeArraySizeException  if capacity < 0.
   */
  def this(capacity: Int) = this(capacity, "")

  /** Constructs a string builder with initial characters 
   *  equal to characters of `str`.
   */
  def this(str: String) = this(16, str)

  def toArray: Array[Char] = {
    val arr = new Array[Char](length)
    underlying.getChars(0, length, arr, 0)
    arr
  }
  
  def length: Int = underlying.length()
  def length_=(n: Int) { underlying.setLength(n) }
 
  /** Clears the builder contents.
   */
  def clear(): Unit = setLength(0)

  /** Sets the length of the character sequence.  If the current sequence
   *  is shorter than the given length, it is padded with nulls; if it is
   *  longer, it is truncated.
   *
   *  @param  len  the new length
   *  @throws IndexOutOfBoundsException if the argument is negative.
   */
  def setLength(len: Int) { underlying setLength len }

  /** Returns the current capacity, which is the size of the underlying array.
   *  A new array will be allocated if the current capacity is exceeded.
   *
   *  @return  the capacity
   */
  def capacity: Int = underlying.capacity()

  @deprecated("Use `ensureCapacity' instead. An assignment is misleading because\n"+
              "it can never decrease the capacity.")
  def capacity_=(n: Int) { ensureCapacity(n) }

  /** Ensure that the capacity is at least the given argument.
   *  If the argument is greater than the current capacity, new
   *  storage will be allocated with size equal to the given
   *  argument or to (2 * capacity + 2), whichever is larger.
   *
   *  @param newCapacity    the minimum desired capacity.
   */
  def ensureCapacity(newCapacity: Int): Unit = underlying ensureCapacity newCapacity
      
  /** Returns the Char at the specified index, counting from 0 as in Arrays.
   *
   *  @param  index   the index to look up
   *  @return         the Char at the given index.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def charAt(index: Int): Char = underlying charAt index

  /** Equivalent to charAt.
   */
  def apply(index: Int): Char = underlying charAt index

  /** Removes the Char at the specified index.  The sequence is
   *  shortened by one.
   *
   *  @param  index  The index to remove.
   *  @return        This StringBuilder.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def deleteCharAt(index: Int): StringBuilder = {
    underlying deleteCharAt index
    this
  }

  /** Update the sequence at the given index to hold the specified Char.
   *
   *  @param  index   the index to modify.
   *  @param  ch      the new Char.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def setCharAt(index: Int, ch: Char): Unit = underlying.setCharAt(index, ch)

  /** Equivalent to setCharAt.
   */
  def update(i: Int, c: Char): Unit = setCharAt(i, c)

  /** Returns a new String made up of a subsequence of this sequence,
   *  beginning at the given index and extending to the end of the sequence.
   *
   *  target.substring(start)  is equivalent to  target.drop(start)
   * 
   *  @param  start  The starting index, inclusive.
   *  @return        The new String.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def substring(start: Int): String = substring(start, length)

  /** Returns a new String made up of a subsequence of this sequence,
   *  beginning at the start index (inclusive) and extending to the
   *  end index (exclusive).
   *
   *  target.substring(start, end)  is equivalent to  target.slice(start, end).mkString
   *
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @return The new String.
   *  @throws StringIndexOutOfBoundsException If either index is out of bounds,
   *          or if start > end.
   */
  def substring(start: Int, end: Int): String = underlying.substring(start, end)

  /** For implementing CharSequence.
   */
  def subSequence(start: Int, end: Int): java.lang.CharSequence = substring(start, end)

  /** Appends the given Char to the end of the sequence.
   */
  def +=(x: Char): this.type = { append(x); this }

  /** !!! This should create a new sequence.
   */
  def +(x: Char): this.type = { +=(x); this }

  /** Appends the string representation of the given argument,
   *  which is converted to a String with String.valueOf.
   *
   *  @param  x   an <code>Any</code> object.
   *  @return     this StringBuilder.
   */
  def append(x: Any): StringBuilder = {
    underlying append String.valueOf(x)
    this
  }

  /** Appends the given String to this sequence.
   *
   *  @param  s   a String.
   *  @return     this StringBuilder.
   */
  def append(s: String): StringBuilder = {
    underlying append s
    this
  }

  /** Appends the specified string builder to this sequence.
   *
   *  @param sb
   *  @return 
   */
  def append(sb: StringBuilder): StringBuilder = {
    underlying append sb
    this
  }

  /** Appends all the Chars in the given Seq[Char] to this sequence.
   *
   *  @param  xs  the characters to be appended.
   *  @return     this StringBuilder.
   */
  def appendAll(xs: TraversableOnce[Char]): StringBuilder = appendAll(xs.toArray)

  /** Appends all the Chars in the given Array[Char] to this sequence.
   *
   *  @param  xs  the characters to be appended.
   *  @return     a reference to this object.
   */
  def appendAll(xs: Array[Char]): StringBuilder = {
    underlying append xs
    this
  }

  /** Appends a portion of the given Array[Char] to this sequence.
   *
   *  @param  xs      the Array containing Chars to be appended.
   *  @param  offset  the index of the first Char to append.
   *  @param  len     the numbers of Chars to append.
   *  @return         this StringBuilder.
   */
  def appendAll(xs: Array[Char], offset: Int, len: Int): StringBuilder = {
    underlying.append(xs, offset, len)
    this
  }

  /** Append the String representation of the given primitive type
   *  to this sequence.  The argument is converted to a String with
   *  String.valueOf.
   *
   *  @param   x  a primitive value
   *  @return     This StringBuilder.
   */
  def append(x: Boolean): StringBuilder = { underlying append x ; this }
  def append(x: Byte): StringBuilder = { underlying append x ; this }
  def append(x: Short): StringBuilder = { underlying append x ; this }
  def append(x: Int): StringBuilder = { underlying append x ; this }
  def append(x: Long): StringBuilder = { underlying append x ; this }
  def append(x: Float): StringBuilder = { underlying append x ; this }
  def append(x: Double): StringBuilder = { underlying append x ; this }
  def append(x: Char): StringBuilder = { underlying append x ; this }

  /** Remove a subsequence of Chars from this sequence, starting at the
   *  given start index (inclusive) and extending to the end index (exclusive)
   *  or to the end of the String, whichever comes first.
   *
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @return        This StringBuilder.
   *  @throws StringIndexOutOfBoundsException   if start < 0 || start > end
   */
  def delete(start: Int, end: Int): StringBuilder = {
    underlying.delete(start, end)
    this
  }

  /** Replaces a subsequence of Chars with the given String.  The semantics
   *  are as in delete, with the String argument then inserted at index 'start'.
   * 
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @param  str    The String to be inserted at the start index.
   *  @return        This StringBuilder.
   *  @throws StringIndexOutOfBoundsException if start < 0, start > length, or start > end
   */
  def replace(start: Int, end: Int, str: String): StringBuilder = {
    underlying.replace(start, end, str)
    this
  }

  /** Inserts a subarray of the given Array[Char] at the given index
   *  of this sequence.
   *
   * @param  index   index at which to insert the subarray.
   * @param  str     the Array from which Chars will be taken.
   * @param  offset  the index of the first Char to insert.
   * @param  len     the number of Chars from 'str' to insert.
   * @return         This StringBuilder.
   *
   * @throws StringIndexOutOfBoundsException  if index < 0, index > length,
   *         offset < 0, len < 0, or (offset + len) > str.length.
   */
  def insertAll(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = {
    underlying.insert(index, str, offset, len)
    this
  }

  /** Inserts the String representation (via String.valueOf) of the given
   *  argument into this sequence at the given index.
   *
   *  @param  index   the index at which to insert.
   *  @param  x       a value.
   *  @return         this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insert(index: Int, x: Any): StringBuilder = insert(index, String.valueOf(x))
  
  /** Inserts the String into this character sequence.
   *
   *  @param  index the index at which to insert.
   *  @param  x     a String.
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insert(index: Int, x: String): StringBuilder = {
    underlying.insert(index, x)
    this
  }

  /** Inserts the given Seq[Char] into this sequence at the given index.
   *
   *  @param  index the index at which to insert.
   *  @param  xs    the Seq[Char].
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insertAll(index: Int, xs: TraversableOnce[Char]): StringBuilder = insertAll(index, xs.toArray)

  /** Inserts the given Array[Char] into this sequence at the given index.
   *
   *  @param  index the index at which to insert.
   *  @param  xs    the Array[Char].
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insertAll(index: Int, xs: Array[Char]): StringBuilder = {
    underlying.insert(index, xs)
    this
  }

  /** Calls String.valueOf on the given primitive value, and inserts the
   *  String at the given index.
   *
   *  @param  index the offset position.
   *  @param  x     a primitive value.
   *  @return       this StringBuilder.
   */
  def insert(index: Int, x: Boolean): StringBuilder = insert(index, String.valueOf(x))
  def insert(index: Int, x: Byte): StringBuilder    = insert(index, String.valueOf(x))
  def insert(index: Int, x: Short): StringBuilder   = insert(index, String.valueOf(x))
  def insert(index: Int, x: Int): StringBuilder     = insert(index, String.valueOf(x))
  def insert(index: Int, x: Long): StringBuilder    = insert(index, String.valueOf(x))
  def insert(index: Int, x: Float): StringBuilder   = insert(index, String.valueOf(x))
  def insert(index: Int, x: Double): StringBuilder  = insert(index, String.valueOf(x))
  def insert(index: Int, x: Char): StringBuilder    = insert(index, String.valueOf(x))
  
  @deprecated("Use appendAll instead. This method is deprecated because of the\n"+
              "possible confusion with `append(Any)'.")
  def append(x: Seq[Char]): StringBuilder = appendAll(x)

  @deprecated("use appendAll instead. This method is deprecated because\n"+
              "of the possible confusion with `append(Any)'.")
  def append(x: Array[Char]): StringBuilder = appendAll(x)

  @deprecated("use appendAll instead. This method is deprecated because\n"+
              "of the possible confusion with `append(Any, Int, Int)'.")
  def append(x: Array[Char], offset: Int, len: Int): StringBuilder = appendAll(x, offset, len)

  @deprecated("use insertAll instead. This method is deprecated because of the\n"+
              "possible confusion with `insert(Int, Any, Int, Int)'.")
  def insert(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = 
    insertAll(index, str, offset, len)

  @deprecated("use insertAll instead. This method is deprecated because of\n"+
              "the possible confusion with `insert(Int, Any)'.")
  def insert(at: Int, x: Seq[Char]): StringBuilder = insertAll(at, x)

  @deprecated("use insertAll instead. This method is deprecated because of\n"+
              "the possible confusion with `insert(Int, Any)'.")
  def insert(at: Int, x: Array[Char]): StringBuilder = insertAll(at, x)

  /** Finds the index of the first occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @return             the first applicable index where target occurs, or -1 if not found.
   */
  def indexOf(str: String): Int = underlying.indexOf(str)

  /** Finds the index of the first occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @param    fromIndex the smallest index in the source string to consider
   *  @return             the first applicable index where target occurs, or -1 if not found.
   */
  def indexOf(str: String, fromIndex: Int): Int = underlying.indexOf(str, fromIndex)

  /** Finds the index of the last occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @return             the last applicable index where target occurs, or -1 if not found.
   */
  def lastIndexOf(str: String): Int = underlying.lastIndexOf(str)

  /** Finds the index of the last occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @param    fromIndex the smallest index in the source string to consider
   *  @return             the last applicable index where target occurs, or -1 if not found.
   */
  def lastIndexOf(str: String, fromIndex: Int): Int = underlying.lastIndexOf(str, fromIndex)
  
  /** Creates a new StringBuilder with the reversed contents of this one.
   *  If surrogate pairs are present, they are treated as indivisible units: each
   *  pair will appear in the same order in the updated sequence.
   *
   *  @return   the reversed StringBuilder
   */
  @migration(2, 8, "Since 2.8 reverse returns a new instance.  Use 'reverseContents' to update in place.")
  override def reverse: StringBuilder = new StringBuilder(new JavaStringBuilder(underlying) reverse)
  
  override def clone(): StringBuilder = new StringBuilder(new JavaStringBuilder(underlying))

  /** Like reverse, but destructively updates the target StringBuilder.
   *
   *  @return   the reversed StringBuilder (same as the target StringBuilder)
   */
  def reverseContents(): StringBuilder = {
    underlying.reverse()
    this
  }

  /** Returns a new String representing the data in this sequence.
   *
   *  @return  the current contents of this sequence as a String
   */
  override def toString = underlying.toString
  
  def result(): String = toString
}
