/*                     __                                               *\
**     ________ ___   / /  ___     Scala Ant Tasks                      **
**    / __/ __// _ | / /  / _ |    (c) 2005-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.tools.ant

import java.io.{File, PrintWriter, BufferedWriter, FileWriter}

import org.apache.tools.ant.{ Project, AntClassLoader}
import org.apache.tools.ant.taskdefs.Java
import org.apache.tools.ant.types.{Path, Reference}
import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, SourceFileScanner}
import org.apache.tools.ant.util.facade.{FacadeTaskHelper, ImplementationSpecificArgument}

import scala.tools.nsc.{Global, Settings, CompilerCommand}
import scala.tools.nsc.io.{Path => SPath}
import scala.tools.nsc.reporters.{Reporter, ConsoleReporter}

/** An Ant task to compile with the new Scala compiler (NSC).
 *
 *  This task can take the following parameters as attributes:
 *  - `srcdir` (mandatory),
 *  - `srcref`,
 *  - `destdir`,
 *  - `classpath`,
 *  - `classpathref`,
 *  - `sourcepath`,
 *  - `sourcepathref`,
 *  - `bootclasspath`,
 *  - `bootclasspathref`,
 *  - `extdirs`,
 *  - `extdirsref`,
 *  - `argfile`,
 *  - `dependencyfile`,
 *  - `encoding`,
 *  - `target`,
 *  - `force`,
 *  - `fork`,
 *  - `logging`,
 *  - `logphase`,
 *  - `debuginfo`,
 *  - `addparams`,
 *  - `explaintypes`,
 *  - `deprecation`,
 *  - `nobootcp`,
 *  - `nowarn`,
 *  - `optimise`,
 *  - `unchecked`,
 *  - `usejavacp`,
 *  - `failonerror`,
 *  - `scalacdebugging`,
 *
 *  It also takes the following parameters as nested elements:
 *  - `src` (for `srcdir`),
 *  - `classpath`,
 *  - `sourcepath`,
 *  - `bootclasspath`,
 *  - `extdirs`,
 *  - `compilerarg`.
 *
 *  @author Gilles Dubochet, Stephane Micheloud
 */
class Scalac extends ScalaMatchingTask with ScalacShared {

  /** The unique Ant file utilities instance to use in this task. */
  private val fileUtils = FileUtils.getFileUtils()

/*============================================================================*\
**                             Ant user-properties                            **
\*============================================================================*/

  abstract class PermissibleValue {
    val values: List[String]
    def isPermissible(value: String): Boolean =
      (value == "") || values.exists(_.startsWith(value))
  }

  /** Defines valid values for the logging property. */
  object LoggingLevel extends PermissibleValue {
    val values = List("none", "verbose", "debug")
  }

  /** Defines valid values for properties that refer to compiler phases. */
  object CompilerPhase extends PermissibleValue {
    val values = List("namer", "typer", "pickler", "refchecks",
                      "uncurry", "tailcalls", "specialize", "explicitouter",
                      "erasure", "fields", "lambdalift", "constructors",
                      "flatten", "mixin", "delambdafy", "cleanup",
                      "jvm", "terminal")
  }

  /** Defines valid values for the `target` property. */
  object Target extends PermissibleValue {
    val values = List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8")
  }

  /** Defines valid values for the `deprecation` and `unchecked` properties. */
  object Flag extends PermissibleValue {
    val values = List("yes", "no", "on", "off", "true", "false")
    def toBoolean(flag: String) =
      if (flag == "yes" || flag == "on" || flag == "true") Some(true)
      else if (flag == "no" || flag == "off" || flag == "false") Some(false)
      else None
  }

  /** The directories that contain source files to compile. */
  protected var origin: Option[Path] = None
  /** The directory to put the compiled files in. */
  protected var destination: Option[File] = None

  /** The class path to use for this compilation. */
  protected var classpath: Option[Path] = None
  /** The source path to use for this compilation. */
  protected var sourcepath: Option[Path] = None
  /** The boot class path to use for this compilation. */
  protected var bootclasspath: Option[Path] = None
  /** The path to use when finding scalac - *only used for forking!*  */
  protected var compilerPath: Option[Path] = None
  /** The external extensions path to use for this compilation. */
  protected var extdirs: Option[Path] = None

