/* NSC -- new Scala compiler
 * Copyright 2007-2009 LAMP/EPFL
 * @author  Sean McDirmid
 */
// $Id: ModelExtractor.scala 18547 2009-08-22 19:52:46Z extempore $

package scala.tools.nsc
package doc

import scala.collection.mutable
import compat.Platform.{EOL => LINE_SEPARATOR}


/** This class attempts to reverse engineer source code intent from compiler
 *  symbol objects.
 *
 * @author Sean McDirmid
 */
trait ModelExtractor {
  val global: Global
  import global._
  def settings: doc.Settings
 
  def assert(b: Boolean) {
    if (!b) throw new Error
  }

  def assert(b: Boolean, message: Any) {
    if (!b) throw new Error(message.toString)
  }

  case class Tag(tag: String, option: String, body: String)

  case class Comment(body: String, attributes: List[Tag]) {
    def decodeAttributes = {
      val map = new mutable.LinkedHashMap[String, List[(String, String)]] {
        override def default(key: String) = Nil
      }
      attributes.foreach(a => {
        map(a.tag) = map(a.tag) ::: List((a.option, a.body))
      });
      map
    }
  }
  protected def decode(sym: Symbol) =
    if (sym == definitions.ScalaObjectClass || sym == definitions.ObjectClass) 
      definitions.AnyRefClass
    else sym match {
      case sym: ModuleClassSymbol => sym.sourceModule
      case sym => sym
    }

  protected def decodeComment(comment0: String): Comment = {
    val comment = { // discard outmost comment delimiters if present
      val begin = if (comment0 startsWith "/**") 3 else 0
      val end = comment0.length - (if (comment0 endsWith "*/") 2 else 0)
      comment0.substring(begin, end)
    }
    val tok = new java.util.StringTokenizer(comment, LINE_SEPARATOR)
    val buf = new StringBuilder
    type AttrDescr = (String, String, StringBuilder)
    val attributes = new collection.mutable.ListBuffer[AttrDescr]
    var attr: AttrDescr = null
    while (tok.hasMoreTokens) {
      val s = tok.nextToken.replaceFirst("\\p{Space}?\\*", "")
      val mat1 = pat1.matcher(s)
      if (mat1.matches) {
        attr = (mat1.group(1), null, new StringBuilder(mat1.group(2)))
        //if (kind != CONSTRUCTOR) 
        attributes += attr
      } else {
        val mat2 = pat2.matcher(s)
        if (mat2.matches) {
          attr = (mat2.group(1), mat2.group(2), new StringBuilder(mat2.group(3)))
          //if (kind != CLASS) 
          attributes += attr
        } else if (attr ne null)
          attr._3.append(s + LINE_SEPARATOR)
        else
          buf.append(s + LINE_SEPARATOR)
      }
    }
    Comment(buf.toString, attributes.toList.map({x => Tag(x._1,x._2,x._3.toString)}))
  }

  sealed abstract class Entity(val sym: Symbol) {
    private[ModelExtractor] def sym0 = sym
    
    override def toString = sym.toString
    def comment: Option[String] = global.comments.get(sym)
    // comments decoded, now what?
    def attributes = sym.annotations
    def decodeComment: Option[Comment] = {
      val comment0 = this.comment
      if (comment0.isEmpty) None
      else Some(ModelExtractor.this.decodeComment(comment0.get.trim))
    }
    protected def accessQualified(core: String, qual: String) = core match {
      case "public" => "" // assert(qual == null); "";
      case core => core + (if (qual == null) "" else "[" + qual + "]")
    }
    
