/* sbt -- Simple Build Tool
 * Copyright 2009, 2010, 2011 Mark Harrah
 */
package xsbt.api

import xsbti.api._

import Function.tupled
import scala.collection.{immutable, mutable}

class NameChanges(val newTypes: Set[String], val removedTypes: Set[String], val newTerms: Set[String], val removedTerms: Set[String])
{
	override def toString =
		(("New types", newTypes) :: ("Removed types", removedTypes) :: ("New terms", newTerms) :: ("Removed terms", removedTerms) :: Nil).map {
			case (label,set) => label + ":\n\t" + set.mkString("\n\t")
		}.mkString("Name changes:\n  ", "\n  ", "\n")

}

object TopLevel
{
	def nameChanges(a: Iterable[Source], b: Iterable[Source]): NameChanges = {
		val api = (_: Source).api
		apiNameChanges(a map api, b map api)
	}
	/** Identifies removed and new top-level definitions by name. */
	def apiNameChanges(a: Iterable[SourceAPI], b: Iterable[SourceAPI]): NameChanges =
	{
		def changes(s: Set[String], t: Set[String]) = (s -- t, t -- s)

		val (avalues, atypes) = definitions(a)
		val (bvalues, btypes) = definitions(b)
		
		val (newTypes, removedTypes) = changes(names(atypes), names(btypes))
		val (newTerms, removedTerms) = changes(names(avalues), names(bvalues))

		new NameChanges(newTypes, removedTypes, newTerms, removedTerms)
	}
	def definitions(i: Iterable[SourceAPI]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions ))
	def names(s: Iterable[Definition]): Set[String] = Set() ++ s.map(_.name)
}

/** Checks the API of two source files for equality.*/
object SameAPI
{
	def apply(a: Source, b: Source): Boolean =
		a.apiHash == b.apiHash && (a.hash.length > 0 && b.hash.length > 0) && apply(a.api, b.api)

	def apply(a: SourceAPI, b: SourceAPI): Boolean =
	{
		val start = System.currentTimeMillis
		
		/*println("\n=========== API #1 ================")
		import DefaultShowAPI._
		println(ShowAPI.show(a))
		println("\n=========== API #2 ================")
		println(ShowAPI.show(b))*/

		val result = (new SameAPI(false, true)).check(a,b)
		val end = System.currentTimeMillis
		//println(" API comparison took: " + (end - start) / 1000.0 + " s")
		result
	}

	def separateDefinitions(s: Seq[Definition]): (Seq[Definition], Seq[Definition]) =
		s.partition(isValueDefinition)
	def isValueDefinition(d: Definition): Boolean =
		d match
		{
			case _: FieldLike | _: Def=> true
			case c: ClassLike => isValue(c.definitionType)
			case _ => false
		}
	def isValue(d: DefinitionType): Boolean =
		d == DefinitionType.Module || d == DefinitionType.PackageModule
	/** Puts the given definitions in a map according to their names.*/
	def byName(s: Seq[Definition]): Map[String, List[Definition]] =
	{
		var map = Map[String, List[Definition]]()
		for(d <- s; name = d.name)
			map = map.updated(name, d :: map.getOrElse(name, Nil) )
		map
	}

	/** Removes definitions that should not be considered for API equality.
	* All top-level definitions are always considered: 'private' only means package-private.
	* Other definitions are considered if they are not qualified with 'private[this]' or 'private'.*/
	def filterDefinitions(d: Seq[Definition], topLevel: Boolean, includePrivate: Boolean) = if(topLevel || includePrivate) d else d.filter(isNonPrivate)
	def isNonPrivate(d: Definition): Boolean = isNonPrivate(d.access)
	/** Returns false if the `access` is `Private` and qualified, true otherwise.*/
	def isNonPrivate(access: Access): Boolean =
		access match
		{
			case p: Private if !p.qualifier.isInstanceOf[IdQualifier] => false
			case _ => true
		}
}
/** Used to implement API equality.
*
* If `includePrivate` is true, `private` and `private[this]` members are included in the comparison.  Otherwise, those members are excluded.
*/
class SameAPI(includePrivate: Boolean, includeParamNames: Boolean)
{
	import SameAPI._