  protected var argfile: Option[File] = None
  /** The dependency tracking file. */
  protected var dependencyfile: Option[File] = None
  /** The character encoding of the files to compile. */
  protected var encoding: Option[String] = None

  // the targeted backend
  protected var backend: Option[String] = None

  /** Whether to force compilation of all files or not. */
  protected var force: Boolean = false
  /** Whether to fork the execution of scalac */
  protected var fork : Boolean = false
  /** If forking, these are the arguments to the JVM */
  protected var jvmArgs : Option[String] = None
  /** How much logging output to print. Either none (default),
    * verbose or debug. */
  protected var logging: Option[String] = None
  /** Which compilation phases should be logged during compilation. */
  protected var logPhase: List[String] = Nil

  /** Instruct the compiler to generate debugging information */
  protected var debugInfo: Option[String] = None
  /** Instruct the compiler to use additional parameters */
  protected var addParams: String = ""
  /** Instruct the compiler to explain type errors in more detail. */
  protected var explaintypes: Option[Boolean] = None
  /** Instruct the compiler to generate deprecation information. */
  protected var deprecation: Option[Boolean] = None
  /** Instruct the compiler to not use the boot classpath for the scala jars. */
  protected var nobootcp: Option[Boolean] = None
  /** Instruct the compiler to generate no warnings. */
  protected var nowarn: Option[Boolean] = None
  /** Instruct the compiler to run optimizations. */
  protected var optimise: Option[Boolean] = None
  /** Instruct the compiler to generate unchecked information. */
  protected var unchecked: Option[Boolean] = None
  /** Instruct the compiler to use `java.class.path` in classpath resolution. */
  protected var usejavacp: Option[Boolean] = None
  /** Indicates whether compilation errors will fail the build; defaults to true. */
  protected var failonerror: Boolean = true

  /** Prints out the files being compiled by the scalac ant task
   *  (not only the number of files). */
  protected var scalacDebugging: Boolean = false

  /** Encapsulates implementation of specific command line arguments. */
  protected var scalacCompilerArgs = new FacadeTaskHelper("compilerarg")

  /** Helpers */
  private def setOrAppend(old: Option[Path], arg: Path): Option[Path] = old match {
    case Some(x)  => x append arg ; Some(x)
    case None     => Some(arg)
  }
  private def pathAsList(p: Option[Path], name: String): List[File] = p match {
    case None     => buildError("Member '" + name + "' is empty.")
    case Some(x)  => x.list.toList map nameToFile
  }
  private def createNewPath(getter: () => Option[Path], setter: (Option[Path]) => Unit) = {
    if (getter().isEmpty)
      setter(Some(new Path(getProject)))

    getter().get.createPath()
  }

  private def plural(xs: List[Any]) = if (xs.size > 1) "s" else ""
  private def plural(x: Int) = if (x > 1) "s" else ""

/*============================================================================*\
**                             Properties setters                             **
\*============================================================================*/


  /** Sets the `srcdir` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `origin`. */
  def setSrcdir(input: Path) {
    origin = setOrAppend(origin, input)
  }

  /** Sets the `origin` as a nested src Ant parameter.
   *  @return An origin path to be configured. */
  def createSrc(): Path = createNewPath(origin _, p => origin = p)

  /** Sets the `origin` as an external reference Ant parameter.
   *  @param input A reference to an origin path. */
  def setSrcref(input: Reference) =
    createSrc().setRefid(input)

  /** Sets the `destdir` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `destination`. */
  def setDestdir(input: File) { destination = Some(input) }

  /** Sets the `classpath` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `classpath`. */
  def setClasspath(input: Path) {
    classpath = setOrAppend(classpath, input)
  }
  /** Sets the `compilerPath` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `compilerPath`. */
  def setCompilerPath(input: Path) {
    compilerPath = setOrAppend(compilerPath, input)
  }

  def createCompilerPath: Path = createNewPath(compilerPath _, p => compilerPath = p)

  /** Sets the `compilerpathref` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `compilerpathref`. */
  def setCompilerPathRef(input: Reference) {
    createCompilerPath.setRefid(input)
  }

  /** Sets the `classpath` as a nested classpath Ant parameter.
   *  @return A class path to be configured. */
  def createClasspath(): Path = createNewPath(classpath _, p => classpath = p)

