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

import scala.util
import xsbti.api._
import util.MurmurHash
import HashAPI.Hash

object HashAPI
{
	type Hash = Int
	def apply(a: SourceAPI): Hash =
		(new HashAPI(false, true)).hashAPI(a)
}

final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
{
	import scala.collection.mutable
	import MurmurHash._

	private[this] val visitedStructures = visitedMap[Structure]
	private[this] val visitedClassLike = visitedMap[ClassLike]
	private[this] def visitedMap[T] = new mutable.HashMap[T, List[Hash]]
	private[this] def visit[T](map: mutable.Map[T, List[Hash]], t: T)(hashF: T => Unit)
	{
		map.put(t, hash :: map.getOrElse(t,Nil)) match {
			case Some(x :: _) => extend(x)
			case _ =>
				hashF(t)
				for(hs <- map(t))
					extend(hs)
				map.put(t, hash :: Nil)
		}
	}

	private[this] final val ValHash = 1
	private[this] final val VarHash = 2
	private[this] final val DefHash = 3
	private[this] final val ClassHash = 4
	private[this] final val TypeDeclHash = 5
	private[this] final val TypeAliasHash = 6

	private[this] final val PublicHash = 30
	private[this] final val ProtectedHash = 31
	private[this] final val PrivateHash = 32	
	private[this] final val UnqualifiedHash = 33
	private[this] final val ThisQualifierHash = 34
	private[this] final val IdQualifierHash = 35

	private[this] final val IdPathHash = 20
	private[this] final val SuperHash = 21
	private[this] final val ThisPathHash = 22

	private[this] final val ValueParamsHash = 40
	private[this] final val ClassPendingHash = 41
	private[this] final val StructurePendingHash = 42

	private[this] final val EmptyTypeHash = 51
	private[this] final val ParameterRefHash = 52
	private[this] final val SingletonHash = 53
	private[this] final val ProjectionHash = 54
	private[this] final val ParameterizedHash = 55
	private[this] final val AnnotatedHash = 56
	private[this] final val PolymorphicHash = 57
	private[this] final val ConstantHash = 58
	private[this] final val ExistentialHash = 59
	private[this] final val StructureHash = 60

	private[this] final val TrueHash = 97
	private[this] final val FalseHash = 98


	private[this] var hash: Hash = startHash(0)
	private[this] var magicA: Hash = startMagicA
	private[this] var magicB: Hash = startMagicB
	
	@inline final def hashString(s: String): Unit = extend(stringHash(s))
	@inline final def hashBoolean(b: Boolean): Unit = extend(if(b) TrueHash else FalseHash)
	@inline final def hashSeq[T](s: Seq[T], hashF: T => Unit)
	{
		extend(s.length)
		s foreach hashF
	}
	final def hashSymmetric[T](ts: TraversableOnce[T], hashF: T => Unit)
	{
		val current = hash
		val mA = magicA
		val mB = magicB
		val (hashes, mAs, mBs) = ts.toList.map { t =>
			hash = startHash(1)
			magicA = startMagicA
			magicB = startMagicB
			hashF(t)
			(finalizeHash(hash), magicA, magicB)
		} unzip3;
		hash = current
		magicA = mA
		magicB = mB
		extend(symmetricHash(hashes, 0xb592f7ae))  // constant from MurmurHash3
	}

	@inline final def extend(a: Hash)
	{
		hash = extendHash(hash, a, magicA, magicB)
		magicA = nextMagicA(magicA)
		magicB = nextMagicB(magicB)
	}
	def hashModifiers(m: Modifiers) = extend(m.raw)

	def hashAPI(s: SourceAPI): Hash =
	{
		hash = startHash(0)
		hashSymmetric(s.packages, hashPackage)
		hashDefinitions(s.definitions, true)
		finalizeHash(hash)
	}

	def hashPackage(p: Package) = hashString(p.name)

	def hashDefinitions(ds: Seq[Definition], topLevel: Boolean): Unit =
	{
		val defs = SameAPI.filterDefinitions(ds, topLevel, includePrivate)
		hashSymmetric(defs, hashDefinition)
	}
	def hashDefinition(d: Definition)
	{
		hashString(d.name)
		hashAnnotations(d.annotations)
		hashModifiers(d.modifiers)
		hashAccess(d.access)
		d match
		{
			case c: ClassLike => hashClass(c)
			case f: FieldLike => hashField(f)
			case d: Def => hashDef(d)
			case t: TypeDeclaration => hashTypeDeclaration(t)
			case t: TypeAlias => hashTypeAlias(t)
		}
	}
	final def hashClass(c: ClassLike): Unit = visit(visitedClassLike, c)(hashClass0)
	def hashClass0(c: ClassLike)
	{
		extend(ClassHash)
		hashParameterizedDefinition(c)
		hashType(c.selfType)
		hashStructure(c.structure)
	}
	def hashField(f: FieldLike)
	{
		f match
		{
			case v: Var => extend(VarHash)
			case v: Val => extend(ValHash)
		}
		hashType(f.tpe)
	}
	def hashDef(d: Def)
	{
		extend(DefHash)
		hashParameterizedDefinition(d)
		hashValueParameters(d.valueParameters)
		hashType(d.returnType)
	}
	def hashAccess(a: Access): Unit =
		a match
		{
			case pub: Public => extend(PublicHash)
			case qual: Qualified => hashQualified(qual)
		}
	def hashQualified(qual: Qualified): Unit =
	{
		qual match
		{
			case p: Protected => extend(ProtectedHash)
			case p: Private => extend(PrivateHash)
		}
		hashQualifier(qual.qualifier)
	}
	def hashQualifier(qual: Qualifier): Unit =
		qual match
		{
			case _: Unqualified => extend(UnqualifiedHash)
			case _: ThisQualifier => extend(ThisQualifierHash)
			case id: IdQualifier =>
				extend(IdQualifierHash)
				hashString(id.value)
		}

	def hashValueParameters(valueParameters: Seq[ParameterList]) = hashSeq(valueParameters, hashValueParameterList)
	def hashValueParameterList(list: ParameterList) =
	{
		extend(ValueParamsHash)
		hashBoolean(list.isImplicit)
		hashSeq(list.parameters, hashValueParameter)
	}
	def hashValueParameter(parameter: MethodParameter) =
	{
		hashString(parameter.name)
		hashType(parameter.tpe)
		extend(parameter.modifier.ordinal)
		hashBoolean(parameter.hasDefault)
	}
		
	def hashParameterizedDefinition[T <: ParameterizedDefinition](d: T)
	{
		hashTypeParameters(d.typeParameters)
	}
	def hashTypeDeclaration(d: TypeDeclaration)
	{
		extend(TypeDeclHash)
		hashParameterizedDefinition(d)
		hashType(d.lowerBound)
		hashType(d.upperBound)
	}
	def hashTypeAlias(d: TypeAlias)
	{
		extend(TypeAliasHash)
		hashParameterizedDefinition(d)
		hashType(d.tpe)
	}
	
	def hashTypeParameters(parameters: Seq[TypeParameter]) = hashSeq(parameters, hashTypeParameter)
	def hashTypeParameter(parameter: TypeParameter)
	{
		hashString(parameter.id)
		extend(parameter.variance.ordinal)
		hashTypeParameters(parameter.typeParameters)
		hashType(parameter.lowerBound)
		hashType(parameter.upperBound)
		hashAnnotations(parameter.annotations)
	}
	def hashAnnotations(annotations: Seq[Annotation]) = hashSeq(annotations, hashAnnotation)
	def hashAnnotation(annotation: Annotation) =
	{
		hashType(annotation.base)
		hashAnnotationArguments(annotation.arguments)
	}
	def hashAnnotationArguments(args: Seq[AnnotationArgument]) = hashSeq(args, hashAnnotationArgument)
	def hashAnnotationArgument(arg: AnnotationArgument)
	{
		hashString(arg.name)
		hashString(arg.value)
	}
	
	def hashTypes(ts: Seq[Type]) = hashSeq(ts, hashType)
	def hashType(t: Type): Unit =
		t match
		{
			case s: Structure => hashStructure(s)
			case e: Existential => hashExistential(e)
			case c: Constant => hashConstant(c)
			case p: Polymorphic => hashPolymorphic(p)
			case a: Annotated => hashAnnotated(a)
			case p: Parameterized => hashParameterized(p)
			case p: Projection => hashProjection(p)
			case _: EmptyType => extend(EmptyTypeHash)
			case s: Singleton => hashSingleton(s)
			case pr: ParameterRef => hashParameterRef(pr)
		}
	
	def hashParameterRef(p: ParameterRef)
	{
		extend(ParameterRefHash)
		hashString(p.id)
	}
	def hashSingleton(s: Singleton)
	{
		extend(SingletonHash)
		hashPath(s.path)
	}
	def hashPath(path: Path) = hashSeq(path.components, hashPathComponent)
	def hashPathComponent(pc: PathComponent) = pc match
	{
		case _: This => extend(ThisPathHash)
		case s: Super => hashSuperPath(s)
		case id: Id => hashIdPath(id)
	}
	def hashSuperPath(s: Super)
	{
		extend(SuperHash)
		hashPath(s.qualifier)
	}
	def hashIdPath(id: Id)
	{
		extend(IdPathHash)
		hashString(id.id)
	}

	def hashConstant(c: Constant) =
	{
		extend(ConstantHash)
		hashString(c.value)
		hashType(c.baseType)
	}
	def hashExistential(e: Existential) =
	{
		extend(ExistentialHash)
		hashParameters(e.clause, e.baseType)
	}
	def hashPolymorphic(p: Polymorphic) =
	{
		extend(PolymorphicHash)
		hashParameters(p.parameters, p.baseType)
	}
	def hashProjection(p: Projection) =
	{
		extend(ProjectionHash)
		hashString(p.id)
		hashType(p.prefix)
	}
	def hashParameterized(p: Parameterized)
	{
		extend(ParameterizedHash)
		hashType(p.baseType)
		hashTypes(p.typeArguments)
	}
	def hashAnnotated(a: Annotated)
	{
		extend(AnnotatedHash)
		hashType(a.baseType)
		hashAnnotations(a.annotations)
	}
	final def hashStructure(structure: Structure) = visit(visitedStructures, structure)(hashStructure0)
	def hashStructure0(structure: Structure)
	{
		extend(StructureHash)
		hashTypes(structure.parents)
		hashDefinitions(structure.declared, false)
		hashDefinitions(structure.inherited, false)
	}
	def hashParameters(parameters: Seq[TypeParameter], base: Type): Unit =
	{
		hashTypeParameters(parameters)
		hashType(base)
	}
}