/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 1999-2005 Julian Hyde
// Copyright (C) 2005-2013 Pentaho and others
// All Rights Reserved.
//
// jhyde, 1 March, 1999
*/

package mondrian.olap;

import mondrian.util.ArrayStack;

import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * Walks over a tree, returning nodes in prefix order.  Objects which are an
 * instance of {@link Walkable} supply their children using
 * <code>getChildren()</code>; other objects are assumed to have no children.
 *
 * <p>If the tree is modified during the enumeration, strange things may happen.
 *
 * <p>Example use:<code><pre>
 *    Tree t;
 *    Walker w = new Walker(t);
 *    while (w.hasMoreElements()) {
 *      Tree node = (Tree) w.nextNode();
 *      System.out.println(node.toString());
 *    }
 * </pre></code>
 */
public class Walker implements Enumeration {
    /**
     * The active parts of the tree from the root to nextNode are held in a
     * stack.  When the stack is empty, the enumeration finishes.  currentFrame
     * holds the frame of the 'current node' (the node last returned from
     * nextElement()) because it may no longer be on the stack.
     */
    private final ArrayStack stack;
    private Frame currentFrame;
    private Object nextNode;

    private class Frame {
        Frame(Frame parent, Object node) {
            this.parent = parent;
            this.node = node;
            this.children = getChildren(node);
            this.childIndex = -1; // haven't visited first child yet
        }
        final Frame parent;
        final Object node;
        final Object[] children;
        int childIndex;
    }

    public Walker(Walkable root)
    {
        stack = new ArrayStack();
        currentFrame = null;
        visit(null, root);
    }

    private void moveToNext()
    {
        if (stack.isEmpty()) {
            return;
        }
        currentFrame = (Frame) stack.peek();

        // Unwind stack until we find a level we have not completed.
        do {
            Frame frame = (Frame) stack.peek();
            if (frame.children != null
                && ++frame.childIndex < frame.children.length)
            {
                // Here is an unvisited child.  Visit it.
                visit(frame, frame.children[frame.childIndex]);
                return;
            }
            stack.pop();
        } while (!stack.isEmpty());
        nextNode = null;
    }

    private void visit(Frame parent, Object node)
    {
        nextNode = node;
        stack.add(new Frame(parent, node));
    }

    public boolean hasMoreElements()
    {
        return nextNode != null;
    }

    public Object nextElement()
    {
        moveToNext();
        return currentFrame.node;
    }

    /** Tell walker that we don't want to visit any (more) children of this
     * node.  The next node visited will be (a return visit to) the node's
     * parent.  Not valid until nextElement() has been called. */
    public void prune()
    {
        if (currentFrame.children != null) {
            currentFrame.childIndex = currentFrame.children.length;
        }
        // we need to make that next frame on the stack is not a child
        // of frame we just pruned. if it is, we need to prune it too
        if (this.hasMoreElements()) {
            Object nextFrameParentNode = ((Frame)stack.peek()).parent.node;
            if (nextFrameParentNode != currentFrame.node) {
                return;
            }
            // delete the child of current member from the stack
            stack.pop();
            if (currentFrame.parent != null) {
                currentFrame = currentFrame.parent;
            }
            nextElement();
        }
    }

    public void pruneSiblings()
    {
        prune();
        currentFrame = currentFrame.parent;
        if (currentFrame != null) {
            prune();
        }
    }


    /** returns the current object.  Not valid until nextElement() has been
        called. */
    public Object currentElement()
    {
        return currentFrame.node;
    }

    /** returns level in the tree of the current element (that is, last element
     * returned from nextElement()).  The level of the root element is 0. */
    public int level()
    {
        int i = 0;
        for (Frame f = currentFrame; f != null; f = f.parent) {
            i++;
        }
        return i;
    }

    public final Object getParent()
    {
        return getAncestor(1);
    }

    public final Object getAncestor(int iDepth)
    {
        Frame f = getAncestorFrame(iDepth);
        return f == null ? null : f.node;
    }

