// NamespacesTree.java
//
// Created: October 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyNamespace;

import org.tulsoft.shared.UUtils;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

import java.util.HashMap;
import java.util.HashSet;

/**
 * A component showing and manipulating a tree of namespaces
 * registered by a Biomoby registry. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: NamespacesTree.java,v 1.10 2006/04/28 00:13:41 senger Exp $
 */

public class NamespacesTree
    extends CommonTree {

    private static org.apache.commons.logging.Log log =
       org.apache.commons.logging.LogFactory.getLog (NamespacesTree.class);

    // remembered from constructor
    RegistryModel registryModel;
    CommonConsole console;

    final static String NAMESPACES_ACCESS_ERROR =
    "An error happened when accessing a list of available namespaces.\n\n"
    + ACCESS_ERROR_INTRO;

    /*********************************************************************
     * Constructor.
     ********************************************************************/
    public NamespacesTree (RegistryModel registryModel,
			   CommonConsole console) {
	super ("Namespaces");
	this.registryModel = registryModel;
	this.console = console;
	createPopups ("Namespaces Menu");
	setLeafIcon (nsLeafIcon);
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void createPopups (String title) {
	super.createPopups (title);
	addSortingItems();
    }

    /*********************************************************************
     * Get data (usually from a registry model, but if not null, take
     * them from 'newData') and update the tree.
     ********************************************************************/
    public void update (int howSorted, Object newData) {
	if (howSorted < 0) howSorted = lastSorted;
	lastSorted = howSorted;
    
	setEnabledPopup (false);
	final Object source = this;
	final int sorted = howSorted;
	final MobyNamespace[] newNamespaces =
	    (newData == null ? null : (MobyNamespace[])newData);

	final SwingWorker worker = new SwingWorker() {
	    MobyException updateException = null;
	    MobyNamespace[] namespaces = null;
		public Object construct() {
		    try {
		    	if (log.isDebugEnabled())
			    log.debug ("Tree " + treeId + " update request. Sorted: "
				       + sorted + ", Data: " + newNamespaces);
			// get namespaces (unless you already have them)
			if (newNamespaces == null) {
			    namespaces = registryModel.getNamespaces (source);
			} else {
			    namespaces = newNamespaces;
			}

		    } catch (MobyException e) {
			updateException = e;
		    }
		    return namespaces;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (updateException != null)
			error (NAMESPACES_ACCESS_ERROR, updateException);
		    if (namespaces != null) {
			if (sorted == SORTED_BY_AUTHORITY)
			    onUpdateNamespacesTreeByAuth (namespaces);
			else
			    onUpdateNamespacesTree (namespaces);
			if (namespaces.length > 0) {
			    setEnabledPopup (true);
			    boolean sortedByName = (sorted == SORTED_BY_NAME);
			    setEnabledPopupItem (AC_EXPAND, ! sortedByName);
			    setEnabledPopupItem (AC_COLLAPSE, ! sortedByName);
			}
		    }
		}
	    };
	worker.start(); 
    }

    void onUpdateNamespacesTree (MobyNamespace[] theNamespaces) {
    	MobyNamespace[] namespaces = copy (theNamespaces);
	java.util.Arrays.sort (namespaces);
    	
	DefaultTreeModel tModel = (DefaultTreeModel)getModel();
	DefaultMutableTreeNode root = (DefaultMutableTreeNode)tModel.getRoot();
	root.removeAllChildren();   // does not harm if no children exist
	for (int i = 0; i < namespaces.length; i++) {
	    String thisName = namespaces[i].getName();
	    root.add (new DefaultMutableTreeNode (new CommonNode (thisName, CommonNode.NODE_NAMESPACE)));
	}
	tModel.reload();
    }


    void onUpdateNamespacesTreeByAuth (MobyNamespace[] theNamespaces) {
    	MobyNamespace[] namespaces = copy (theNamespaces);
	java.util.Arrays.sort (namespaces, MobyNamespace.getAuthorityComparator());
    	
	DefaultTreeModel tModel = (DefaultTreeModel)getModel();
	DefaultMutableTreeNode root = (DefaultMutableTreeNode)tModel.getRoot();
	root.removeAllChildren();   // does not harm if no children exist
	HashMap nodes = new HashMap (namespaces.length);
	for (int i = 0; i < namespaces.length; i++) {
	    String thisName = namespaces[i].getName();
	    DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode)nodes.get (thisName);
	    if (thisNode == null) {
		thisNode = new DefaultMutableTreeNode (new CommonNode (thisName, CommonNode.NODE_NAMESPACE));
		nodes.put (thisName, thisNode);
	    }
	    String authority = namespaces[i].getAuthority();
	    if (UUtils.isEmpty (authority))
		authority = "<unknown>";
	    DefaultMutableTreeNode authNode = (DefaultMutableTreeNode)nodes.get (authority);
	    if (authNode == null) {
		authNode = new DefaultMutableTreeNode (new CommonNode (authority, CommonNode.NODE_AUTHORITY));
		nodes.put (authority, authNode);
		root.add (authNode);
	    }
	    authNode.add (thisNode);
	}
	tModel.reload();
    }

    /*********************************************************************
     * Make a private copy (of pointers) that will be used for sorting...
     ********************************************************************/
    private MobyNamespace[] copy (MobyNamespace[] s) {
        synchronized (s) {
	    MobyNamespace[] result = new MobyNamespace [s.length];
            System.arraycopy (s, 0, result, 0, s.length);
            return result;
        }
    }

    /*********************************************************************
     * Reload the tree from the Biomoby registry, ignoring (and
     * updating) cache.
     ********************************************************************/
    protected void reload() {
	update (lastSorted, null);
    }

    /*********************************************************************
     * Search underlying objects and highligh nodes corresponding to
     * the found objects.
     ********************************************************************/
    protected void search (String searchText) {
	final String regex = searchText;
	final SwingWorker worker = new SwingWorker() {
		HashSet found = new HashSet();
		public Object construct() {
		    try {
			if (UUtils.notEmpty (regex))
			    found = registryModel.findInNamespaces (regex);
		    } catch (MobyException e) {
			error (NAMESPACES_ACCESS_ERROR, e);
		    }
		    return found;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (found != null)
			highlightAndJumpTo (found);
		}
	    };
	worker.start(); 
    }

    /*********************************************************************
     * Get selected item from a registry model and print it to a
     * console window. Note that the 'node' can be null (if nothing is
     * selected).
     ********************************************************************/
    protected void selected (DefaultMutableTreeNode node) {
	if (node == null) return;
	final CommonNode nodeObject = (CommonNode)node.getUserObject();
	final SwingWorker worker = new SwingWorker() {
		MobyNamespace namespace;
		public Object construct() {
		    try {
			namespace = registryModel.getNamespace (nodeObject.getValue());
		    } catch (MobyException e) {
			error (NAMESPACES_ACCESS_ERROR, e);
		    }
		    return namespace;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (namespace != null)
			console.setText (namespace.toString() + "\n");
		}
	    };
	worker.start(); 
    }

}
