/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id: Models.scala 18387 2009-07-24 15:28:37Z odersky $

package scala.tools.nsc
package models

import scala.tools.nsc.Global
import scala.tools.nsc.util.{Position,NoPosition}

/** This abstract class ...
 *
 *  @author  Sean McDirmid
 *  @version 1.0
 */
abstract class Models {
  val global: Global
  import global._

  def acceptPrivate = true

  object Kinds extends Enumeration  {
    type Kind = Value
    val CONSTRUCTOR = Value("Constructor")
    val OBJECT      = Value("Object")
    val CLASS       = Value("Class")
    val TRAIT       = Value("Trait")
    val DEF         = Value("Def")
    val VAL         = Value("Val")
    val VAR         = Value("Var")
    val ARG         = Value("Arg")
    val TPARAM      = Value("Type")
  }
  import Kinds._

  def KINDS = List(CLASS, TRAIT, OBJECT, CONSTRUCTOR, TPARAM, VAL, VAR, DEF)

  def labelFor(kind: Kind): String = kind.toString

  def stringsFor(mods: Modifiers) = {
    var modString: List[String] = Nil
    if (mods.isPrivate  ) modString = "private"   :: modString
    if (mods.isProtected) modString = "protected" :: modString
    if (mods.isOverride ) modString = "override"  :: modString
    if (mods.isAbstract ) modString = "abstract"  :: modString
    if (mods.isDeferred ) modString = "abstract"  :: modString
    if (mods.isCase     ) modString = "case"      :: modString
    if (mods.isSealed   ) modString = "sealed"    :: modString
    if (mods.isFinal    ) modString = "final"     :: modString
    if (mods.isImplicit ) modString = "implicit"  :: modString
    modString
  }

  def codeFor(kind: Kind): String = kind match {
    case CONSTRUCTOR => codeFor(DEF)
    case _ => labelFor(kind).toLowerCase()
  }

  def pluralFor(kind: Kind): String = kind match {
    case CLASS  => "Classes"
    case _ => labelFor(kind) + "s"
  }

  def kindOf(tree: Tree) = {
    val term0 = tree.symbol;
    if (term0 != NoSymbol) {
      if (term0.isVariable) VAR
      else if (term0.isValueParameter) ARG
      else if (term0.isMethod) {
        if (term0.nameString.equals("this")) CONSTRUCTOR
        else DEF
      }
      else if (term0.isClass) {
        if (tree.asInstanceOf[MemberDef].mods.isTrait) TRAIT
        else CLASS
      }
      else if (term0.isModule) OBJECT
      else if (term0.isValue) VAL
      else if (term0.isTypeParameter) TPARAM
      else if (term0.isType) TPARAM
      else {
        // Console.err.println("UNRECOGNIZED SYMBOL: " + term0 + " " + name);
        null
      }
    } else {
      val ddef = tree.asInstanceOf[ValOrDefDef];
      if (ddef.mods.hasFlag(symtab.Flags.MUTABLE)) VAR;
      else VAL;
    }
  }

  abstract class Model

  // def textFor(tp : AbsTypeDef) : String = tp.toString()

  /**
   *  @param tree ...
   *  @return     ...
   */
  def textFor(tree: Tree): String = {
    var ret = ""
    if (tree.symbol != NoSymbol) tree.symbol.name.toString()
    if (ret.equals("<init>")) ret = "this"
    tree match {
      case cdef: ClassDef =>
        ret = ret + "[" +
          (for (tparam <- cdef.tparams) yield textFor(tparam)) + "]";
        cdef.mods
      case vdef: ValOrDefDef =>
        vdef match {
          case ddef: DefDef =>
          ret = ret + "[" +
            (for (tparam <- ddef.tparams) yield textFor(tparam)) + "]";
          for (vparams <- ddef.vparamss) {
            ret = ret + "(" +
              (for (vparam <- vparams) yield textFor(vparam)) + ")";
          }
          case _ =>
        }
        ret = ret + " : " + textFor(vdef.tpt)
/* Martin to Sean: Please check whether this can be dropped or does it need to be adapted? 
      case atd: AbsTypeDef =>
        ret = ret + "[" + (for (tparam <- atd.tparams) yield textFor(tparam)) + "]" +
                     ((if(atd.hi ne null) " <: " + textFor(atd.hi) else "") +
                     (if(atd.lo ne null) " >: " + textFor(atd.lo) else ""));
*/
      case _ =>
        ret = ret + tree.toString()
    }
    ret
  }