	private val pending = new mutable.HashSet[AnyRef]
	private[this] val debugEnabled = java.lang.Boolean.getBoolean("xsbt.api.debug")
	def debug(flag: Boolean, msg: => String): Boolean =
	{
		if(debugEnabled && !flag) println(msg)
		flag
	}

	/** Returns true if source `a` has the same API as source `b`.*/
	def check(a: SourceAPI, b: SourceAPI): Boolean =
	{
		samePackages(a, b) &&
		debug(sameDefinitions(a, b), "Definitions differed")
	}

	def samePackages(a: SourceAPI, b: SourceAPI): Boolean =
		sameStrings(packages(a), packages(b))
	def packages(s: SourceAPI): Set[String] =
		Set() ++ s.packages.map(_.name)

	def sameDefinitions(a: SourceAPI, b: SourceAPI): Boolean =
		sameDefinitions(a.definitions, b.definitions, true)
	def sameDefinitions(a: Seq[Definition], b: Seq[Definition], topLevel: Boolean): Boolean =
	{
		val (avalues, atypes) = separateDefinitions(filterDefinitions(a, topLevel, includePrivate))
		val (bvalues, btypes) = separateDefinitions(filterDefinitions(b, topLevel, includePrivate))
		debug(sameDefinitions(byName(avalues), byName(bvalues)), "Value definitions differed") &&
		debug(sameDefinitions(byName(atypes), byName(btypes)), "Type definitions differed")
	}
	def sameDefinitions(a: scala.collection.Map[String, List[Definition]], b: scala.collection.Map[String, List[Definition]]): Boolean =
		debug(sameStrings(a.keySet, b.keySet), "\tDefinition strings differed (a: " + (a.keySet -- b.keySet) + ", b: " + (b.keySet -- a.keySet) + ")") &&
		zippedEntries(a,b).forall(tupled(sameNamedDefinitions))

	/** Checks that the definitions in `a` are the same as those in `b`, ignoring order.
	* Each list is assumed to have already been checked to have the same names (by `sameDefinitions`, for example).*/
	def sameNamedDefinitions(a: List[Definition], b: List[Definition]): Boolean =
	{
		def sameDefs(a: List[Definition], b: List[Definition]): Boolean =
		{
			a match
			{
				case adef :: atail =>
					def sameDef(seen: List[Definition], remaining: List[Definition]): Boolean =
						remaining match
						{
							case Nil => debug(false, "Definition different in new API: \n" + adef.name )
							case bdef :: btail =>
								val eq = sameDefinitionContent(adef, bdef)
								if(eq) sameDefs(atail, seen ::: btail) else sameDef(bdef :: seen, btail)
						}
					sameDef(Nil, b)
				case Nil => true
			}
		}
		debug((a.length == b.length), "\t\tLength differed for " + a.headOption.map(_.name).getOrElse("empty")) && sameDefs(a, b)
	}

	/** Checks that the two definitions are the same, other than their name.*/
	def sameDefinitionContent(a: Definition, b: Definition): Boolean =
		samePending(a,b)(sameDefinitionContentDirect)
	def sameDefinitionContentDirect(a: Definition, b: Definition): Boolean =
	{
		//a.name == b.name &&
		debug(sameAccess(a.access, b.access), "Access differed") &&
		debug(sameModifiers(a.modifiers, b.modifiers), "Modifiers differed") &&
		debug(sameAnnotations(a.annotations, b.annotations), "Annotations differed") &&
		debug(sameDefinitionSpecificAPI(a, b), "Definition-specific differed")
	}

