package scala.tools.nsc
package backend.jvm

import scala.annotation.switch

/**
 * Core BTypes and some other definitions. The initialization of these definitions requies access
 * to symbols / types (global).
 *
 * The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To
 * make sure the definitions are consistent with the symbols in the current run, the
 * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each
 * compiler run.
 *
 * The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The
 * reason is that having a `var bTypes: CoreBTypes` would not allow `import bTypes._`. Instead, the
 * proxy class holds a `CoreBTypes` in a variable field and forwards to this instance.
 *
 * The definitions in `CoreBTypes` need to be lazy vals to break an initialization cycle. When
 * creating a new instance to assign to the proxy, the `classBTypeFromSymbol` invoked in the
 * constructor will actucally go through the proxy. The lazy vals make sure the instance is assigned
 * in the proxy before the fields are initialized.
 *
 * Note: if we did not re-create the core BTypes on each compiler run, BType.classBTypeFromInternalNameMap
 * could not be a perRunCache anymore: the classes defeined here need to be in that map, they are
 * added when the ClassBTypes are created. The per run cache removes them, so they would be missing
 * in the second run.
 */
class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
  import bTypes._
  import global._
  import rootMirror.{requiredClass, getClassIfDefined}
  import definitions._

  /**
   * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above
   * the first use of `classBTypeFromSymbol` because that method looks at the map.
   */
  lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map(
    UnitClass    -> UNIT,
    BooleanClass -> BOOL,
    CharClass    -> CHAR,
    ByteClass    -> BYTE,
    ShortClass   -> SHORT,
    IntClass     -> INT,
    LongClass    -> LONG,
    FloatClass   -> FLOAT,
    DoubleClass  -> DOUBLE
  )

  lazy val BOXED_UNIT    : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void])
  lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass)
  lazy val BOXED_BYTE    : ClassBType = classBTypeFromSymbol(BoxedByteClass)
  lazy val BOXED_SHORT   : ClassBType = classBTypeFromSymbol(BoxedShortClass)
  lazy val BOXED_CHAR    : ClassBType = classBTypeFromSymbol(BoxedCharacterClass)
  lazy val BOXED_INT     : ClassBType = classBTypeFromSymbol(BoxedIntClass)
  lazy val BOXED_LONG    : ClassBType = classBTypeFromSymbol(BoxedLongClass)
  lazy val BOXED_FLOAT   : ClassBType = classBTypeFromSymbol(BoxedFloatClass)
  lazy val BOXED_DOUBLE  : ClassBType = classBTypeFromSymbol(BoxedDoubleClass)

  /**
   * Map from primitive types to their boxed class type. Useful when pushing class literals onto the
   * operand stack (ldc instruction taking a class literal), see genConstant.
   */
  lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map(
    UNIT   -> BOXED_UNIT,
    BOOL   -> BOXED_BOOLEAN,
    BYTE   -> BOXED_BYTE,
    SHORT  -> BOXED_SHORT,
    CHAR   -> BOXED_CHAR,
    INT    -> BOXED_INT,
    LONG   -> BOXED_LONG,
    FLOAT  -> BOXED_FLOAT,
    DOUBLE -> BOXED_DOUBLE
  )

  lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet

  /**
   * Maps the method symbol for a box method to the boxed type of the result. For example, the
   * method symbol for `Byte.box()` is mapped to the ClassBType `java/lang/Byte`.
   */
  lazy val boxResultType: Map[Symbol, ClassBType] = {
    for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod)
    yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym))
  }

  /**
   * Maps the method symbol for an unbox method to the primitive type of the result.
   * For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */
  lazy val unboxResultType: Map[Symbol, PrimitiveBType] = {
    for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod)
    yield unboxMethodSym -> primitiveTypeMap(valueClassSym)
  }

  /*
   * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in
   * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs.
   *
   * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal
   * names of NothingClass and NullClass can't be emitted as-is.
   * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$`
   */
  lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$])
  lazy val RT_NULL    : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$"))    // (requiredClass[scala.runtime.Null$])

  lazy val ObjectReference   : ClassBType = classBTypeFromSymbol(ObjectClass)
  lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference)

  lazy val StringReference             : ClassBType = classBTypeFromSymbol(StringClass)
  lazy val StringBuilderReference      : ClassBType = classBTypeFromSymbol(StringBuilderClass)
  lazy val ThrowableReference          : ClassBType = classBTypeFromSymbol(ThrowableClass)
  lazy val jlCloneableReference        : ClassBType = classBTypeFromSymbol(JavaCloneableClass)        // java/lang/Cloneable
  lazy val jlNPEReference              : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException
  lazy val jioSerializableReference    : ClassBType = classBTypeFromSymbol(JavaSerializableClass)     // java/io/Serializable
  lazy val scalaSerializableReference  : ClassBType = classBTypeFromSymbol(SerializableClass)         // scala/Serializable
  lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass)   // java/lang/ClassCastException

  lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef])
  lazy val srByteRef    : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef])
  lazy val srCharRef    : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef])
  lazy val srIntRef     : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef])
  lazy val srLongRef    : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef])
  lazy val srFloatRef   : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef])
  lazy val srDoubleRef  : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef])

  lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_)

  // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
  lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable")
  lazy val AndroidCreatorClass        : Symbol = getClassIfDefined("android.os.Parcelable$Creator")

  lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo]

  /* The Object => String overload. */
  lazy val String_valueOf: Symbol = {
    getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match {
      case List(pt) => pt.typeSymbol == ObjectClass
      case _        => false
    })
  }

  // scala.FunctionX and scala.runtim.AbstractFunctionX
  lazy val FunctionReference         : Vector[ClassBType]   = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut)
  lazy val AbstractFunctionReference : Vector[ClassBType]   = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut)
  lazy val AbstractFunctionArityMap  : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap

  lazy val PartialFunctionReference         : ClassBType = classBTypeFromSymbol(PartialFunctionClass)
  lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass)

  lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])

  /**
   * Methods in scala.runtime.BoxesRuntime
   */
  lazy val asmBoxTo  : Map[BType, MethodNameAndType] = Map(
    BOOL   -> MethodNameAndType("boxToBoolean",   MethodBType(List(BOOL),   BOXED_BOOLEAN)),
    BYTE   -> MethodNameAndType("boxToByte",      MethodBType(List(BYTE),   BOXED_BYTE)),
    CHAR   -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR),   BOXED_CHAR)),
    SHORT  -> MethodNameAndType("boxToShort",     MethodBType(List(SHORT),  BOXED_SHORT)),
    INT    -> MethodNameAndType("boxToInteger",   MethodBType(List(INT),    BOXED_INT)),
    LONG   -> MethodNameAndType("boxToLong",      MethodBType(List(LONG),   BOXED_LONG)),
    FLOAT  -> MethodNameAndType("boxToFloat",     MethodBType(List(FLOAT),  BOXED_FLOAT)),
    DOUBLE -> MethodNameAndType("boxToDouble",    MethodBType(List(DOUBLE), BOXED_DOUBLE))
  )

  lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map(
    BOOL   -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)),
    BYTE   -> MethodNameAndType("unboxToByte",    MethodBType(List(ObjectReference), BYTE)),
    CHAR   -> MethodNameAndType("unboxToChar",    MethodBType(List(ObjectReference), CHAR)),
    SHORT  -> MethodNameAndType("unboxToShort",   MethodBType(List(ObjectReference), SHORT)),
    INT    -> MethodNameAndType("unboxToInt",     MethodBType(List(ObjectReference), INT)),
    LONG   -> MethodNameAndType("unboxToLong",    MethodBType(List(ObjectReference), LONG)),
    FLOAT  -> MethodNameAndType("unboxToFloat",   MethodBType(List(ObjectReference), FLOAT)),
    DOUBLE -> MethodNameAndType("unboxToDouble",  MethodBType(List(ObjectReference), DOUBLE))
  )

  lazy val typeOfArrayOp: Map[Int, BType] = {
    import scalaPrimitives._
    Map(
        (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL))   ++
        (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE))   ++
        (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT))  ++
        (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR))   ++
        (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT))    ++
        (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG))   ++
        (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT))  ++
        (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++
        (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _*
    )
  }
}