  def mods1(tree: Tree) = tree match {
    case mdef: MemberDef => mdef.mods
    case _ => NoMods
  }

  abstract class HasTree(val parent: Composite) extends Model with Ordered[HasTree] {
    var tree : Tree = _
    def update(tree0: Tree): Boolean = {
      tree = tree0
      false
    }
    def replacedBy(tree0: Tree): Boolean = true
    def text: String = textFor(tree)
    var mods0 = NoMods

    def mods = if (mods0 != NoMods) mods0 else mods1(tree)

    override def toString(): String = tree.toString()

    def compare(that: HasTree): Int = {
      val idx = KINDS.indexOf(kind)
      val jdx = KINDS.indexOf(that.kind)
      if (idx != jdx) return idx - jdx
      val result = tree.symbol.nameString.compare(that.tree.symbol.nameString)
      if (result != 0) result
      else toString().compare(that.toString())
    }
    def compare [b >: HasTree <% Ordered[b]](that: b): Int = {
      if (that.isInstanceOf[HasTree])
        compare(that.asInstanceOf[HasTree])
      else -1
    }

    def kind = kindOf(tree)

    //override def    add(from: Composite, model: HasTree): Unit = { parent.add(from, model) }
    //override def remove(from: Composite, model: HasTree): Unit = { parent.remove(from, model) }
  }

  class ImportMod(parent0: Composite) extends HasTree(parent0) {
    def treex = tree.asInstanceOf[Import]

    override def replacedBy(tree0: Tree): Boolean =
      if (super.replacedBy(tree0) && tree0.isInstanceOf[Import]) {
        val tree1 = tree0.asInstanceOf[Import]
        tree1.tpe == treex.tpe
      } else
        false
  }

  class PackageMod(parent0: Composite) extends HasTree(parent0) {
    def treex = tree.asInstanceOf[PackageDef]
  }

  trait Composite extends Model {
    import scala.collection.mutable._

    class Members extends HashSet[HasTree]
    // val members = new Members
    object members extends Members

    def isMember(tree: Tree): Boolean = tree.isInstanceOf[Import] // imports welcome anywhere.

    def member(tree: Tree, members: List[Tree]): Tree = tree

    def update0(members1: List[Tree]): Boolean = {
      // Console.err.println("update0 " + this + " " + members1)
      // Martin: This is rather ugly code. We should use pattern matching here!
      if (members1.length == 1 && members1.head.isInstanceOf[PackageDef])
        return update0(members1.head.asInstanceOf[PackageDef].stats)
      val marked = new HashSet[HasTree]
      var updated = false
      for (mmbr1 <- members1) if (mmbr1.isInstanceOf[PackageDef]) {
        Console.err.println("PACKAGE: " + mmbr1.symbol + " " + members1.length)
      } else if (isMember(mmbr1)) {
        val mmbr2 = member(mmbr1, members1)
        if (mmbr2 ne null) {
          var found = false
          for (mmbr <- members) if (!found && mmbr.replacedBy(mmbr2)) {
            //Console.err.println("REPLACE: " + mmbr + " with " + mmbr2)
            mmbr.mods0 = mods1(mmbr1)
            found = true
            updated = mmbr.update(mmbr2) || updated
            marked += mmbr
          }
          if (!found) {
            updated = true
            val add = modelFor(mmbr2, this)
            add.update(mmbr2)
            add.mods0 = mods1(mmbr1) & 
              ~symtab.Flags.ACCESSOR & ~symtab.Flags.SYNTHETIC
            val sz = members.size
            members += (add)
            assert(members.size == sz + 1)
            marked += add
          }
        }
        // Console.err.println("update1 " + this + " " + members + " " + marked)
      }
      val sz = members.size
      members.intersect(marked)
      updated = updated || sz < members.size
      // check if anything was removed!
      updated
    }
  }
  abstract class MemberMod(parent0: Composite) extends HasTree(parent0) {
    def treex = tree.asInstanceOf[MemberDef]

