package org.jcon.util;

import java.util.Vector;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;

/**
 * A wrapper for a DefaultMutableTreeNode that allows easier
 * use and provides additional behavior. The main ease of use
 * improvement is classes no longer have to import
 * DefaultMutableTreeNode or related classes. The wrapper
 * also allows easy future enhancements.
 *
 * The best way to design a hierarchy is for the items to
 * contain a node, not be a node. This class can be that node.
 *
 * @author Jack Harich
 */
public class TNode extends DefaultMutableTreeNode {

//---------- Private Fields ------------------------------
private boolean childrenAreLoaded;
private Vector  listeners = new Vector();

//---------- Initialization ------------------------------
public TNode() {
    super();
}
public TNode(Object userObject) {
    super(userObject);
}
public TNode(Object userObject, boolean allowsChildren) {
    super(userObject, allowsChildren);
}
//---------- Superclass Overrides ------------------------
public int getChildCount() {
    if(! childrenAreLoaded) {
        fireLoadChildren();
        //print(".getChildCount() - " + getUserObject().toString());
        childrenAreLoaded = true;
    }
    return super.getChildCount();
}
// Not an exact override
public TNode getTheChildAt(int index) {
    return (TNode)super.getChildAt(index);
}
//---------- Properties ----------------------------------
public boolean isRoot() {
    return (getParent() == null ? true : false);
}
//----- childrenAreLoaded
/**
 * Useful to avoid loading extra root children. If a
 * dynamically loaded tree is used, it's common to provide
 * the root node with its children already loaded, and to
 * set this property to true for that root.
 */
public void setChildrenAreLoaded(boolean loaded) {
    childrenAreLoaded = loaded;
}
public boolean isChildrenAreLoaded() {
    return childrenAreLoaded;
}
//---------- Events --------------------------------------
/**
 * Adds a listener for TNodeListener callbacks. This only
 * needs to be done to the root node. The root node
 * listeners are the ones that are notified, regardless of
 * which node the event occurred in or which node the
 * listener was added to.
 */
public void addTNodeListener(TNodeListener listener) {
    listeners.addElement(listener);
}
public void removeTNodeListener(TNodeListener listener) {
    listeners.removeElement(listener);
}
// Use root listeners
private void fireLoadChildren() {
    Vector list;
    synchronized(this) {
        list = (Vector)getRootNode().listeners.clone();
    }
//print(".fireLoadChildren() - root listeners size = " + list.size());
    for (int i = 0; i < list.size(); i++) {
        TNodeListener listener = (TNodeListener)list.elementAt(i);
        listener.loadTNodeChildren(this);
    }
}
//---------- Public Methods ------------------------------
public TNode getParentNode() {
    return (TNode)this.getParent();
}
/**
 * This replaces public TreeNode[] getPath(). It allows the
 * client to not import TreeNode and avoid casting to the
 * node's actual class. *** Just cast the array??? ***
 */
public TNode[] getPathNodes() {
    //return (TNode[])getPath();
    
    TreeNode[] p1 = getPath();
    TNode[]    p2 = new TNode[p1.length];
    System.arraycopy(p1, 0, p2, 0, p1.length);
    return p2;
}
/**
 * Removes this node from the tree. The root node cannot
 * currently be removed.
 */
public void removeFromTree() {
  removeFromParent();
  /*    if (getParent() != null) {
        ((DefaultMutableTreeNode)getParent()).remove((MutableTreeNode)this);
        ((DefaultMutableTreeNode)getParent()).nodeStructureChanged((MutableTreeNode)this);
    }
  */
}
public void removeAllChildren() {
    while (getChildCount() > 0) {
        ( (TNode)getFirstChild() ).removeFromTree();
    }
}
/**
 * Returns the tree in a visual format, starting at this
 * node. If this node is the root then the entire tree is
 * returned. Designed for development only.
 */
public String listTree() {
    TNodeList list = new TNodeList(this);
//    print("'" + name + "' ParamNode tree:\n" + list.toString());
    return list.toString();
}
public TNode getRootNode() {
    return (TNode)this.getRoot();
}
/**
 * Inserts the newNode into this node's children at the
 * correct location, preserving the children's toString()
 * order. Assumes the children are already ordered.
 */
public void insertAlphabetically(TNode newNode) {
    String newText = newNode.toString().toLowerCase();
    int childCount = getChildCount();

    // *** Swing 1.02 bug workaround. Was adding 2 nodes if add(NewNode);
    // *** No help, so commented out
    /** if (childCount == 0) {
        insert(newNode, 0);
        return;
    } */
    for (int i = 0; i < childCount; i++) {
        String nodeText = getChildAt(i).toString().toLowerCase();
        if (newText.compareTo(nodeText) < 0) {
            insert(newNode, i);
            //print(".insertAlphabetically() - inserted at " + i);
            return;
        }
    }
    // *** was add(newNode); // Add at end
    insert(newNode, childCount); // *** Another try, works!
    //print(".insertAlphabetically() - inserted at end into " + childCount);
}
/**
* Inserts the newNode at the end of this node's children.
*/
public void insertAtEnd(TNode newNode) {
    insert(newNode, getChildCount());
}
/**
 * Moves this node to the correct location in its siblings,
 * using the toString() to preserve alpha order. This is
 * useful to call after changing a node's toString().
 */
public void moveToCorrectAlphaLocation() {
    TNode parentNode = getParentNode();
    if (parentNode == null) return; // Root

    removeFromTree();
    parentNode.insertAlphabetically(this);
}
//----- static
/**
* Returns a Vector of all nodes in the root. This
* is useful for walking the entire tree to get or set
* properties, etc. RECURSIVE.
*/
public static Vector loadAllTreeNodes(TNode root) {
    Vector nodes = new Vector();
    nodes.addElement(root);
    int count = root.getChildCount();
    for (int i = 0; i < count; i++) {
        TNode node = root.getTheChildAt(i);
        Vector childNodes = loadAllTreeNodes(node);
        DataLib.appendVector(nodes, childNodes);
    }
    return nodes;
}    
// For development
public static String listPathNodes(TNode[] nodes) {
    if (nodes.length == 0) {
        return "#No path nodes#";
    } else {
        String list = "";
        for (int i = 0; i < nodes.length; i++) {
            list += i + " '" + nodes[i].toString() + "'\n";
        }
        return list;
    }
}
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("TNode" + text);
}

} // End class