    def flagsString = {
      import symtab.Flags
      //val isLocal = sym.hasFlag(Flags.LOCAL)
      val x =
        if (sym hasFlag Flags.PRIVATE) "private"
        else if (sym hasFlag Flags.PROTECTED) "protected"
        else "public"
      var string = accessQualified(x,
        if (sym hasFlag Flags.LOCAL) "this"
        else if (sym.privateWithin != null && sym.privateWithin != NoSymbol)
          sym.privateWithin.nameString
        else null
      )
      def f(flag: Int, str: String) {
        if (sym hasFlag flag) string = string + " " + str
      }
      f(Flags.IMPLICIT, "implicit")
      f(Flags.SEALED, "sealed")
      f(Flags.OVERRIDE, "override")
      f(Flags.CASE, "case")
      if (!sym.isTrait) f(Flags.ABSTRACT, "abstract")
      if (!sym.isModule) f(Flags.FINAL, "final")
      if (!sym.isTrait) f(Flags.DEFERRED, "abstract")
      string.trim
    }
    def listName = name
    def name = sym.nameString
    def fullName(sep: Char) = sym.fullNameString(sep)
    def kind: String
    def header { }
    def typeParams: List[TypeParam] = Nil
    def valueParams: List[List[ValueParam]] = Nil
    def resultType: Option[Type] = None
    def parents: Iterable[Type] = Nil
    def lo: Option[Type] = sym.info match {
      case TypeBounds(lo, hi) if decode(lo.typeSymbol) != definitions.NothingClass => Some(lo)
      case _ => None
    }
    def hi: Option[Type] = sym.info match {
      case TypeBounds(lo, hi) if decode(hi.typeSymbol) != definitions.AnyClass => Some(hi)
      case _ => None
    }
    def variance = {
      import symtab.Flags._
      if (sym hasFlag COVARIANT) "+"
      else if (sym hasFlag CONTRAVARIANT) "-"
      else ""
    }
    def overridden: Iterable[Symbol] = Nil
  }

  class ValueParam(sym: Symbol) extends Entity(sym) {
    override def resultType = Some(sym.tpe)
    //def kind = if (sym.isPublic) "val" else "";
    def kind = ""
  }

  class ConstructorParam(sym: Symbol) extends ValueParam(sym) {
    override protected def accessQualified(core: String, qual: String) = core match {
      case "public" => "val"
      case "protected" => super.accessQualified(core,qual) + " val"
      case "private" if qual == "this" => ""
      case core => super.accessQualified(core, qual)
    }
  }

  def ValueParam(sym: Symbol) = new ValueParam(sym)
  class TypeParam(sym: Symbol) extends Entity(sym) {
    def kind = ""
  }
  def TypeParam(sym: Symbol) = new TypeParam(sym)
  
  trait Clazz extends ClassOrObject {
    private def csym = sym.asInstanceOf[TypeSymbol]
    override def typeParams = csym.typeParams.map(TypeParam)
    override def valueParams = {
      if (constructorArgs.isEmpty) Nil
      else constructorArgs.valuesIterator.toList :: Nil
    }
    def isTrait = csym.isTrait
    override def kind = if (sym.isTrait) "trait" else "class"
  }

  trait Object extends ClassOrObject {
    override def kind = "object"
  }

  case class Package(override val sym: Symbol) extends Entity(sym) {
    override def kind = "package"
    override def name = fullName('.')
  }

  trait TopLevel extends ClassOrObject
  class TopLevelClass (sym: Symbol) extends Entity(sym) with TopLevel with Clazz
  class TopLevelObject(sym: Symbol) extends Entity(sym) with TopLevel with Object {
    override def attributes = sym.moduleClass.annotations
  }

  def compare(pathA: List[ClassOrObject], pathB: List[ClassOrObject]): Int = {
    var pA = pathA
    var pB = pathB
    while (true) {
      if (pA.isEmpty) return -1
      if (pB.isEmpty) return +1
      val diff = pA.head.name compare pB.head.name
      if (diff != 0) return diff
      pA = pA.tail
      pB = pB.tail
    }
    0
  }

  def isAccessible(sym: Symbol): Boolean = {
    import symtab.Flags._
    settings.memberaccess.value match {
      case "private"   => sym.isPublic || (sym hasFlag PROTECTED) || (sym hasFlag PRIVATE)
      case "protected" => sym.isPublic || (sym hasFlag PROTECTED)
      case "public"    => sym.isPublic
      case _           => false
    }
  }

  trait ClassOrObject extends Entity {
    def path: List[ClassOrObject] = this :: Nil
    override def listName = path map (_.name) mkString "."

