/*
 * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 * THIS FILE WAS MODIFIED BY SUN MICROSYSTEMS, INC.
 */

package com.sun.xml.internal.fastinfoset.tools;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
import com.sun.xml.internal.fastinfoset.CommonResourceBundle;

public class SAXEventSerializer extends DefaultHandler
        implements LexicalHandler {

    private Writer _writer;
    private boolean _charactersAreCDATA;
    private StringBuffer _characters;

    private Stack _namespaceStack = new Stack();
    protected List _namespaceAttributes;

    public SAXEventSerializer(OutputStream s) throws IOException {
        _writer = new OutputStreamWriter(s);
        _charactersAreCDATA = false;
    }

    // -- ContentHandler interface ---------------------------------------

    public void startDocument() throws SAXException {
        try {
            _writer.write("<sax xmlns=\"http://www.sun.com/xml/sax-events\">\n");
            _writer.write("<startDocument/>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }

    public void endDocument() throws SAXException {
        try {
            _writer.write("<endDocument/>\n");
            _writer.write("</sax>");
            _writer.flush();
            _writer.close();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }


    public void startPrefixMapping(String prefix, String uri)
        throws SAXException
    {
        if (_namespaceAttributes == null) {
            _namespaceAttributes = new ArrayList();
        }

        String qName = (prefix.length() == 0) ? "xmlns" : "xmlns" + prefix;
        AttributeValueHolder attribute = new AttributeValueHolder(
                qName,
                prefix,
                uri,
                null,
                null);
        _namespaceAttributes.add(attribute);
    }

    public void endPrefixMapping(String prefix)
        throws SAXException
    {
        /*
        try {
            outputCharacters();

            _writer.write("<endPrefixMapping prefix=\"" +
                prefix + "\"/>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
         */
    }

    public void startElement(String uri, String localName,
            String qName, Attributes attributes)
        throws SAXException
    {
        try {
            outputCharacters();

            if (_namespaceAttributes != null) {

                AttributeValueHolder[] attrsHolder = new AttributeValueHolder[0];
                attrsHolder = (AttributeValueHolder[])_namespaceAttributes.toArray(attrsHolder);

                // Sort attributes
                quicksort(attrsHolder, 0, attrsHolder.length - 1);

                for (int i = 0; i < attrsHolder.length; i++) {
                    _writer.write("<startPrefixMapping prefix=\"" +
                        attrsHolder[i].localName + "\" uri=\"" + attrsHolder[i].uri + "\"/>\n");
                    _writer.flush();
                }

                _namespaceStack.push(attrsHolder);
                _namespaceAttributes = null;
            } else {
                _namespaceStack.push(null);
            }

            AttributeValueHolder[] attrsHolder =
                new AttributeValueHolder[attributes.getLength()];
            for (int i = 0; i < attributes.getLength(); i++) {
                attrsHolder[i] = new AttributeValueHolder(
                    attributes.getQName(i),
                    attributes.getLocalName(i),
                    attributes.getURI(i),
                    attributes.getType(i),
                    attributes.getValue(i));
            }

            // Sort attributes
            quicksort(attrsHolder, 0, attrsHolder.length - 1);

            int attributeCount = 0;
            for (int i = 0; i < attrsHolder.length; i++) {
                if (attrsHolder[i].uri.equals("http://www.w3.org/2000/xmlns/")) {
                    // Ignore XMLNS attributes
                    continue;
                }
                attributeCount++;
            }

            if (attributeCount == 0) {
                _writer.write("<startElement uri=\"" + uri
                    + "\" localName=\"" + localName + "\" qName=\""
                    + qName + "\"/>\n");
                return;
            }

            _writer.write("<startElement uri=\"" + uri
                + "\" localName=\"" + localName + "\" qName=\""
                + qName + "\">\n");

            // Serialize attributes as children
            for (int i = 0; i < attrsHolder.length; i++) {
                if (attrsHolder[i].uri.equals("http://www.w3.org/2000/xmlns/")) {
                    // Ignore XMLNS attributes
                    continue;
                }
                _writer.write(
                    "  <attribute qName=\"" + attrsHolder[i].qName +
                    "\" localName=\"" + attrsHolder[i].localName +
                    "\" uri=\"" + attrsHolder[i].uri +
                    // "\" type=\"" + attrsHolder[i].type +
                    "\" value=\"" + attrsHolder[i].value +
                    "\"/>\n");
            }

            _writer.write("</startElement>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }

    public void endElement(String uri, String localName, String qName)
            throws SAXException
    {
        try {
            outputCharacters();

            _writer.write("<endElement uri=\"" + uri
                + "\" localName=\"" + localName + "\" qName=\""
                + qName + "\"/>\n");
            _writer.flush();

            // Write out the end prefix here rather than waiting
            // for the explicit events
            AttributeValueHolder[] attrsHolder = (AttributeValueHolder[])_namespaceStack.pop();
            if (attrsHolder != null) {
                for (int i = 0; i < attrsHolder.length; i++) {
                    _writer.write("<endPrefixMapping prefix=\"" +
                        attrsHolder[i].localName  + "\"/>\n");
                    _writer.flush();
                }
            }

        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException
    {
        if (length == 0) {
            return;
        }

        if (_characters == null) {
            _characters = new StringBuffer();
        }

        // Coalesce multiple character events
        _characters.append(ch, start, length);

        /*
        try {
            _writer.write("<characters>" +
                (_charactersAreCDATA ? "<![CDATA[" : "") +
                new String(ch, start, length) +
                (_charactersAreCDATA ? "]]>" : "") +
                "</characters>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
         */
    }

    private void outputCharacters() throws SAXException {
        if (_characters == null) {
            return;
        }

        try {
            _writer.write("<characters>" +
                (_charactersAreCDATA ? "<![CDATA[" : "") +
                _characters +
                (_charactersAreCDATA ? "]]>" : "") +
                "</characters>\n");
            _writer.flush();

            _characters = null;
        } catch (IOException e) {
            throw new SAXException(e);
        }
    }

    public void ignorableWhitespace(char[] ch, int start, int length)
            throws SAXException
    {
        // Report ignorable ws as characters (assumes validation off)
        characters(ch, start, length);
    }

    public void processingInstruction(String target, String data)
            throws SAXException
    {
        try {
            outputCharacters();

            _writer.write("<processingInstruction target=\"" + target
                + "\" data=\"" + data + "\"/>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }

    // -- LexicalHandler interface ---------------------------------------

    public void startDTD(String name, String publicId, String systemId)
            throws SAXException {
        // Not implemented
    }

    public void endDTD()
            throws SAXException {
        // Not implemented
    }

    public void startEntity(String name)
            throws SAXException {
        // Not implemented
    }

    public void endEntity(String name)
            throws SAXException {
        // Not implemented
    }

    public void startCDATA()
            throws SAXException {
        _charactersAreCDATA = true;
    }

    public void endCDATA()
            throws SAXException {
        _charactersAreCDATA = false;
    }

    public void comment(char[] ch, int start, int length)
            throws SAXException
    {
        try {
            outputCharacters();

            _writer.write("<comment>" +
                new String(ch, start, length) +
                "</comment>\n");
            _writer.flush();
        }
        catch (IOException e) {
            throw new SAXException(e);
        }
    }

    // -- Utility methods ------------------------------------------------

    private void quicksort(AttributeValueHolder[] attrs, int p, int r) {
        while (p < r) {
            final int q = partition(attrs, p, r);
            quicksort(attrs, p, q);
            p = q + 1;
        }
    }

    private int partition(AttributeValueHolder[] attrs, int p, int r) {
        AttributeValueHolder x = attrs[(p + r) >>> 1];
        int i = p - 1;
        int j = r + 1;
        while (true) {
            while (x.compareTo(attrs[--j]) < 0);
            while (x.compareTo(attrs[++i]) > 0);
            if (i < j) {
                final AttributeValueHolder t = attrs[i];
                attrs[i] = attrs[j];
                attrs[j] = t;
            }
            else {
                return j;
            }
        }
    }

    public static class AttributeValueHolder implements Comparable {
        public final String qName;
        public final String localName;
        public final String uri;
        public final String type;
        public final String value;

        public AttributeValueHolder(String qName,
            String localName,
            String uri,
            String type,
            String value)
        {
            this.qName = qName;
            this.localName = localName;
            this.uri = uri;
            this.type = type;
            this.value = value;
        }

        public int compareTo(Object o) {
            try {
                return qName.compareTo(((AttributeValueHolder) o).qName);
            } catch (Exception e) {
                throw new RuntimeException(CommonResourceBundle.getInstance().getString("message.AttributeValueHolderExpected"));
            }
        }

        @Override
        public boolean equals(Object o) {
            try {
                return (o instanceof AttributeValueHolder) &&
                        qName.equals(((AttributeValueHolder) o).qName);
            } catch (Exception e) {
                throw new RuntimeException(CommonResourceBundle.getInstance().getString("message.AttributeValueHolderExpected"));
            }
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 97 * hash + (this.qName != null ? this.qName.hashCode() : 0);
            return hash;
        }
    }

}