/*
 * Copyright (c) 1998, 2021, 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.
 */

package javax.swing.text;

import java.util.Enumeration;
import java.util.Stack;

/**
 * {@code ElementIterator}, as the name suggests, iterates over the
 * {@code Element} tree. The constructor can be invoked with either
 * {@code Document} or an {@code Element} as an argument. If the constructor is
 * invoked with a {@code Document} as an argument then the root of the iteration
 * is the return value of {@code document.getDefaultRootElement()}.
 * <p>
 * The iteration happens in a depth-first manner. In terms of how boundary
 * conditions are handled:
 * <ul>
 *   <li>if {@link #next} is called before {@link #first} or {@link #current},
 *   the root will be returned
 *   <li>{@link #next} returns {@code null} to indicate the end of the list
 *   <li>{@link #previous} returns {@code null} when the current element is the
 *   root or {@link #next} has returned {@code null}
 * </ul>
 * <p>
 * The {@code ElementIterator} does no locking of the {@code Element} tree. This
 * means that it does not track any changes. It is the responsibility of the
 * user of this class, to ensure that no changes happen during element
 * iteration.
 * <p>
 * Simple usage example:
 * <pre>{@code public void iterate() {
 *      ElementIterator it = new ElementIterator(root);
 *      Element elem;
 *      while (true) {
 *          if ((elem = it.next()) != null) {
 *              // process element
 *              System.out.println("elem: " + elem.getName());
 *          } else {
 *              break;
 *          }
 *      }
 * }}</pre>
 *
 * @author Sunita Mani
 */
public class ElementIterator implements Cloneable {

    private Element root;
    private Stack<StackItem> elementStack = null;

    /**
     * The {@code StackItem} class stores the element as well as a child index.
     * If the index is -1, then the element represented on the stack is the
     * element itself. Otherwise, the index functions as an index into the
     * vector of children of the element. In this case, the item on the stack
     * represents the "index"th child of the element.
     */
    private static class StackItem implements Cloneable {
        Element item;
        int childIndex;

        private StackItem(Element elem) {
            /**
             * -1 index implies a self reference,
             * as opposed to an index into its
             * list of children.
             */
            this.item = elem;
            this.childIndex = -1;
        }

        private void incrementIndex() {
            childIndex++;
        }

        private Element getElement() {
            return item;
        }

        private int getIndex() {
            return childIndex;
        }

        protected Object clone() throws java.lang.CloneNotSupportedException {
            return super.clone();
        }
    }

    /**
     * Creates a new {@code ElementIterator}. The root element is taken to get
     * the default root element of the document.
     *
     * @param  document a {@code Document}
     */
    public ElementIterator(Document document) {
        root = document.getDefaultRootElement();
    }

    /**
     * Creates a new {@code ElementIterator}.
     *
     * @param  root the root {@code Element}
     */
    public ElementIterator(Element root) {
        this.root = root;
    }

