package sbt
package ivyint

import java.io.File
import java.net.URI
import java.util.{Collection, Collections => CS}
import CS.singleton

import org.apache.ivy.{core, plugins, util, Ivy}
import core.module.descriptor.{DependencyArtifactDescriptor, DefaultDependencyArtifactDescriptor}
import core.module.descriptor.{DefaultDependencyDescriptor => DDD, DependencyDescriptor}
import core.module.id.{ArtifactId,ModuleId, ModuleRevisionId}
import plugins.namespace.Namespace
import util.extendable.ExtendableItem

private[sbt] object MergeDescriptors
{
	def apply(a: DependencyDescriptor, b: DependencyDescriptor): DependencyDescriptor =
	{
		assert(a.isForce == b.isForce)
		assert(a.isChanging == b.isChanging)
		assert(a.isTransitive == b.isTransitive)
		assert(a.getParentRevisionId == b.getParentRevisionId)
		val amrid = a.getDependencyRevisionId
		val bmrid = b.getDependencyRevisionId
		assert(amrid == bmrid)
		val adyn = a.getDynamicConstraintDependencyRevisionId
		val bdyn = b.getDynamicConstraintDependencyRevisionId
		assert(adyn == bdyn)
		assert(a.getNamespace == b.getNamespace)

		new MergedDescriptors(a,b)
	}
}

// combines the artifacts, configurations, includes, and excludes for DependencyDescriptors `a` and `b`
// that otherwise have equal IDs
private final class MergedDescriptors(a: DependencyDescriptor, b: DependencyDescriptor) extends DependencyDescriptor
{
	def getDependencyId = a.getDependencyId
	def isForce = a.isForce
	def isChanging = a.isChanging
	def isTransitive = a.isTransitive
	def getNamespace = a.getNamespace
	def getParentRevisionId = a.getParentRevisionId
	def getDependencyRevisionId = a.getDependencyRevisionId
	def getDynamicConstraintDependencyRevisionId = a.getDynamicConstraintDependencyRevisionId

	def getModuleConfigurations = concat(a.getModuleConfigurations, b.getModuleConfigurations)

	def getDependencyConfigurations(moduleConfiguration: String, requestedConfiguration: String) = 
		concat(a.getDependencyConfigurations(moduleConfiguration, requestedConfiguration), b.getDependencyConfigurations(moduleConfiguration))

	def getDependencyConfigurations(moduleConfiguration: String) = 
		concat(a.getDependencyConfigurations(moduleConfiguration), b.getDependencyConfigurations(moduleConfiguration))

	def getDependencyConfigurations(moduleConfigurations: Array[String]) = 
		concat(a.getDependencyConfigurations(moduleConfigurations), b.getDependencyConfigurations(moduleConfigurations))

	def getAllDependencyArtifacts = concatArtifacts(a, a.getAllDependencyArtifacts, b, b.getAllDependencyArtifacts)

	def getDependencyArtifacts(moduleConfigurations: String) = 
		concatArtifacts(a, a.getDependencyArtifacts(moduleConfigurations), b, b.getDependencyArtifacts(moduleConfigurations))

	def getDependencyArtifacts(moduleConfigurations: Array[String]) = 
		concatArtifacts(a, a.getDependencyArtifacts(moduleConfigurations), b, b.getDependencyArtifacts(moduleConfigurations))

	def getAllIncludeRules = concat(a.getAllIncludeRules, b.getAllIncludeRules)

	def getIncludeRules(moduleConfigurations: String) =
		concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations))

	def getIncludeRules(moduleConfigurations: Array[String]) =
		concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations))

	private[this] def concatArtifacts(a: DependencyDescriptor, as: Array[DependencyArtifactDescriptor], b: DependencyDescriptor, bs: Array[DependencyArtifactDescriptor]) =
	{
		if(as.isEmpty)
			if(bs.isEmpty) as
			else defaultArtifact(a) +: explicitConfigurations(b, bs)
		else if(bs.isEmpty) explicitConfigurations(a, as) :+ defaultArtifact(b)
		else concat(explicitConfigurations(a, as), explicitConfigurations(b, bs))
	}
	private[this] def explicitConfigurations(base: DependencyDescriptor, arts: Array[DependencyArtifactDescriptor]): Array[DependencyArtifactDescriptor] =
		arts map { art => explicitConfigurations(base, art) }
	private[this] def explicitConfigurations(base: DependencyDescriptor, art: DependencyArtifactDescriptor): DependencyArtifactDescriptor =
	{
		val aConfs = art.getConfigurations
		if(aConfs == null || aConfs.isEmpty)
			copyWithConfigurations(art, base.getModuleConfigurations)
		else
			art
	}
	private[this] def defaultArtifact(a: DependencyDescriptor): DependencyArtifactDescriptor =
	{
		val dd = new DefaultDependencyArtifactDescriptor(a, a.getDependencyRevisionId.getName, "jar", "jar", null, null)
		addConfigurations(dd, a.getModuleConfigurations)
		dd
	}
	private[this] def copyWithConfigurations(dd: DependencyArtifactDescriptor, confs: Seq[String]): DependencyArtifactDescriptor =
	{
		val dextra = dd.getQualifiedExtraAttributes
		val newd = new DefaultDependencyArtifactDescriptor(dd.getDependencyDescriptor, dd.getName, dd.getType, dd.getExt, dd.getUrl, dextra)
		addConfigurations(newd, confs)
		newd
	}
	private[this] def addConfigurations(dd: DefaultDependencyArtifactDescriptor, confs: Seq[String]): Unit =
		confs foreach dd.addConfiguration

	private[this] def concat[T: ClassManifest](a: Array[T], b: Array[T]): Array[T] = (a ++ b).distinct.toArray

	def getAllExcludeRules = concat(a.getAllExcludeRules, b.getAllExcludeRules)

	def getExcludeRules(moduleConfigurations: String) = concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations))

	def getExcludeRules(moduleConfigurations: Array[String]) = concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations))

	def doesExclude(moduleConfigurations: Array[String], artifactId: ArtifactId) = a.doesExclude(moduleConfigurations, artifactId) || b.doesExclude(moduleConfigurations, artifactId)

	def canExclude = a.canExclude || b.canExclude

	def asSystem = this

	def clone(revision: ModuleRevisionId) = new MergedDescriptors(a.clone(revision), b.clone(revision))

	def getAttribute(name: String): String = a.getAttribute(name)
	def getAttributes = a.getAttributes
	def getExtraAttribute(name: String) = a.getExtraAttribute(name)
	def getExtraAttributes = a.getExtraAttributes
	def getQualifiedExtraAttributes = a.getQualifiedExtraAttributes
	def getSourceModule = a.getSourceModule
}