	def sameAccess(a: Access, b: Access): Boolean =
		(a, b) match
		{
			case (_: Public, _: Public) => true
			case (qa: Protected, qb: Protected) => sameQualifier(qa, qb)
			case (qa: Private, qb: Private) => sameQualifier(qa, qb)
			case _ => debug(false, "Different access categories")
		}
	def sameQualifier(a: Qualified, b: Qualified): Boolean =
		sameQualifier(a.qualifier, b.qualifier)
	def sameQualifier(a: Qualifier, b: Qualifier): Boolean =
		(a, b) match
		{
			case (_: Unqualified, _: Unqualified) => true
			case (_: ThisQualifier, _: ThisQualifier) => true
			case (ia: IdQualifier, ib: IdQualifier) => debug(ia.value == ib.value, "Different qualifiers")
			case _ => debug(false, "Different qualifier categories: " + a.getClass.getName +  " -- " +b.getClass.getName)
		}

	def sameModifiers(a: Modifiers, b: Modifiers): Boolean =
		bitSet(a) == bitSet(b)

	def bitSet(m: Modifiers): immutable.BitSet =
	{
		import m._
		val bs = new mutable.BitSet
		setIf(bs, isAbstract, 0)
		setIf(bs, isOverride, 1)
		setIf(bs, isFinal, 2)
		setIf(bs, isSealed, 3)
		setIf(bs, isImplicit, 4)
		setIf(bs, isLazy, 5)
		setIf(bs, isMacro, 6)
		bs.toImmutable
	}
	def setIf(bs: mutable.BitSet, flag: Boolean, i: Int): Unit =
		if(flag) bs += i

	def sameAnnotations(a: Seq[Annotation], b: Seq[Annotation]): Boolean =
		sameSeq(a, b)(sameAnnotation)
	def sameAnnotation(a: Annotation, b: Annotation): Boolean =
		debug(sameType(a.base, b.base), "Annotation base type differed") &&
		debug(sameAnnotationArguments(a.arguments, b.arguments), "Annotation arguments differed (" + a + ") and (" + b + ")")
	def sameAnnotationArguments(a: Seq[AnnotationArgument], b: Seq[AnnotationArgument]): Boolean =
		argumentMap(a) == argumentMap(b)
	def argumentMap(a: Seq[AnnotationArgument]): Map[String,String] =
		Map() ++ a.map(arg => (arg.name, arg.value))
		
	def sameDefinitionSpecificAPI(a: Definition, b: Definition): Boolean =
		(a, b) match
		{
			case (fa: FieldLike, fb: FieldLike) => sameFieldSpecificAPI(fa, fb)
			case (pa: ParameterizedDefinition, pb: ParameterizedDefinition) => sameParameterizedDefinition(pa, pb)
			case _ => false
		}

	def sameParameterizedDefinition(a: ParameterizedDefinition, b: ParameterizedDefinition): Boolean =
		debug(sameTypeParameters(a.typeParameters, b.typeParameters), "Different type parameters for " + a.name) &&
		sameParameterizedSpecificAPI(a, b)

	def sameParameterizedSpecificAPI(a: ParameterizedDefinition, b: ParameterizedDefinition): Boolean =
		(a, b) match
		{
			case (da: Def, db: Def) => sameDefSpecificAPI(da, db)
			case (ca: ClassLike, cb: ClassLike) => sameClassLikeSpecificAPI(ca, cb)
			case (ta: TypeAlias, tb: TypeAlias) => sameAliasSpecificAPI(ta, tb)
			case (ta: TypeDeclaration, tb: TypeDeclaration) => sameDeclarationSpecificAPI(ta, tb)
			case _ => false
		}

	def sameDefSpecificAPI(a: Def, b: Def): Boolean =
		debug(sameValueParameters(a.valueParameters, b.valueParameters), "Different def value parameters for " + a.name) &&
		debug(sameType(a.returnType, b.returnType), "Different def return type for " + a.name)
	def sameAliasSpecificAPI(a: TypeAlias, b: TypeAlias): Boolean =
		debug(sameType(a.tpe, b.tpe), "Different alias type for " + a.name)
	def sameDeclarationSpecificAPI(a: TypeDeclaration, b: TypeDeclaration): Boolean =
		debug(sameType(a.lowerBound, b.lowerBound), "Different lower bound for declaration " + a.name) &&
		debug(sameType(a.upperBound, b.upperBound), "Different upper bound for declaration " + a.name)
	def sameFieldSpecificAPI(a: FieldLike, b: FieldLike): Boolean =
		debug(sameFieldCategory(a, b), "Different field categories (" + a.name + "=" + a.getClass.getName +  " -- " +a.name + "=" + a.getClass.getName + ")")&&
		debug(sameType(a.tpe, b.tpe), "Different field type for " + a.name)

