/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE. */

package org.kxml2.kdom;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
/** A common base class for Document and Element, also used for
    storing XML fragments. */

public class Node { //implements XmlIO{

    public static final int DOCUMENT = 0;
    public static final int ELEMENT = 2;
    public static final int TEXT = 4;
    public static final int CDSECT = 5;
    public static final int ENTITY_REF = 6;
    public static final int IGNORABLE_WHITESPACE = 7;
    public static final int PROCESSING_INSTRUCTION = 8;
    public static final int COMMENT = 9;
    public static final int DOCDECL = 10;

    protected List<Object> children;
    protected StringBuilder types;

    /** inserts the given child object of the given type at the
    given index. */

    public void addChild(final int index, final int type, final Object child) {

        if (child == null) {
			throw new NullPointerException();
		}

        if (children == null) {
            children = new ArrayList<Object>();
            types = new StringBuilder();
        }

        if (type == ELEMENT) {
            if (!(child instanceof Element)) {
				throw new RuntimeException("Element obj expected)");
			}

            ((Element) child).setParent(this);
        }
        else if (!(child instanceof String)) {
			throw new RuntimeException("String expected");
		}

        children.add(index, child);
        types.insert(index, (char) type);
    }

    /** convenience method for addChild (getChildCount (), child) */

    public void addChild(final int type, final Object child) {
        addChild(getChildCount(), type, child);
    }

    /** Builds a default element with the given properties. Elements
    should always be created using this method instead of the
    constructor in order to enable construction of specialized
    subclasses by deriving custom Document classes. Please note:
    For no namespace, please use Xml.NO_NAMESPACE, null is not a
    legal value. Currently, null is converted to Xml.NO_NAMESPACE,
    but future versions may throw an exception. */

    public Element createElement(final String namespace, final String name) {

    	final Element e = new Element();
        e.namespace = namespace == null ? "" : namespace;
        e.name = name;
        return e;
    }

    /** Returns the child object at the given index.  For child
        elements, an Element object is returned. For all other child
        types, a String is returned. */

    public Object getChild(final int index) {
        return children.get(index);
    }

    /** Returns the number of child objects */

    public int getChildCount() {
        return children == null ? 0 : children.size();
    }

    /** returns the element at the given index. If the node at the
    given index is a text node, null is returned */

    public Element getElement(final int index) {
    	final Object child = getChild(index);
        return (child instanceof Element) ? (Element) child : null;
    }

    /** Returns the element with the given namespace and name. If the
        element is not found, or more than one matching elements are
        found, an exception is thrown. */

    public Element getElement(final String namespace, final String name) {

    	final int i = indexOf(namespace, name, 0);
    	final int j = indexOf(namespace, name, i + 1);

        if (i == -1 || j != -1) {
			throw new RuntimeException(
                "Element {"
                    + namespace
                    + '}'
                    + name
                    + (i == -1 ? " not found in " : " more than once in ")
                    + this);
		}

        return getElement(i);
    }

    /* returns "#document-fragment". For elements, the element name is returned 
    
    public String getName() {
        return "#document-fragment";
    }
    
    /** Returns the namespace of the current element. For Node
        and Document, Xml.NO_NAMESPACE is returned. 
    
    public String getNamespace() {
        return "";
    }
    
    public int getNamespaceCount () {
    	return 0;
    }
    
    /** returns the text content if the element has text-only
    content. Throws an exception for mixed content
    
    public String getText() {
    
        StringBuffer buf = new StringBuffer();
        int len = getChildCount();
    
        for (int i = 0; i < len; i++) {
            if (isText(i))
                buf.append(getText(i));
            else if (getType(i) == ELEMENT)
                throw new RuntimeException("not text-only content!");
        }
    
        return buf.toString();
    }
    */

    /** Returns the text node with the given index or null if the node
        with the given index is not a text node. */

    public String getText(final int index) {
        return (isText(index)) ? (String) getChild(index) : null;
    }

    /** Returns the type of the child at the given index. Possible 
    types are ELEMENT, TEXT, COMMENT, and PROCESSING_INSTRUCTION */

    public int getType(final int index) {
        return types.charAt(index);
    }

    /** Convenience method for indexOf (getNamespace (), name,
        startIndex). 
    
    public int indexOf(String name, int startIndex) {
        return indexOf(getNamespace(), name, startIndex);
    }
    */

