package scala.reflect
package internal
import scala.collection.mutable.WeakHashMap

trait Importers { self: SymbolTable =>

  abstract class Importer {

    val from: SymbolTable

    lazy val symMap: WeakHashMap[from.Symbol, Symbol] = new WeakHashMap
    lazy val tpeMap: WeakHashMap[from.Type, Type] = new WeakHashMap

    // fixups and maps prevent stackoverflows in importer
    var pendingSyms = 0
    var pendingTpes = 0
    lazy val fixups = collection.mutable.MutableList[Function0[Unit]]()
    def addFixup(fixup: => Unit): Unit = fixups += (() => fixup)
    def tryFixup(): Unit = {
      if (pendingSyms == 0 && pendingTpes == 0) {
        val fixups = this.fixups.toList
        this.fixups.clear()
        fixups foreach { _() }
      }
    }

    object reverse extends from.Importer {
      val from: self.type = self
      for ((fromsym, mysym) <- Importer.this.symMap) symMap += ((mysym, fromsym))
      for ((fromtpe, mytpe) <- Importer.this.tpeMap) tpeMap += ((mytpe, fromtpe))
    }

    def importPosition(pos: from.Position): Position = NoPosition

    def importSymbol(sym0: from.Symbol): Symbol = {
      def doImport(sym: from.Symbol): Symbol = {
        if (symMap.contains(sym))
          return symMap(sym)

        val myowner = importSymbol(sym.owner)
        val mypos   = importPosition(sym.pos)
        val myname  = importName(sym.name).toTermName
        val myflags = sym.flags
        def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
          symMap(x) = mysym
          mysym.referenced = op(x.referenced)
          mysym
        }
        val mysym = sym match {
          case x: from.MethodSymbol =>
            linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
          case x: from.ModuleSymbol =>
            linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
          case x: from.FreeVar =>
            newFreeVar(importName(x.name).toTermName, importType(x.tpe), x.value, myflags)
          case x: from.TermSymbol =>
            linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
          case x: from.TypeSkolem =>
            val origin = x.unpackLocation match {
              case null           => null
              case y: from.Tree   => importTree(y)
              case y: from.Symbol => importSymbol(y)
            }
            myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
          case x: from.ModuleClassSymbol =>
            val mysym = myowner.newModuleClassSymbol(myname.toTypeName, mypos, myflags)
            symMap(x) = mysym
            mysym.sourceModule = importSymbol(x.sourceModule)
            mysym
          case x: from.ClassSymbol =>
            val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
            symMap(x) = mysym
            if (sym.thisSym != sym) {
              mysym.typeOfThis = importType(sym.typeOfThis)
              mysym.thisSym.name = importName(sym.thisSym.name)
            }
            mysym
          case x: from.TypeSymbol =>
            myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
        }
        symMap(sym) = mysym
        mysym setFlag Flags.LOCKED
        mysym setInfo {
          val mytypeParams = sym.typeParams map importSymbol
          new LazyPolyType(mytypeParams) {
            override def complete(s: Symbol) {
              val result = sym.info match {
                case from.PolyType(_, res) => res
                case result => result
              }
              s setInfo polyType(mytypeParams, importType(result))
              s setAnnotations (sym.annotations map importAnnotationInfo)
            }
          }
        }
        mysym resetFlag Flags.LOCKED
      } // end doImport

