import java.util.*;
import java.beans.ConstructorProperties;
import java.io.*;
import javax.swing.event.*;
import javax.swing.tree.*;

/**
 * A simple tree data model that uses TreeNodes. For further
 * information and examples that use MyTreeModel, see
 * https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html
 * in The Java Tutorial.
 *
 * @author Rob Davis
 * @author Ray Ryan
 * @author Scott Violet
 */
public class MyTreeModel implements Serializable, TreeModel {
  static final long serialVersionUID = 42;
  /** Root of the tree. */
  protected MyTreeNode root;
  /** Listeners. */
  protected EventListenerList listenerList = new EventListenerList();
  /**
   * Determines how the isLeaf method figures out if a node is a leaf
   * node. If true, a node is a leaf node if it does not allow
   * children. (If it allows children, it is not a leaf node, even if
   * no children are present.) That lets you distinguish between
   * folder nodes and file nodes in a file system, for example.
   * 
   * If this value is false, then any node which has no children is a
   * leaf node, and any node may acquire children.
   */
  protected boolean asksAllowsChildren;

  /**
   * Creates a tree in which any node can have children.
   *
   * @param root a TreeNode object that is the root of the tree
   */
  public MyTreeModel(MyTreeNode root) {
    this(root, false);
  }

  /**
   * Creates a tree specifying whether any node can have children, or
   * whether only certain nodes can have children.
   *
   * @param root a TreeNode object that is the root of the tree
   * @param asksAllowsChildren a boolean, false if any node can
   *        have children, true if each node is asked to see if
   *        it can have children
   * @see #asksAllowsChildren
   */
  public MyTreeModel(MyTreeNode root, boolean asksAllowsChildren) {
    super();
    this.root = root;
    this.asksAllowsChildren = asksAllowsChildren;
  }

  /**
   * Sets whether or not to test leafness by asking getAllowsChildren()
   * or isLeaf() to the TreeNodes. If newvalue is true, getAllowsChildren()
   * is messaged, otherwise isLeaf() is messaged.
   */
  public void setAsksAllowsChildren(boolean newValue) {
    asksAllowsChildren = newValue;
  }

  /**
   * Tells how leaf nodes are determined.
   *
   * @return true if only nodes which do not allow children are
   *         leaf nodes, false if nodes which have no children
   *         (even if allowed) are leaf nodes
   */
  public boolean asksAllowsChildren() {
    return asksAllowsChildren;
  }

  /**
   * Sets the root to root. A null root implies
   * the tree is to display nothing, and is legal.
   */
  // public void setRoot(MyTreeNode root) {
  //   Object oldRoot = this.root;
  //   this.root = root;
  //   if (root == null && oldRoot != null) {
  //     fireTreeStructureChanged(this, null);
  //   }
  //   else {
  //     nodeStructureChanged(root);
  //   }
  // }

  /**
   * Returns the root of the tree. Returns null only if the tree has
   * no nodes.
   *
   * @return  the root of the tree
   */
  public MyTreeNode getRoot() {
    return root;
  }

  /**
   * Returns the index of child in parent.
   * If either the parent or child is null, returns -1.
   * @param parent a note in the tree, obtained from this data source
   * @param child the node we are interested in
   * @return the index of the child in the parent, or -1
   *    if either the parent or the child is null
   */
  public int getIndexOfChild(Object parent, Object child) {
    if(parent == null || child == null)
      return -1;
    return ((MyTreeNode)parent).getIndex((MyTreeNode)child);
  }

  /**
   * Returns the child of parent at index index in the parent's child
   * array. parent must be a node previously obtained from this data
   * source. This should not return null if index is a valid index for
   * parent (that is index >= 0 && index < getChildCount(parent)).
   *
   * @param   parent a node in the tree, obtained from this data source
   * @return  the child of parent at index index
   */
  public MyTreeNode getChild(Object parent, int index) {
    return ((MyTreeNode)parent).getChildAt(index);
  }

  /**
   * Returns the number of children of parent. Returns 0 if the node
   * is a leaf or if it has no children. parent must be a node
   * previously obtained from this data source.
   *
   * @param   parent a node in the tree, obtained from this data source
   * @return  the number of children of the node parent
   */
  public int getChildCount(Object parent) {
    return ((MyTreeNode)parent).getChildCount();
  }

  /**
   * Returns whether the specified node is a leaf node.  The way the
   * test is performed depends on the askAllowsChildren setting.
   *
   * @param node the node to check
   * @return true if the node is a leaf node
   */
  public boolean isLeaf(Object node) {
    if(asksAllowsChildren)
      return !((MyTreeNode)node).getAllowsChildren();
    return ((MyTreeNode)node).isLeaf();
  }

  /**
   * Invoke this method if you've modified the TreeNodes upon which
   * this model depends. The model will notify all of its listeners
   * that the model has changed.
   */
  public void reload() {
    reload(root);
  }

  /**
   * This sets the user object of the TreeNode identified by path and
   * posts a node changed. If you use custom user objects in the
   * TreeModel you're going to need to subclass this and set the user
   * object of the changed node to something meaningful.
   */
  public void valueForPathChanged(TreePath path, Object newValue) {
    MyTreeNode aNode = (MyTreeNode)path.getLastPathComponent();

    aNode.setUserObject(newValue);
    nodeChanged(aNode);
  }

