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

// $Id: ElementValidator.scala 18387 2009-07-24 15:28:37Z odersky $


package scala.xml
package dtd

import ContentModel.ElemName
import scala.util.automata._

/** validate children and/or attributes of an element
 *  exceptions are created but not thrown.
 */
class ElementValidator() extends Function1[Node,Boolean] {

  var exc: List[ValidationException] = Nil

  protected var contentModel: ContentModel           = _
  protected var dfa:          DetWordAutom[ElemName] = _
  protected var adecls:       List[AttrDecl]         = _

  /** set content model, enabling element validation */
  def setContentModel(cm:ContentModel) = {
    contentModel = cm; cm match {
      case ELEMENTS(r) =>
        val nfa = ContentModel.Translator.automatonFrom(r, 1)
        dfa = new SubsetConstruction(nfa).determinize
      case _ =>
        dfa = null
    }
  }

  def getContentModel = contentModel

  /** set meta data, enabling attribute validation */
  def setMetaData(adecls: List[AttrDecl]) { this.adecls = adecls }

  def getIterator(nodes: Seq[Node], skipPCDATA: Boolean): Iterator[ElemName] = 
    nodes.toList
	 .filter { x => x match {
           case y:SpecialNode => y match { 
             
             case a:Atom[_] if (a.data.isInstanceOf[String] && 
                                a.data.asInstanceOf[String].trim.length == 0 ) => 
                false; // always skip all-whitespace nodes

              case _ => 
                !skipPCDATA

            }
            case _ =>
              x.namespace eq null
          }}
          .map { x => ElemName(x.label) }
          .iterator;

  /** check attributes, return true if md corresponds to attribute declarations in adecls.
   */
  def check(md: MetaData): Boolean = {
    //@todo other exceptions
    import MakeValidationException._;
    val len: Int = exc.length;
    var j = 0;
    var ok = new scala.collection.mutable.BitSet(adecls.length);
    def find(Key:String): AttrDecl = {
      var attr: AttrDecl = null;
      val jt = adecls.iterator; while(j < adecls.length) {
        jt.next match {
          case a @ AttrDecl(Key, _, _) => attr = a; ok += j; j = adecls.length;
          case _                       => j = j + 1;
        }
      }
      attr
    }
    val it = md.iterator; while(it.hasNext) {
      val attr = it.next
      j = 0
      find(attr.key) match {

        case null => 
          //Console.println("exc");
          exc = fromUndefinedAttribute( attr.key ) :: exc;

        case AttrDecl(_, tpe, DEFAULT(true, fixedValue)) if attr.value.toString != fixedValue => 
          exc = fromFixedAttribute( attr.key, fixedValue, attr.value.toString) :: exc;

        case s =>
          //Console.println("s: "+s);

      }
    }
    
    //val missing = ok.toSet(false); FIXME: it doesn't seem to be used anywhere
    j = 0
    var kt = adecls.iterator
    while (kt.hasNext) {
      kt.next match {
        case AttrDecl(key, tpe, REQUIRED) if !ok(j) => 
          exc = fromMissingAttribute( key, tpe ) :: exc;
          j = j + 1;
        case _ =>
          j = j + 1;
      }
    }
    exc.length == len //- true if no new exception
  }

  /** check children, return true if conform to content model
   *  @pre contentModel != null
   */
  def check(nodes: Seq[Node]): Boolean = contentModel match {
    case ANY =>
      true

    case EMPTY =>
      !getIterator(nodes, false).hasNext

    case PCDATA =>
      !getIterator(nodes, true).hasNext

    case MIXED(ContentModel.Alt(branches @ _*))  => //@todo
      val j = exc.length
      def find(Key: String): Boolean = {
        var res = false
        val jt = branches.iterator
        while (jt.hasNext && !res) 
          jt.next match { // !!! check for match translation problem
            case ContentModel.Letter(ElemName(Key)) => res = true;
            case _                                  =>
          }
        res
      }
    
      var it = getIterator(nodes, true); while(it.hasNext) {
        var label = it.next.name;
        if (!find(label)) {
          exc = MakeValidationException.fromUndefinedElement(label) :: exc;
        }
      }

      (exc.length == j) //- true if no new exception

    case _:ELEMENTS => 
      var q = 0
      val it = getIterator(nodes, false)
      while (it.hasNext) {
        val e = it.next      
        dfa.delta(q).get(e) match {
          case Some(p) => q = p
          case _       => throw ValidationException("element "+e+" not allowed here")
        }
      }
      dfa.isFinal(q) //- true if arrived in final state
  }

  /** applies various validations - accumulates error messages in exc
   *  @todo: fail on first error, ignore other errors (rearranging conditions)
   */
  def apply(n: Node): Boolean = {
    //- ? check children
    var res = (null == contentModel) || check( n.child );	  
      
    //- ? check attributes
    res = ((null == adecls) || check( n.attributes )) && res;      

    res
  }	
}