  /** Sets the `classpath` as an external reference Ant parameter.
   *  @param input A reference to a class path. */
  def setClasspathref(input: Reference) {
    createClasspath().setRefid(input)
  }

  /** Sets the `sourcepath` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `sourcepath`. */
  def setSourcepath(input: Path) {
    sourcepath = setOrAppend(sourcepath, input)
  }

  /** Sets the `sourcepath` as a nested sourcepath Ant parameter.
   *  @return A source path to be configured. */
  def createSourcepath(): Path = createNewPath(sourcepath _, p => sourcepath = p)

  /** Sets the `sourcepath` as an external reference Ant parameter.
   *  @param input A reference to a source path. */
  def setSourcepathref(input: Reference) {
    createSourcepath().setRefid(input)
  }

  /** Sets the boot classpath attribute. Used by [[http://ant.apache.org Ant]].
   *
   *  @param input The value of `bootclasspath`. */
  def setBootclasspath(input: Path) {
    bootclasspath = setOrAppend(bootclasspath, input)
  }

  /** Sets the `bootclasspath` as a nested bootclasspath Ant parameter.
   *  @return A source path to be configured. */
  def createBootclasspath(): Path = createNewPath(bootclasspath _, p => bootclasspath = p)

  /** Sets the `bootclasspath` as an external reference Ant
   *  parameter.
   *  @param input A reference to a source path. */
  def setBootclasspathref(input: Reference) =
    createBootclasspath().setRefid(input)

  /** Sets the external extensions path attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `extdirs`. */
  def setExtdirs(input: Path) {
    extdirs = setOrAppend(extdirs, input)
  }

  /** Sets the `extdirs` as a nested extdirs Ant parameter.
   *  @return An extensions path to be configured. */
  def createExtdirs(): Path = createNewPath(extdirs _, p => extdirs = p)

  /** Sets the `extdirs` as an external reference Ant parameter.
   *  @param input A reference to an extensions path. */
  def setExtdirsref(input: Reference) =
    createExtdirs().setRefid(input)

  /** Sets the `argfile` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `argfile`. */
  def setArgfile(input: File) {
    argfile = Some(input)
  }

  /** Sets the `dependencyfile` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `dependencyfile`. */
  def setDependencyfile(input: File) {
    dependencyfile = Some(input)
  }

  /** Sets the `encoding` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value of `encoding`. */
  def setEncoding(input: String) {
    encoding = Some(input)
  }

  /** Sets the `target` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `target`. */
  def setTarget(input: String): Unit =
    if (Target.isPermissible(input)) backend = Some(input)
    else buildError("Unknown target '" + input + "'")

  /** Sets the `force` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `force`. */
  def setForce(input: Boolean) { force = input }

  /** Sets the `fork` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `fork`. */
  def setFork(input : Boolean) { fork = input }
  /**
   * Sets the `jvmargs` attribute.  Used by [[http://ant.apache.org Ant]].
   * @param input The value for `jvmargs`
   */
  def setJvmargs(input : String) {
    jvmArgs = Some(input)
  }

  /** Sets the logging level attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `logging`. */
  def setLogging(input: String) {
    if (LoggingLevel.isPermissible(input)) logging = Some(input)
    else buildError("Logging level '" + input + "' does not exist.")
  }

  /** Sets the `logphase` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `logPhase`. */
  def setLogPhase(input: String) {
    logPhase = input.split(",").toList.flatMap { s: String =>
      val st = s.trim()
      if (CompilerPhase.isPermissible(st))
        (if (input != "") List(st) else Nil)
      else {
        buildError("Phase " + st + " in log does not exist.")
      }
    }
  }

  /** Set the `debug` info attribute.
   *  @param input The value for `debug`. */
  def setDebuginfo(input: String) { debugInfo = Some(input) }

  /** Set the `addparams` info attribute.
   *  @param input The value for `addparams`. */
  def setAddparams(input: String) { addParams = input }