      def importOrRelink: Symbol = {
        val sym = sym0 // makes sym visible in the debugger
        if (sym == null)
          null
        else if (sym == from.NoSymbol)
          NoSymbol
        else if (sym.isRoot)
          definitions.RootClass
        else {
          val name = sym.name
          val owner = sym.owner
          var scope = if (owner.isClass && !owner.isRefinementClass) owner.info else from.NoType
          var existing = scope.decl(name)
          if (sym.isPackageClass || sym.isModuleClass) existing = existing.moduleClass
          if (!existing.exists) scope = from.NoType

          val myname = importName(name)
          val myowner = importSymbol(owner)
          val myscope = if (scope != from.NoType && !(myowner hasFlag Flags.LOCKED)) myowner.info else NoType
          var myexisting = if (myscope != NoType) myowner.info.decl(myname) else NoSymbol // cannot load myexisting in general case, because it creates cycles for methods
          if (sym.isPackageClass || sym.isModuleClass) myexisting = importSymbol(sym.sourceModule).moduleClass
          if (!sym.isOverloaded && myexisting.isOverloaded) {
            myexisting =
              if (sym.isMethod) {
                val localCopy = doImport(sym)
                myexisting filter (_.tpe matches localCopy.tpe)
              } else {
                myexisting filter (!_.isMethod)
              }
            assert(!myexisting.isOverloaded,
                "import failure: cannot determine unique overloaded method alternative from\n "+
                (myexisting.alternatives map (_.defString) mkString "\n")+"\n that matches "+sym+":"+sym.tpe)
          }

          val mysym = {
            if (sym.isOverloaded) {
              myowner.newOverloaded(myowner.thisType, sym.alternatives map importSymbol)
            } else if (sym.isTypeParameter && sym.paramPos >= 0 && !(myowner hasFlag Flags.LOCKED)) {
              assert(myowner.typeParams.length > sym.paramPos,
                  "import failure: cannot determine parameter "+sym+" (#"+sym.paramPos+") in "+
                  myowner+typeParamsString(myowner.rawInfo)+"\n original symbol was: "+
                  sym.owner+from.typeParamsString(sym.owner.info))
              myowner.typeParams(sym.paramPos)
            } else {
              if (myexisting != NoSymbol) {
                myexisting
              } else {
                val mysym = doImport(sym)

                if (myscope != NoType) {
                  assert(myowner.info.decls.lookup(myname) == NoSymbol, myname+" "+myowner.info.decl(myname)+" "+myexisting)
                  myowner.info.decls enter mysym
                }

                mysym
              }
            }
          }

          mysym
        }
      } // end importOrRelink

      val sym = sym0
      if (symMap contains sym) {
        symMap(sym)
      } else {
        pendingSyms += 1

        try {
          symMap getOrElseUpdate (sym, importOrRelink)
        } finally {
          pendingSyms -= 1
          tryFixup()
        }
      }
    }

    def importType(tpe: from.Type): Type = {
      def doImport(tpe: from.Type): Type = tpe match {
        case from.TypeRef(pre, sym, args) =>
          TypeRef(importType(pre), importSymbol(sym), args map importType)
        case from.ThisType(clazz) =>
          ThisType(importSymbol(clazz))
        case from.SingleType(pre, sym) =>
          SingleType(importType(pre), importSymbol(sym))
        case from.MethodType(params, restpe) =>
          MethodType(params map importSymbol, importType(restpe))
        case from.PolyType(tparams, restpe) =>
          PolyType(tparams map importSymbol, importType(restpe))
        case from.NullaryMethodType(restpe) =>
          NullaryMethodType(importType(restpe))
        case from.ConstantType(constant @ from.Constant(_)) =>
          ConstantType(importConstant(constant))
        case from.SuperType(thistpe, supertpe) =>
          SuperType(importType(thistpe), importType(supertpe))
        case from.TypeBounds(lo, hi) =>
          TypeBounds(importType(lo), importType(hi))
        case from.BoundedWildcardType(bounds) =>
          BoundedWildcardType(importTypeBounds(bounds))
        case from.ClassInfoType(parents, decls, clazz) =>
          val myclazz = importSymbol(clazz)
          val myscope = if (myclazz.isPackageClass) newPackageScope(myclazz) else newScope
          val myclazzTpe = ClassInfoType(parents map importType, myscope, myclazz)
          myclazz setInfo polyType(myclazz.typeParams, myclazzTpe) // needed so that newly created symbols find their scope
          decls foreach importSymbol // will enter itself into myclazz
          myclazzTpe
        case from.RefinedType(parents, decls) =>
          RefinedType(parents map importType, importScope(decls), importSymbol(tpe.typeSymbol))
        case from.ExistentialType(tparams, restpe) =>
          newExistentialType(tparams map importSymbol, importType(restpe))
        case from.OverloadedType(pre, alts) =>
          OverloadedType(importType(pre), alts map importSymbol)
        case from.AntiPolyType(pre, targs) =>
          AntiPolyType(importType(pre), targs map importType)
        case x: from.TypeVar =>
          TypeVar(importType(x.origin), importTypeConstraint(x.constr0), x.typeArgs map importType, x.params map importSymbol)
        case from.NotNullType(tpe) =>
          NotNullType(importType(tpe))
        case from.AnnotatedType(annots, tpe, selfsym) =>
          AnnotatedType(annots map importAnnotationInfo, importType(tpe), importSymbol(selfsym))
        case from.ErrorType =>
          ErrorType
        case from.WildcardType =>
          WildcardType
        case from.NoType =>
          NoType
        case from.NoPrefix =>
          NoPrefix
        case null =>
          null
      } // end doImport

      def importOrRelink: Type =
        doImport(tpe)

      if (tpeMap contains tpe) {
        tpeMap(tpe)
      } else {
        pendingTpes += 1

        try {
          tpeMap getOrElseUpdate (tpe, importOrRelink)
        } finally {
          pendingTpes -= 1
          tryFixup()
        }
      }
    }

