/*
 * Copyright (C) 2004, 2005, 2006 Joe Walnes.
 * Copyright (C) 2006, 2007 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 * 
 * Created on 07. March 2004 by Joe Walnes
 */
package com.thoughtworks.xstream.io.path;

import java.util.HashMap;
import java.util.Map;

/**
 * Maintains the current {@link Path} as a stream is moved through.
 *
 * <p>Can be linked to a <a href="../HierarchicalStreamWriter.html">HierarchicalStreamWriter</a> or
 * <a href="../HierarchicalStreamReader.html">HierarchicalStreamReader</a> by wrapping them with a
 * <a href="PathTrackingWriter.html">PathTrackingWriter</a> or
 * <a href="PathTrackingReader.html">PathTrackingReader</a>.</p>
 *
 * <h3>Example</h3>
 *
 * <pre>
 * PathTracker tracker = new PathTracker();
 * tracker.pushElement("table");
 * tracker.pushElement("tr");
 * tracker.pushElement("td");
 * tracker.pushElement("form");
 * tracker.popElement("form");
 * tracker.popElement("td");
 * tracker.pushElement("td");
 * tracker.pushElement("div");
 *
 * Path path = tracker.getPath(); // returns "/table/tr/td[2]/div"
 * </pre>
 *
 * @see Path
 * @see PathTrackingReader
 * @see PathTrackingWriter
 *
 * @author Joe Walnes
 */
public class PathTracker {

    private int pointer;
    private int capacity;
    private String[] pathStack;
    private Map[] indexMapStack;

    private Path currentPath;

    public PathTracker() {
        this(16);
    }

    /**
     * @param initialCapacity Size of the initial stack of nodes (one level per depth in the tree). Note that this is
     *                        only for optimizations - the stack will resize itself if it exceeds its capacity. If in doubt,
     *                        use the other constructor.
     */
    public PathTracker(int initialCapacity) {
        this.capacity = Math.max(1, initialCapacity);
        pathStack = new String[capacity];
        indexMapStack = new Map[capacity];
    }

    /**
     * Notify the tracker that the stream has moved into a new element.
     *
     * @param name Name of the element
     */
    public void pushElement(String name) {
        if (pointer + 1 >= capacity) {
            resizeStacks(capacity * 2);
        }
        pathStack[pointer] = name;
        Map indexMap = indexMapStack[pointer];
        if (indexMap == null) {
            indexMap = new HashMap();
            indexMapStack[pointer] = indexMap;
        }
        if (indexMap.containsKey(name)) {
            indexMap.put(name, new Integer(((Integer) indexMap.get(name)).intValue() + 1));
        } else {
            indexMap.put(name, new Integer(1));
        }
        pointer++;
        currentPath = null;
    }

    /**
     * Notify the tracker that the stream has moved out of an element.
     */
    public void popElement() {
        indexMapStack[pointer] = null;
        currentPath = null;
        pointer--;
    }

    /**
     * @deprecated Use {@link #getPath()} instead.
     */
    public String getCurrentPath() {
        return getPath().toString();
    }

    private void resizeStacks(int newCapacity) {
        String[] newPathStack = new String[newCapacity];
        Map[] newIndexMapStack = new Map[newCapacity];
        int min = Math.min(capacity, newCapacity);
        System.arraycopy(pathStack, 0, newPathStack, 0, min);
        System.arraycopy(indexMapStack, 0, newIndexMapStack, 0, min);
        pathStack = newPathStack;
        indexMapStack = newIndexMapStack;
        capacity = newCapacity;
    }

    /**
     * Current Path in stream.
     */
    public Path getPath() {
        if (currentPath == null) {
            String[] chunks = new String[pointer + 1];
            chunks[0] = "";
            for (int i = 0; i < pointer; i++) {
                Integer integer = ((Integer) indexMapStack[i].get(pathStack[i]));
                int index = integer.intValue();
                if (index > 1) {
                    StringBuffer chunk = new StringBuffer(pathStack[i].length() + 6);
                    chunk.append(pathStack[i]).append('[').append(index).append(']');
                    chunks[i + 1] = chunk.toString();
                } else {
                    chunks[i + 1] = pathStack[i];
                }
            }
            currentPath = new Path(chunks);
        }
        return currentPath;
    }
}
