package org.jcon.ba.tree;

import org.jcon.ba.system.*;
import org.jcon.param.Param;
import org.jcon.param.ParamDriven;
import org.jcon.inspect.Inspector;
import org.jcon.ui.JPopupMenuHelper;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import org.jcon.util.TNode;
import org.jcon.util.msg.Message;
import org.jcon.util.msg.MessageDef;
import org.jcon.util.msg.MessageListener;
import org.jcon.util.msg.MessageSourceStd;
import java.util.Vector;

/**
 * Manages the visual item tree subsystem. Each "instance
 * tree" is the hierarchy of Items (Baskets and/or Beans)
 * which are a running system.
 *
 * Currently this class is also the view's CONTROLLER.
 *
 * @author Jack Harich
 */
public class TreeMgr extends MessageSourceStd
    implements MessageListener {

//---------- Private Fields ------------------------------
private Vector views = new Vector(); // TreeView
private Vector itemTreeListeners = new Vector();

//---------- MessageSourceStd Abstractions ---------------
public Vector getMessageDefs() {
    Vector defs = new Vector();
    MessageDef def;

    def = new MessageDef("GroupClosed", this);
    def.add("GroupID", Object.class, "Identifies an item group.");
    defs.addElement(def);

    def = new MessageDef("EditParam", this);
    def.add("Param", Param.class, "The Param to edit.");
    def.add("GroupID", Object.class, "Identifies an item group.");
    def.add("Item", Item.class, "The item selected.");
    defs.addElement(def);

    def = new MessageDef("EditNode", this);
    def.add("EditType", String.class, "Type of edit.");
    def.add("Item", Item.class, "The item to edit.");
    defs.addElement(def);

    def = new MessageDef("InspectItem", this);
    def.add("GroupID", Object.class, "Identifies an item group.");
    def.add("Item", Item.class, "The item to edit.");
    defs.addElement(def);

    def = new MessageDef("ItemSelected", this);
    def.add("GroupID", Object.class, "Identifies an item group.");
    def.add("Item", Item.class, "The item selected.");
// ***    def.add("ContainerPreferred", Boolean.TYPE, "True if container preferred.");
    def.add("Param", Param.class, "Selected and preferred Param, may be null.");
    defs.addElement(def);

    return defs;
}
//---------- MessageListener Implementation --------------
public void processMessage(Message message) {
    String name = message.getName();
    //print(".processMessage() - name " + name);

    if (name == "TreeOpened") {
        treeOpened(message);

    } else if (name == "CloseAllTrees") {
        closeAllTrees();

    } else if (name == "ParamApplied") {
        paramApplied(message);
    }
}
public String[] loadMessageInterests() {
    return new String[] {"TreeOpened", "CloseAllTrees",
        "ParamApplied"};
}
//---------- Events --------------------------------------
/**
 * Note these are named Messages.
 */
public void addMessageListener(String eventName, MessageListener listener) {
    messageRouter.addListener(eventName, listener);
}
public void removeMessageListener(String eventName, MessageListener listener) {
    messageRouter.removeListener(eventName, listener);
}
//----- itemTreeListeners
public void addItemTreeListener(ItemTreeListener listener) {
    itemTreeListeners.addElement(listener);
}
public void removeItemTreeListener(ItemTreeListener listener) {
    itemTreeListeners.removeElement(listener);
}
//---------- Public Methods ------------------------------
public boolean closeTreeRequested(TreeView view) {
    TNode rootNode = view.getRootNode();
    Item rootItem = (Item)rootNode.getUserObject();
    // Note we do not completely close the BATree, ie ourself
    boolean isBATree = view.isBATree();
    String canCloseMessage = rootItem.canClose();
    if (canCloseMessage == null) {
        if (! isBATree) rootItem.close();
        view.dispose();
        views.removeElement(view); // <-----<<<

        // Fire TreeClosed
        ItemTreeEvent evt = new ItemTreeEvent(rootItem);
        evt.setBATree(isBATree);
        evt.setGroupID(view.getRootNode());
        evt.makeImmutable();
        //print(".closeTreeRequested() - sending TreeClosed event");
        evt.notifyListeners(itemTreeListeners, this);

        // Fire GroupClosed
        Message message = new Message("GroupClosed");
        message.set("GroupID", view.getRootNode());
        messageRouter.fire(message, this);

        return true;
    } else {
        GenLib.helpfulHint("Sorry, this tree cannot be " +
            "closed because:\n\n" + canCloseMessage);
        return false;
    }
}
public void closeAllTrees() {
    Vector viewsClone = new Vector();
    synchronized(this) {
        viewsClone = (Vector)views.clone();
    }
    for (int i = 0; i < viewsClone.size(); i++) {
        TreeView view = (TreeView)viewsClone.elementAt(i);
        closeTreeRequested(view);
    }
}
//---------- Package Methods -----------------------------
//----- Tool commands
void inspectItem(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else {
        // Fire InspectItem. GroupID = item.getRootNode().
        Message message = new Message("InspectItem");
        message.set("GroupID", view.getRootNode());
        message.set("Item", item);
        messageRouter.fire(message, this);
    }
}
void editParam(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
        return;
    }
    // Get param to edit
    Param param = getSelectedParam(view);

    // Do not fire event if nothing to edit
    if (param == null) {
        GenLib.beep();
        return;
    }
    // Fire EditParam Message event
    Message message = new Message("EditParam");
    message.set("Param", param);
    message.set("GroupID", view.getRootNode());
    message.set("Item", item);

    TNode rootNode = view.getRootNode();
    Item  rootItem = (Item)rootNode.getUserObject();
    message.set("RootItem", rootItem);

//print(".editParam() - about to fire " + message.getName());
    messageRouter.fire(message, this);
}
//----- Other
void editNode(String command, TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else {
        Message message = new Message("EditNode");
        message.set("EditType", command);
        message.set("Item", item);
        messageRouter.fire(message, this);
    }
}
//---------- Package Methods -----------------------------
// These serve all views we are managing
//----- ItemState commands
void startItem(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else if (! item.isStartable() ) {
        GenLib.beep();
    } else {
        item.start();
        view.refreshSelectedNode();
    }
}
void pauseItem(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else if (! item.isPausable() ) {
        GenLib.beep();
    } else {
        item.pause();
        view.refreshSelectedNode();
    }
}
void resumeItem(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else if (! item.isResumable() ) {
        GenLib.beep();
    } else {
        item.resume();
        view.refreshSelectedNode();
    }
}
void closeItem(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else if (! item.isClosable() ) {
        GenLib.beep();
    } else {
        item.close();
        view.refreshSelectedNode();
    }
}
//----- Open commands
void openNode(TreeView view, boolean openBranch) {
    Item item = view.getSelectedItem();
    if (item == null) {
        GenLib.beep();
    } else if (! item.isContainer()) {
        GenLib.beep();
    } else {
        Basket basket = item.getBasket();
        if (openBranch) {
            basket.openBranch();
            view.refreshSelectedNode();
        } else if (! openBranch) {
            basket.openChildren();
            view.refreshSelectedNode();
        }
    }
}
//----- Other
void configItemPopup(JPopupMenuHelper helper, Item item,
        TreeView view) {

    helper.setEnabled("StartItem",  item.isStartable());
    helper.setEnabled("PauseItem",  item.isPausable());
    helper.setEnabled("ResumeItem", item.isResumable());
    helper.setEnabled("CloseItem",  item.isClosable());

    helper.setEnabled("OpenChildren", item.isContainer());
    helper.setEnabled("OpenBranch",   item.isContainer());

    Param param = getSelectedParam(view);
    helper.setEnabled("EditParam", param != null);
}
void itemSelected(Item item, TreeView view) {
    // Refresh status text
    String text = "Neither Container nor BeanWrapper";
    if (item.isBeanWrapper()) {
        // Bean. Not enough room for itemState since className so long.
        text = item.getBeanInstance().getClass().getName();
    } else if (item.isContainer()) {
        text = item.getItemStateString() + " container has " +
            (item.getBasket().getItemCount()) + " items.";
    }
    view.setStatusText(text);
    // Fire ItemSelected Message
    Message message = new Message("ItemSelected");
    message.set("GroupID", view.getRootNode());
    message.set("Item", item);
// ***    message.setBoolean("ContainerPreferred", view.isContainerPreferred());
    message.set("Param", getSelectedParam(view));
    messageRouter.fire(message, this);
}
//---------- Private Methods -----------------------------
// Returns null if none
private Param getSelectedParam(TreeView view) {
    Item item = view.getSelectedItem();
    if (item == null) return null;

    Param param = null;
    if (item.isContainer() && item.isBeanWrapper()) {
        if (view.isContainerPreferred()) {
            param = item.getBasket().getParam();
        } else {
            Object bean = item.getBeanInstance();
            if (bean instanceof ParamDriven) {
                param = ((ParamDriven)bean).getParam();
            } else {
                param = item.getBasket().getParam();
            }
        }
    } else if (item.isContainer()) {
        param = item.getBasket().getParam();

    } else if (item.isBeanWrapper()) {
        Object bean = item.getBeanInstance();
        if (bean instanceof ParamDriven) {
            param = ((ParamDriven)bean).getParam();
        }
    }
    return param; // May be null
}
private void treeOpened(Message message) {

    String rootClassName = message.getString("RootClassName");
    Item rootItem = (Item)message.get("RootItem");

    // Avoid opening same tree twice
    Object groupID = rootItem.getNode();
    if (findViewWithGroupID(groupID) != null) {
        print(".treeOpened() - Ignoring request to open tree '" + rootClassName + "' twice");
        return;
    }
    boolean isBATree = message.isTrue("IsBATree");

    //print(".treeOpened() - rootClassName " + rootClassName);
    TreeView view = new TreeView();
    view.setController(this);
    view.setRootNode(rootItem.getNode());
    view.setBATree(isBATree);
    view.setTitle(DataLib.getLastToken(rootClassName, ".")
        + " - System Tree");
    views.addElement(view); // <-----<<<

    // Done
    view.setVisible(true);
}
private void paramApplied(Message message) {
    // We want to refresh the node that was applied
    TreeView view = findViewWithGroupID(message.get("GroupID"));
    if (view == null) {
        GenLib.error("TreeMgr.paramApplied()",
            "Cannot find view with GroupID " + message.get("GroupID"));
    } else {
        // For now, refreshSelectedNode(), since tree collapsed anyhow
        view.refreshSelectedNode();
        // Later search tree for Param in Message
        // view.refreshNode(nodeWithParam);
    }
}
private TreeView findViewWithGroupID(Object groupID) {
    for (int i = 0; i < views.size(); i++) {
        TreeView view = (TreeView) views.elementAt(i);
        if (view.getRootNode() == groupID) return view;
    }
    return null;
}
//--- Std
private static void print(String text) {
    System.out.println("TreeMgr" + text);
}

} // End class
