package org.exolab.castor.xml.parsing;

import org.exolab.castor.types.AnyNode;
import org.exolab.castor.xml.util.SAX2ANY;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

public class AnyNodeUnmarshalHandler {

  private NamespaceHandling _namespaceHandling;

  /**
   * A SAX2ANY unmarshaller in case we are dealing with {@literal <any>}.
   */
  private SAX2ANY _anyUnmarshaller = null;

  /**
   * The any branch depth.
   */
  private int _depth = 0;

  /**
   * Keeps track of the current element information as passed by the parser.
   */
  private ElementInfo _elemInfo = null;

  public AnyNodeUnmarshalHandler(NamespaceHandling namespaceHandling) {
    _namespaceHandling = namespaceHandling;
  }

  /**
   * Delegates startElement to SAX2ANY. Sets any branch depth counter to 1.
   * 
   * @param name Name of the element if we use SAX 2
   * @param namespace Namespace of the element
   * @param wsPreserve preserve whitespaces ?
   * @return Object anyUnmarshaller get StartingNode
   * @throws SAXException
   */
  public Object commonStartElement(String name, String namespace, boolean wsPreserve)
      throws SAXException {
    // 1- creates a new SAX2ANY handler
    _anyUnmarshaller = new SAX2ANY(_namespaceHandling.getNamespaceStack(), wsPreserve);
    // 2- delegates the element handling
    if (_elemInfo._attributeList != null) {
      // -- SAX 1
      startElement(_elemInfo._qName, _elemInfo._attributeList);
    } else {
      // -- SAX 2
      startElement(namespace, name, _elemInfo._qName, _elemInfo._attributes);
    }
    // first element so depth can only be one at this point
    _depth = 1;
    return _anyUnmarshaller.getStartingNode();
  }

  /**
   * delegates ignorableWhitespace call to SAX2ANY.
   * 
   * @param ch Characters
   * @param start Offset
   * @param length Length
   * @throws SAXException
   */
  public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
    _anyUnmarshaller.ignorableWhitespace(ch, start, length);
  }

  /**
   * Delegates SAX1 startElement to SAX2ANY. Increases any branch depth counter.
   * 
   * @param name Name of the element.
   * @param attList Attribute list.
   * @throws SAXException
   */
  public void startElement(String name, AttributeList attList) throws SAXException {
    _depth++;
    _anyUnmarshaller.startElement(name, attList);
    return;
  }

  /**
   * Delegates SAX2 startElement to SAX2ANY. Increases any branch depth counter.
   * 
   * @param namespaceURI
   * @param localName
   * @param qName
   * @param atts
   * @throws SAXException
   */
  public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
      throws SAXException {
    _depth++;
    _anyUnmarshaller.startElement(namespaceURI, localName, qName, atts);
    return;
  }

  /**
   * Delegates endElement to SAX2ANY.
   * 
   * @param name
   * @throws SAXException
   */
  public void endElement(String name) throws SAXException {
    _anyUnmarshaller.endElement(name);
    _depth--;
  }

  /**
   * delegates characters to SAX2ANY.
   * 
   * @param ch
   * @param start
   * @param length
   * @throws SAXException
   */
  public void characters(char[] ch, int start, int length) throws SAXException {
    _anyUnmarshaller.characters(ch, start, length);
  }

  /**
   * delegates startPrefixMapping to SAX2ANY.
   * 
   * @param prefix
   * @param uri
   * @throws SAXException
   */
  public void startPrefixMapping(String prefix, String uri) throws SAXException {
    _anyUnmarshaller.startPrefixMapping(prefix, uri);
  }

  /**
   * delegates endPrefixMapping to SAX2ANY.
   * 
   * @param prefix
   * @throws SAXException
   */
  public void endPrefixMapping(String prefix) throws SAXException {
    _anyUnmarshaller.endPrefixMapping(prefix);
  }

  /**
   * Checks if there is a SAX2ANY object
   * 
   * @return True if there is a SAX2ANY.
   */
  public boolean hasAnyUnmarshaller() {
    return _anyUnmarshaller != null;
  }

  /**
   * Checks if any branch depth counter is zero.
   * 
   * @return true if 0
   */
  public boolean isStartingNode() {
    return _depth == 0;
  }

  /**
   * Returns SAX2ANY startingNode.
   * 
   * @return
   */
  public AnyNode getStartingNode() {
    AnyNode startingNode = _anyUnmarshaller.getStartingNode();
    _anyUnmarshaller = null;
    return startingNode;
  }

  /**
   * Preserves passed name and attributes.
   * 
   * @param name Name
   * @param atts Attributes
   */
  public void preservePassedArguments(String name, Attributes atts) {
    _elemInfo = setElementInfo(_elemInfo, name, atts);
  }

  /**
   * Preserves passed name and attributes.
   * 
   * @param name Name
   * @param attList AttributeList
   */
  public void preservePassedArguments(String name, AttributeList attList) {
    _elemInfo = setElementInfo(_elemInfo, name, attList);
  }

  /**
   * Sets name and attList on element
   * 
   * @param element ElementInfo, can be null
   * @param name Name
   * @param attList Attributes
   * @return element on which name and attList are set
   */
  private ElementInfo setElementInfo(ElementInfo element, String name, AttributeList attList) {
    if (element == null) {
      return new ElementInfo(name, attList);
    }
    element.clear();
    element._qName = name;
    element._attributeList = attList;
    return element;
  }

  /**
   * Sets name and attributes on element
   * 
   * @param element ElementInfo, can be null
   * @param name Name
   * @param attributes Attributes
   * @return element on which name and attributes are set
   */
  private ElementInfo setElementInfo(ElementInfo element, String name, Attributes attributes) {
    if (element == null) {
      return new ElementInfo(name, attributes);
    }
    element.clear();
    element._qName = name;
    element._attributes = attributes;
    return element;
  }

  /**
   * A utility class for keeping track of the qName and how the SAX parser passed attributes.
   */
  class ElementInfo {
    String _qName = null;
    Attributes _attributes = null;
    AttributeList _attributeList = null;

    ElementInfo() {
      super();
    }

    ElementInfo(String qName, Attributes atts) {
      super();
      _qName = qName;
      _attributes = atts;
    }

    ElementInfo(String qName, AttributeList atts) {
      super();
      _qName = qName;
      _attributeList = atts;
    }

    void clear() {
      _qName = null;
      _attributes = null;
      _attributeList = null;
    }

  } // -- ElementInfo

}
