package scala.tools.nsc
package symtab

import scala.reflect.ClassTag
import scala.reflect.internal.{Phase, NoPhase, SomePhase}
import scala.tools.nsc.classpath.FlatClassPath
import scala.tools.nsc.settings.ClassPathRepresentationType
import scala.tools.util.FlatClassPathResolver
import scala.tools.util.PathResolver
import util.ClassPath
import io.AbstractFile

/**
 * A complete SymbolTable implementation designed to be used in JUnit tests.
 *
 * It enables `usejavacp` setting so classpath of JUnit runner is being used
 * for symbol table's classpath.
 *
 * This class contains enough of logic implemented to make it possible to
 * initialize definitions and inspect symbols.
 */
class SymbolTableForUnitTesting extends SymbolTable {
  // Members declared in scala.reflect.api.Trees
  override def newStrictTreeCopier: TreeCopier = new StrictTreeCopier
  override def newLazyTreeCopier: TreeCopier = new LazyTreeCopier
  trait TreeCopier extends InternalTreeCopierOps
  // these should be mocks
  class StrictTreeCopier extends super.StrictTreeCopier with TreeCopier
  class LazyTreeCopier extends super.LazyTreeCopier with TreeCopier

  override def isCompilerUniverse: Boolean = true

  def classPath = platform.classPath
  def flatClassPath: FlatClassPath = platform.flatClassPath

  object platform extends backend.Platform {
    val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
    lazy val loaders: SymbolTableForUnitTesting.this.loaders.type = SymbolTableForUnitTesting.this.loaders

    def platformPhases: List[SubComponent] = Nil

    lazy val classPath: ClassPath[AbstractFile] = {
      assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive,
        "It's not possible to use the recursive classpath representation, when it's not the chosen classpath scanning method")
      new PathResolver(settings).result
    }

    private[nsc] lazy val flatClassPath: FlatClassPath = {
      assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
        "It's not possible to use the flat classpath representation, when it's not the chosen classpath scanning method")
      new FlatClassPathResolver(settings).result
    }

    def isMaybeBoxed(sym: Symbol): Boolean = ???
    def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ???
    def externalEquals: Symbol = ???
    def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]): Unit = ???
  }

  object loaders extends symtab.SymbolLoaders {
    val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
    lazy val platform: symbolTable.platform.type = symbolTable.platform
    def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol =
      sym.info.member(name)
    protected override def compileLate(srcfile: AbstractFile): Unit =
      sys.error(s"We do not expect compileLate to be called in SymbolTableTest. The srcfile passed in is $srcfile")
  }

  class GlobalMirror extends Roots(NoSymbol) {
    val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this

    def rootLoader: LazyType = settings.YclasspathImpl.value match {
      case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath)
      case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(classPath)
    }

    override def toString = "compiler mirror"
  }

  lazy val rootMirror: Mirror = {
    val rm = new GlobalMirror
    rm.init()
    rm.asInstanceOf[Mirror]
  }

  lazy val settings: Settings = {
    val s = new Settings
    // initialize classpath using java classpath
    s.usejavacp.value = true
    s
  }

   // Members declared in scala.reflect.internal.Required
  def picklerPhase: scala.reflect.internal.Phase = SomePhase
  def erasurePhase: scala.reflect.internal.Phase = SomePhase

  // Members declared in scala.reflect.internal.Reporting
  def reporter = new scala.reflect.internal.ReporterImpl {
    protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = println(msg)
  }

  // minimal Run to get Reporting wired
  def currentRun = new RunReporting {}
  class PerRunReporting extends PerRunReportingBase {
    def deprecationWarning(pos: Position, msg: String): Unit = reporter.warning(pos, msg)
  }
  protected def PerRunReporting = new PerRunReporting

  // Members declared in scala.reflect.internal.SymbolTable
  def currentRunId: Int = 1
  def log(msg: => AnyRef): Unit = println(msg)
  def mirrorThatLoaded(sym: Symbol): Mirror = rootMirror
  val phases: Seq[Phase] = List(NoPhase, SomePhase)
  val phaseWithId: Array[Phase] = {
    val maxId = phases.map(_.id).max
    val phasesArray = Array.ofDim[Phase](maxId+1)
    phases foreach { phase =>
      phasesArray(phase.id) = phase
    }
    phasesArray
  }
  lazy val treeInfo = new scala.reflect.internal.TreeInfo {
    val global: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
  }

  val currentFreshNameCreator = new reflect.internal.util.FreshNameCreator

  phase = SomePhase

  type RuntimeClass = java.lang.Class[_]
  implicit val RuntimeClassTag: ClassTag[RuntimeClass] = ClassTag[RuntimeClass](classOf[RuntimeClass])
  implicit val MirrorTag: ClassTag[Mirror] = ClassTag[Mirror](classOf[GlobalMirror])
  implicit val TreeCopierTag: ClassTag[TreeCopier] = ClassTag[TreeCopier](classOf[TreeCopier])
}
