/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.sax;

import org.xml.sax.Locator;
import org.xml.sax.SAXParseException;

import java.util.ArrayList;

import android.util.Log;

/**
 * An XML element. Provides access to child elements and hooks to listen
 * for events related to this element.
 *
 * @see RootElement
 */
public class Element {

    final String uri;
    final String localName;
    final int depth;
    final Element parent;
    
    Children children;
    ArrayList<Element> requiredChilden;

    boolean visited;

    StartElementListener startElementListener;
    EndElementListener endElementListener;
    EndTextElementListener endTextElementListener;

    Element(Element parent, String uri, String localName, int depth) {
        this.parent = parent;
        this.uri = uri;
        this.localName = localName;
        this.depth = depth;
    }

    /**
     * Gets the child element with the given name. Uses an empty string as the
     * namespace.
     */
    public Element getChild(String localName) {
        return getChild("", localName);
    }

    /**
     * Gets the child element with the given name.
     */
    public Element getChild(String uri, String localName) {
        if (endTextElementListener != null) {
            throw new IllegalStateException("This element already has an end"
                    + " text element listener. It cannot have children.");
        }

        if (children == null) {
            children = new Children();
        }

        return children.getOrCreate(this, uri, localName);
    }

    /**
     * Gets the child element with the given name. Uses an empty string as the
     * namespace. We will throw a {@link org.xml.sax.SAXException} at parsing
     * time if the specified child is missing. This helps you ensure that your
     * listeners are called.
     */
    public Element requireChild(String localName) {
        return requireChild("", localName);
    }

    /**
     * Gets the child element with the given name. We will throw a
     * {@link org.xml.sax.SAXException} at parsing time if the specified child
     * is missing. This helps you ensure that your listeners are called.
     */
    public Element requireChild(String uri, String localName) {
        Element child = getChild(uri, localName);

        if (requiredChilden == null) {
            requiredChilden = new ArrayList<Element>();
            requiredChilden.add(child);
        } else {
            if (!requiredChilden.contains(child)) {
                requiredChilden.add(child);
            }
        }

        return child;
    }
        
    /**
     * Sets start and end element listeners at the same time.
     */
    public void setElementListener(ElementListener elementListener) {
        setStartElementListener(elementListener);
        setEndElementListener(elementListener);
    }

    /**
     * Sets start and end text element listeners at the same time.
     */
    public void setTextElementListener(TextElementListener elementListener) {
        setStartElementListener(elementListener);
        setEndTextElementListener(elementListener);
    }

    /**
     * Sets a listener for the start of this element.
     */
    public void setStartElementListener(
            StartElementListener startElementListener) {
        if (this.startElementListener != null) {
            throw new IllegalStateException(
                    "Start element listener has already been set.");
        }
        this.startElementListener = startElementListener;
    }

    /**
     * Sets a listener for the end of this element.
     */
    public void setEndElementListener(EndElementListener endElementListener) {
        if (this.endElementListener != null) {
            throw new IllegalStateException(
                    "End element listener has already been set.");
        }
        this.endElementListener = endElementListener;
    }

    /**
     * Sets a listener for the end of this text element.
     */
    public void setEndTextElementListener(
            EndTextElementListener endTextElementListener) {
        if (this.endTextElementListener != null) {
            throw new IllegalStateException(
                    "End text element listener has already been set.");
        }

        if (children != null) {
            throw new IllegalStateException("This element already has children."
                    + " It cannot have an end text element listener.");
        }

        this.endTextElementListener = endTextElementListener;
    }

    @Override
    public String toString() {
        return toString(uri, localName);
    }

    static String toString(String uri, String localName) {
        return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'";
    }

    /**
     * Clears flags on required children.
     */
    void resetRequiredChildren() {
        ArrayList<Element> requiredChildren = this.requiredChilden;
        if (requiredChildren != null) {
            for (int i = requiredChildren.size() - 1; i >= 0; i--) {
                requiredChildren.get(i).visited = false;
            }
        }
    }

    /**
     * Throws an exception if a required child was not present.
     */
    void checkRequiredChildren(Locator locator) throws SAXParseException {
        ArrayList<Element> requiredChildren = this.requiredChilden;
        if (requiredChildren != null) {
            for (int i = requiredChildren.size() - 1; i >= 0; i--) {
                Element child = requiredChildren.get(i);
                if (!child.visited) {
                    throw new BadXmlException(
                            "Element named " + this + " is missing required"
                                    + " child element named "
                                    + child + ".", locator);
                }
            }
        }
    }
}