    def name: Name = treex.name

    override def replacedBy(tree0: Tree): Boolean =
      if (super.replacedBy(tree0) && tree0.isInstanceOf[MemberDef]) {
        val tree1 = tree0.asInstanceOf[MemberDef]
        treex.toString().equals(tree1.toString())
      } else false

    override def update(tree0: Tree): Boolean = {
      val updated = (tree eq null) || (treex.mods != tree0.asInstanceOf[MemberDef].mods)
      super.update(tree0) || updated;
    }
  }

  abstract class MemberComposite(parent0: Composite) extends MemberMod(parent0) with Composite

  trait HasClassObjects extends Composite {
    override def isMember(tree: Tree): Boolean =
      super.isMember(tree) || tree.isInstanceOf[ImplDef]
  }

  abstract class ValOrDefMod(parent0: Composite) extends MemberComposite(parent0) with HasClassObjects {
    def treey = tree.asInstanceOf[ValOrDefDef]
    override def replacedBy(tree0: Tree): Boolean =
      super.replacedBy(tree0) && tree0.isInstanceOf[ValOrDefDef]

    override def update(tree0: Tree): Boolean = {
      val tree1 = tree0.asInstanceOf[ValOrDefDef]
      val updated = (tree eq null) || treex.tpe != tree1.tpe
      update0(flatten(tree1.rhs, (tree2: Tree) => isMember(tree2)))
      super.update(tree0) || updated
    }
  }

  class ValMod(parent0: Composite) extends ValOrDefMod(parent0) {
    def treez = tree.asInstanceOf[ValDef]
    override def replacedBy(tree0: Tree): Boolean =
      super.replacedBy(tree0) && tree0.isInstanceOf[ValDef]
  }

  class DefMod(parent0: Composite) extends ValOrDefMod(parent0) {
    def treez = tree.asInstanceOf[DefDef]

    override def replacedBy(tree0: Tree) : Boolean =
      if (super.replacedBy(tree0) && tree0.isInstanceOf[DefDef]) {
        val tree1 = tree0.asInstanceOf[DefDef]
        if (tree1.vparamss.length == treez.vparamss.length) {
          val tpz = for (vd <- treez.vparamss) yield for (xd <- vd) yield xd.tpe;
          val tp1 = for (vd <- tree1.vparamss) yield for (xd <- vd) yield xd.tpe;
          tpz == tp1
        } else false
      } else false
  }

  abstract class ImplMod(parent0: Composite)
  extends MemberComposite(parent0) with HasClassObjects {
    def treey = tree.asInstanceOf[ImplDef]
    override def replacedBy(tree0: Tree): Boolean =
      super.replacedBy(tree0) && tree0.isInstanceOf[ImplDef]
    override def isMember(tree: Tree): Boolean = (super.isMember(tree) ||
      (tree.isInstanceOf[ValOrDefDef] &&
        (acceptPrivate || !tree.asInstanceOf[ValOrDefDef].mods.isPrivate)
       /* && !tree.asInstanceOf[ValOrDefDef].mods.isPrivate */
       /* && !tree.asInstanceOf[ValOrDefDef].mods.isAccessor */) ||
      treeInfo.isAliasTypeDef(tree))

    override def member(tree: Tree, members: List[Tree]): Tree = {
      val tree0 = if (tree.isInstanceOf[DefDef]) {
        val ddef = tree.asInstanceOf[DefDef]
        ddef.mods
        if (ddef.mods.isAccessor && (ddef.symbol ne null)) {
          val sym0 = ddef.symbol;
          if (sym0.isSetter) return null;
          assert(sym0.isGetter);
          val sym = sym0.accessed
          val ret = if (sym == NoSymbol) {
            val sym = analyzer.underlying(sym0)
            //val name = nme.getterToSetter(sym0.name)
            //val setter = sym0.owner.info.decl(name);
            val isVar = sym.isVariable;
            val mods = (ddef.mods | 
              (if (isVar) symtab.Flags.MUTABLE else 0) | symtab.Flags.DEFERRED) &
                ~symtab.Flags.ACCESSOR & ~symtab.Flags.SYNTHETIC
            val tree = 
              ValDef(mods, ddef.name, ddef.tpt, ddef.rhs).setPos(ddef.pos).setSymbol(sym);
            tree :: Nil;
          } else for (member <- members if member.symbol == sym) yield member
          if (ret.isEmpty) null
          else ret.head
        } else tree
      } else super.member(tree, members)

      def sym = tree0.symbol
      if ((tree0 eq null) || tree0.pos == NoPosition) null
      else if (!acceptPrivate &&
               tree0.isInstanceOf[ValOrDefDef] &&
               tree.asInstanceOf[ValOrDefDef].mods.isPrivate) null
      else tree0
    }

    def children(tree0: Tree): List[Tree] =
      tree0.asInstanceOf[ImplDef].impl.body

    override def update(tree0: Tree): Boolean = {
      var updated = update0(children(tree0))
      super.update(tree0) || updated
    }
  }