  /**
   * Invoked this to insert newChild at location index in parents
   * children. This will then message nodesWereInserted to create the
   * appropriate event. This is the preferred way to add children as
   * it will create the appropriate event.
   */
  public void insertNodeInto(MyTreeNode newChild,
                             MyTreeNode parent, int index){
    parent.insert(newChild, index);

    int[] newIndexs = new int[1];

    newIndexs[0] = index;
    nodesWereInserted(parent, newIndexs);
  }

  /**
   * Message this to remove node from its parent. This will message
   * nodesWereRemoved to create the appropriate event. This is the
   * preferred way to remove a node as it handles the event creation
   * for you.
   */
  public void removeNodeFromParent(MyTreeNode node) {
    MyTreeNode parent = node.getParent();

    if(parent == null)
      throw new IllegalArgumentException("node does not have a parent.");

    int[] childIndex = new int[1];
    MyTreeNode[] removedArray = new MyTreeNode[1];

    childIndex[0] = parent.getIndex(node);
    parent.remove(childIndex[0]);
    removedArray[0] = node;
    nodesWereRemoved(parent, childIndex, removedArray);
  }

  /**
   * Invoke this method after you've changed how node is to be
   * represented in the tree.
   */
  public void nodeChanged(MyTreeNode node) {
    if(listenerList != null && node != null) {
      MyTreeNode parent = node.getParent();

      if(parent != null) {
        int anIndex = parent.getIndex(node);
        if(anIndex != -1) {
          int[] cIndexs = new int[1];

          cIndexs[0] = anIndex;
          nodesChanged(parent, cIndexs);
        }
      }
      else if (node == getRoot()) {
        nodesChanged(node, null);
      }
    }
  }

