import scala.reflect.internal.util.{ BatchSourceFile, SourceFile }
import scala.tools.nsc.doc
import scala.tools.nsc.doc.base._
import scala.tools.nsc.doc.base.comment._
import scala.tools.nsc.interactive._
import scala.tools.nsc.interactive.tests._

object Test extends InteractiveTest {
  val tags = Seq(
    "@example  `\"abb\".permutations = Iterator(abb, bab, bba)`",
    "@version 1.0, 09/07/2012",
    "@since 2.10",
    "@todo this is unsafe!",
    "@note Don't inherit!",
    "@see something else"
  )

  val names = Seq("Class", "Def", "Val", "Var", "AbstracType", "TypeAlias", "Trait", "InnerClass")
  val bareText =
    """abstract class %s {
    |  def %s = ""
    |  val %s = ""
    |  var %s: String = _
    |  type %s
    |  type %s = String
    |  class %s
    |}
    |trait %s""".stripMargin.format(names: _*)

  def docComment(nTags: Int) = "/**\n%s*/".format(tags.take(nTags).mkString("\n"))

  def text(name: String, nTags: Int) = {
    val nameIndex = bareText.indexOf(name)
    val (pre, post) = bareText.splitAt(nameIndex)
    val crIndex = pre.lastIndexOf("\n")
    val (prepre, prepost) = pre.splitAt(crIndex)
    prepre + docComment(nTags) + prepost + post
  }

  override lazy val compiler = {
    prepareSettings(settings)
    new Global(settings, compilerReporter) with MemberLookupBase with CommentFactoryBase with doc.ScaladocGlobalTrait {
      outer =>

      val global: this.type = this

      override lazy val analyzer = new {
        val global: outer.type = outer
      } with doc.ScaladocAnalyzer with InteractiveAnalyzer {
        override def newTyper(context: Context): InteractiveTyper with ScaladocTyper =
          new Typer(context) with InteractiveTyper with ScaladocTyper
      }

      def chooseLink(links: List[LinkTo]): LinkTo = links.head
      def internalLink(sym: Symbol, site: Symbol) = None
      def toString(link: LinkTo) = link.toString
      def warnNoLink = false
      def findExternalLink(sym: Symbol, name: String) = None

      override def forScaladoc = true

      def getComment(sym: Symbol, source: SourceFile, fragments: List[(Symbol,SourceFile)]): Option[Comment] = {
        val docResponse = new Response[(String, String, Position)]
        askDocComment(sym, source, sym.owner, fragments, docResponse)
        docResponse.get.left.toOption flatMap {
          case (expanded, raw, pos) =>
            if (expanded.isEmpty)
              None
            else
              Some(ask { () => parseAtSymbol(expanded, raw, pos, sym.owner) })
        }
      }
    }
  }

  override def runDefaultTests() {
    import compiler._
    def findSource(name: String) = sourceFiles.find(_.file.name == name).get

    val className = names.head
    for (name <- names;
         i <- 1 to tags.length) {
      val newText = text(name, i)
      val source = findSource("Class.scala")
      val batch = new BatchSourceFile(source.file, newText.toCharArray)
      val reloadResponse = new Response[Unit]
      compiler.askReload(List(batch), reloadResponse)
      reloadResponse.get.left.toOption match {
        case None =>
          println("Couldn't reload")
        case Some(_) =>
          val parseResponse = new Response[Tree]
          askParsedEntered(batch, true, parseResponse)
          parseResponse.get.left.toOption match {
            case None =>
              println("Couldn't parse")
            case Some(_) =>
              val sym = compiler.ask { () =>
                val toplevel = compiler.rootMirror.EmptyPackage.info.decl(TypeName(name))
                if (toplevel eq NoSymbol) {
                  val clazz = compiler.rootMirror.EmptyPackage.info.decl(TypeName(className))
                  val term = clazz.info.decl(TermName(name))
                  if (term eq NoSymbol) clazz.info.decl(TypeName(name)) else
                    if (term.isAccessor) term.accessed else term
                } else toplevel
              }

              getComment(sym, batch, (sym,batch)::Nil) match {
                case None => println(s"Got no doc comment for $name")
                case Some(comment) =>
                  import comment._
                  def cnt(bodies: Iterable[Body]) = bodies.size
                  val actual = cnt(example) + cnt(version) + cnt(since) + cnt(todo) + cnt(note) + cnt(see)
                  if (actual != i)
                    println(s"Got docComment with $actual tags instead of $i, file text:\n$newText")
              }
          }
      }
    }

    // Check inter-classes documentation one-time retrieved ok.
    val baseSource = findSource("Base.scala")
    val derivedSource = findSource("Derived.scala")
    def existsText(where: Any, text: String): Boolean = where match {
      case s: String => s contains text
      case s: Seq[_] => s exists (existsText(_, text))
      case p: Product => p.productIterator exists (existsText(_, text))
      case c: Comment => existsText(c.body, text)
    }
    val (derived, base) = compiler.ask { () =>
      val derived = compiler.rootMirror.RootPackage.info.decl(newTermName("p")).info.decl(newTypeName("Derived"))
      (derived, derived.ancestors(0))
    }
    val cmt1 = getComment(derived, derivedSource, (base, baseSource)::(derived, derivedSource)::Nil)
    if (!existsText(cmt1, "This is Derived comment"))
      println("Unexpected Derived class comment:"+cmt1)

    val (fooDerived, fooBase) = compiler.ask { () =>
      val decl = derived.tpe.decl(newTermName("foo"))
      (decl, decl.allOverriddenSymbols(0))
    }

    val cmt2 = getComment(fooDerived, derivedSource, (fooBase, baseSource)::(fooDerived, derivedSource)::Nil)
    if (!existsText(cmt2, "Base method has documentation"))
      println("Unexpected foo method comment:"+cmt2)
  }
}
