/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc
package ast

import scala.collection.mutable.ListBuffer
import symtab.Flags._
import scala.language.postfixOps

/** XXX to resolve: TreeGen only assumes global is a SymbolTable, but
 *  TreeDSL at the moment expects a Global.  Can we get by with SymbolTable?
 */
abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
  val global: Global

  import global._
  import definitions._

  /** Builds a fully attributed, synthetic wildcard import node.
   */
  def mkWildcardImport(pkg: Symbol): Import =
    mkImportFromSelector(pkg, ImportSelector.wildList)

  /** Builds a fully attributed, synthetic import node.
    * import `qualSym`.{`name` => `toName`}
    */
  def mkImport(qualSym: Symbol, name: Name, toName: Name): Import =
    mkImportFromSelector(qualSym, ImportSelector(name, 0, toName, 0) :: Nil)

  private def mkImportFromSelector(qualSym: Symbol, selector: List[ImportSelector]): Import = {
    assert(qualSym ne null, this)
    val qual = gen.mkAttributedStableRef(qualSym)
    val importSym = (
      NoSymbol
        newImport NoPosition
          setFlag SYNTHETIC
          setInfo ImportType(qual)
    )
    val importTree = (
      Import(qual, selector)
        setSymbol importSym
          setType NoType
    )
    importTree
  }

  // wrap the given expression in a SoftReference so it can be gc-ed
  def mkSoftRef(expr: Tree): Tree = atPos(expr.pos) {
    val constructor = SoftReferenceClass.info.nonPrivateMember(nme.CONSTRUCTOR).suchThat(_.paramss.flatten.size == 1)
    NewFromConstructor(constructor, expr)
  }

  // Builds a tree of the form "{ lhs = rhs ; lhs  }"
  def mkAssignAndReturn(lhs: Symbol, rhs: Tree): Tree = {
    def lhsRef = if (lhs.owner.isClass) Select(This(lhs.owner), lhs) else Ident(lhs)
    Block(Assign(lhsRef, rhs) :: Nil, lhsRef)
  }

  def newModule(accessor: Symbol, tpe: Type) = {
    val ps = tpe.typeSymbol.primaryConstructor.info.paramTypes
    if (ps.isEmpty) New(tpe)
    else New(tpe, This(accessor.owner.enclClass))
  }

  def mkRuntimeCall(meth: Name, args: List[Tree]): Tree =
    mkRuntimeCall(meth, Nil, args)

  def mkRuntimeCall(meth: Name, targs: List[Type], args: List[Tree]): Tree =
    mkMethodCall(ScalaRunTimeModule, meth, targs, args)

  def mkThrowNewRuntimeException(message: String) =
    Throw(RuntimeExceptionClass.tpe, Literal(Constant(message)))

  /** A creator for a call to a scala.reflect.Manifest or ClassManifest factory method.
   *
   *  @param    full          full or partial manifest (target will be Manifest or ClassManifest)
   *  @param    constructor   name of the factory method (e.g. "classType")
   *  @param    tparg         the type argument
   *  @param    args          value arguments
   *  @return   the tree
   */
  def mkManifestFactoryCall(full: Boolean, constructor: String, tparg: Type, args: List[Tree]): Tree =
    mkMethodCall(
      if (full) FullManifestModule else PartialManifestModule,
      newTermName(constructor),
      List(tparg),
      args
    )

  /** Make a synchronized block on 'monitor'. */
  def mkSynchronized(monitor: Tree)(body: Tree): Tree =
    Apply(Select(monitor, Object_synchronized), List(body))

  def mkAppliedTypeForCase(clazz: Symbol): Tree = {
    val numParams = clazz.typeParams.size
    if (clazz.typeParams.isEmpty) Ident(clazz)
    else AppliedTypeTree(Ident(clazz), 1 to numParams map (_ => Bind(tpnme.WILDCARD, EmptyTree)) toList)
  }
  def mkBindForCase(patVar: Symbol, clazz: Symbol, targs: List[Type]): Tree = {
    Bind(patVar, Typed(Ident(nme.WILDCARD),
      if (targs.isEmpty) mkAppliedTypeForCase(clazz)
      else AppliedTypeTree(Ident(clazz), targs map TypeTree)
    ))
  }

  def wildcardStar(tree: Tree) =
    atPos(tree.pos) { Typed(tree, Ident(tpnme.WILDCARD_STAR)) }

  def paramToArg(vparam: Symbol): Tree =
    paramToArg(Ident(vparam), isRepeatedParamType(vparam.tpe))

  def paramToArg(vparam: ValDef): Tree =
    paramToArg(Ident(vparam.name), treeInfo.isRepeatedParamType(vparam.tpt))

  def paramToArg(arg: Ident, isRepeatedParam: Boolean): Tree  =
    if (isRepeatedParam) wildcardStar(arg) else arg

  /** Make forwarder to method `target`, passing all parameters in `params` */
  def mkForwarder(target: Tree, vparamss: List[List[Symbol]]) =
    (target /: vparamss)((fn, vparams) => Apply(fn, vparams map paramToArg))

  /** Applies a wrapArray call to an array, making it a WrappedArray.
   *  Don't let a reference type parameter be inferred, in case it's a singleton:
   *  apply the element type directly.
   */
  def mkWrapArray(tree: Tree, elemtp: Type) = {
    mkMethodCall(
      PredefModule,
      wrapArrayMethodName(elemtp),
      if (isPrimitiveValueType(elemtp)) Nil else List(elemtp),
      List(tree)
    )
  }

  /** Cast `tree` to type `pt` by creating
   *  one of the calls of the form
   *
   *    x.asInstanceOf[`pt`]     up to phase uncurry
   *    x.asInstanceOf[`pt`]()   if after uncurry but before erasure
   *    x.$asInstanceOf[`pt`]()  if at or after erasure
   */
  override def mkCast(tree: Tree, pt: Type): Tree = {
    debuglog("casting " + tree + ":" + tree.tpe + " to " + pt + " at phase: " + phase)
    assert(!tree.tpe.isInstanceOf[MethodType], tree)
    assert(!pt.isInstanceOf[MethodType], tree)
    atPos(tree.pos) {
      mkAsInstanceOf(tree, pt, any = !phase.next.erasedTypes, wrapInApply = isAtPhaseAfter(currentRun.uncurryPhase))
    }
  }

  // drop annotations generated by CPS plugin etc, since its annotationchecker rejects T @cps[U] <: Any
  // let's assume for now annotations don't affect casts, drop them there, and bring them back using the outer Typed tree
  def mkCastPreservingAnnotations(tree: Tree, pt: Type) =
    Typed(mkCast(tree, pt.withoutAnnotations.dealias), TypeTree(pt))
    // ^^^ I think we should either normalize or do nothing, but the half measure of dealias does not make sense,
    // as the logic behind a cast operates on the fully normalized type, not just on a dealiased type (think refinements with type aliases).
    // It would be ok to do nothing here, because erasure will convert the type to something that can be casted anyway.

  /** Generate a cast for tree Tree representing Array with
   *  elem type elemtp to expected type pt.
   */
  def mkCastArray(tree: Tree, elemtp: Type, pt: Type) =
    if (elemtp.typeSymbol == AnyClass && isPrimitiveValueType(tree.tpe.typeArgs.head))
      mkCast(mkRuntimeCall(nme.toObjectArray, List(tree)), pt)
    else
      mkCast(tree, pt)

  /** Translate names in Select/Ident nodes to type names.
   */
  def convertToTypeName(tree: Tree): Option[RefTree] = tree match {
    case Select(qual, name) => Some(Select(qual, name.toTypeName))
    case Ident(name)        => Some(Ident(name.toTypeName))
    case _                  => None
  }

  /** Try to convert Select(qual, name) to a SelectFromTypeTree.
   */
  def convertToSelectFromType(qual: Tree, origName: Name) = convertToTypeName(qual) match {
    case Some(qual1)  => SelectFromTypeTree(qual1 setPos qual.pos, origName.toTypeName)
    case _            => EmptyTree
  }

  /** Create a ValDef initialized to the given expression, setting the
   *  symbol to its packed type, and an function for creating Idents
   *  which refer to it.
   */
  private def mkPackedValDef(expr: Tree, owner: Symbol, name: Name): (ValDef, () => Ident) = {
    val packedType = typer.packedType(expr, owner)
    val sym = owner.newValue(name.toTermName, expr.pos.makeTransparent, SYNTHETIC) setInfo packedType

    (ValDef(sym, expr), () => Ident(sym) setPos sym.pos.focus setType expr.tpe)
  }

  /** Used in situations where you need to access value of an expression several times
   */
  def evalOnce(expr: Tree, owner: Symbol, unit: CompilationUnit)(within: (() => Tree) => Tree): Tree = {
    var used = false
    if (treeInfo.isExprSafeToInline(expr)) {
      within(() => if (used) expr.duplicate else { used = true; expr })
    }
    else {
      val (valDef, identFn) = mkPackedValDef(expr, owner, unit.freshTermName("ev$"))
      val containing = within(identFn)
      ensureNonOverlapping(containing, List(expr))
      Block(List(valDef), containing) setPos (containing.pos union expr.pos)
    }
  }

  def evalOnceAll(exprs: List[Tree], owner: Symbol, unit: CompilationUnit)(within: (List[() => Tree]) => Tree): Tree = {
    val vdefs = new ListBuffer[ValDef]
    val exprs1 = new ListBuffer[() => Tree]
    val used = new Array[Boolean](exprs.length)
    var i = 0
    for (expr <- exprs) {
      if (treeInfo.isExprSafeToInline(expr)) {
        exprs1 += {
          val idx = i
          () => if (used(idx)) expr.duplicate else { used(idx) = true; expr }
        }
      }
      else {
        val (valDef, identFn) = mkPackedValDef(expr, owner, unit.freshTermName("ev$"))
        vdefs += valDef
        exprs1 += identFn
      }
      i += 1
    }
    val prefix = vdefs.toList
    val containing = within(exprs1.toList)
    ensureNonOverlapping(containing, exprs)
    if (prefix.isEmpty) containing
    else Block(prefix, containing) setPos (prefix.head.pos union containing.pos)
  }

  /** Creates a tree representing new Object { stats }.
   *  To make sure an anonymous subclass of Object is created,
   *  if there are no stats, a () is added.
   */
  def mkAnonymousNew(stats: List[Tree]): Tree = {
    val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats
    mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition)
  }


  // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type)
  def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = {
    // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)`
    val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol
    if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)()
    else {
      val samMethType = fun.tpe memberInfo sam
      mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType)
    }
  }

  // used to create the lifted method that holds a function's body
  def mkLiftedFunctionBodyMethod(localTyper: global.analyzer.Typer)(owner: global.Symbol, fun: global.Function) = {
    def nonLocalEnclosingMember(sym: Symbol): Symbol = {
      if (sym.isLocalDummy) sym.enclClass.primaryConstructor
      else if (sym.isLocalToBlock) nonLocalEnclosingMember(sym.originalOwner)
      else sym
    }
    val ownerName = nonLocalEnclosingMember(fun.symbol.originalOwner).name match {
      case nme.CONSTRUCTOR => nme.NEWkw // do as javac does for the suffix, prefer "new" to "$lessinit$greater$1"
      case x => x.dropLocal
    }
    val newName = nme.ANON_FUN_NAME.append(nme.NAME_JOIN_STRING).append(ownerName)
    mkMethodForFunctionBody(localTyper)(owner, fun, newName)(additionalFlags = ARTIFACT)
  }


  // the result type of a function or corresponding SAM type
  private def functionResultType(tp: Type): Type = {
    val dealiased = tp.dealiasWiden
    if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last
    else samOf(tp) match {
      case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst
      case _ => NoType
    }
  }

  /**
    * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R]
    *
    * It creates a method definition with value params derived from the original lambda
    * or `methParamProtos` (used to create the correct override for sam methods).
    *
    * Replace the `fun.vparams` symbols by the newly created method params,
    * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol.
    *
    * @param owner The owner for the new method
    * @param fun  the function to take the body from
    * @param name name for the new method
    * @param additionalFlags flags to be put on the method in addition to FINAL
    */
  private def mkMethodForFunctionBody(localTyper: analyzer.Typer)
                                     (owner: Symbol, fun: Function, name: TermName)
                                     (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol),
                                      resTp: Type = functionResultType(fun.tpe),
                                      additionalFlags: FlagSet = NoFlags): DefDef = {
    val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
    // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parameter types)
    val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) }
    methSym setInfo MethodType(methParamSyms, resTp)

    // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms
    val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms)
    // we're now owned by the method that holds the body, and not the function
    val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym)
    def substThisForModule(tree: Tree) = {
      // Rewrite This(enclModuleClass) to Ident(enclModuleClass) to avoid unnecessary capture of the module
      // class, which might hamper serializability.
      //
      // Analagous to this special case in ExplicitOuter: https://github.com/scala/scala/blob/d2d33ddf8c/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala#L410-L412
      // that understands that such references shouldn't give rise to outer params.
      val enclosingStaticModules = owner.enclClassChain.filter(x => !x.hasPackageFlag && x.isModuleClass && x.isStatic)
      enclosingStaticModules.foldLeft(tree)((tree, moduleClass) => tree.substituteThis(moduleClass, gen.mkAttributedIdent(moduleClass.sourceModule)) )
    }

    newDefDef(methSym, substThisForModule(moveToMethod(useMethodParams(fun.body))))(tpt = TypeTree(resTp))
  }

  /**
    * Create a new `DefDef` based on `orig` with an explicit self parameter.
    *
    * Details:
    *   - Must by run after erasure
    *   - If `maybeClone` is the identity function, this runs "in place"
    *     and mutates the symbol of `orig`. `orig` should be discarded
    *   - Symbol owners and returns are substituted, as are parameter symbols
    *   - Recursive calls are not rewritten. This is correct if we assume
    *     that we either:
    *       - are in "in-place" mode, but can guarantee that no recursive calls exists
    *       - are associating the RHS with a cloned symbol, but intend for the original
    *         method to remain and for recursive calls to target it.
    */
  final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
    assert(phase.erasedTypes, phase)
    assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
    val origSym = orig.symbol
    val origParams = orig.symbol.info.params
    val newSym = maybeClone(orig.symbol)
    newSym.setName(newName)
    newSym.setFlag(STATIC)
    // Add an explicit self parameter
    val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)
    newSym.updateInfo(newSym.info match {
      case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res)
    })
    val selfParam = ValDef(selfParamSym)
    val rhs = orig.rhs.substituteThis(newSym.owner, gen.mkAttributedIdent(selfParamSym)) // SD-186 intentionally leaving Ident($this) is unpositioned
      .substituteSymbols(origParams, newSym.info.params.drop(1)).changeOwner(origSym -> newSym)
    treeCopy.DefDef(orig, orig.mods, orig.name, orig.tparams, (selfParam :: orig.vparamss.head) :: Nil, orig.tpt, rhs).setSymbol(newSym)
  }

  def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
    val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag)
    val parents = if (isFunctionType(fun.tpe)) {
      anonClass addAnnotation SerialVersionUIDAnnotation
      addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
    } else {
      if (fun.tpe.typeSymbol.isSubClass(JavaSerializableClass))
        anonClass addAnnotation SerialVersionUIDAnnotation
      fun.tpe :: Nil
    }
    anonClass setInfo ClassInfoType(parents, newScope, anonClass)

    // The original owner is used in the backend for the EnclosingMethod attribute. If fun is
    // nested in a value-class method, its owner was already changed to the extension method.
    // Saving the original owner allows getting the source structure from the class symbol.
    defineOriginalOwner(anonClass, fun.symbol.originalOwner)

    val samDef = mkMethodFromFunction(localTyper)(anonClass, fun)
    anonClass.info.decls enter samDef.symbol

    localTyper.typedPos(fun.pos) {
      Block(
        ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos),
        Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
    }
  }

  override def isPatVarWarnable = settings.warnUnusedPatVars
}
