package org.iso_relax.verifier.impl;

import java.util.Enumeration;

import jp.gr.xml.relax.dom.DOMVisitorException;
import jp.gr.xml.relax.dom.IDOMVisitor;
import jp.gr.xml.relax.dom.UDOM;
import jp.gr.xml.relax.dom.UDOMVisitor;
import jp.gr.xml.relax.sax.DeclHandlerBase;
import jp.gr.xml.relax.sax.LexicalHandlerBase;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Entity;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Notation;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.LocatorImpl;
import org.xml.sax.helpers.NamespaceSupport;

/**
 * Generates SAX events from a DOM tree.
 * 
 * <p>
 * call the <code>makeEvent</code> method to use it.
 * 
 * @author
 *            ASAMI, Tomoharu (asami@zeomtech.com)
 *            <a href="kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
 */
public class SAXEventGenerator implements IDOMVisitor {
    private Node root_;
    private boolean needDocumentEmulation_ = true;
    
    private String systemID_;
    private String publicID_;
    private DTDHandler dtd_;
    private ContentHandler content_;
    private DeclHandler decl_;
    private LexicalHandler lexical_;
    private ErrorHandler error_;
    private final NamespaceSupport namespace_ = new NamespaceSupport();

    public SAXEventGenerator(Node node) {
        root_ = node;
        
        DefaultHandler handler = new DefaultHandler();
        dtd_ = handler;
        content_ = handler;
        error_ = handler;
        lexical_ = new LexicalHandlerBase();
        decl_ = new DeclHandlerBase();
    }

    public void setDocumentEmulation(boolean emulate) {
        needDocumentEmulation_ = emulate;
    }

    public void setDTDHandler(DTDHandler dtd) {
        dtd_ = dtd;
    }

    public void setContentHandler(ContentHandler content) {
        content_ = content;
    }

    public void setLexicalHandler(LexicalHandler lexical) {
        lexical_ = lexical;
    }

    public void setDeclHandler(DeclHandler decl) {
        decl_ = decl;
    }

    public void setErrorHandler(ErrorHandler error) {
        error_ = error;
    }

    public void makeEvent() throws SAXException {
        try {
            if (!(root_ instanceof Document) && needDocumentEmulation_) {
                emulateStartDocument();
                UDOMVisitor.traverse(root_, this);
                emulateEndDocument();
            } else {
                UDOMVisitor.traverse(root_, this);
            }
        } catch (DOMVisitorException e) {
            Exception cause = e.getCauseException();
            if (cause == null) {
                throw (new SAXException(e.getMessage()));
            } else if (cause instanceof SAXException) {
                throw ((SAXException)cause);
            } else {
                throw (new SAXException(e.getMessage()));
            }
        }
    }

    public void makeEvent(ContentHandler handler) throws SAXException {
        setContentHandler(handler);
        makeEvent();
    }
    
    
    
    
    