	def sameFieldCategory(a: FieldLike, b: FieldLike): Boolean =
		(a,b) match
		{
			case (_: Val, _: Val) => true
			case (_: Var, _: Var) => true
			case _=> false
		}

	def sameClassLikeSpecificAPI(a: ClassLike, b: ClassLike): Boolean =
		sameDefinitionType(a.definitionType, b.definitionType) &&
		sameType(a.selfType, b.selfType) &&
		sameStructure(a.structure, b.structure)

	def sameValueParameters(a: Seq[ParameterList], b: Seq[ParameterList]): Boolean =
		sameSeq(a, b)(sameParameterList)

	def sameParameterList(a: ParameterList, b: ParameterList): Boolean =
		(a.isImplicit == b.isImplicit) &&
		sameParameters(a.parameters, b.parameters)
	def sameParameters(a: Seq[MethodParameter], b: Seq[MethodParameter]): Boolean =
		sameSeq(a, b)(sameMethodParameter)
	def sameMethodParameter(a: MethodParameter, b: MethodParameter): Boolean =
		(!includeParamNames || a.name == b.name) &&
		sameType(a.tpe, b.tpe) &&
		(a.hasDefault == b.hasDefault) &&
		sameParameterModifier(a.modifier, b.modifier)
	def sameParameterModifier(a: ParameterModifier, b: ParameterModifier) =
		a == b
	def sameDefinitionType(a: DefinitionType, b: DefinitionType): Boolean =
		a == b
	def sameVariance(a: Variance, b: Variance): Boolean =
		a == b

	def sameTypeParameters(a: Seq[TypeParameter], b: Seq[TypeParameter]): Boolean =
		debug(sameSeq(a, b)(sameTypeParameter), "Different type parameters")
	def sameTypeParameter(a: TypeParameter, b: TypeParameter): Boolean =
	{
		sameTypeParameters(a.typeParameters, b.typeParameters) &&
		debug(sameAnnotations(a.annotations, b.annotations), "Different type parameter annotations") &&
		debug(sameVariance(a.variance, b.variance), "Different variance") &&
		debug(sameType(a.lowerBound, b.lowerBound), "Different lower bound") &&
		debug(sameType(a.upperBound, b.upperBound), "Different upper bound") &&
		sameTags(a.id, b.id)
	}
	def sameTags(a: String, b: String): Boolean =
		debug(a == b, "Different type parameter bindings: " + a + ", " + b)

	def sameType(a: Type, b: Type): Boolean =
		samePending(a,b)(sameTypeDirect)
	def sameTypeDirect(a: Type, b: Type): Boolean =
		(a, b) match
		{
			case (sa: SimpleType, sb: SimpleType) => debug(sameSimpleTypeDirect(sa, sb), "Different simple types: " + DefaultShowAPI(sa) + " and " + DefaultShowAPI(sb))
			case (ca: Constant, cb: Constant) => debug(sameConstantType(ca, cb), "Different constant types: " + DefaultShowAPI(ca) + " and " + DefaultShowAPI(cb))
			case (aa: Annotated, ab: Annotated) => debug(sameAnnotatedType(aa, ab), "Different annotated types")
			case (sa: Structure, sb: Structure) => debug(sameStructureDirect(sa, sb), "Different structure type")
			case (ea: Existential, eb: Existential) => debug(sameExistentialType(ea, eb), "Different existential type")
			case (pa: Polymorphic, pb: Polymorphic) => debug(samePolymorphicType(pa, pb), "Different polymorphic type")
			case _ => differentCategory("type", a, b)
		}