  /** Set the `explaintypes` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setExplaintypes(input: String) {
    explaintypes = Flag toBoolean input orElse buildError("Unknown explaintypes flag '" + input + "'")
  }

  /** Set the `deprecation` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setDeprecation(input: String) {
    deprecation = Flag toBoolean input orElse buildError("Unknown deprecation flag '" + input + "'")
  }

  /** Set the `nobootcp` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setNobootcp(input: String) {
    nobootcp = Flag toBoolean input orElse buildError("Unknown nobootcp flag '" + input + "'")
  }

  /** Set the `nowarn` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setNowarn(input: String) {
    nowarn = Flag toBoolean input orElse buildError("Unknown nowarn flag '" + input + "'")
  }

  /** Set the `optimise` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setOptimise(input: String) {
    optimise = Flag toBoolean input orElse buildError("Unknown optimisation flag '" + input + "'")
  }

  /** Set the `unchecked` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setUnchecked(input: String) {
    unchecked = Flag toBoolean input orElse buildError("Unknown unchecked flag '" + input + "'")
  }

  /** Set the `usejavacp` info attribute.
   *  @param input One of the flags `yes/no` or `on/off`. */
  def setUsejavacp(input: String) {
    usejavacp = Flag toBoolean input orElse buildError("Unknown usejavacp flag '" + input + "'")
  }

  /** Sets the `failonerror` attribute. Used by [[http://ant.apache.org Ant]].
   *  @param input The value for `failonerror`. */
  def setFailonerror(input: Boolean) { failonerror = input }

  /** Set the `scalacdebugging` info attribute. If set to
   *  `'''true'''`, the scalac ant task will print out the filenames
   *  being compiled.
   *  @param input The specified flag */
  def setScalacdebugging(input: Boolean) { scalacDebugging = input }

  /** Sets the `compilerarg` as a nested compilerarg Ant parameter.
   *  @return A compiler argument to be configured. */
  def createCompilerArg(): ImplementationSpecificArgument = {
    val arg = new ImplementationSpecificArgument()
    scalacCompilerArgs addImplementationArgument arg
    arg
  }

/*============================================================================*\
**                             Properties getters                             **
\*============================================================================*/

  /** Gets the value of the `classpath` attribute in a
   *  Scala-friendly form.
   *  @return The class path as a list of files. */
  protected def getClasspath: List[File] = pathAsList(classpath, "classpath")

  /** Gets the value of the `origin` attribute in a
   *  Scala-friendly form.
   *  @return The origin path as a list of files. */
  protected def getOrigin: List[File] = pathAsList(origin, "origin")

  /** Gets the value of the `destination` attribute in a
   *  Scala-friendly form.
   *  @return The destination as a file. */
  protected def getDestination: File =
    if (destination.isEmpty) buildError("Member 'destination' is empty.")
    else existing(getProject resolveFile destination.get.toString)

  /** Gets the value of the `sourcepath` attribute in a
   *  Scala-friendly form.
   *  @return The source path as a list of files. */
  protected def getSourcepath: List[File] = pathAsList(sourcepath, "sourcepath")

  /** Gets the value of the `bootclasspath` attribute in a
   *  Scala-friendly form.
   *  @return The boot class path as a list of files. */
  protected def getBootclasspath: List[File] = pathAsList(bootclasspath, "bootclasspath")

  /** Gets the value of the `extdirs` attribute in a
   *  Scala-friendly form.
   *  @return The extensions path as a list of files. */
  protected def getExtdirs: List[File] = pathAsList(extdirs, "extdirs")

/*============================================================================*\
**                       Compilation and support methods                      **
\*============================================================================*/

  /** Transforms a string name into a file relative to the provided base
   *  directory.
   *  @param base A file pointing to the location relative to which the name
   *              will be resolved.
   *  @param name A relative or absolute path to the file as a string.
   *  @return     A file created from the name and the base file. */
  protected def nameToFile(base: File)(name: String): File =
    existing(fileUtils.resolveFile(base, name))

  /** Transforms a string name into a file relative to the build root
   *  directory.
   *  @param name A relative or absolute path to the file as a string.
   *  @return     A file created from the name. */
  protected def nameToFile(name: String): File =
    existing(getProject resolveFile name)

  /** Tests if a file exists and prints a warning in case it doesn't. Always
   *  returns the file, even if it doesn't exist.
   *  @param file A file to test for existence.
   *  @return     The same file. */
  protected def existing(file: File): File = {
    if (!file.exists)
      log("Element '" + file.toString + "' does not exist.",
          Project.MSG_WARN)
    file
  }

