package sbt.complete

import DefaultParsers._
import TypeString._

/**
 * Basic representation of types parsed from Manifest.toString.
 * This can only represent the structure of parameterized types.
 * All other types are represented by a TypeString with an empty `args`.
 */
private[sbt] final class TypeString(val base: String, val args: List[TypeString]) {
  override def toString =
    if (base.startsWith(FunctionName))
      args.dropRight(1).mkString("(", ",", ")") + " => " + args.last
    else if (base.startsWith(TupleName))
      args.mkString("(", ",", ")")
    else
      cleanupTypeName(base) + (if (args.isEmpty) "" else args.mkString("[", ",", "]"))
}

private[sbt] object TypeString {
  /** Makes the string representation of a type as returned by Manifest.toString more readable.*/
  def cleanup(typeString: String): String =
    parse(typeString, typeStringParser) match {
      case Right(ts) => ts.toString
      case Left(err) => typeString
    }

  /**
   * Makes a fully qualified type name provided by Manifest.toString more readable.
   * The argument should be just a name (like scala.Tuple2) and not a full type (like scala.Tuple2[Int,Boolean])
   */
  def cleanupTypeName(base: String): String =
    dropPrefix(base).replace('$', '.')

  /**
   * Removes prefixes from a fully qualified type name that are unnecessary in the presence of standard imports for an sbt setting.
   * This does not use the compiler and is therefore a conservative approximation.
   */
  def dropPrefix(base: String): String =
    if (base.startsWith(SbtPrefix))
      base.substring(SbtPrefix.length)
    else if (base.startsWith(CollectionPrefix)) {
      val simple = base.substring(CollectionPrefix.length)
      if (ShortenCollection(simple)) simple else base
    } else if (base.startsWith(ScalaPrefix))
      base.substring(ScalaPrefix.length)
    else if (base.startsWith(JavaPrefix))
      base.substring(JavaPrefix.length)
    else
      TypeMap.getOrElse(base, base)

  final val CollectionPrefix = "scala.collection."
  final val FunctionName = "scala.Function"
  final val TupleName = "scala.Tuple"
  final val SbtPrefix = "sbt."
  final val ScalaPrefix = "scala."
  final val JavaPrefix = "java.lang."
  /* scala.collection.X -> X */
  val ShortenCollection = Set("Seq", "List", "Set", "Map", "Iterable")
  val TypeMap = Map(
    "java.io.File" -> "File",
    "java.net.URL" -> "URL",
    "java.net.URI" -> "URI"
  )

  /**
   * A Parser that extracts basic structure from the string representation of a type from Manifest.toString.
   * This is rudimentary and essentially only decomposes the string into names and arguments for parameterized types.
   */
  lazy val typeStringParser: Parser[TypeString] =
    {
      def isFullScalaIDChar(c: Char) = isScalaIDChar(c) || c == '.' || c == '$'
      lazy val fullScalaID = identifier(IDStart, charClass(isFullScalaIDChar, "Scala identifier character"))
      lazy val tpe: Parser[TypeString] =
        for (id <- fullScalaID; args <- ('[' ~> rep1sep(tpe, ',') <~ ']').?) yield new TypeString(id, args.toList.flatten)
      tpe
    }
}