    /**
     * Clones the {@code ElementIterator}.
     *
     * @return a cloned {@code ElementIterator} Object
     */
    public synchronized Object clone() {

        try {
            ElementIterator it = new ElementIterator(root);
            if (elementStack != null) {
                it.elementStack = new Stack<StackItem>();
                for (int i = 0; i < elementStack.size(); i++) {
                    StackItem item = elementStack.elementAt(i);
                    StackItem clonee = (StackItem)item.clone();
                    it.elementStack.push(clonee);
                }
            }
            return it;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * Fetches the first element.
     *
     * @return an {@code Element}
     */
    public Element first() {
        // just in case...
        if (root == null) {
            return null;
        }

        elementStack = new Stack<StackItem>();
        if (root.getElementCount() != 0) {
            elementStack.push(new StackItem(root));
        }
        return root;
    }

    /**
     * Fetches the current depth of element tree.
     *
     * @return the depth
     */
    public int depth() {
        if (elementStack == null) {
            return 0;
        }
        return elementStack.size();
    }

    /**
     * Fetches the current {@code Element}.
     *
     * @return element on top of the stack or {@code null} if the root element
     *         is {@code null}
     */
    public Element current() {

        if (elementStack == null) {
            return first();
        }

        /*
          get a handle to the element on top of the stack.
        */
        if (! elementStack.empty()) {
            StackItem item = elementStack.peek();
            Element elem = item.getElement();
            int index = item.getIndex();
            // self reference
            if (index == -1) {
                return elem;
            }
            // return the child at location "index".
            return elem.getElement(index);
        }
        return null;
    }

    /**
     * Fetches the next {@code Element}. The strategy used to locate the next
     * element is a depth-first search.
     *
     * @return the next element or {@code null} at the end of the list
     */
    public Element next() {

        /* if current() has not been invoked
           and next is invoked, the very first
           element will be returned. */
        if (elementStack == null) {
            return first();
        }

        // no more elements
        if (elementStack.isEmpty()) {
            return null;
        }

        // get a handle to the element on top of the stack

        StackItem item = elementStack.peek();
        Element elem = item.getElement();
        int index = item.getIndex();

        if (index+1 < elem.getElementCount()) {
            Element child = elem.getElement(index+1);
            if (child.isLeaf()) {
                /* In this case we merely want to increment
                   the child index of the item on top of the
                   stack.*/
                item.incrementIndex();
            } else {
                /* In this case we need to push the child(branch)
                   on the stack so that we can iterate over its
                   children. */
                elementStack.push(new StackItem(child));
            }
            return child;
        } else {
            /* No more children for the item on top of the
               stack therefore pop the stack. */
            elementStack.pop();
            if (!elementStack.isEmpty()) {
                /* Increment the child index for the item that
                   is now on top of the stack. */
                StackItem top = elementStack.peek();
                top.incrementIndex();
                /* We now want to return its next child, therefore
                   call next() recursively. */
                return next();
            }
        }
        return null;
    }

    /**
     * Fetches the previous {@code Element}. If however the current element is
     * the last element, or the current element is {@code null}, then
     * {@code null} is returned.
     *
     * @return previous {@code Element} if available
     */
    public Element previous() {

        int stackSize;
        if (elementStack == null || (stackSize = elementStack.size()) == 0) {
            return null;
        }

        // get a handle to the element on top of the stack
        //
        StackItem item = elementStack.peek();
        Element elem = item.getElement();
        int index = item.getIndex();

        if (index > 0) {
            /* return child at previous index. */
            return getDeepestLeaf(elem.getElement(--index));
        } else if (index == 0) {
            /* this implies that current is the element's
               first child, therefore previous is the
               element itself. */
            return elem;
        } else if (index == -1) {
            if (stackSize == 1) {
                // current is the root, nothing before it.
                return null;
            }
            /* We need to return either the item
               below the top item or one of the
               former's children. */
            StackItem top = elementStack.pop();
            item = elementStack.peek();

            // restore the top item.
            elementStack.push(top);
            elem = item.getElement();
            index = item.getIndex();
            return ((index == -1) ? elem : getDeepestLeaf(elem.getElement
                                                          (index)));
        }
        // should never get here.
        return null;
    }

    /**
     * Returns the last child of {@code parent} that is a leaf. If the last
     * child is a not a leaf, this method is called with the last child.
     */
    private Element getDeepestLeaf(Element parent) {
        if (parent.isLeaf()) {
            return parent;
        }
        int childCount = parent.getElementCount();
        if (childCount == 0) {
            return parent;
        }
        return getDeepestLeaf(parent.getElement(childCount - 1));
    }

    /**
     * Iterates through the element tree and prints out each element and its
     * attributes.
     */
    private void dumpTree() {

        Element elem;
        while (true) {
            if ((elem = next()) != null) {
                System.out.println("elem: " + elem.getName());
                AttributeSet attr = elem.getAttributes();
                String s = "";
                Enumeration<?> names = attr.getAttributeNames();
                while (names.hasMoreElements()) {
                    Object key = names.nextElement();
                    Object value = attr.getAttribute(key);
                    if (value instanceof AttributeSet) {
                        // don't go recursive
                        s = s + key + "=**AttributeSet** ";
                    } else {
                        s = s + key + "=" + value + " ";
                    }
                }
                System.out.println("attributes: " + s);
            } else {
                break;
            }
        }
    }
}