    def importTypeBounds(bounds: from.TypeBounds) = importType(bounds).asInstanceOf[TypeBounds]

    def importAnnotationInfo(ann: from.AnnotationInfo): AnnotationInfo = {
      val atp1 = importType(ann.atp)
      val args1 = ann.args map importTree
      val assocs1 = ann.assocs map { case (name, arg) => (importName(name), importAnnotArg(arg)) }
      val original1 = importTree(ann.original)
      AnnotationInfo(atp1, args1, assocs1) setOriginal original1
    }

    def importAnnotArg(arg: from.ClassfileAnnotArg): ClassfileAnnotArg = arg match {
      case from.LiteralAnnotArg(constant @ from.Constant(_)) =>
        LiteralAnnotArg(importConstant(constant))
      case from.ArrayAnnotArg(args) =>
        ArrayAnnotArg(args map importAnnotArg)
      case from.ScalaSigBytes(bytes) =>
        ScalaSigBytes(bytes)
      case from.NestedAnnotArg(annInfo) =>
        NestedAnnotArg(importAnnotationInfo(annInfo))
    }

    def importTypeConstraint(constr: from.TypeConstraint): TypeConstraint = {
      val result = new TypeConstraint(constr.loBounds map importType, constr.hiBounds map importType)
      result.inst = importType(constr.inst)
      result
    }

    // !!! todo: override to cater for PackageScopes
    def importScope(decls: from.Scope): Scope =
      newScopeWith(decls.toList map importSymbol: _*)

    def importName(name: from.Name): Name =
      if (name.isTypeName) newTypeName(name.ToString) else newTermName(name.ToString)
    def importTypeName(name: from.TypeName): TypeName = importName(name).toTypeName
    def importTermName(name: from.TermName): TermName = importName(name).toTermName

    def importModifiers(mods: from.Modifiers): Modifiers =
      new Modifiers(mods.flags, importName(mods.privateWithin), mods.annotations map importTree)

    def importImportSelector(sel: from.ImportSelector): ImportSelector =
      new ImportSelector(importName(sel.name), sel.namePos, if (sel.rename != null) importName(sel.rename) else null, sel.renamePos)