  /** Transforms a path into a Scalac-readable string.
   *  @param path A path to convert.
   *  @return     A string-representation of the path like `a.jar:b.jar`. */
  protected def asString(path: List[File]): String =
    path.map(asString) mkString File.pathSeparator

  /** Transforms a file into a Scalac-readable string.
   *  @param file A file to convert.
   *  @return     A string-representation of the file like `/x/k/a.scala`. */
  protected def asString(file: File): String =
    file.getAbsolutePath()

/*============================================================================*\
**                      Hooks for variants of Scala                           **
\*============================================================================*/

  protected def newSettings(error: String=>Unit): Settings =
    new Settings(error)

  protected def newGlobal(settings: Settings, reporter: Reporter) =
    Global(settings, reporter)

/*============================================================================*\
**                           The big execute method                           **
\*============================================================================*/

  /** Initializes settings and source files */
  protected def initialize: (Settings, List[File], Boolean) = {
    if (scalacDebugging)
      log("Base directory is `%s`".format(SPath("").normalize))

    // Tests if all mandatory attributes are set and valid.
    if (origin.isEmpty) buildError("Attribute 'srcdir' is not set.")
    if (!destination.isEmpty && !destination.get.isDirectory())
      buildError("Attribute 'destdir' does not refer to an existing directory.")
    if (destination.isEmpty) destination = Some(getOrigin.head)

    val mapper = new GlobPatternMapper()
    mapper setTo "*.class"
    mapper setFrom "*.scala"

    var javaOnly = true

    def getOriginFiles(originDir: File) = {
      val includedFiles = getDirectoryScanner(originDir).getIncludedFiles
      val javaFiles = includedFiles filter (_ endsWith ".java")
      val scalaFiles = {
        val xs = includedFiles filter (_ endsWith ".scala")
        if (force) xs
        else new SourceFileScanner(this).restrict(xs, originDir, destination.get, mapper)
      }

      javaOnly = javaOnly && (scalaFiles.length == 0)
      val list = (scalaFiles ++ javaFiles).toList

      if (scalacDebugging && !list.isEmpty)
        log("Compiling source file%s: %s to %s".format(
          plural(list),
          list.mkString(", "),
          getDestination.toString
        ))
      else if (!list.isEmpty) {
        val str =
          if (javaFiles.isEmpty) "%d source file%s".format(list.length, plural(list))
          else "%d scala and %d java source files".format(scalaFiles.length, javaFiles.length)
        log(s"Compiling $str to $getDestination")
      }
      else log("No files selected for compilation", Project.MSG_VERBOSE)

      list
    }

    // Scans source directories to build up a compile lists.
    // If force is false, only files were the .class file in destination is
    // older than the .scala file will be used.
    val sourceFiles: List[File] =
      for (originDir <- getOrigin ; originFile <- getOriginFiles(originDir)) yield {
        log(originFile, Project.MSG_DEBUG)
        nameToFile(originDir)(originFile)
      }

    // Builds-up the compilation settings for Scalac with the existing Ant
    // parameters.
    val settings = newSettings(buildError)
    settings.outdir.value = asString(destination.get)
    if (!classpath.isEmpty)
      settings.classpath.value = asString(getClasspath)
    if (!sourcepath.isEmpty)
      settings.sourcepath.value = asString(getSourcepath)
    if (!bootclasspath.isEmpty)
      settings.bootclasspath.value = asString(getBootclasspath)
    if (!extdirs.isEmpty) settings.extdirs.value = asString(getExtdirs)
    if (!dependencyfile.isEmpty)
      settings.dependencyfile.value = asString(dependencyfile.get)
    if (!encoding.isEmpty) settings.encoding.value = encoding.get
    if (!backend.isEmpty) settings.target.value = backend.get
    if (!logging.isEmpty && logging.get == "verbose")
      settings.verbose.value = true
    else if (!logging.isEmpty && logging.get == "debug") {
      settings.verbose.value = true
      settings.debug.value = true
    }
    if (!logPhase.isEmpty) settings.log.value = logPhase
    if (!debugInfo.isEmpty) settings.debuginfo.value = debugInfo.get
    if (!explaintypes.isEmpty) settings.explaintypes.value = explaintypes.get
    if (!deprecation.isEmpty) settings.deprecation.value = deprecation.get
    if (!nobootcp.isEmpty) settings.nobootcp.value = nobootcp.get
    if (!nowarn.isEmpty) settings.nowarn.value = nowarn.get
    if (!optimise.isEmpty) settings.optimise.value = optimise.get
    if (!unchecked.isEmpty) settings.unchecked.value = unchecked.get
    if (!usejavacp.isEmpty) settings.usejavacp.value = usejavacp.get

    val jvmargs = scalacCompilerArgs.getArgs filter (_ startsWith "-J")
    if (!jvmargs.isEmpty) settings.jvmargs.value = jvmargs.toList
    val defines = scalacCompilerArgs.getArgs filter (_ startsWith "-D")
    if (!defines.isEmpty) settings.defines.value = defines.toList

    log("Scalac params = '" + addParams + "'", Project.MSG_DEBUG)

    // let CompilerCommand processes all params
    val command = new CompilerCommand(settings.splitParams(addParams), settings)

    // resolve dependenciesFile path from project's basedir, so <ant antfile ...> call from other project works.
    // the dependenciesFile may be relative path to basedir or absolute path, in either case, the following code
    // will return correct answer.
    command.settings.dependenciesFile.value match {
      case "none" =>
      case x =>
        val depFilePath = SPath(x)
        command.settings.dependenciesFile.value = SPath(getProject.getBaseDir).normalize.resolve(depFilePath).path
    }

    (command.settings, sourceFiles, javaOnly)
  }