    object freshParents extends mutable.LinkedHashSet[Type] {
      this ++= sym.tpe.parents
      this.toList foreach (this --= _.parents)
    }
    object constructorArgs extends mutable.LinkedHashMap[Symbol, ValueParam] {
      import symtab.Flags._
      sym.constrParamAccessors.filter(arg => ! (arg hasFlag SYNTHETIC)).foreach(arg => {
        val str = flagsToString(arg.flags)
        assert((arg hasFlag PRIVATE) && (arg hasFlag LOCAL), arg)
        val argName = arg.name.toString.trim
        val actual = sym.tpe.decls.iterator.find(e => {
          val eName = e.name.toString.trim;
          argName == eName && {
            val str = flagsToString(e.flags);
            !e.hasFlag(LOCAL);
          }
        });
        val param = actual getOrElse arg
        this(param) = new ConstructorParam(param)
      });
    }
    object decls extends mutable.LinkedHashMap[Symbol, Member] {
      sym.tpe.decls.iterator.foreach(e => {
        if (!constructorArgs.contains(e)) {
          val m = Member(e)
          if (!m.isEmpty && !this.contains(e)) this.put(e, m.get)
        }
      });
    }
    def members0(f: Symbol => Boolean) = decls.filterKeys(f).valuesIterator.toList
    def members(c: Category): Iterable[Member] = members0(c.f)
    object inherited extends mutable.LinkedHashMap[Symbol, List[Member]]() {
      override def default(tpe: Symbol) = Nil
      for (m <- sym.tpe.members if !sym.tpe.decls.iterator.contains(m) && 
          (Values.f(m) || Methods.f(m))) {
        val o = m.overridingSymbol(sym)
        if (o == NoSymbol) {
          val parent = decode(m.enclClass)
          val mo = Member(m)
          if (!mo.isEmpty) {
            this(parent) = mo.get :: this(parent)
          }
        }
      }
    }
    override def parents = freshParents
    abstract class Member(sym: Symbol) extends Entity(sym) {
      private def overriding = sym.allOverriddenSymbols
      override def comment = super.comment match {
      case ret @ Some(comment) =>
        ret
      case None =>
        val o = overriding.find(comments.contains)
        o.map(comments.apply)
      }
    }
    abstract class ValDef(sym: Symbol) extends Member(sym) {
      override def resultType = Some(resultType0)
      protected def resultType0: Type
      override def overridden: Iterable[Symbol] = {
        var ret: mutable.LinkedHashSet[Symbol] = null
        for (parent <- ClassOrObject.this.parents) {
          val sym0 = sym.overriddenSymbol(parent.typeSymbol)
          if (sym0 != NoSymbol) {
            if (ret == null) ret = new mutable.LinkedHashSet[Symbol];
            ret += sym0
          }
        }
        if (ret == null) Nil else ret
      }
    }
    case class Def(override val sym : TermSymbol) extends ValDef(sym) {
      override def resultType0 = sym.tpe.finalResultType
      override def typeParams = sym.tpe.typeParams.map(TypeParam)
      override def valueParams = methodArgumentNames.get(sym) match {
        case Some(argss) if argss.length > 1 || (!argss.isEmpty && !argss(0).isEmpty) => 
          argss map (_.map(ValueParam))
        case _ => 
          var i = 0
          val ret = for (tpe <- sym.tpe.paramTypes) yield {
            val ret = sym.newValueParameter(sym.pos, newTermName("arg" + i));
            ret setInfo tpe
            i += 1
            ValueParam(ret)
          }
          if (ret.isEmpty) Nil
          else ret :: Nil
      }
      override def kind = "def"
    }
    case class Val(override val sym: TermSymbol) extends ValDef(sym) {
      import symtab.Flags._
      def resultType0: Type = sym.tpe
      override def kind: String =
        if (sym hasFlag ACCESSOR) {
          val setterName = nme.getterToSetter(sym.name)
          val setter = sym.owner.info.decl(setterName)
          val lazyMod = if (sym hasFlag LAZY) "lazy " else ""
          lazyMod + (if (setter == NoSymbol) "val" else "var")
        } else {
          assert(sym hasFlag JAVA)
          if (sym hasFlag FINAL) "val" else "var"
        }
    }

    case class AbstractType(override val sym: Symbol) extends Member(sym) {
      override def kind = "type"
    }

    abstract class NestedClassOrObject(override val sym: Symbol) extends Member(sym) with ClassOrObject {
      override def path: List[ClassOrObject] = ClassOrObject.this.path ::: super.path
    }

    case class NestedClass(override val sym: ClassSymbol) extends NestedClassOrObject(sym) with Clazz

    case class NestedObject(override val sym: ModuleSymbol) extends NestedClassOrObject(sym) with Object {
      override def attributes = sym.moduleClass.annotations
    }

