/* NSC -- new Scala compiler
 * Copyright 2007-2009 LAMP/EPFL
 * @author Lex Spoon
 * Updated by Anders Bach Nielsen
 */
// $Id: Plugins.scala 18387 2009-07-24 15:28:37Z odersky $

package scala.tools.nsc
package plugins

import java.io.File

/** Support for run-time loading of compiler plugins.
 *
 *  @author Lex Spoon 
 *  @version 1.1, 2009/1/2
 *  Updated 2009/1/2 by Anders Bach Nielsen: Added features to implement SIP 00002
 */
trait Plugins { self: Global =>

  /** Load a rough list of the plugins.  For speed, it
   *  does not instantiate a compiler run.  Therefore it cannot
   *  test for same-named phases or other problems that are
   *  filtered from the final list of plugins.
   */
  protected def loadRoughPluginsList(): List[Plugin] = {
    val jars = settings.plugin.value.map(new File(_))
    val dirs =
      for (name <- settings.pluginsDir.value.split(File.pathSeparator).toList)
	yield new File(name)

    for (plugClass <- Plugin.loadAllFrom(jars, dirs, settings.disable.value))
    yield Plugin.instantiate(plugClass, this)
  }

  private var roughPluginsListCache: Option[List[Plugin]] = None

  protected def roughPluginsList: List[Plugin] =
    roughPluginsListCache match {
      case Some(list) => list
      case None =>
	roughPluginsListCache = Some(loadRoughPluginsList)
        roughPluginsListCache.get
    }

  /** Load all available plugins.  Skips plugins that
   *  either have the same name as another one, or which
   *  define a phase name that another one does.
   */
  protected def loadPlugins(): List[Plugin] = {
    // remove any with conflicting names or subcomponent names
    def pick(
      plugins: List[Plugin], 
      plugNames: Set[String], 
      phaseNames: Set[String]): List[Plugin] =
    {
      plugins match {
	case Nil => Nil
	case plug :: rest =>
	  val plugPhaseNames = Set.empty ++ plug.components.map(_.phaseName)
	  def withoutPlug = pick(rest, plugNames, plugPhaseNames)
	  def withPlug =
	    (plug :: 
	     pick(rest, 
		  plugNames+plug.name,
		  phaseNames++plugPhaseNames))

	  if (plugNames.contains(plug.name)) {
	    if (settings.verbose.value)
	      inform("[skipping a repeated plugin: " + plug.name + "]")
	    withoutPlug
	  } else if (settings.disable.value contains(plug.name)) {
	    if (settings.verbose.value)
	      inform("[disabling plugin: " + plug.name + "]")
	    withoutPlug
	  } else {
	    val commonPhases = phaseNames.intersect(plugPhaseNames)
	    if (!commonPhases.isEmpty) {
	      if (settings.verbose.value)
		inform("[skipping plugin " + plug.name +
		       "because it repeats phase names: " +
		       commonPhases.mkString(", ") + "]")
	      withoutPlug
	    } else {
	      if (settings.verbose.value)
		inform("[loaded plugin " + plug.name + "]")
	      withPlug
	    }
	  }
      }
    }

    val plugs =
    pick(roughPluginsList,
	 Set.empty,
	 Set.empty ++ phasesSet.map(_.phaseName))

    for (req <- settings.require.value; if !plugs.exists(p => p.name==req))
      error("Missing required plugin: " + req)

    
    for (plug <- plugs) {
      val nameColon = plug.name + ":"
      val opts = for {
	raw <- settings.pluginOptions.value
	if raw.startsWith(nameColon)
      } yield raw.substring(nameColon.length)

      if (!opts.isEmpty)
	plug.processOptions(opts, error)
    }

    for {
      opt <- settings.pluginOptions.value
      if !plugs.exists(p => opt.startsWith(p.name + ":"))
    } error("bad option: -P:" + opt)

    plugs
  }

  private var pluginsCache: Option[List[Plugin]] = None

  def plugins: List[Plugin] = {
    if (pluginsCache.isEmpty)
      pluginsCache = Some(loadPlugins)
    pluginsCache.get
  }

  /** A description of all the plugins that are loaded */
  def pluginDescriptions: String = {
    val messages =
      for (plugin <- roughPluginsList)
	yield plugin.name + " - " + plugin.description
    messages.mkString("\n")
  }

  /**
   * Extract all phases supplied by plugins and add them to the phasesSet.
   * @see phasesSet
   */
  protected def computePluginPhases() {    
    val plugPhases = plugins.flatMap(_.components)
    for (pPhase <- plugPhases) {
      phasesSet += pPhase
     }
   }

  /** Summary of the options for all loaded plugins */
  def pluginOptionsHelp: String = {
    val buf = new StringBuffer
    for (plug <- roughPluginsList; help <- plug.optionsHelp) {
      buf append ("Options for plugin " + plug.name + ":\n")
      buf append help
      buf append "\n"
    }
    buf.toString
  }
}