/*
 * Copyright (c) 2002 World Wide Web Consortium,
 * (Massachusetts Institute of Technology, Institut National de
 * Recherche en Informatique et en Automatique, Keio University). All
 * Rights Reserved. This program is distributed under the W3C's Software
 * Intellectual Property License. This program is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.
 * See W3C License http://www.w3.org/Consortium/Legal/ for more details.
 */

package org.w3c.domts;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *   This class implements the generic parser and configuation
 *   abstract class the HTML DOM implementation in Xerces-J
 */
public class XercesHTMLDocumentBuilderFactory
    extends DOMTestDocumentBuilderFactory {

  private SAXParserFactory factory;
  private Constructor htmlBuilderConstructor;
  private Method getHTMLDocumentMethod;
  private DOMImplementation domImpl;
  private static final Class[] NO_CLASSES = new Class[0];
  private static final Object[] NO_OBJECTS = new Object[0];

  /**
   * Creates a implementation of DOMTestDocumentBuilderFactory
   * using org.apache.html.dom.HTMLBuilder
   * @param settings array of settings, may be null.
   */
  public XercesHTMLDocumentBuilderFactory(DocumentBuilderSetting[] settings) throws
      DOMTestIncompatibleException {
    super(settings);

    try {
      ClassLoader classLoader = ClassLoader.getSystemClassLoader();
      Class htmlBuilderClass =
          classLoader.loadClass("org.apache.html.dom.HTMLBuilder");

      htmlBuilderConstructor =
          htmlBuilderClass.getConstructor(NO_CLASSES);

      getHTMLDocumentMethod =
          htmlBuilderClass.getMethod("getHTMLDocument", NO_CLASSES);

      Class htmlDOMImpl =
          classLoader.loadClass(
          "org.apache.html.dom.HTMLDOMImplementationImpl");
      Method method =
          htmlDOMImpl.getMethod("getHTMLDOMImplementation", NO_CLASSES);
      domImpl = (DOMImplementation) method.invoke(null, NO_OBJECTS);

      Class saxFactoryClass =
          classLoader.loadClass(
          "org.apache.xerces.jaxp.SAXParserFactoryImpl");
      factory = (SAXParserFactory) saxFactoryClass.newInstance();
    }
    catch (InvocationTargetException ex) {
      throw new DOMTestIncompatibleException(
          ex.getTargetException(),
          null);
    }
    catch (Exception ex) {
      throw new DOMTestIncompatibleException(ex, null);
    }

    //
    //    apply settings to selected document builder
    //         may throw exception if incompatible
    if (settings != null) {
      for (int i = 0; i < settings.length; i++) {
        //        settings[i].applySetting(factory);
      }
    }
    try {
      factory.newSAXParser();
    }
    catch (ParserConfigurationException ex) {
      throw new DOMTestIncompatibleException(ex, null);
    }
    catch (SAXException ex) {
      throw new DOMTestIncompatibleException(ex, null);
    }
  }

  public DOMTestDocumentBuilderFactory newInstance(DocumentBuilderSetting[]
      newSettings) throws DOMTestIncompatibleException {
    if (newSettings == null) {
      return this;
    }
    DocumentBuilderSetting[] mergedSettings = mergeSettings(newSettings);
    return new XercesHTMLDocumentBuilderFactory(mergedSettings);
  }

  private class HTMLHandler
      extends DefaultHandler
      implements AttributeList {
    private final DocumentHandler htmlBuilder;
    private final Method getHTMLDocumentMethod;
    private Attributes currentAttributes;

    public HTMLHandler(
        Constructor htmlBuilderConstructor,
        Method getHTMLDocumentMethod) throws Exception {
      htmlBuilder =
          (DocumentHandler) htmlBuilderConstructor.newInstance(
          new Object[0]);
      this.getHTMLDocumentMethod = getHTMLDocumentMethod;
    }

    public void startDocument() throws SAXException {
      htmlBuilder.startDocument();
    }

    public void endDocument() throws SAXException {
      htmlBuilder.endDocument();
    }

    public void startElement(
        String uri,
        String localName,
        String qName,
        Attributes attributes) throws SAXException {
      currentAttributes = attributes;
      htmlBuilder.startElement(qName, this);
    }

    public void endElement(String uri, String localName, String qName) throws
        SAXException {
      htmlBuilder.endElement(qName);
    }

    public void characters(char ch[], int start, int length) throws
        SAXException {
      htmlBuilder.characters(ch, start, length);
    }

    public void ignorableWhitespace(char ch[], int start, int length) throws
        SAXException {
      htmlBuilder.ignorableWhitespace(ch, start, length);
    }

    public void processingInstruction(String target, String data) throws
        SAXException {
      htmlBuilder.processingInstruction(target, data);
    }

    public Document getHTMLDocument() throws Exception {
      return (Document) getHTMLDocumentMethod.invoke(
          htmlBuilder,
          NO_OBJECTS);
    }

    public int getLength() {
      return currentAttributes.getLength();
    }

    public String getName(int i) {
      return currentAttributes.getQName(i);
    }

    public String getType(int i) {
      return currentAttributes.getType(i);
    }

    public String getValue(int i) {
      return currentAttributes.getValue(i);
    }

    public String getType(String name) {
      return currentAttributes.getType(name);
    }

    public String getValue(String name) {
      return currentAttributes.getValue(name);
    }

  }

  public Document load(java.net.URL url) throws DOMTestLoadException {
    Document doc = null;
    try {
      SAXParser parser = factory.newSAXParser();
      HTMLHandler handler =
          new HTMLHandler(htmlBuilderConstructor, getHTMLDocumentMethod);
      parser.parse(url.toString(), handler);
      doc = handler.getHTMLDocument();
    }
    catch (Exception ex) {
      throw new DOMTestLoadException(ex);
    }
    return doc;
  }

  public DOMImplementation getDOMImplementation() {
    return domImpl;
  }

  public boolean hasFeature(String feature, String version) {
    return domImpl.hasFeature(feature, version);
  }

  public boolean isCoalescing() {
    return false;
  }

  public boolean isExpandEntityReferences() {
    return false;
  }

  public boolean isIgnoringElementContentWhitespace() {
    return false;
  }

  public boolean isNamespaceAware() {
    return factory.isNamespaceAware();
  }

  public boolean isValidating() {
    return factory.isValidating();
  }

  public static DocumentBuilderSetting[] getConfiguration1() {
    return new DocumentBuilderSetting[] {
        DocumentBuilderSetting.notCoalescing,
        DocumentBuilderSetting.notExpandEntityReferences,
        DocumentBuilderSetting.notIgnoringElementContentWhitespace,
        DocumentBuilderSetting.notNamespaceAware,
        DocumentBuilderSetting.notValidating};
  }

  public static DocumentBuilderSetting[] getConfiguration2() {
    return new DocumentBuilderSetting[] {
        DocumentBuilderSetting.notCoalescing,
        DocumentBuilderSetting.expandEntityReferences,
        DocumentBuilderSetting.ignoringElementContentWhitespace,
        DocumentBuilderSetting.namespaceAware,
        DocumentBuilderSetting.validating};

  }

}
