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

import xsbti.api._
import scala.collection.mutable

class Visit {
  private[this] val visitedStructures = new mutable.HashSet[Structure]
  private[this] val visitedClassLike = new mutable.HashSet[ClassLike]

  def visit(s: Source): Unit = visitAPI(s.api)
  def visitAPI(s: SourceAPI): Unit =
    {
      s.packages foreach visitPackage
      s.definitions foreach visitDefinition
    }

  def visitPackage(p: Package) {
    visitString(p.name)
  }

  def visitDefinitions(ds: Seq[Definition]) = ds foreach visitDefinition
  def visitDefinition(d: Definition) {
    visitString(d.name)
    visitAnnotations(d.annotations)
    visitModifiers(d.modifiers)
    visitAccess(d.access)
    d match {
      case c: ClassLike       => visitClass(c)
      case f: FieldLike       => visitField(f)
      case d: Def             => visitDef(d)
      case t: TypeDeclaration => visitTypeDeclaration(t)
      case t: TypeAlias       => visitTypeAlias(t)
    }
  }
  final def visitClass(c: ClassLike): Unit = if (visitedClassLike add c) visitClass0(c)
  def visitClass0(c: ClassLike) {
    visitParameterizedDefinition(c)
    visitType(c.selfType)
    visitStructure(c.structure)
  }
  def visitField(f: FieldLike) {
    visitType(f.tpe)
    f match {
      case v: Var => visitVar(v)
      case v: Val => visitVal(v)
    }
  }
  def visitVar(v: Var) {}
  def visitVal(v: Val) {}
  def visitDef(d: Def) {
    visitParameterizedDefinition(d)
    visitValueParameters(d.valueParameters)
    visitType(d.returnType)
  }
  def visitAccess(a: Access): Unit =
    a match {
      case pub: Public     => visitPublic(pub)
      case qual: Qualified => visitQualified(qual)
    }
  def visitQualified(qual: Qualified): Unit =
    qual match {
      case p: Protected => visitProtected(p)
      case p: Private   => visitPrivate(p)
    }
  def visitQualifier(qual: Qualifier): Unit =
    qual match {
      case unq: Unqualified     => visitUnqualified(unq)
      case thisq: ThisQualifier => visitThisQualifier(thisq)
      case id: IdQualifier      => visitIdQualifier(id)
    }
  def visitIdQualifier(id: IdQualifier) {
    visitString(id.value)
  }
  def visitUnqualified(unq: Unqualified) {}
  def visitThisQualifier(thisq: ThisQualifier) {}
  def visitPublic(pub: Public) {}
  def visitPrivate(p: Private) { visitQualifier(p.qualifier) }
  def visitProtected(p: Protected) { visitQualifier(p.qualifier) }
  def visitModifiers(m: Modifiers) {}

  def visitValueParameters(valueParameters: Seq[ParameterList]) = valueParameters foreach visitValueParameterList
  def visitValueParameterList(list: ParameterList) = list.parameters foreach visitValueParameter
  def visitValueParameter(parameter: MethodParameter) =
    {
      visitString(parameter.name)
      visitType(parameter.tpe)
    }

  def visitParameterizedDefinition[T <: ParameterizedDefinition](d: T) {
    visitTypeParameters(d.typeParameters)
  }
  def visitTypeDeclaration(d: TypeDeclaration) {
    visitParameterizedDefinition(d)
    visitType(d.lowerBound)
    visitType(d.upperBound)
  }
  def visitTypeAlias(d: TypeAlias) {
    visitParameterizedDefinition(d)
    visitType(d.tpe)
  }

  def visitTypeParameters(parameters: Seq[TypeParameter]) = parameters foreach visitTypeParameter
  def visitTypeParameter(parameter: TypeParameter) {
    visitTypeParameters(parameter.typeParameters)
    visitType(parameter.lowerBound)
    visitType(parameter.upperBound)
    visitAnnotations(parameter.annotations)
  }
  def visitAnnotations(annotations: Seq[Annotation]) = annotations foreach visitAnnotation
  def visitAnnotation(annotation: Annotation) =
    {
      visitType(annotation.base)
      visitAnnotationArguments(annotation.arguments)
    }
  def visitAnnotationArguments(args: Seq[AnnotationArgument]) = args foreach visitAnnotationArgument
  def visitAnnotationArgument(arg: AnnotationArgument) {
    visitString(arg.name)
    visitString(arg.value)
  }

  def visitTypes(ts: Seq[Type]) = ts.foreach(visitType)
  def visitType(t: Type) {
    t match {
      case s: Structure     => visitStructure(s)
      case e: Existential   => visitExistential(e)
      case c: Constant      => visitConstant(c)
      case p: Polymorphic   => visitPolymorphic(p)
      case a: Annotated     => visitAnnotated(a)
      case p: Parameterized => visitParameterized(p)
      case p: Projection    => visitProjection(p)
      case _: EmptyType     => visitEmptyType()
      case s: Singleton     => visitSingleton(s)
      case pr: ParameterRef => visitParameterRef(pr)
    }
  }

  def visitEmptyType() {}
  def visitParameterRef(p: ParameterRef) {}
  def visitSingleton(s: Singleton) { visitPath(s.path) }
  def visitPath(path: Path) = path.components foreach visitPathComponent
  def visitPathComponent(pc: PathComponent) = pc match {
    case t: This  => visitThisPath(t)
    case s: Super => visitSuperPath(s)
    case id: Id   => visitIdPath(id)
  }
  def visitThisPath(t: This) {}
  def visitSuperPath(s: Super) { visitPath(s.qualifier) }
  def visitIdPath(id: Id) { visitString(id.id) }

  def visitConstant(c: Constant) =
    {
      visitString(c.value)
      visitType(c.baseType)
    }
  def visitExistential(e: Existential) = visitParameters(e.clause, e.baseType)
  def visitPolymorphic(p: Polymorphic) = visitParameters(p.parameters, p.baseType)
  def visitProjection(p: Projection) =
    {
      visitString(p.id)
      visitType(p.prefix)
    }
  def visitParameterized(p: Parameterized) {
    visitType(p.baseType)
    visitTypes(p.typeArguments)
  }
  def visitAnnotated(a: Annotated) {
    visitType(a.baseType)
    visitAnnotations(a.annotations)
  }
  final def visitStructure(structure: Structure) = if (visitedStructures add structure) visitStructure0(structure)
  def visitStructure0(structure: Structure) {
    visitTypes(structure.parents)
    visitDefinitions(structure.declared)
    visitDefinitions(structure.inherited)
  }
  def visitParameters(parameters: Seq[TypeParameter], base: Type): Unit =
    {
      visitTypeParameters(parameters)
      visitType(base)
    }
  def visitString(s: String) {}
}