  /**
   * Invoke this method if you've modified the TreeNodes upon which
   * this model depends. The model will notify all of its listeners
   * that the model has changed below the given node.
   *
   * @param node the node below which the model has changed
   */
  public void reload(MyTreeNode node) {
    if(node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }

  /**
   * Invoke this method after you've inserted some TreeNodes into
   * node. childIndices should be the index of the new elements and
   * must be sorted in ascending order.
   */
  public void nodesWereInserted(MyTreeNode node, int[] childIndices) {
    if(listenerList != null && node != null && childIndices != null
       && childIndices.length > 0) {
      int cCount = childIndices.length;
      Object[] newChildren = new Object[cCount];

      for(int counter = 0; counter < cCount; counter++)
        newChildren[counter] = node.getChildAt(childIndices[counter]);
      fireTreeNodesInserted(this, getPathToRoot(node), childIndices,
                            newChildren);
    }
  }

  /**
   * Invoke this method after you've removed some TreeNodes from
   * node. childIndices should be the index of the removed elements
   * and must be sorted in ascending order. And removedChildren should
   * be the array of the children objects that were removed.
   */
  public void nodesWereRemoved(MyTreeNode node, int[] childIndices,
                               MyTreeNode[] removedChildren) {
    if(node != null && childIndices != null) {
      fireTreeNodesRemoved(this, getPathToRoot(node), childIndices,
                           removedChildren);
    }
  }

  /**
   * Invoke this method after you've changed how the children
   * identified by childIndicies are to be represented in the tree.
   */
  public void nodesChanged(MyTreeNode node, int[] childIndices) {
    if(node != null) {
      if (childIndices != null) {
        int cCount = childIndices.length;

        if(cCount > 0) {
          Object[] cChildren = new Object[cCount];

          for(int counter = 0; counter < cCount; counter++)
            cChildren[counter] = node.getChildAt
              (childIndices[counter]);
          fireTreeNodesChanged(this, getPathToRoot(node),
                               childIndices, cChildren);
        }
      }
      else if (node == getRoot()) {
        fireTreeNodesChanged(this, getPathToRoot(node), null, null);
      }
    }
  }

  /**
   * Invoke this method if you've totally changed the children of node
   * and its children's children... This will post a
   * treeStructureChanged event.
   */
  public void nodeStructureChanged(MyTreeNode node) {
    if(node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }

  /**
   * Builds the parents of node up to and including the root node,
   * where the original node is the last element in the returned
   * array. The length of the returned array gives the node's depth in
   * the tree.
   *
   * @param aNode the TreeNode to get the path for
   */
  public MyTreeNode[] getPathToRoot(MyTreeNode aNode) {
    return getPathToRoot(aNode, 0);
  }

  /**
   * Builds the parents of node up to and including the root node,
   * where the original node is the last element in the returned
   * array. The length of the returned array gives the node's depth
   * in the tree.
   *
   * @param aNode  the TreeNode to get the path for
   * @param depth  an int giving the number of steps already taken towards
   *        the root (on recursive calls), used to size the returned array
   * @return an array of TreeNodes giving the path from the root to the
   *         specified node
   */
  protected MyTreeNode[] getPathToRoot(MyTreeNode aNode, int depth) {
    MyTreeNode[] retNodes;
    // This method recurses, traversing towards the root in order
    // size the array. On the way back, it fills in the nodes,
    // starting from the root and working back to the original node.

    /* Check for null, in case someone passed in a null node, or
       they passed in an element that isn't rooted at root. */
    if(aNode == null) {
      if(depth == 0)
        return null;
      else
        retNodes = new MyTreeNode[depth];
    }
    else {
      depth++;
      if(aNode == root)
        retNodes = new MyTreeNode[depth];
      else
        retNodes = getPathToRoot(aNode.getParent(), depth);
      retNodes[retNodes.length - depth] = aNode;
    }
    return retNodes;
  }

  //
  //  Events
  //

  /**
   * Adds a listener for the TreeModelEvent posted after the tree
   * changes.
   *
   * @param   l       the listener to add
   */
  public void addTreeModelListener(TreeModelListener l) {
    listenerList.add(TreeModelListener.class, l);
  }

  /**
   * Removes a listener previously added with addTreeModelListener().
   *
   * @see     #addTreeModelListener
   * @param   l       the listener to remove
   */
  public void removeTreeModelListener(TreeModelListener l) {
    listenerList.remove(TreeModelListener.class, l);
  }

  /**
   * Returns an array of all the tree model listeners registered on
   * this model.
   *
   * @return all of this model's TreeModelListeners
   *         or an empty
   *         array if no tree model listeners are currently registered
   */
  public TreeModelListener[] getTreeModelListeners() {
    return listenerList.getListeners(TreeModelListener.class);
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type. The event instance is lazily
   * created using the parameters passed into the fire method.
   *
   * @param source the source of the TreeModelEvent;
   *               typically this
   * @param path the path to the parent of the nodes that changed; use
   *             null to identify the root has changed
   * @param childIndices the indices of the changed elements
   * @param children the changed elements
   */
  protected void fireTreeNodesChanged(Object source, Object[] path,
                                      int[] childIndices,
                                      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==TreeModelListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new TreeModelEvent(source, path,
                                 childIndices, children);
        ((TreeModelListener)listeners[i+1]).treeNodesChanged(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type. The event instance is lazily
   * created using the parameters passed into the fire method.
   *
   * @param source the source of the TreeModelEvent;
   *               typically this
   * @param path the path to the parent the nodes were added to
   * @param childIndices the indices of the new elements
   * @param children the new elements
   */
  protected void fireTreeNodesInserted(Object source, Object[] path,
                                       int[] childIndices,
                                       Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==TreeModelListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new TreeModelEvent(source, path,
                                 childIndices, children);
        ((TreeModelListener)listeners[i+1]).treeNodesInserted(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type. The event instance is lazily
   * created using the parameters passed into the fire method.
   *
   * @param source the source of the TreeModelEvent;
   *               typically this
   * @param path the path to the parent the nodes were removed from
   * @param childIndices the indices of the removed elements
   * @param children the removed elements
   */
  protected void fireTreeNodesRemoved(Object source, Object[] path,
                                      int[] childIndices,
                                      Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==TreeModelListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new TreeModelEvent(source, path,
                                 childIndices, children);
        ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type. The event instance is lazily
   * created using the parameters passed into the fire method.
   *
   * @param source the source of the TreeModelEvent;
   *               typically this
   * @param path the path to the parent of the structure that has changed;
   *             use null to identify the root has changed
   * @param childIndices the indices of the affected elements
   * @param children the affected elements
   */
  protected void fireTreeStructureChanged(Object source, Object[] path,
                                          int[] childIndices,
                                          Object[] children) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==TreeModelListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new TreeModelEvent(source, path,
                                 childIndices, children);
        ((TreeModelListener)listeners[i+1]).treeStructureChanged(e);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type. The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   *
   * @param source the source of the TreeModelEvent;
   *               typically this
   * @param path the path to the parent of the structure that has changed;
   *             use null to identify the root has changed
   */
  private void fireTreeStructureChanged(Object source, TreePath path) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==TreeModelListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new TreeModelEvent(source, path);
        ((TreeModelListener)listeners[i+1]).treeStructureChanged(e);
      }
    }
  }

  /**
   * Returns an array of all the objects currently registered as
   * FooListeners upon this model. FooListeners are registered using
   * the addFooListener method.
   *
   * You can specify the listenerType argument with a class literal,
   * such as FooListener.class. For example, you can query a
   * MyTreeModel m for its tree model listeners with the following
   * code:
   *
   * TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));
   *
   * If no such listeners exist, this method returns an empty array.
   *
   * @param listenerType the type of listeners requested; this parameter
   *          should specify an interface that descends from
   *          java.util.EventListener
   * @return an array of all objects registered as
   *          FooListeners on this component,
   *          or an empty array if no such
   *          listeners have been added
   * @exception ClassCastException if listenerType
   *          doesn't specify a class or interface that implements
   *          java.util.EventListener
   *
   * @see #getTreeModelListeners
   *
   * @since 1.3
   */
  public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
    return listenerList.getListeners(listenerType);
  }

} // End of class MyTreeModel
