/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)

package org.xmlpull.v1.wrapper.classic;

import java.io.IOException;
import java.io.StringReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.wrapper.XmlPullParserWrapper;
import org.xmlpull.v1.wrapper.XmlPullWrapperFactory;
import org.xmlpull.v1.wrapper.XmlSerializerWrapper;

/**
 * This class seemlesly extends exisiting serialzier implementation by adding new methods
 * (provided by XmlPullUtil) and delegating exisiting methods to parser implementation.
 *
 * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
 * @author Naresh Bhatia
 */
public class StaticXmlSerializerWrapper extends XmlSerializerDelegate
    implements XmlSerializerWrapper
{
    private final static String PROPERTY_XMLDECL_STANDALONE =
        "http://xmlpull.org/v1/doc/features.html#xmldecl-standalone";
    private static final boolean TRACE_SIZING = false;
    protected String currentNs;
    protected XmlPullWrapperFactory wf;
    protected XmlPullParserWrapper fragmentParser;

    public StaticXmlSerializerWrapper(XmlSerializer xs, XmlPullWrapperFactory wf) {
        super(xs);
        this.wf = wf;
    }

    public String getCurrentNamespaceForElements() { return currentNs; }

    public String setCurrentNamespaceForElements(String value)
    {
        String old = currentNs;
        currentNs = value;
        return old;
    }

    public XmlSerializerWrapper attribute (String name, String value)
        throws IOException, IllegalArgumentException, IllegalStateException
    {
        xs.attribute(null, name, value);
        return this;
    }

    public XmlSerializerWrapper startTag (String name)
        throws IOException, IllegalArgumentException, IllegalStateException
    {
        xs.startTag(currentNs, name);
        return this;
    }

    public XmlSerializerWrapper endTag(String name)
        throws IOException, IllegalArgumentException, IllegalStateException
    {
        endTag(currentNs, name);
        return this;
    }

    /** Write simple text element in current namespace */
    public XmlSerializerWrapper element(String elementName, String elementText)
        throws IOException, XmlPullParserException
    {
        return element(currentNs, elementName, elementText);
    }

    public XmlSerializerWrapper element(String namespace, String elementName, String elementText)
        throws IOException, XmlPullParserException
    {

        if (elementName == null) {
            throw new XmlPullParserException("name for element can not be null");
        }

        xs.startTag(namespace, elementName);
        if (elementText == null) {
            xs.attribute(XSI_NS, "nil", "true");
        }
        else {
            xs.text(elementText);
        }
        xs.endTag(namespace, elementName);
        return this;
    }


    //namespace stack
    //protected int elNamespaceCount[] = new int[ 2 ];
    //protected int currentDepth = -1;

    protected int namespaceEnd = 0;
    protected String namespacePrefix[] = new String[ 8 ];
    protected String namespaceUri[] = new String[ namespacePrefix.length ];
    protected int namespaceDepth[] = new int[ namespacePrefix.length ];

    private void ensureNamespacesCapacity() {
        int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8;
        if(TRACE_SIZING) {
            System.err.println(
                getClass().getName()+" namespaceSize "+namespacePrefix.length+" ==> "+newSize);
        }
        String[] newNamespacePrefix = new String[newSize];
        String[] newNamespaceUri = new String[newSize];
        int[] newNamespaceDepth = new int[newSize];
        if(namespacePrefix != null) {
            System.arraycopy(namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd);
            System.arraycopy(namespaceUri, 0, newNamespaceUri, 0, namespaceEnd);
            System.arraycopy(namespaceDepth, 0, newNamespaceDepth, 0, namespaceEnd);
        }
        namespacePrefix = newNamespacePrefix;
        namespaceUri = newNamespaceUri;
        namespaceDepth = newNamespaceDepth;
    }

    //    public XmlSerializer endTag(String namespace, String name) throws IOException
    //    {
    //        xs.endTag(currentNs, name);
    //        namespaceEnd = elNamespaceCount[ getDepth() ];
    //        return this;
    //    }

    public void setPrefix(String prefix, String namespace)
        throws IOException, IllegalArgumentException, IllegalStateException
    {
        xs.setPrefix(prefix, namespace);

        int depth = getDepth();
        for(int pos = namespaceEnd - 1; pos >= 0; --pos) {
            if(namespaceDepth[ pos ] <= depth) {
                break;
            }
            --namespaceEnd;
        }

        if(namespaceEnd >= namespacePrefix.length) {
            ensureNamespacesCapacity();
        }
        namespacePrefix[ namespaceEnd ] = prefix;
        namespaceUri[ namespaceEnd ] = namespace;
        ++namespaceEnd;

    }

    public void fragment(String xmlFragment)
        throws IOException, IllegalArgumentException, IllegalStateException, XmlPullParserException
    {
        StringBuffer buf = new StringBuffer(xmlFragment.length() + namespaceEnd * 30);
        buf.append("<fragment");
        LOOP:
        for(int pos = namespaceEnd - 1; pos >= 0; --pos) {
            String prefix  = namespacePrefix[ pos ];
            for(int i = namespaceEnd - 1; i > pos; --i) {
                if(prefix.equals( namespacePrefix[i] )) {
                    continue LOOP;
                }
            }
            buf.append(" xmlns");
            if(prefix.length() >  0) {
                buf.append(':').append(prefix);
            }
            buf.append("='");
            buf.append(escapeAttributeValue(namespaceUri[ pos ]));
            buf.append("'");
        }

        buf.append(">");
        buf.append(xmlFragment);
        buf.append("</fragment>");

        if(fragmentParser == null) {
            fragmentParser = wf.newPullParserWrapper();
        }
        String s = buf.toString();
        //System.err.println(getClass().getName()+" fragment XML="+s);
        fragmentParser.setInput(new StringReader( s ) );
        fragmentParser.nextTag();
        fragmentParser.require(XmlPullParser.START_TAG, null, "fragment");
        while(true) {
            fragmentParser.nextToken();
            if(fragmentParser.getDepth() == 1
               && fragmentParser.getEventType() == XmlPullParser.END_TAG)
            {
                break;
            }
            event(fragmentParser);
        }
        fragmentParser.require(XmlPullParser.END_TAG, null, "fragment");
    }

    public void event(XmlPullParser pp) throws XmlPullParserException, IOException {
        int eventType = pp.getEventType();
        switch (eventType) {
            case XmlPullParser.START_DOCUMENT:
                //use Boolean.TRUE to make it standalone
                Boolean standalone = (Boolean) pp.getProperty(PROPERTY_XMLDECL_STANDALONE);
                startDocument(pp.getInputEncoding(), standalone);
                break;

            case XmlPullParser.END_DOCUMENT:
                endDocument();
                break;

            case XmlPullParser.START_TAG:
                writeStartTag(pp);
                break;

            case XmlPullParser.END_TAG:
                endTag(pp.getNamespace (), pp.getName ());
                break;

            case XmlPullParser.IGNORABLE_WHITESPACE:
                //comment it to remove ignorable whtespaces from XML infoset
                String s = pp.getText ();
                ignorableWhitespace(s);
                break;

            case XmlPullParser.TEXT:
                if(pp.getDepth() > 0)  {
                    text(pp.getText ());
                } else {
                    ignorableWhitespace(pp.getText ());
                }
                break;

            case XmlPullParser.ENTITY_REF:
                entityRef (pp.getName ());
                break;

            case XmlPullParser.CDSECT:
                cdsect( pp.getText () );
                break;

            case XmlPullParser.PROCESSING_INSTRUCTION:
                processingInstruction( pp.getText ());
                break;

            case XmlPullParser.COMMENT:
                comment (pp.getText ());
                break;

            case XmlPullParser.DOCDECL:
                docdecl (pp.getText ());
                break;
        }
    }

    private void writeStartTag(XmlPullParser pp) throws XmlPullParserException, IOException {
        if (!pp.getFeature (XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES)) {
            int nsStart = pp.getNamespaceCount(pp.getDepth()-1);
            int nsEnd = pp.getNamespaceCount(pp.getDepth());
            for (int i = nsStart; i < nsEnd; i++) {
                String prefix = pp.getNamespacePrefix(i);
                String ns = pp.getNamespaceUri(i);
                setPrefix(prefix, ns);
            }
        }
        startTag(pp.getNamespace(), pp.getName());

        for (int i = 0; i < pp.getAttributeCount(); i++) {
            attribute
                (pp.getAttributeNamespace (i),
                 pp.getAttributeName (i),
                 pp.getAttributeValue (i));
        }
        //ser.closeStartTag();
    }


    public String escapeAttributeValue(String value)
        //protected void writeAttributeValue(String value, Writer out) throws IOException
    {
        int posLt = value.indexOf('<');
        int posAmp = value.indexOf('&');
        int posQuot = value.indexOf('"');
        int posApos = value.indexOf('\'');
        if(posLt == -1 && posAmp == -1 && posQuot == -1 && posApos == -1) {
            return value;
        }
        StringBuffer buf = new StringBuffer(value.length() +  10);

        // painful loop ...
        for(int pos = 0, len = value.length(); pos < len; ++pos) {
            char ch = value.charAt(pos);
            switch(ch) {
                case '<':
                    buf.append("&lt;");
                    break;
                case '&':
                    buf.append("&amp;");
                    break;
                case '\'':
                    buf.append("&apos;");
                    break;
                case '"':
                    buf.append("&quot;");
                    break;
                default:
                    buf.append(ch);
            }
        }
        return buf.toString();
    }

    //protected void writeElementContent(String text, Writer out) throws IOException
    public String escapeText(String text)
    {
        //<, & esccaped]
        //out.write(text);
        int posLt = text.indexOf('<');
        int posAmp = text.indexOf('&');
        if(posLt == -1 && posAmp == -1) { // this is shortcut
            return text;
        }
        StringBuffer buf = new StringBuffer(text.length() + 10);
        // painful loop ...
        int pos = 0;
        while(true) {
            if(posLt == -1 && posAmp == -1) { // this is shortcut
                buf.append(text.substring(pos));
                break;
            } else if(posLt == -1
                      || (posLt != -1 && posAmp != -1 && posAmp < posLt))
            {
                if(pos < posAmp) buf.append(text.substring(pos, posAmp));
                buf.append("&amp;");
                pos = posAmp + 1;
                posAmp = text.indexOf('&', pos);
            } else if(posAmp == -1
                      || (posLt != -1 && posAmp != -1 && posLt < posAmp))
            {
                if(pos < posLt) buf.append(text.substring(pos, posLt));
                buf.append("&lt;");
                pos = posLt + 1;
                posLt = text.indexOf('<', pos);
            } else {
                throw new IllegalStateException("wrong state posLt="+posLt
                                                    +" posAmp="+posAmp+" for "+text);
            }
        }
        return buf.toString();
    }


    public void writeDouble(double d)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        if(d == Double.POSITIVE_INFINITY) {
            xs.text("INF");
        } else if(d == Double.NEGATIVE_INFINITY) {
            xs.text("-INF");
        } else {
            xs.text(Double.toString(d));
        }
    }

    public void writeFloat(float f)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        if(f == Float.POSITIVE_INFINITY) {
            xs.text("INF");
        } else if(f == Float.NEGATIVE_INFINITY) {
            xs.text("-INF");
        } else {
            xs.text(Float.toString(f));
        }
    }

    public void writeInt(int i)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        xs.text(Integer.toString(i));
    }

    public void writeString(String s)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        if( s == null ) {
            throw new IllegalArgumentException("null string can not be written");
        }
        xs.text(s);
    }

    public void writeDoubleElement(String namespace, String name, double d)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        xs.startTag(namespace, name);
        writeDouble(d);
        xs.endTag(namespace, name);
    }

    public void writeFloatElement(String namespace, String name, float f)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        xs.startTag(namespace, name);
        writeFloat(f);
        xs.endTag(namespace, name);
    }

    public void writeIntElement(String namespace, String name, int i)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        xs.startTag(namespace, name);
        writeInt(i);
        xs.endTag(namespace, name);
    }

    public void writeStringElement(String namespace, String name, String s)
        throws XmlPullParserException, IOException, IllegalArgumentException
    {
        xs.startTag(namespace, name);
        if(s == null) {
            xs.attribute(XSD_NS, "nil", "true");
        } else {
            writeString(s);
        }
        xs.endTag(namespace, name);
    }

}

