/* -*-             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.util;

import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

/**
 * Handy functions that combines XmlPull API into higher level functionality.
 *
 * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
 * @author Naresh Bhatia
 */
public class XmlPullUtil {

    public static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";

    private XmlPullUtil() {}

    /**
     * Return value of attribute with given name and no namespace.
     */
    public static String getAttributeValue(XmlPullParser pp, String name) {
        return pp.getAttributeValue(XmlPullParser.NO_NAMESPACE, name);
    }


    /**
     * Return PITarget from Processing Instruction (PI) as defined in
     * XML 1.0 Section 2.6 Processing Instructions
     *  <code>[16] PI ::= '&lt;?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'</code>
     */
    public static String getPITarget(XmlPullParser pp) throws IllegalStateException
    {
        int eventType;
        try {
            eventType = pp.getEventType();
        } catch(XmlPullParserException ex) {
            // should never happen ...
            throw new IllegalStateException(
                "could not determine parser state: "+ex+pp.getPositionDescription());
        }
        if( eventType != XmlPullParser.PROCESSING_INSTRUCTION ) {
            throw new IllegalStateException(
                "parser must be on processing instruction and not "
                    +XmlPullParser.TYPES[ eventType ]+pp.getPositionDescription());
        }
        final String PI = pp.getText();
        for (int i = 0; i < PI.length(); i++)
        {
            if( isS(PI.charAt(i)) ) {
                // assert i > 0
                return PI.substring(0,i);
            }
        }
        return PI;
    }

    /**
     * Return everything past PITarget and S from Processing Instruction (PI) as defined in
     * XML 1.0 Section 2.6 Processing Instructions
     *  <code>[16] PI ::= '&lt;?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'</code>
     *
     * <p><b>NOTE:</b> if there is no PI data it returns empty string.
     */
    public static String getPIData(XmlPullParser pp) throws IllegalStateException
    {
        int eventType;
        try {
            eventType = pp.getEventType();
        } catch(XmlPullParserException ex) {
            // should never happen ...
            throw new IllegalStateException(
                "could not determine parser state: "+ex+pp.getPositionDescription());
        }
        if( eventType != XmlPullParser.PROCESSING_INSTRUCTION ) {
            throw new IllegalStateException(
                "parser must be on processing instruction and not "
                    +XmlPullParser.TYPES[ eventType ]+pp.getPositionDescription());
        }
        final String PI = pp.getText();
        int pos = -1;
        for (int i = 0; i < PI.length(); i++)
        {
            if( isS(PI.charAt(i)) ) {
                pos = i;
            } else if(pos > 0) {
                return PI.substring(i);
            }
        }
        return "";

    }

    /**
     * Return true if chacters is S as defined in XML 1.0
     * <code>S ::=  (#x20 | #x9 | #xD | #xA)+</code>
     */
    private static  boolean isS(char ch) {
        return (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
    }

    /**
     * Skip sub tree that is currently porser positioned on.
     * <br>NOTE: parser must be on START_TAG and when funtion returns
     * parser will be positioned on corresponding END_TAG
     */
    public static void skipSubTree(XmlPullParser pp)
        throws XmlPullParserException, IOException
    {
        pp.require(XmlPullParser.START_TAG, null, null);
        int level = 1;
        while(level > 0) {
            int eventType = pp.next();
            if(eventType == XmlPullParser.END_TAG) {
                --level;
            } else if(eventType == XmlPullParser.START_TAG) {
                ++level;
            }
        }
    }

    /**
     * call parser nextTag() and check that it is START_TAG, throw exception if not.
     */
    public static void nextStartTag(XmlPullParser pp)
        throws XmlPullParserException, IOException
    {
        if(pp.nextTag() != XmlPullParser.START_TAG) {
            throw new XmlPullParserException(
                "expected START_TAG and not "+pp.getPositionDescription());
        }
    }

    /**
     * combine nextTag(); pp.require(XmlPullParser.START_TAG, null, name);
     */
    public static void nextStartTag(XmlPullParser pp, String name)
        throws XmlPullParserException, IOException
    {
        pp.nextTag();
        pp.require(XmlPullParser.START_TAG, null, name);
    }

    /**
     * combine nextTag(); pp.require(XmlPullParser.START_TAG, namespace, name);
     */
    public static void nextStartTag(XmlPullParser pp, String namespace, String name)
        throws XmlPullParserException, IOException
    {
        pp.nextTag();
        pp.require(XmlPullParser.START_TAG, namespace, name);
    }




    /**
     * combine nextTag(); pp.require(XmlPullParser.END_TAG, namespace, name);
     */
    public static void nextEndTag(XmlPullParser pp, String namespace, String name)
        throws XmlPullParserException, IOException
    {
        pp.nextTag();
        pp.require(XmlPullParser.END_TAG, namespace, name);
    }


    /**
     * Read text content of element ith given namespace and name
     * (use null namespace do indicate that nemspace should not be checked)
     */

    public static String nextText(XmlPullParser pp, String namespace, String name)
        throws IOException, XmlPullParserException
    {
        if(name == null) {
            throw new XmlPullParserException("name for element can not be null");
        }
        pp.require(XmlPullParser.START_TAG, namespace, name);
        return pp.nextText();
    }

    /**
     * Read attribute value and return it or throw exception if
     * current element does not have such attribute.
     */

    public static String getRequiredAttributeValue(XmlPullParser pp, String namespace, String name)
        throws IOException, XmlPullParserException
    {
        String value = pp.getAttributeValue(namespace, name);
        if (value == null) {
            throw new XmlPullParserException("required attribute "+name+" is not present");
        } else {
            return value;
        }
    }

    /**
     * Call parser nextTag() and check that it is END_TAG, throw exception if not.
     */
    public static void nextEndTag(XmlPullParser pp) throws XmlPullParserException, IOException
    {
        if(pp.nextTag() != XmlPullParser.END_TAG) {
            throw new XmlPullParserException(
                "expected END_TAG and not"+pp.getPositionDescription());
        }
    }


    /**
     * Tests if the current event is of the given type and if the namespace and name match.
     * null will match any namespace and any name. If the test passes a true is returned
     * otherwise a false is returned.
     */
    public static boolean matches(XmlPullParser pp, int type, String namespace, String name)
        throws XmlPullParserException
    {
        boolean matches = type == pp.getEventType()
            && (namespace == null || namespace.equals (pp.getNamespace()))
            && (name == null || name.equals (pp.getName ()));

        return matches;
    }


    /**
     * Writes a simple element such as <username>johndoe</username>. The namespace
     * and elementText are allowed to be null. If elementText is null, an xsi:nil="true"
     * will be added as an attribute.
     */
    public static void writeSimpleElement(XmlSerializer serializer,
                                   String namespace,
                                   String elementName,
                                   String elementText)
        throws IOException, XmlPullParserException {

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

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

}