    /** Performs search for an element with the given namespace and
    name, starting at the given start index. A null namespace
    matches any namespace, please use Xml.NO_NAMESPACE for no
    namespace).  returns -1 if no matching element was found. */

    public int indexOf(final String namespace, final String name, final int startIndex) {

    	final int len = getChildCount();

        for (int i = startIndex; i < len; i++) {

        	final Element child = getElement(i);

            if (child != null
                && name.equals(child.getName())
                && (namespace == null || namespace.equals(child.getNamespace()))) {
				return i;
			}
        }
        return -1;
    }

    public boolean isText(final int i) {
    	final int t = getType(i);
        return t == TEXT || t == IGNORABLE_WHITESPACE || t == CDSECT;
    }

    /** Recursively builds the child elements from the given parser
    until an end tag or end document is found. 
        The end tag is not consumed. */

    public void parse(final XmlPullParser parser)
        throws IOException, XmlPullParserException {

        boolean leave = false;

        do {
        	final int type = parser.getEventType();
            
   //         System.out.println(parser.getPositionDescription());
            
            switch (type) {

                case XmlPullParser.START_TAG :
                    {
                    	final Element child =
                            createElement(
                                parser.getNamespace(),
                                parser.getName());
                        //    child.setAttributes (event.getAttributes ());
                        addChild(ELEMENT, child);

                        // order is important here since 
                        // setparent may perform some init code!

                        child.parse(parser);
                        break;
                    }

                case XmlPullParser.END_DOCUMENT :
                case XmlPullParser.END_TAG :
                    leave = true;
                    break;

                default :
                    if (parser.getText() != null) {
						addChild(
                            type == XmlPullParser.ENTITY_REF ? TEXT : type,
                            parser.getText());
					} else if (
                        type == XmlPullParser.ENTITY_REF
                            && parser.getName() != null) {
                        addChild(ENTITY_REF, parser.getName());
                    }
                    parser.nextToken();
            }
        }
        while (!leave);
    }

    /** Removes the child object at the given index */

    public void removeChild(final int idx) {
        children.remove(idx);

        /***  Modification by HHS - start ***/
        //      types.deleteCharAt (index);
        /***/
        final int n = types.length() - 1;

        for (int i = idx; i < n; i++) {
			types.setCharAt(i, types.charAt(i + 1));
		}

        types.setLength(n);

        /***  Modification by HHS - end   ***/
    }

    /* returns a valid XML representation of this Element including
    	attributes and children. 
    public String toString() {
        try {
            ByteArrayOutputStream bos =
                new ByteArrayOutputStream();
            XmlWriter xw =
                new XmlWriter(new OutputStreamWriter(bos));
            write(xw);
            xw.close();
            return new String(bos.toByteArray());
        }
        catch (IOException e) {
            throw new RuntimeException(e.toString());
        }
    }
    */

    /** Writes this node to the given XmlWriter. For node and document,
        this method is identical to writeChildren, except that the
        stream is flushed automatically. */

    public void write(final XmlSerializer writer) throws IOException {
        writeChildren(writer);
        writer.flush();
    }

    /** Writes the children of this node to the given XmlWriter. */

    public void writeChildren(final XmlSerializer writer) throws IOException {
        if (children == null) {
			return;
		}

        final  int len = children.size();

        for (int i = 0; i < len; i++) {
        	final int type = getType(i);
        	final Object child = children.get(i);
            switch (type) {
                case ELEMENT :
                     ((Element) child).write(writer);
                    break;

                case TEXT :
                    writer.text((String) child);
                    break;

                case IGNORABLE_WHITESPACE :
                    writer.ignorableWhitespace((String) child);
                    break;

                case CDSECT :
                    writer.cdsect((String) child);
                    break;

                case COMMENT :
                    writer.comment((String) child);
                    break;

                case ENTITY_REF :
                    writer.entityRef((String) child);
                    break;

                case PROCESSING_INSTRUCTION :
                    writer.processingInstruction((String) child);
                    break;

                case DOCDECL :
                    writer.docdecl((String) child);
                    break;

                default :
                    throw new RuntimeException("Illegal type: " + type);
            }
        }
    }
}