/**
 * This trait make some core BTypes availalbe that don't depend on a Global instance. Some core
 * BTypes are required to be accessible in the BTypes trait, which does not have access to Global.
 *
 * BTypes cannot refer to CoreBTypesProxy because some of its members depend on global, for example
 * the type Symbol in
 *   def primitiveTypeMap: Map[Symbol, PrimitiveBType]
 */
trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] {
  val bTypes: BTS
  import bTypes._

  def boxedClasses: Set[ClassBType]

  def RT_NOTHING : ClassBType
  def RT_NULL    : ClassBType

  def ObjectReference          : ClassBType
  def jlCloneableReference     : ClassBType
  def jioSerializableReference : ClassBType
}

/**
 * See comment in class [[CoreBTypes]].
 */
final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) extends CoreBTypesProxyGlobalIndependent[BTFS] {
  import bTypes._
  import global._

  private[this] var _coreBTypes: CoreBTypes[bTypes.type] = _
  def setBTypes(coreBTypes: CoreBTypes[BTFS]): Unit = {
    _coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]]
  }

  def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap

  def BOXED_UNIT    : ClassBType = _coreBTypes.BOXED_UNIT
  def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN
  def BOXED_BYTE    : ClassBType = _coreBTypes.BOXED_BYTE
  def BOXED_SHORT   : ClassBType = _coreBTypes.BOXED_SHORT
  def BOXED_CHAR    : ClassBType = _coreBTypes.BOXED_CHAR
  def BOXED_INT     : ClassBType = _coreBTypes.BOXED_INT
  def BOXED_LONG    : ClassBType = _coreBTypes.BOXED_LONG
  def BOXED_FLOAT   : ClassBType = _coreBTypes.BOXED_FLOAT
  def BOXED_DOUBLE  : ClassBType = _coreBTypes.BOXED_DOUBLE

  def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses

  def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive

  def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType

  def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType

  def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING
  def RT_NULL    : ClassBType = _coreBTypes.RT_NULL

  def ObjectReference   : ClassBType = _coreBTypes.ObjectReference
  def objArrayReference : ArrayBType = _coreBTypes.objArrayReference

  def StringReference             : ClassBType = _coreBTypes.StringReference
  def StringBuilderReference      : ClassBType = _coreBTypes.StringBuilderReference
  def ThrowableReference          : ClassBType = _coreBTypes.ThrowableReference
  def jlCloneableReference        : ClassBType = _coreBTypes.jlCloneableReference
  def jlNPEReference              : ClassBType = _coreBTypes.jlNPEReference
  def jioSerializableReference    : ClassBType = _coreBTypes.jioSerializableReference
  def scalaSerializableReference  : ClassBType = _coreBTypes.scalaSerializableReference
  def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference

  def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef
  def srByteRef    : ClassBType = _coreBTypes.srByteRef
  def srCharRef    : ClassBType = _coreBTypes.srCharRef
  def srIntRef     : ClassBType = _coreBTypes.srIntRef
  def srLongRef    : ClassBType = _coreBTypes.srLongRef
  def srFloatRef   : ClassBType = _coreBTypes.srFloatRef
  def srDoubleRef  : ClassBType = _coreBTypes.srDoubleRef

  def hashMethodSym: Symbol = _coreBTypes.hashMethodSym

  def AndroidParcelableInterface : Symbol = _coreBTypes.AndroidParcelableInterface
  def AndroidCreatorClass        : Symbol = _coreBTypes.AndroidCreatorClass

  def BeanInfoAttr: Symbol = _coreBTypes.BeanInfoAttr

  def String_valueOf: Symbol = _coreBTypes.String_valueOf

  def FunctionReference         : Vector[ClassBType]   = _coreBTypes.FunctionReference
  def AbstractFunctionReference : Vector[ClassBType]   = _coreBTypes.AbstractFunctionReference
  def AbstractFunctionArityMap  : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap

  def PartialFunctionReference         : ClassBType = _coreBTypes.PartialFunctionReference
  def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference

  def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime

  def asmBoxTo  : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo
  def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo

  def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp
}
