package sbt

	import java.net.URI

final class BuildUtil[Proj](
	val keyIndex: KeyIndex,
	val data: Settings[Scope],
	val root: URI,
	val rootProjectID: URI => String,
	val project: (URI, String) => Proj,
	val configurations: Proj => Seq[ConfigKey],
	val aggregates: Relation[ProjectRef, ProjectRef]
)
{
	def rootProject(uri: URI): Proj =
		project(uri, rootProjectID(uri))

	def resolveRef(ref: Reference): ResolvedReference =
		Scope.resolveReference(root, rootProjectID, ref)

	def projectFor(ref: ResolvedReference): Proj = ref match {
		case ProjectRef(uri, id) => project(uri, id)
		case BuildRef(uri) => rootProject(uri)
	}
	def projectRefFor(ref: ResolvedReference): ProjectRef = ref match {
		case p: ProjectRef => p
		case BuildRef(uri) => ProjectRef(uri, rootProjectID(uri))
	}
	def projectForAxis(ref: Option[ResolvedReference]): Proj = ref match {
		case Some(ref) => projectFor(ref)
		case None => rootProject(root)
	}
	def exactProject(refOpt: Option[Reference]): Option[Proj] = refOpt map resolveRef flatMap {
		case ProjectRef(uri, id) => Some(project(uri, id))
		case _ => None
	}

	val configurationsForAxis: Option[ResolvedReference] => Seq[String] = 
		refOpt => configurations(projectForAxis(refOpt)).map(_.name)
}
object BuildUtil
{
	def apply(root: URI, units: Map[URI, LoadedBuildUnit], keyIndex: KeyIndex, data: Settings[Scope]): BuildUtil[ResolvedProject] =
	{
		val getp = (build: URI, project: String) => Load.getProject(units, build, project)
		val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name))
		val aggregates = aggregationRelation(units)
		new BuildUtil(keyIndex, data, root, Load getRootProject units, getp, configs, aggregates)
	}

	def checkCycles(units: Map[URI, LoadedBuildUnit])
	{
		def getRef(pref: ProjectRef) = units(pref.build).defined(pref.project)
		def deps(proj: ResolvedProject)(base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject]  =  Dag.topologicalSort(proj)(p => base(p) map getRef)
		 // check for cycles
		for( (_, lbu) <- units; proj <- lbu.defined.values) {
			deps(proj)(_.dependencies.map(_.project))
			deps(proj)(_.delegates)
			deps(proj)(_.aggregate)
		}
	}
	def baseImports: Seq[String] = "import sbt._, Process._, Keys._" :: Nil
	def getImports(unit: BuildUnit): Seq[String] = getImports(unit.plugins.pluginNames, unit.definitions.buildNames)
	def getImports(pluginNames: Seq[String], buildNames: Seq[String]): Seq[String] = baseImports ++ importAllRoot(pluginNames ++ buildNames)
	def importAll(values: Seq[String]): Seq[String] = if(values.isEmpty) Nil else values.map( _ + "._" ).mkString("import ", ", ", "") :: Nil
	def importAllRoot(values: Seq[String]): Seq[String] = importAll(values map rootedName)
	def rootedName(s: String): String = if(s contains '.') "_root_." + s else s

	def aggregationRelation(units: Map[URI, LoadedBuildUnit]): Relation[ProjectRef, ProjectRef] =
	{
		val depPairs =
			for {
				(uri, unit) <- units.toIterable
				project <- unit.defined.values
				ref = ProjectRef(uri, project.id)
				agg <- project.aggregate
			} yield
				(ref, agg)
		Relation.empty ++ depPairs
	}
}