/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */
package org.jivesoftware.util;

import java.io.Reader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.dom4j.*;


/**
 * Creates a document from the current XPP position. This is specialized
 * for Jabber packets allowing a depth limit.
 * We terminate the Document when we reach the closing tag of a subelement
 * that's the depth rather than the end of the real document. This lets us
 * extract subdocuments from a running XML stream.
 *
 * @author Iain Shigeoka
 */
public class XPPReader {

    private static XMLInputFactory xmlFactory;

    /**
     * Empty constructor.
     */
    private XPPReader() {
    }

    public static Document parseDocument(Reader input, Class context)
            throws DocumentException, XMLStreamException {
        if (xmlFactory == null) {
            xmlFactory = XMLInputFactory.newInstance();
            xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
        }
        XMLStreamReader xpp = xmlFactory.createXMLStreamReader(input);
        Document document = DocumentHelper.createDocument();
        for (int event = xpp.next(); event != XMLStreamConstants.START_ELEMENT; event = xpp.next()) {
            if (event == XMLStreamConstants.COMMENT) {
                document.addComment(xpp.getText());
            }
        }
        document = parseDocument(xpp, document);
        return document;
    }

    public static Document parseDocument(XMLStreamReader input)
            throws DocumentException, XMLStreamException {
        Document document = DocumentHelper.createDocument();
        document = parseDocument(input, document);
        return document;
    }

    private static Document parseDocument(XMLStreamReader xpp,
                                          Document document) throws
            DocumentException, XMLStreamException {
        Branch parent = document;
        int depth = 0;
        int type = xpp.getEventType();
        for (boolean endFound = false; ; type = xpp.next()) {
            switch (type) {
                case XMLStreamConstants.END_DOCUMENT:
                    throw new DocumentException("End of document");
                case XMLStreamConstants.START_ELEMENT:
                    depth++;
                    Element newElement;
                    if (xpp.getNamespaceURI().equals("jabber:client")) {
                        newElement = DocumentHelper.createElement(xpp.getLocalName());
                    }
                    else {
                        newElement = DocumentHelper.createElement(QName.get(xpp.getLocalName(),
                                xpp.getNamespaceURI()));
                    }
                    for (int i = 0; i < xpp.getAttributeCount(); i++) {
                        newElement.addAttribute(xpp.getAttributeLocalName(i),
                                xpp.getAttributeValue(i));
                    }
                    parent.add(newElement);
                    parent = newElement;
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    depth--;
                    if (parent != null) {
                        parent = parent.getParent();
                    }
                    if (depth <= 0) {
                        endFound = true;
                    }
                    break;
                case XMLStreamConstants.CDATA:
                    String cdata = xpp.getText();
                    if (parent != null) {
                        parent.add(DocumentHelper.createCDATA(cdata));
                    }
                    else {
                        throw new DocumentException(LocaleUtils.getLocalizedString("admin.error.packet.text"));
                    }
                    break;
                case XMLStreamConstants.CHARACTERS:
                    String chars = xpp.getText();
                    if (parent != null) {
                        parent.add(DocumentHelper.createText(chars));
                    }
                    else {
                        throw new DocumentException(LocaleUtils.getLocalizedString("admin.error.packet.text"));
                    }
                    break;
                case XMLStreamConstants.COMMENT:
                    parent.add(DocumentHelper.createComment(xpp.getText()));
                    break;
                case XMLStreamConstants.ENTITY_REFERENCE:
                    Log.warn("Unexpected entity reference event occurred " + xpp.getText());
                    break;
                default:
                    Log.warn("Unknown XML event occurred " + type);
            }
            if (endFound) {
                break;
            }
        }
        return document;
    }
}