    def isVisible(sym: Symbol): Boolean = {
      import symtab.Flags._
      if (sym.isLocalClass) return false
      if (sym.isLocal) return false
      if (sym.isPrivateLocal) return false      
      // the next line used to return !inIDE - now it returns true.  The underlying
      // logic being applied here is somewhat mysterious (if PRIVATE return isVisible == true?)
      // but changing it causes the docgenerator.scala test case to break, so I leave as-is.
      if (sym hasFlag PRIVATE) return true
      if (sym hasFlag SYNTHETIC) return false
      if (sym hasFlag BRIDGE) return false
      if ((sym.nameString indexOf "$") != -1) return false
      if ((sym hasFlag CASE) && sym.isMethod) return false
      true
    }

    def Member(sym: Symbol): Option[Member] = {
      import global._
      import symtab.Flags
      if (!isVisible(sym))
        None
      else if (!isAccessible(sym))
        None
      else if (sym hasFlag Flags.ACCESSOR) {
        if (sym.isSetter) return None;
        assert(sym.isGetter);
        Some[Member](new Val(sym.asInstanceOf[TermSymbol]))
      }
      else if (sym.isValue && !sym.isMethod && !sym.isModule) {
        if (!sym.hasFlag(Flags.JAVA)) {
          Console.println("SYM: " + sym + " " + sym.fullNameString('.'))
          Console.println("FLA: " + Flags.flagsToString(sym.flags))
        }
        assert(sym hasFlag Flags.JAVA)
        Some[Member](new Val(sym.asInstanceOf[TermSymbol]))
      }
      else if (sym.isValue && !sym.isModule) {
        val str = Flags.flagsToString(sym.flags)
        assert(sym.isMethod)
        Some[Member](new Def(sym.asInstanceOf[TermSymbol]))
      }
      else if (sym.isAliasType || sym.isAbstractType)
        Some(new AbstractType(sym))
      else if (sym.isClass)
        Some(new NestedClass(sym.asInstanceOf[ClassSymbol]))
      else if (sym.isModule)
        Some(new NestedObject(sym.asInstanceOf[ModuleSymbol]))
      else
        None
    }
    
  }
  case class Category(label: String)(g: Symbol => Boolean) {
    val f = g
    def plural = label + "s"
  }
  val Constructors = new Category("Additional Constructor")(e => e.isConstructor && !e.isPrimaryConstructor) {
    // override def plural = "Additional Constructors";
  }
  val Objects = Category("Object")(_.isModule);
  val Classes = new Category("Class")(sym => sym.isClass || (sym == definitions.AnyRefClass)) {
    override def plural = "Classes"
  }
  val Values = new Category("Value")(e => e.isValue && e.hasFlag(symtab.Flags.ACCESSOR)) {
    override def plural = "Values and Variables"
  }
  val Methods = Category("Method")(e => e.isValue && e.isMethod && !e.isConstructor && !e.hasFlag(symtab.Flags.ACCESSOR));
  val Types = Category("Type")(e => e.isAliasType || e.isAbstractType); 
  
  val categories = Constructors :: Types :: Values :: Methods :: Classes :: Objects :: Nil;

  
  import java.util.regex.Pattern
  // patterns for standard tags with 1 and 2 arguments
  private val pat1 = Pattern.compile(
    "[ \t]*@(author|deprecated|owner|pre|return|see|since|todo|version|ex|note)[ \t]*(.*)")
  private val pat2 = Pattern.compile(  
    "[ \t]*@(exception|param|throws)[ \t]+(\\p{Graph}*)[ \t]*(.*)")

  def sort[E <: Entity](entities: Iterable[E]): Iterable[E] = {
    val set = new collection.immutable.TreeSet[E]()(new Ordering[E] {
      def compare(eA : E, eB: E): Int = {
        if (eA eq eB) return 0;
        (eA, eB) match {
          case (eA: ClassOrObject, eB: ClassOrObject) => 
            val diff = ModelExtractor.this.compare(eA.path, eB.path)
            if (diff!= 0) return diff
          case _ => 
        }
        if (eA.getClass != eB.getClass) {
          val diff = eA.getClass.getName.compare(eB.getClass.getName)
          assert(diff != 0)
          return diff
        }
        if (!eA.sym0.isPackage) {
          val diff = eA.sym0.nameString compare eB.sym0.nameString
          if (diff != 0) return diff
        } 
        val diff0 = eA.sym0.fullNameString compare eB.sym0.fullNameString
        assert(diff0 != 0)
        diff0
      }
    })
    set ++ entities
  }
}