  override def execute() {
    val (settings, sourceFiles, javaOnly) = initialize
    if (sourceFiles.isEmpty || javaOnly)
      return

    if (fork) executeFork(settings, sourceFiles)  // TODO - Error
    else executeInternal(settings, sourceFiles)
  }

  protected def executeFork(settings: Settings, sourceFiles: List[File]) {
      val java = new Java(this)
      java setFork true
      // using 'setLine' creates multiple arguments out of a space-separated string
      jvmArgs foreach { java.createJvmarg() setLine _ }

      // use user-provided path or retrieve from classloader
      // TODO - Allow user to override the compiler classpath
      val scalacPath: Path = {
        val path = new Path(getProject)
        if (compilerPath.isDefined) path add compilerPath.get
        else getClass.getClassLoader match {
          case cl: AntClassLoader => path add new Path(getProject, cl.getClasspath)
          case _                  => buildError("Cannot determine default classpath for scalac, please specify one!")
        }
        path
      }

      java setClasspath scalacPath
      java setClassname MainClass

      // Write all settings to a temporary file
      def writeSettings(): File = {
        def escapeArgument(arg : String) = if (arg matches ".*\\s.*") '"' + arg + '"' else arg
        val file = File.createTempFile("scalac-ant-",".args")
        file.deleteOnExit()
        val out = new PrintWriter(new BufferedWriter(new FileWriter(file)))

        try {
          for (setting <- settings.visibleSettings ; arg <- setting.unparse)
            out println escapeArgument(arg)
          for (file <- sourceFiles)
            out println escapeArgument(file.getAbsolutePath)
        }
        finally out.close()

        file
      }
      val res = execWithArgFiles(java, List(writeSettings().getAbsolutePath))
      if (failonerror && res != 0)
        buildError("Compilation failed because of an internal compiler error;"+
              " see the error output for details.")
  }

  /** Performs the compilation. */
  protected def executeInternal(settings: Settings, sourceFiles : List[File]) {
    val reporter = new ConsoleReporter(settings)
    val compiler = newGlobal(settings, reporter)  // compiles the actual code

    try new compiler.Run compile (sourceFiles map (_.toString))
    catch {
      case ex: Throwable =>
        ex.printStackTrace()
        val msg = if (ex.getMessage == null) "no error message provided" else ex.getMessage
        buildError("Compile failed because of an internal compiler error (" + msg + "); see the error output for details.")
    }

    reporter.printSummary()
    if (reporter.hasErrors) {
      val msg = "Compile failed with %d error%s; see the compiler error output for details.".format(
        reporter.ERROR.count, plural(reporter.ERROR.count))
      if (failonerror) buildError(msg) else log(msg)
    }
    else if (reporter.WARNING.count > 0)
      log("Compile succeeded with %d warning%s; see the compiler output for details.".format(
        reporter.WARNING.count, plural(reporter.WARNING.count)))
  }
}
