/* sbt -- Simple Build Tool
 * Copyright 2010  Mark Harrah
 */
package sbt.complete

/**
 * Represents a set of completions.
 * It exists instead of implicitly defined operations on top of Set[Completion]
 *  for laziness.
 */
sealed trait Completions {
  def get: Set[Completion]
  final def x(o: Completions): Completions = flatMap(_ x o)
  final def ++(o: Completions): Completions = Completions(get ++ o.get)
  final def +:(o: Completion): Completions = Completions(get + o)
  final def filter(f: Completion => Boolean): Completions = Completions(get filter f)
  final def filterS(f: String => Boolean): Completions = filter(c => f(c.append))
  override def toString = get.mkString("Completions(", ",", ")")
  final def flatMap(f: Completion => Completions): Completions = Completions(get.flatMap(c => f(c).get))
  final def map(f: Completion => Completion): Completions = Completions(get map f)
  override final def hashCode = get.hashCode
  override final def equals(o: Any) = o match { case c: Completions => get == c.get; case _ => false }
}
object Completions {
  /** Returns a lazy Completions instance using the provided Completion Set. */
  def apply(cs: => Set[Completion]): Completions = new Completions {
    lazy val get = cs
  }

  /** Returns a strict Completions instance using the provided Completion Set. */
  def strict(cs: Set[Completion]): Completions = apply(cs)

  /**
   * No suggested completions, not even the empty Completion.
   * This typically represents invalid input.
   */
  val nil: Completions = strict(Set.empty)

  /**
   * Only includes an empty Suggestion.
   * This typically represents valid input that either has no completions or accepts no further input.
   */
  val empty: Completions = strict(Set.empty + Completion.empty)

  /** Returns a strict Completions instance containing only the provided Completion.*/
  def single(c: Completion): Completions = strict(Set.empty + c)
}

/**
 * Represents a completion.
 * The abstract members `display` and `append` are best explained with an example.
 *
 * Assuming space-delimited tokens, processing this:
 *   am is are w<TAB>
 * could produce these Completions:
 *   Completion { display = "was"; append = "as" }
 *   Completion { display = "were"; append = "ere" }
 * to suggest the tokens "was" and "were".
 *
 * In this way, two pieces of information are preserved:
 *  1) what needs to be appended to the current input if a completion is selected
 *  2) the full token being completed, which is useful for presenting a user with choices to select
 */
sealed trait Completion {
  /** The proposed suffix to append to the existing input to complete the last token in the input.*/
  def append: String
  /** The string to present to the user to represent the full token being suggested.*/
  def display: String
  /** True if this Completion is suggesting the empty string.*/
  def isEmpty: Boolean

  /** Appends the completions in `o` with the completions in this Completion.*/
  def ++(o: Completion): Completion = Completion.concat(this, o)
  final def x(o: Completions): Completions = if (Completion evaluatesRight this) o.map(this ++ _) else Completions.strict(Set.empty + this)
  override final lazy val hashCode = Completion.hashCode(this)
  override final def equals(o: Any) = o match { case c: Completion => Completion.equal(this, c); case _ => false }
}
final class DisplayOnly(val display: String) extends Completion {
  def isEmpty = display.isEmpty
  def append = ""
  override def toString = "{" + display + "}"
}
final class Token(val display: String, val append: String) extends Completion {
  @deprecated("Retained only for compatibility.  All information is now in `display` and `append`.", "0.12.1")
  lazy val prepend = display.stripSuffix(append)
  def isEmpty = display.isEmpty && append.isEmpty
  override final def toString = "[" + display + "]++" + append
}
final class Suggestion(val append: String) extends Completion {
  def isEmpty = append.isEmpty
  def display = append
  override def toString = append
}
object Completion {
  def concat(a: Completion, b: Completion): Completion =
    (a, b) match {
      case (as: Suggestion, bs: Suggestion)    => suggestion(as.append + bs.append)
      case (at: Token, _) if at.append.isEmpty => b
      case _ if a.isEmpty                      => b
      case _                                   => a
    }
  def evaluatesRight(a: Completion): Boolean =
    a match {
      case _: Suggestion                  => true
      case at: Token if at.append.isEmpty => true
      case _                              => a.isEmpty
    }

  def equal(a: Completion, b: Completion): Boolean =
    (a, b) match {
      case (as: Suggestion, bs: Suggestion)   => as.append == bs.append
      case (ad: DisplayOnly, bd: DisplayOnly) => ad.display == bd.display
      case (at: Token, bt: Token)             => at.display == bt.display && at.append == bt.append
      case _                                  => false
    }

  def hashCode(a: Completion): Int =
    a match {
      case as: Suggestion  => (0, as.append).hashCode
      case ad: DisplayOnly => (1, ad.display).hashCode
      case at: Token       => (2, at.display, at.append).hashCode
    }

  val empty: Completion = suggestion("")
  def single(c: Char): Completion = suggestion(c.toString)

  // TODO: make strict in 0.13.0 to match DisplayOnly
  def displayOnly(value: => String): Completion = new DisplayOnly(value)
  @deprecated("Use displayOnly.", "0.12.1")
  def displayStrict(value: String): Completion = displayOnly(value)

  // TODO: make strict in 0.13.0 to match Token
  def token(prepend: => String, append: => String): Completion = new Token(prepend + append, append)
  @deprecated("Use token.", "0.12.1")
  def tokenStrict(prepend: String, append: String): Completion = token(prepend, append)

  /** @since 0.12.1 */
  def tokenDisplay(append: String, display: String): Completion = new Token(display, append)

  // TODO: make strict in 0.13.0 to match Suggestion
  def suggestion(value: => String): Completion = new Suggestion(value)
  @deprecated("Use suggestion.", "0.12.1")
  def suggestStrict(value: String): Completion = suggestion(value)
}