/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2002-2009, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// $Id: ValidatingMarkupHandler.scala 18511 2009-08-19 03:27:37Z extempore $


package scala.xml
package parsing

import scala.xml.dtd._
import scala.util.logging.Logged

abstract class ValidatingMarkupHandler extends MarkupHandler with Logged {

  var rootLabel:String = _
  var qStack: List[Int] = Nil
  var qCurrent: Int = -1

  var declStack: List[ElemDecl] = Nil
  var declCurrent: ElemDecl = null

  final override val isValidating = true

  override def log(msg: String) {}
  
  /*
  override def checkChildren(pos: Int, pre: String, label:String,ns:NodeSeq): Unit = {
    Console.println("checkChildren()");
    val decl = lookupElemDecl(label);
    // @todo: nice error message
    val res = decl.contentModel.validate(ns);
    Console.println("res = "+res);
    if(!res)
      //error("invalid!");
  }
  */

  override def endDTD(n:String) = {
    rootLabel = n
  }
  override def elemStart(pos: Int, pre: String, label: String, attrs: MetaData, scope:NamespaceBinding) {

    def advanceDFA(dm:DFAContentModel) = {
      val trans = dm.dfa.delta(qCurrent)
      log("advanceDFA(dm): " + dm)
      log("advanceDFA(trans): " + trans)
      trans.get(ContentModel.ElemName(label)) match {
          case Some(qNew) => qCurrent = qNew
          case _          => reportValidationError(pos, "DTD says, wrong element, expected one of "+trans.keysIterator);
        }
    }
    // advance in current automaton
    log("[qCurrent = "+qCurrent+" visiting "+label+"]")

    if (qCurrent == -1) { // root
      log("  checking root")
      if (label != rootLabel)
        reportValidationError(pos, "this element should be "+rootLabel)
    } else {
      log("  checking node")
      declCurrent.contentModel match {
        case ANY =>
        case EMPTY => 
          reportValidationError(pos, "DTD says, no elems, no text allowed here")
        case PCDATA => 
          reportValidationError(pos, "DTD says, no elements allowed here")
        case m @ MIXED(r) =>
          advanceDFA(m)
        case e @ ELEMENTS(r) =>
          advanceDFA(e)
      }
    }
    // push state, decl
    qStack    =    qCurrent :: qStack
    declStack = declCurrent :: declStack

    declCurrent = lookupElemDecl(label)
    qCurrent = 0
    log("  done  now")
  }

  override def elemEnd(pos: Int, pre: String, label: String) {
    log("  elemEnd")
    qCurrent = qStack.head
    qStack   = qStack.tail
    declCurrent = declStack.head
    declStack   = declStack.tail
    log("    qCurrent now" + qCurrent)
    log("    declCurrent now" + declCurrent)
  }

  final override def elemDecl(name: String, cmstr: String) {
    decls = ElemDecl(name, ContentModel.parse(cmstr)) :: decls
  }
    
  final override def attListDecl(name: String, attList: List[AttrDecl]) {
    decls = AttListDecl(name, attList) :: decls
  }

  final override def unparsedEntityDecl(name: String, extID: ExternalID, notat: String) {
    decls = UnparsedEntityDecl(name, extID, notat) :: decls
  }

  final override def notationDecl(notat: String, extID: ExternalID) {
    decls = NotationDecl(notat, extID) :: decls;
  }

  final override def peReference(name: String) {
    decls = PEReference(name) :: decls
  }

  /** report a syntax error */
  def reportValidationError(pos: Int, str: String): Unit

}