  class ClassMod(parent0: Composite) extends ImplMod(parent0) {
    def treez = tree.asInstanceOf[ClassDef]
    override def replacedBy(tree0: Tree): Boolean =
      super.replacedBy(tree0) && tree0.isInstanceOf[ClassDef]
  }

  class ObjectMod(parent0: Composite) extends ImplMod(parent0) {
    def treez = tree.asInstanceOf[ModuleDef]
    override def replacedBy(tree0: Tree): Boolean =
      super.replacedBy(tree0) && tree0.isInstanceOf[ModuleDef]
  }
  class TypeMod(parent0: Composite) extends MemberMod(parent0) {
    def treey = tree.asInstanceOf[TypeDef];
    override def replacedBy(tree0 : Tree) : Boolean = (super.replacedBy(tree0) && tree0.isInstanceOf[TypeDef]);
  }
  def SourceMod(original: CompilationUnit) = new SourceMod(original)

  class SourceMod(val original: CompilationUnit) extends Composite with HasClassObjects {
    update(original)
    //var listener : Listener = null;
    def update(unit: CompilationUnit) = unit.body match {
      case pdef: PackageDef => try {
        update0(pdef.stats)
      } catch {
        case e: Error => members.clear; update0(pdef.stats)
      }
      case _ =>
    }

    override def isMember(tree: Tree): Boolean =
      super.isMember(tree) || tree.isInstanceOf[Import]
  }

  def flatten0(exprs: List[Tree], filter: (Tree) => Boolean): List[Tree] =
    for (expr <- exprs; t: Tree <- flatten(expr,filter)) yield t

  def flatten(expr: Tree, filter: (Tree) => Boolean): List[Tree] =
    if (filter(expr)) expr :: Nil; else expr match {
      case Block(stats, last) =>
        flatten0(stats, filter) ::: flatten(last, filter)
      case If(cond, thenp, elsep) =>
        flatten(cond,filter) ::: flatten(thenp,filter) ::: flatten(elsep,filter);
      case Assign(lhs, rhs) =>
        flatten(rhs, filter)
      case CaseDef(pat, guard, body) =>
        flatten(body, filter)
      case Return(expr0) =>
        flatten(expr0, filter)
      case Throw(expr0) =>
        flatten(expr0,filter)
      case Try(block, catches, finalizer) =>
        flatten(block, filter) ::: flatten(finalizer, filter) ::: flatten0(catches, filter)
      case Match(selector, cases) =>
        flatten(selector, filter) ::: flatten0(cases, filter)
      case Apply(fun, args) =>
        flatten(fun, filter) ::: flatten0(args, filter)
      case TypeApply(fun, args) =>
        flatten(fun, filter) ::: flatten0(args, filter)
      case _ =>
        Nil
    }

  def modelFor(tree: Tree, parent: Composite): HasTree = tree match {
    case _: ValDef       => new ValMod(parent)
    case _: DefDef       => new DefMod(parent)
    case _: ClassDef     => new ClassMod(parent)
    case _: ModuleDef    => new ObjectMod(parent)
    case _: TypeDef      => new TypeMod(parent)
    case _: Import       => new ImportMod(parent)
  }

}