    def importTree(tree: from.Tree): Tree = {
      val mytree = tree match {
        case from.ClassDef(mods, name, tparams, impl) =>
          new ClassDef(importModifiers(mods), importName(name).toTypeName, tparams map importTypeDef, importTemplate(impl))
        case from.PackageDef(pid, stats) =>
          new PackageDef(importRefTree(pid), stats map importTree)
        case from.ModuleDef(mods, name, impl) =>
          new ModuleDef(importModifiers(mods), importName(name).toTermName, importTemplate(impl))
        case from.emptyValDef =>
          emptyValDef
        case from.ValDef(mods, name, tpt, rhs) =>
          new ValDef(importModifiers(mods), importName(name).toTermName, importTree(tpt), importTree(rhs))
        case from.DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
          new DefDef(importModifiers(mods), importName(name).toTermName, tparams map importTypeDef, vparamss map (_ map importValDef), importTree(tpt), importTree(rhs))
        case from.TypeDef(mods, name, tparams, rhs) =>
          new TypeDef(importModifiers(mods), importName(name).toTypeName, tparams map importTypeDef, importTree(rhs))
        case from.LabelDef(name, params, rhs) =>
          new LabelDef(importName(name).toTermName, params map importIdent, importTree(rhs))
        case from.Import(expr, selectors) =>
          new Import(importTree(expr), selectors map importImportSelector)
        case from.Template(parents, self, body) =>
          new Template(parents map importTree, importValDef(self), body map importTree)
        case from.Block(stats, expr) =>
          new Block(stats map importTree, importTree(expr))
        case from.CaseDef(pat, guard, body) =>
          new CaseDef(importTree(pat), importTree(guard), importTree(body))
        case from.Alternative(trees) =>
          new Alternative(trees map importTree)
        case from.Star(elem) =>
          new Star(importTree(elem))
        case from.Bind(name, body) =>
          new Bind(importName(name), importTree(body))
        case from.UnApply(fun, args) =>
          new UnApply(importTree(fun), args map importTree)
        case from.ArrayValue(elemtpt ,elems) =>
          new ArrayValue(importTree(elemtpt), elems map importTree)
        case from.Function(vparams, body) =>
          new Function(vparams map importValDef, importTree(body))
        case from.Assign(lhs, rhs) =>
          new Assign(importTree(lhs), importTree(rhs))
        case from.AssignOrNamedArg(lhs, rhs) =>
          new AssignOrNamedArg(importTree(lhs), importTree(rhs))
        case from.If(cond, thenp, elsep) =>
          new If(importTree(cond), importTree(thenp), importTree(elsep))
        case from.Match(selector, cases) =>
          new Match(importTree(selector), cases map importCaseDef)
        case from.Return(expr) =>
          new Return(importTree(expr))
        case from.Try(block, catches, finalizer) =>
          new Try(importTree(block), catches map importCaseDef, importTree(finalizer))
        case from.Throw(expr) =>
          new Throw(importTree(expr))
        case from.New(tpt) =>
          new New(importTree(tpt))
        case from.Typed(expr, tpt) =>
          new Typed(importTree(expr), importTree(tpt))
        case from.TypeApply(fun, args) =>
          new TypeApply(importTree(fun), args map importTree)
        case from.Apply(fun, args) => tree match {
          case _: from.ApplyToImplicitArgs =>
            new ApplyToImplicitArgs(importTree(fun), args map importTree)
          case _: from.ApplyImplicitView =>
            new ApplyImplicitView(importTree(fun), args map importTree)
          case _ =>
            new Apply(importTree(fun), args map importTree)
        }
        case from.ApplyDynamic(qual, args) =>
          new ApplyDynamic(importTree(qual), args map importTree)
        case from.Super(qual, mix) =>
          new Super(importTree(qual), importTypeName(mix))
        case from.This(qual) =>
          new This(importName(qual).toTypeName)
        case from.Select(qual, name) =>
          new Select(importTree(qual), importName(name))
        case from.Ident(name) => tree match {
          case _: from.BackQuotedIdent =>
            new BackQuotedIdent(importName(name))
          case _ =>
            new Ident(importName(name))
        }
        case from.Literal(constant @ from.Constant(_)) =>
          new Literal(importConstant(constant))
        case from.TypeTree() =>
          new TypeTree()
        case from.Annotated(annot, arg) =>
          new Annotated(importTree(annot), importTree(arg))
        case from.SingletonTypeTree(ref) =>
          new SingletonTypeTree(importTree(ref))
        case from.SelectFromTypeTree(qual, name) =>
          new SelectFromTypeTree(importTree(qual), importName(name).toTypeName)
        case from.CompoundTypeTree(templ) =>
          new CompoundTypeTree(importTemplate(templ))
        case from.AppliedTypeTree(tpt, args) =>
          new AppliedTypeTree(importTree(tpt), args map importTree)
        case from.TypeBoundsTree(lo, hi) =>
          new TypeBoundsTree(importTree(lo), importTree(hi))
        case from.ExistentialTypeTree(tpt, whereClauses) =>
          new ExistentialTypeTree(importTree(tpt), whereClauses map importTree)
        case from.EmptyTree =>
          EmptyTree
        case null =>
          null
      }
      addFixup({
        if (mytree != null) {
          val mysym = if (tree hasSymbol) importSymbol(tree.symbol) else NoSymbol
          val mytpe = importType(tree.tpe)

          mytree match {
            case mytt: TypeTree =>
              val tt = tree.asInstanceOf[from.TypeTree]
              if (mytree hasSymbol) mytt.symbol = mysym
              if (tt.wasEmpty) mytt.defineType(mytpe) else mytt.setType(mytpe)
              if (tt.original != null) mytt.setOriginal(importTree(tt.original))
            case _ =>
              if (mytree hasSymbol) mytree.symbol = importSymbol(tree.symbol)
              mytree.tpe = importType(tree.tpe)
          }
        }
      })
      tryFixup()
      mytree
    }

    def importValDef(tree: from.ValDef): ValDef = importTree(tree).asInstanceOf[ValDef]
    def importTypeDef(tree: from.TypeDef): TypeDef = importTree(tree).asInstanceOf[TypeDef]
    def importTemplate(tree: from.Template): Template = importTree(tree).asInstanceOf[Template]
    def importRefTree(tree: from.RefTree): RefTree = importTree(tree).asInstanceOf[RefTree]
    def importIdent(tree: from.Ident): Ident = importTree(tree).asInstanceOf[Ident]
    def importCaseDef(tree: from.CaseDef): CaseDef = importTree(tree).asInstanceOf[CaseDef]
    def importConstant(constant: from.Constant): Constant = new Constant(constant.tag match {
      case ClassTag => importType(constant.value.asInstanceOf[from.Type])
      case EnumTag => importSymbol(constant.value.asInstanceOf[from.Symbol])
      case _ => constant.value
    })
  }
}