/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.axiom.om.impl.dom;

import org.apache.axiom.om.OMContainer;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;

/**
 * This is exactly the same as org.apache.axiom.om.impl.om.OMNavigator, only the llom specifics are
 * changed to dom. Refer to the testClass to find out how to use features like isNavigable,
 * isComplete and step.
 */
public class DOMNavigator {
    /** Field node */
    protected OMNode node;

    /** Field visited */
    private boolean visited;

    /** Field next */
    private OMNode next;

    // root is the starting element. Once the navigator comes back to the
    // root, the traversal is terminated

    /** Field root */
    private OMNode root;

    /** Field backtracked */
    private boolean backtracked;

    // flags that tell the status of the navigator

    /** Field end */
    private boolean end = false;

    /** Field start */
    private boolean start = true;

    /** Constructor OMNavigator. */
    public DOMNavigator() {
    }

    /**
     * Constructor OMNavigator.
     *
     * @param node
     */
    public DOMNavigator(OMNode node) {
        init(node);
    }

    /**
     * Method init.
     *
     * @param node
     */
    public void init(OMNode node) {
        next = node;
        root = node;
        backtracked = false;
    }

    /**
     * Gets the next node.
     *
     * @return Returns OMNode in the sequence of preorder traversal. Note however that an element
     *         node is treated slightly differently. Once the element is passed it returns the same
     *         element in the next encounter as well.
     */
    public OMNode next() {
        if (next == null) {
            return null;
        }
        node = next;
        visited = backtracked;
        backtracked = false;
        updateNextNode();

        // set the starting and ending flags
        if (root.equals(node)) {
            if (!start) {
                end = true;
            } else {
                start = false;
            }
        }
        return node;
    }

    /** Private method to encapsulate the searching logic */
    private void updateNextNode() {
        if ((next instanceof OMElement) && !visited) {
            ElementImpl e = (ElementImpl) next;
            if (e.firstChild != null) {
                next = e.firstChild;
            } else if (e.isComplete()) {
                backtracked = true;
            } else {
                next = null;
            }
        } else {
            OMNode nextSibling = ((ChildNode) next).nextSibling;
            OMContainer parent = next.getParent();
            if (nextSibling != null) {
                next = nextSibling;
            } else if ((parent != null) && parent.isComplete()) {
                next = (NodeImpl) parent;
                backtracked = true;
            } else {
                next = null;
            }
        }
    }

    /**
     * Method visited.
     *
     * @return Returns boolean.
     */
    public boolean visited() {
        return visited;
    }

    /**
     * This is a very special method. This allows the navigator to step once it has reached the
     * existing OM. At this point the isNavigable method will return false but the isComplete method
     * may return false which means that the navigating the given element is not complete but the
     * navigator cannot proceed.
     */
    public void step() {
        if (!end) {
            next = node;
            updateNextNode();
        }
    }

    /**
     * Returns the navigable status.
     *
     * @return Returns boolean.
     */
    public boolean isNavigable() {
        if (end) {
            return false;
        } else {
            return !(next == null);
        }
    }

    /**
     * Returns the completed status.
     *
     * @return Returns boolean.
     */
    public boolean isCompleted() {
        return end;
    }
}
