/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala.tools.nsc
package settings

/** A Settings abstraction boiled out of the original highly mutable Settings
 *  class with the intention of creating an ImmutableSettings which can be used
 *  interchangeably.   Except of course without the mutants.
 */

trait AbsSettings extends scala.reflect.internal.settings.AbsSettings {
  type Setting <: AbsSetting      // Fix to the concrete Setting type
  type ResultOfTryToSet           // List[String] in mutable, (Settings, List[String]) in immutable
  def errorFn: String => Unit
  protected def allSettings: scala.collection.Set[Setting]

  // settings minus internal usage settings
  def visibleSettings = allSettings filterNot (_.isInternalOnly)

  // only settings which differ from default
  def userSetSettings = visibleSettings filterNot (_.isDefault)

  // an argument list which (should) be usable to recreate the Settings
  def recreateArgs = userSetSettings.toList flatMap (_.unparse)

  // checks both name and any available abbreviations
  def lookupSetting(cmd: String): Option[Setting] = allSettings find (_ respondsTo cmd)

  // two AbsSettings objects are equal if their visible settings are equal.
  override def hashCode() = visibleSettings.size  // going for cheap
  override def equals(that: Any) = that match {
    case s: AbsSettings => this.userSetSettings == s.userSetSettings
    case _              => false
  }
  override def toString() = "Settings {\n%s}\n" format (userSetSettings map ("  " + _ + "\n")).mkString
  def toConciseString = userSetSettings.mkString("(", " ", ")")

  def checkDependencies =
    visibleSettings filterNot (_.isDefault) forall (setting => setting.dependencies forall {
      case (dep, value) =>
        (Option(dep.value) exists (_.toString == value)) || {
          errorFn("incomplete option %s (requires %s)".format(setting.name, dep.name))
          false
        }
    })

  trait AbsSetting extends Ordered[Setting] with AbsSettingValue {
    def name: String
    def helpDescription: String
    def unparse: List[String]     // A list of Strings which can recreate this setting.

    /* For tools which need to populate lists of available choices */
    def choices : List[String] = Nil

    /** In mutable Settings, these return the same object with a var set.
     *  In immutable, of course they will return a new object, which means
     *  we can't use "this.type", at least not in a non-casty manner, which
     *  is unfortunate because we lose type information without it.
     *
     *  ...but now they're this.type because of #3462.  The immutable
     *  side doesn't exist yet anyway.
     */
    def withAbbreviation(name: String): this.type
    def withHelpSyntax(help: String): this.type
    def withDeprecationMessage(msg: String): this.type

    def helpSyntax: String = name
    def deprecationMessage: Option[String] = None
    def abbreviations: List[String] = Nil
    def dependencies: List[(Setting, String)] = Nil
    def respondsTo(label: String) = (name == label) || (abbreviations contains label)

    /** If the setting should not appear in help output, etc. */
    private var internalSetting = false
    def isInternalOnly = internalSetting
    def internalOnly(): this.type = {
      internalSetting = true
      this
    }

    /** Issue error and return */
    def errorAndValue[T](msg: String, x: T): T = { errorFn(msg) ; x }

    /** After correct Setting has been selected, tryToSet is called with the
     *  remainder of the command line.  It consumes any applicable arguments and
     *  returns the unconsumed ones.
     */
    protected[nsc] def tryToSet(args: List[String]): Option[ResultOfTryToSet]

    /** Commands which can take lists of arguments in form -Xfoo:bar,baz override
     *  this method and accept them as a list.  It returns List[String] for
     *  consistency with tryToSet, and should return its incoming arguments
     *  unmodified on failure, and Nil on success.
     */
    protected[nsc] def tryToSetColon(args: List[String]): Option[ResultOfTryToSet] =
      errorAndValue("'%s' does not accept multiple arguments" format name, None)

    /** Attempt to set from a properties file style property value.
     *  Currently used by Eclipse SDT only.
     *  !!! Needs test.
     */
    def tryToSetFromPropertyValue(s: String): Unit = tryToSet(s :: Nil)

    /** These categorizations are so the help output shows -X and -P among
     *  the standard options and -Y among the advanced options.
     */
    def isAdvanced   = name match { case "-Y" => true ; case "-X" => false ; case _  => name startsWith "-X" }
    def isPrivate    = name match { case "-Y" => false ; case _  => name startsWith "-Y" }
    def isStandard   = !isAdvanced && !isPrivate
    def isForDebug   = name endsWith "-debug" // by convention, i.e. -Ytyper-debug
    def isDeprecated = deprecationMessage.isDefined

    def compare(that: Setting): Int = name compare that.name

    /** Equality tries to sidestep all the drama and define it simply and
     *  in one place: two AbsSetting objects are equal if their names and
     *  values compare equal.
     */
    override def equals(that: Any) = that match {
      case x: AbsSettings#AbsSetting  => (name == x.name) && (value == x.value)
      case _                          => false
    }
    override def hashCode() = name.hashCode + value.hashCode
    override def toString() = name + " = " + (if (value == "") "\"\"" else value)
  }

  trait InternalSetting extends AbsSetting {
    override def isInternalOnly = true
  }
}