	def sameConstantType(ca: Constant, cb: Constant): Boolean =
		sameType(ca.baseType, cb.baseType) &&
		ca.value == cb.value
	def sameExistentialType(a: Existential, b: Existential): Boolean =
		sameTypeParameters(a.clause, b.clause) &&
		sameType(a.baseType, b.baseType)
	def samePolymorphicType(a: Polymorphic, b: Polymorphic): Boolean =
		sameTypeParameters(a.parameters, b.parameters) &&
		sameType(a.baseType, b.baseType)
	def sameAnnotatedType(a: Annotated, b: Annotated): Boolean =
		sameSimpleType(a.baseType, b.baseType) &&
		sameAnnotations(a.annotations, b.annotations)
	def sameStructure(a: Structure, b: Structure): Boolean =
		samePending(a,b)(sameStructureDirect)

	private[this] def samePending[T](a: T, b: T)(f: (T,T) => Boolean): Boolean =
		if(pending add ((a,b)) ) f(a,b) else true

	def sameStructureDirect(a: Structure, b: Structure): Boolean =
	{
		sameSeq(a.parents, b.parents)(sameType) &&
		sameMembers(a.declared, b.declared) &&
		sameMembers(a.inherited, b.inherited)
	}

	def sameMembers(a: Seq[Definition], b: Seq[Definition]): Boolean =
		sameDefinitions(a, b, false)

	def sameSimpleType(a: SimpleType, b: SimpleType): Boolean =
		samePending(a,b)(sameSimpleTypeDirect)
	def sameSimpleTypeDirect(a: SimpleType, b: SimpleType): Boolean =
		(a, b) match
		{
			case (pa: Projection, pb: Projection) => debug(sameProjection(pa, pb), "Different projection")
			case (pa: ParameterRef, pb: ParameterRef) => debug(sameParameterRef(pa, pb), "Different parameter ref")
			case (sa: Singleton, sb: Singleton) => debug(sameSingleton(sa, sb), "Different singleton")
			case (_: EmptyType, _: EmptyType) => true
			case (pa: Parameterized, pb: Parameterized) => debug(sameParameterized(pa, pb), "Different parameterized")
			case _ => differentCategory("simple type", a, b)
		}
	def differentCategory(label: String, a: AnyRef, b: AnyRef): Boolean =
		debug(false, "Different category of " + label + " (" + a.getClass.getName + " and " + b.getClass.getName + ") for (" + a + " and " + b + ")")

	def sameParameterized(a: Parameterized, b: Parameterized): Boolean =
		sameSimpleType(a.baseType, b.baseType) &&
		sameSeq(a.typeArguments, b.typeArguments)(sameType)
	def sameParameterRef(a: ParameterRef, b: ParameterRef): Boolean = sameTags(a.id, b.id)
	def sameSingleton(a: Singleton, b: Singleton): Boolean =
		samePath(a.path, b.path)
	def sameProjection(a: Projection, b: Projection): Boolean =
		sameSimpleType(a.prefix, b.prefix) &&
		(a.id == b.id)

	def samePath(a: Path, b: Path): Boolean =
		samePathComponents(a.components, b.components)
	def samePathComponents(a: Seq[PathComponent], b: Seq[PathComponent]): Boolean =
		sameSeq(a, b)(samePathComponent)
	def samePathComponent(a: PathComponent, b: PathComponent): Boolean =
		(a, b) match
		{
			case (_: This, _: This) => true
			case (sa: Super, sb: Super) => samePathSuper(sa, sb)
			case (ia: Id, ib: Id) => samePathId(ia, ib)
			case _ => false
		}
	def samePathSuper(a: Super, b: Super): Boolean =
		samePath(a.qualifier, b.qualifier)
	def samePathId(a: Id, b: Id): Boolean =
		a.id == b.id

	// precondition: a.keySet == b.keySet
	protected def zippedEntries[A,B](a: scala.collection.Map[A,B], b: scala.collection.Map[A,B]): Iterable[(B,B)] =
		for( (key, avalue) <- a) yield (avalue, b(key))

	def sameStrings(a: scala.collection.Set[String], b: scala.collection.Set[String]): Boolean =
		a == b
	final def sameSeq[T](a: Seq[T], b: Seq[T])(eq: (T,T) => Boolean): Boolean =
		(a.length == b.length) && (a zip b).forall(tupled(eq))
}