    public void emulateStartDocument() {
        try {
            _handleLocator();
            content_.startDocument();
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public void emulateEndDocument() {
        try {
            content_.endDocument();
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public boolean enter(Element element) {
        try {
            namespace_.pushContext();
            String namespaceURI = element.getNamespaceURI();
            if (namespaceURI == null) {
                namespaceURI = "";
            }
            String localName = element.getLocalName();
            String qName = element.getTagName();
			if(localName==null)	localName = qName;
			
            NamedNodeMap attrMap = element.getAttributes();
            AttributesImpl attrs = new AttributesImpl();
            int size = attrMap.getLength();
            for (int i = 0;i < size;i++) {
                Attr attr = (Attr)attrMap.item(i);
                String attrNamespaceURI = attr.getNamespaceURI();
                if (attrNamespaceURI == null) {
                    attrNamespaceURI = "";
                }
                String attrLocalName = attr.getLocalName();
                String attrQName = attr.getName();
				if(attrLocalName==null)	attrLocalName = attrQName;
                String attrValue = attr.getValue();
                if (attrQName.startsWith("xmlns")) {
                    String prefix;
                    int index = attrQName.indexOf(':');
                    if (index == -1) {
                        prefix = "";
                    } else {
                        prefix = attrQName.substring(index + 1);
                    }
                    if (!namespace_.declarePrefix(prefix, attrValue)) {
                        _errorReport("bad prefix = " + prefix);
                    } else {
                        content_.startPrefixMapping(prefix, attrValue);
                    }
                } else {
                    attrs.addAttribute(
                        attrNamespaceURI,
                        attrLocalName,
                        attrQName,
                        "CDATA",
                        attrValue
                    );
                }
            }
            content_.startElement(namespaceURI, localName, qName, attrs);
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (true);
    }

    public boolean enter(Attr attr) {
        return (false);
    }

    public boolean enter(Text text) {
        try {
            String data = text.getData();
            content_.characters(data.toCharArray(), 0, data.length());
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (false);
    }

    public boolean enter(CDATASection cdata) {
        try {
            lexical_.startCDATA();
            String data = cdata.getData();
            content_.characters(data.toCharArray(), 0, data.length());
            lexical_.endCDATA();
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (false);
    }

    public boolean enter(EntityReference entityRef) {
        try {
            lexical_.startEntity(entityRef.getNodeName());
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (true);
    }

    public boolean enter(Entity entity) {
        return (false);
    }

    public boolean enter(ProcessingInstruction pi) {
        try {
            content_.processingInstruction(pi.getTarget(), pi.getData());
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (false);
    }

    public boolean enter(Comment comment) {
        try {
            String data = comment.getData();
            lexical_.comment(data.toCharArray(), 0, data.length());
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (false);
    }

    public boolean enter(Document doc) {
        try {
            _handleLocator();
            content_.startDocument();
            _handleDoctype(doc.getDoctype());
        } catch (SAXException e) {
            _errorReport(e);
        }
        return (true);
    }

    private void _handleLocator() {
        if (systemID_ == null && publicID_ == null) {
            return;
        }
        _locatorEvent(systemID_, publicID_);
    }

    private void _locatorEvent(String systemID, String publicID) {
        LocatorImpl locator = new LocatorImpl();
        locator.setSystemId(systemID_);
        locator.setPublicId(publicID_);
        locator.setLineNumber(-1);
        locator.setColumnNumber(-1);
        content_.setDocumentLocator(locator);
    }

    private void _handleDoctype(DocumentType doctype) {
        try {
            if (doctype == null) {
                return;
            }
            String systemID = doctype.getSystemId();
            String publicID = doctype.getPublicId();
            String internalSubset = doctype.getInternalSubset();
            if (systemID != null) {
                lexical_.startDTD(doctype.getName(), publicID, systemID);
                if (internalSubset == null) {
                    lexical_.endDTD();
                    _handleEntities(doctype);
                } else {
                    _handleEntities(doctype);
                    lexical_.endDTD();
                }
            } else {
                _handleEntities(doctype);
            }
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    private void _handleEntities(DocumentType doctype) {
        try {
            NamedNodeMap entities = doctype.getEntities();
            int nEntities = entities.getLength();
            for (int i = 0;i < nEntities;i++) {
                Entity entity = (Entity)entities.item(i);
                String publicID = entity.getPublicId();
                String systemID = entity.getSystemId();
                String notationName = entity.getNotationName();
                if (publicID != null || systemID != null) {
                    _handleExternalEntity(
                        entity.getNodeName(),
                        publicID,
                        systemID,
                        notationName
                    );
                } else {
                    _handleInternalEntity(entity);
                }
            }
            NamedNodeMap notations = doctype.getNotations();
            int nNotations = notations.getLength();
            for (int i = 0;i < nNotations;i++) {
                Notation notation = (Notation)notations.item(i);
                String publicID = notation.getPublicId();
                String systemID = notation.getSystemId();
                dtd_.notationDecl(notation.getNodeName(), publicID, systemID);
            }
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    private void _handleExternalEntity(
        String name,
        String publicID,
        String systemID,
        String notationName
    ) {
        try {
            if (notationName == null) {
                decl_.externalEntityDecl(name, publicID, systemID);
            } else {
                dtd_.unparsedEntityDecl(
                    name,
                    publicID,
                    systemID,
                    notationName
                );
            }
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    private void _handleInternalEntity(Entity entity) {
        try {
            decl_.internalEntityDecl(
                entity.getNodeName(),
                UDOM.getXMLText(entity)
            );
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public boolean enter(DocumentType doctype) {
        return (false);
    }

    public boolean enter(DocumentFragment docfrag) {
        return (true);
    }

    public boolean enter(Notation notation) {
        return (false);
    }

    public boolean enter(Node node) {
        return (false);
    }

    public void leave(Element element) {
        try {
            String namespaceURI = element.getNamespaceURI();
            if (namespaceURI == null) {
                namespaceURI = "";
            }
            String localName = element.getLocalName();
            String qName = element.getTagName();
			if(localName==null)		localName=qName;
            content_.endElement(namespaceURI, localName, qName);
            
            for (Enumeration e=namespace_.getDeclaredPrefixes(); e.hasMoreElements(); )
                content_.endPrefixMapping( (String)e.nextElement() );
            
            namespace_.popContext();
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public void leave(Attr attr) {
    }

    public void leave(Text text) {
    }

    public void leave(CDATASection cdata) {
    }

    public void leave(EntityReference entityRef) {
        try {
            lexical_.endEntity(entityRef.getNodeName());
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public void leave(Entity entity) {
    }

    public void leave(ProcessingInstruction pi) {
    }

    public void leave(Comment comment) {
    }

    public void leave(Document doc) {
        try {
            content_.endDocument();
        } catch (SAXException e) {
            _errorReport(e);
        }
    }

    public void leave(DocumentType doctype) {
    }

    public void leave(DocumentFragment docfrag) {
    }

    public void leave(Notation notation) {
    }

    public void leave(Node node) {
    }

    private void _errorReport(String message) throws DOMVisitorException {
        _errorReport(
            new SAXParseException(
                message,
                publicID_,
                systemID_,
                -1,
                -1
            )
        );
    }

    private void _errorReport(SAXException e) throws DOMVisitorException {
        try {
            SAXParseException parseException;
            if (e instanceof SAXParseException) {
                parseException = (SAXParseException)e;
            } else {
                parseException = new SAXParseException(
                    e.getMessage(),
                    publicID_,
                    systemID_,
                    -1,
                    -1,
                    e
                );
            }
            error_.fatalError(parseException);
            throw new DOMVisitorException(e);
        } catch (SAXException ee) {
            throw new DOMVisitorException(ee);
        }
    }
    
}