    /** returns the <code>iDepth</code>th ancestor of the current element */
    private Frame getAncestorFrame(int iDepth)
    {
        for (Frame f = currentFrame; f != null; f = f.parent) {
            if (iDepth-- == 0) {
                return f;
            }
        }
        return null;
    }

    /** get the ordinal within its parent node of the current node.  Returns 0
        for the root element.  Equivalent to getAncestorOrdinal(0). */
    public int getOrdinal()
    {
        // We can't use currentFrame.parent.iChild because moveToNext() may
        // have changed it.
        return currentFrame.parent == null
            ? 0
            : arrayFind(currentFrame.parent.children, currentFrame.node);
    }

    /** get the ordinal within its parent node of the <code>iDepth</code>th
     * ancestor. */
    public int getAncestorOrdinal(int iDepth)
    {
        Frame f = getAncestorFrame(iDepth);
        return f == null
            ? -1
            : f.parent == null
            ? 0
            : arrayFind(f.parent.children, f.node);
    }

    /** Override this function to prune the tree, or to allow objects which are
     * not Walkable to have children. */
    public Object[] getChildren(Object node)
    {
        if (node instanceof Walkable) {
            return ((Walkable) node).getChildren();
        } else {
            return null;
        }
    }

    private static int arrayFind(Object[] array, Object o)
    {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == o) {
                return i;
            }
        }
        return -1;
    }

    private static class Region implements Walkable
    {
        String name;
        Region[] children;

        Region(String name, Region[] children)
        {
            this.name = name;
            this.children = children;
        }

        public Object[] getChildren() {
            return children;
        }

        public static void walkUntil(Walker walker, String name) {
            while (walker.hasMoreElements()) {
                Region region = (Region) walker.nextElement();
                if (region.name.equals(name)) {
                    break;
                }
            }
        }
    };

    public static void main(String[] args)
    {
        PrintWriter pw = new PrintWriter(System.out);
        Region usa = new Region(
            "USA", new Region[] {
            new Region(
                "CA", new Region[] {
                    new Region(
                        "San Francisco", new Region[] {
            new Region(
                "WesternAddition", new Region[] {
                    new Region("Haight", null)}),
                    new Region("Soma", null)
                }),
                new Region("Los Angeles", null)}),
            new Region(
                "WA", new Region[] {
                    new Region("Seattle", null),
                    new Region("Tacoma", null)})});

        Walker walker = new Walker(usa);
        if (false) {
            while (walker.hasMoreElements()) {
                Region region = (Region) walker.nextElement();
                pw.println(region.name);
                pw.flush();
            }
        }

        Region.walkUntil(walker, "CA");
        walker.prune();
        Region region = (Region) walker.nextElement(); // should be WA
        pw.println(region.name);
        pw.flush();

        walker = new Walker(usa);
        Region.walkUntil(walker, "USA");
        walker.prune();
        region = (Region) walker.nextElement(); // should be null
        if (region == null) {
            pw.println("null");
        }
        pw.flush();

        walker = new Walker(usa);
        Region.walkUntil(walker, "Los Angeles");
        walker.prune();
        region = (Region) walker.nextElement(); // should be WA
        pw.println(region.name);
        pw.flush();

        walker = new Walker(usa);
        Region.walkUntil(walker, "Haight");
        walker.prune();
        region = (Region) walker.nextElement(); // should be Soma
        pw.println(region.name);
        pw.flush();

        walker = new Walker(usa);
        Region.walkUntil(walker, "Soma");
        walker.prune();
        region = (Region) walker.nextElement(); // should be Los Angeles
        pw.println(region.name);
        pw.flush();

        walker = new Walker(usa);
        Region.walkUntil(walker, "CA");
        walker.pruneSiblings();
        region = (Region) walker.nextElement(); // should be Los Angeles
        if (region == null) {
            pw.println("null");
            pw.flush();
        }

        walker = new Walker(usa);
        Region.walkUntil(walker, "Soma");
        walker.pruneSiblings();
        region = (Region) walker.nextElement(); // should be Los Angeles
        if (region == null) {
            pw.println("null");
            pw.flush();
        }
    }
}


// End Walker.java
