package sbt

	import java.io.File
	import java.net.URI
	import Project._
	import Def.{ScopedKey, Setting}
	import Scope.{GlobalScope,ThisScope}
	import Types.{const, idFun, Id}
	import complete._
	import DefaultParsers._

/** The resulting `session` and verbose and quiet summaries of the result of a set operation.
* The verbose summary will typically use more vertical space and show full details,
* while the quiet summary will be a couple of lines and truncate information. */
private[sbt] class SetResult(val session: SessionSettings, val verboseSummary: String, val quietSummary: String)

/** Defines methods for implementing the `set` command.*/
private[sbt] object SettingCompletions
{
	/** Implementation of the `set every` command.  Each setting in the provided `settings` sequence will be applied in all scopes, 
	* overriding all previous definitions of the underlying AttributeKey.
	* The settings injected by this method cannot be later persisted by the `session save` command. */
	def setAll(extracted: Extracted, settings: Seq[Setting[_]]): SetResult =
	{
			import extracted._
		val r = relation(extracted.structure, true)
		val allDefs = r._1s.toSeq
		val projectScope = Load.projectScope(currentRef)
		def resolve(s: Setting[_]): Seq[Setting[_]] = Load.transformSettings(projectScope, currentRef.build, rootProject, s :: Nil)
		def rescope[T](setting: Setting[T]): Seq[Setting[_]] =
		{
			val akey = setting.key.key
			val global = ScopedKey(Global, akey)
			val globalSetting = resolve( Def.setting(global, setting.init, setting.pos) )
			globalSetting ++ allDefs.flatMap { d =>
				if(d.key == akey)
					Seq( SettingKey(akey) in d.scope <<= global)
				else
					Nil
			}
		}
		val redefined = settings.flatMap(x => rescope(x))
		val session = extracted.session.appendRaw(redefined)
		setResult(session, r, redefined)
	}

	/** Implementation of the `set` command that will reload the current project with `settings` appended to the current settings. */
	def setThis(s: State, extracted: Extracted, settings: Seq[Def.Setting[_]], arg: String): SetResult =
	{
		import extracted._
		val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
		val r = relation(extracted.structure, true)
		val newSession = session.appendSettings( append map (a => (a, arg.split('\n').toList)))
		setResult(newSession, r, append)
	}

	private[this] def setResult(session: SessionSettings, r: Relation[ScopedKey[_], ScopedKey[_]], redefined: Seq[Setting[_]])(implicit show: Show[ScopedKey[_]]): SetResult =
	{
		val redefinedKeys = redefined.map(_.key).toSet
		val affectedKeys = redefinedKeys.flatMap(r.reverse)
		def summary(verbose: Boolean): String = setSummary(redefinedKeys, affectedKeys, verbose)
		new SetResult(session, summary(true), summary(false))
	}

	private[this] def setSummary(redefined: Set[ScopedKey[_]], affected: Set[ScopedKey[_]], verbose: Boolean)(implicit display: Show[ScopedKey[_]]): String =
	{
		val QuietLimit = 3
		def strings(in: Set[ScopedKey[_]]): Seq[String] = in.toSeq.map(sk => display(sk)).sorted
		def lines(in: Seq[String]): (String, Boolean) =
			if(in.isEmpty)
				("no settings or tasks.", false)
			else if(verbose)
				(in.mkString("\n\t", "\n\t", "\n"), false)
			else
				quietList(in)
		def quietList(in: Seq[String]): (String, Boolean) =
		{
			val (first, last) = in.splitAt(QuietLimit)
			if(last.isEmpty)
				(first.mkString(", "), false)
			else
			{
				val s = first.take(QuietLimit - 1).mkString("", ", ", " and " + last.size + " others.")
				(s, true)
			}
		}
		if(redefined.isEmpty)
			"No settings or tasks were redefined."
		else
		{
			val (redef, trimR) = lines(strings(redefined))
			val (used, trimU) = lines(strings(affected))
			val details = if(trimR || trimU) "\n\tRun `last` for details." else ""
			val valuesString = if(redefined.size == 1) "value" else "values"
			"Defining %s\nThe new %s will be used by %s%s".format(redef, valuesString, used, details)
		}
	}

	/** Parser that provides tab completion for the main argument to the `set` command.  
	* `settings` are the evaluated settings for the build, `rawKeyMap` maps the hypenated key identifier to the key object,
	* and `context` is the current project.
	* The tab completion will try to present the most relevant information first, with additional descriptions or keys available
	* when there are fewer choices or tab is pressed multiple times.
	* The last part of the completion will generate a template for the value or function literal that will initialize the setting or task. */
	def settingParser(settings: Settings[Scope], rawKeyMap: Map[String, AttributeKey[_]], context: ResolvedProject): Parser[String] =
	{
		val cutoff = KeyRanks.MainCutoff
		val keyMap: Map[String, AttributeKey[_]] = rawKeyMap.map { case (k,v) => (keyScalaID(k), v) } toMap ;
		def inputScopedKey(pred: AttributeKey[_] => Boolean): Parser[ScopedKey[_]] =
			scopedKeyParser(keyMap.filter { case (_, k) => pred(k) }, settings, context)
		val full = for {
			defineKey <- scopedKeyParser(keyMap, settings, context)
			a <- assign(defineKey)
			deps <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
		} yield
			() // parser is currently only for completion and the parsed data structures are not used

		matched( full ) | any.+.string
	}

	/** Parser for a Scope+AttributeKey (ScopedKey). */
	def scopedKeyParser(keyMap: Map[String, AttributeKey[_]], settings: Settings[Scope], context: ResolvedProject): Parser[ScopedKey[_]] =
	{
		val cutoff = KeyRanks.MainCutoff
		val keyCompletions = fixedCompletions { (seen, level) => completeKey(seen, keyMap, level, cutoff, 10).toSet }
		val keyID: Parser[AttributeKey[_]] = scalaID(keyMap, "key")
		val keyParser = token(keyID, keyCompletions)
		for(key <- keyParser; scope <- scopeParser(key, settings, context) ) yield
			ScopedKey(scope, key)
	}

	/** Parser for the `in` method name that slightly augments the naive completion to give a hint of the purpose of `in`.*/
	val inParser = tokenDisplay(Space ~> InMethod, "%s <scope>".format(InMethod))

	/** Parser for the initialization expression for the assignment method `assign` on the key `sk`.
	* `scopedKeyP` is used to parse and complete the input keys for an initialization that depends on other keys. */
	def valueParser(sk: ScopedKey[_], assign: Assign.Value, scopedKeyP: Parser[ScopedKey[_]]): Parser[Seq[ScopedKey[_]]] =
	{
		val fullTypeString = keyTypeString(sk.key)
		val typeString = if(assignNoAppend(assign)) fullTypeString else "..."
		if( assign == Assign.Update )
		{
			val function = "{(prev: " + typeString + ") => /*" + typeString + "*/ }"
			token(OptSpace ~ function) ^^^ Nil
		}
		else
		{
			val value = "/* value of type " + typeString + " */"
			token(Space ~ value) ^^^ Nil
		}
	}

	/** For a setting definition `definingKey <<= (..., in, ...) { ... }`, 
	* `keyFilter(definingKey)(in)` returns true when `in` is an allowed input for `definingKey` based on whether they are settings or not.
	* For example, if `definingKey` is for a setting, `in` may only be a setting itself.  */
	def keyFilter(definingKey: AttributeKey[_]): AttributeKey[_] => Boolean =
		if(isSetting(definingKey)) isSetting _ else isTaskOrSetting _

	/** Parser for a Scope for a `key` given the current project `context` and evaluated `settings`.
	* The completions are restricted to be more useful.  Currently, this parser will suggest 
	* only known axis values for configurations and tasks and only in that order.*/
	def scopeParser(key: AttributeKey[_], settings: Settings[Scope], context: ResolvedProject): Parser[Scope] = {
		val data = settings.data
		val allScopes = data.keys.toSeq
		val definedScopes = data.toSeq flatMap { case (scope, attrs) => if(attrs contains key) scope :: Nil else Nil }
		scope(key, allScopes, definedScopes, context)
	}

	private[this] def scope(key: AttributeKey[_], allScopes: Seq[Scope], definedScopes: Seq[Scope], context: ResolvedProject): Parser[Scope] =
	{
		def axisParser[T](axis: Scope => ScopeAxis[T], name: T => String, description: T => Option[String], label: String): Parser[ScopeAxis[T]] =
		{
			def getChoice(s: Scope): Seq[(String,T)] = axis(s) match {
				case Select(t) => (name(t), t) :: Nil
				case _ => Nil
			}
			def getChoices(scopes: Seq[Scope]): Map[String,T] = scopes.flatMap(getChoice).toMap
			val definedChoices: Set[String] = definedScopes.flatMap(s => axis(s).toOption.map(name)).toSet
			val fullChoices: Map[String,T] = getChoices(allScopes.toSeq)
			val completions = fixedCompletions { (seen, level) => completeScope(seen, level, definedChoices, fullChoices)(description).toSet }
			Act.optionalAxis(inParser ~> token(Space) ~> token(scalaID(fullChoices, label), completions), This)
		}
		val configurations: Map[String, Configuration] = context.configurations.map(c => (configScalaID(c.name), c)).toMap
		val configParser = axisParser[ConfigKey](_.config, c => configScalaID(c.name), ck => configurations.get(ck.name).map(_.description), "configuration")
		val taskParser = axisParser[AttributeKey[_]](_.task, k => keyScalaID(k.label), _.description, "task")
		val nonGlobal = (configParser ~ taskParser) map { case (c,t) => Scope(This, c, t, Global) }
		val global = inParser ~> token( (Space ~ GlobalID) ^^^ GlobalScope)
		global | nonGlobal
	}

	/** Parser for the assignment method (such as `:=`) for defining `key`. */
	def assign(key: ScopedKey[_]): Parser[Assign.Value] =
	{
		val completions = fixedCompletions { (seen, level) => completeAssign(seen, level, key).toSet }
		val identifier = Act.filterStrings(Op, Assign.values.map(_.toString), "assignment method") map Assign.withName
		token(Space) ~> token(optionallyQuoted(identifier), completions)
	}

	private[this] def fixedCompletions(f: (String, Int) => Set[Completion]): TokenCompletions =
		TokenCompletions.fixed( (s,l) => Completions( f(s,l) ) )

	private[this] def scalaID[T](keyMap: Map[String, T], label: String): Parser[T] =
	{
		val identifier = Act.filterStrings(ScalaID, keyMap.keySet, label) map keyMap
		optionallyQuoted(identifier)
	}

	/** Produce a new parser that allows the input accepted by `p` to be quoted in backticks. */
	def optionallyQuoted[T](p: Parser[T]): Parser[T] =
		(Backtick.? ~ p) flatMap { case (quote, id) => if(quote.isDefined) Backtick.? ^^^ id else success(id) }
	
	/** Completions for an assignment method for `key` given the tab completion `level` and existing partial string `seen`.
	* This will filter possible assignment methods based on the underlying type of `key`, so that only `<<=` is shown for input tasks, for example. */
	def completeAssign(seen: String, level: Int, key: ScopedKey[_]): Seq[Completion] =
	{
		val allowed: Iterable[Assign.Value] =
			if(appendable(key.key)) Assign.values
			else assignNoAppend
		val applicable = allowed.toSeq.flatMap { a =>
			val s = a.toString
			if(s startsWith seen) (s, a) :: Nil else Nil
		}
		completeDescribed(seen, true, applicable)(assignDescription)
	}

	def completeKey(seen: String, keys: Map[String, AttributeKey[_]], level: Int, prominentCutoff: Int, detailLimit: Int): Seq[Completion] =
		completeSelectDescribed(seen, level, keys, detailLimit)(_.description) { case (k,v) => v.rank <= prominentCutoff }

	def completeScope[T](seen: String, level: Int, definedChoices: Set[String], allChoices: Map[String,T])(description: T => Option[String]): Seq[Completion] =
		completeSelectDescribed(seen, level, allChoices, 10)(description) { case (k,v) => definedChoices(k) }

	def completeSelectDescribed[T](seen: String, level: Int, all: Map[String,T], detailLimit: Int)(description: T => Option[String])(prominent: (String, T) => Boolean): Seq[Completion] =
	{
		val applicable = all.toSeq.filter { case (k,v) => k startsWith seen }
		val prominentOnly = applicable filter { case (k,v) => prominent(k,v) }

		val showAll = (level >= 3) || (level == 2 && prominentOnly.size <= detailLimit) || prominentOnly.isEmpty
		val showKeys = if(showAll) applicable else prominentOnly
		val showDescriptions = (level >= 2) || (showKeys.size <= detailLimit)
		completeDescribed(seen, showDescriptions, showKeys)(s => description(s).toList.mkString)
	}	
	def completeDescribed[T](seen: String, showDescriptions: Boolean, in: Seq[(String,T)])(description: T => String): Seq[Completion] =
	{
		def appendString(id: String): String = id.stripPrefix(seen) + " "
		if(in.isEmpty)
			Nil
		else if(showDescriptions)
		{
			val withDescriptions = in map { case (id, key) => (id, description(key)) }
			val padded = CommandUtil.aligned("", "   ", withDescriptions)
			(padded, in).zipped.map { case (line, (id, key)) =>
				Completion.tokenDisplay(append = appendString(id), display = line + "\n")
			}
		}
		else
			in map { case (id, key) =>
				Completion.tokenDisplay(display= id, append = appendString(id))
			}
	}

	/** Transforms the hypenated key label `k` into camel-case and quotes it with backticks if it is a Scala keyword.
	* This is intended to be an estimate of the Scala identifier that may be used to reference the keyword in the default sbt context. */
	def keyScalaID(k: String): String = Util.quoteIfKeyword(Util.hyphenToCamel(k))

	/** Transforms the configuration name `c` so that the first letter is capitalized and the name is quoted with backticks if it is a Scala keyword.
	* This is intended to be an estimate of the Scala identifier that may be used to reference the keyword in the default sbt context. */
	def configScalaID(c: String): String = Util.quoteIfKeyword(c.capitalize)

	/** Applies a function on the underlying manifest for T for `key` depending if it is for a `Setting[T]`, `Task[T]`, or `InputTask[T]`.*/
	def keyType[S](key: AttributeKey[_])(onSetting: Manifest[_] => S, onTask: Manifest[_] => S, onInput: Manifest[_] => S)(implicit tm: Manifest[Task[_]], im: Manifest[InputTask[_]]): S =
	{
		def argTpe = key.manifest.typeArguments.head
		val e = key.manifest.erasure
		if(e == tm.erasure) onTask(argTpe)
		else if(e == im.erasure) onInput(argTpe)
		else onSetting(key.manifest)
	}

	/** For a Task[T], InputTask[T], or Setting[T], this returns the manifest for T. */
	def keyUnderlyingType(key: AttributeKey[_]): Manifest[_] = keyType(key)(idFun, idFun, idFun)

	/** Returns a string representation of the underlying type T for a `key` representing a `Setting[T]`, `Task[T]`, or `InputTask[T]`.
	* This string representation is currently a cleaned up toString of the underlying Manifest. */
	def keyTypeString[T](key: AttributeKey[_]): String =
	{
		val mfToString = (mf: Manifest[_]) => complete.TypeString.cleanup(mf.toString)
		keyType(key)(mfToString, mfToString ,mfToString)
	}

	/** True if the `key` represents an input task, false if it represents a task or setting. */
	def isInputTask(key: AttributeKey[_]): Boolean = keyType(key)(const(false), const(false), const(true))

	/** True if the `key` represents a setting, false if it represents a task or an input task.*/
	def isSetting(key: AttributeKey[_]): Boolean = keyType(key)(const(true), const(false), const(false))

	/** True if the `key` represents a setting or task, false if it is for an input task. */
	def isTaskOrSetting(key: AttributeKey[_]): Boolean = keyType(key)(const(true), const(true), const(false))

	/** True if the `key` represents a setting or task that may be appended using an assignment method such as `+=`. */
	def appendable(key: AttributeKey[_]): Boolean =
	{
		val underlying = keyUnderlyingType(key).erasure
		appendableClasses.exists(_ isAssignableFrom underlying)
	}

	/** The simple name of the global scope axis, which can be used to reference it in the default setting context. */
	final val GlobalID = Global.getClass.getSimpleName.stripSuffix("$")

	/** Character used to quote a Scala identifier that would otherwise be interpreted as a keyword.*/
	final val Backtick = '`'

	/** Name of the method that modifies the scope of a key. */
	final val InMethod = "in"

	/** Assignment methods that may be called on a setting or task. */
	object Assign extends Enumeration {
		val AppendValue = Value("+=")
		val AppendValues = Value("++=")
		val Define = Value(":=")
		val Update = Value("~=")
	}
	import Assign._
	/** Returns the description associated with the provided assignment method. */
	def assignDescription(a: Assign.Value): String = a match {
		case AppendValue => "append value"
		case AppendValues => "append values"
		case Define => "define value, overwriting any existing value"
		case Update => "transform existing value"
	}
	/** The assignment methods except for the ones that append. */
	val assignNoAppend: Set[Assign.Value] = Set(Define, Update)

	/** Class values to approximate which types can be appended*/
	val appendableClasses = Seq(
		classOf[Seq[_]],
		classOf[Map[_,_]],
		classOf[Set[_]],
		classOf[Int],
		classOf[Double],
		classOf[Long],
		classOf[String]
	)
}