package pipe.gui;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import pipe.dataLayer.DataLayer;

/**
 * The ModuleManager class contains methods to create swing components to allow the user
 * to load modules and execute methods within them. To use, instantiate a ModuleManager
 * object and use the methods to return the required components.
 *
 * @author Camilla Clifford
 */
public class ModuleManager implements Constants {
//	public CreateGui gui;
	private HashSet installedModules;
	private JTree moduleTree;
	private DefaultTreeModel treeModel;
	private DefaultMutableTreeNode load_modules;
	private String loadNodeString;
	Component parent;

	public ModuleManager() {
		parent = (Component)CreateGui.getApp();
		installedModules = new HashSet();
	}

	/** Returns the root directory under which the modules will be found.
	 * At present this is the same directory in which all the classes are found - this could change.
	 */
	private File getModuleDir() {
		String AppDir = ExtFileManager.getClassRoot(this.getClass()).toString();
		
		// We will now search from the modules directory downwards.
		// This can be changed if it affects performance.
		File modLocation = new File(AppDir + System.getProperty("file.separator"));
		
		if (!modLocation.exists())
			System.out.println("Unable to find Module directory: " + modLocation.getPath());
		
		return modLocation;
	}

	
	/** Finds all the fully qualified (ie: full package names) module
	 *  classnames by recursively searching the rootDirectories
	 * @param rootDir The root directory to start searching from
	 */
	private Vector getModuleClasses(File rootDir)
	{
		Vector classes = new Vector();
		File temp[] = rootDir.listFiles();
		Class aModuleClass;
		
		// our base case just returns the empty vector
		if (temp == null || temp.length == 0 )
			return classes;
		
		//get all the files and directories that are children of the root
		File children[] = rootDir.listFiles();
		for (int a = 0 ; a < children.length ; a++) {
			aModuleClass = ModuleLoader.importModule(children[a]);

      if (aModuleClass != null)
				classes.addElement(aModuleClass);
			
			// go round again
			classes.addAll(getModuleClasses(children[a]));
		}
		
		return classes;
	}

	/** Method creates and returns a Module management tree.
	 *  This consists of two nodes, one resposible for listing
	 *  all the available modules from the module directory,
	 *  and another for admin options such as list refreshing.
	 *  Each node of the tree has it's own user object, for class
	 *  nodes this will be ModuleClass, for method nodes ModuleMethod,
	 *  and another one yet to be implemented for other options.
	 *  When the user clicks on a method node the method is invoked.
	 */
  
  private void addClassToTree(Class moduleClass) {
    DefaultMutableTreeNode modNode = null;
    if (installedModules.add(moduleClass)) {
      modNode  = new DefaultMutableTreeNode(new ModuleClassContainer(moduleClass));

      Class[] desiredParams ={new DataLayer().getClass()};

      // get all this classes public methods
      Method[] methods = methods = moduleClass.getMethods();

      // iterate over the methods to see if they match what we want
      for (int a=0;a<methods.length;a++) {
        Class[] params = methods[a].getParameterTypes();
        // if they match create a new node with that method
        if (Arrays.equals(params,desiredParams)) {
          ModuleMethod m=new ModuleMethod(moduleClass,methods[a]);
          if(methods[a].getName()=="run") m.setName(modNode.getUserObject().toString()); // rename "run" to parent name 
          modNode.add(new DefaultMutableTreeNode(m));
        }
      }
      
      // Replace single leaf branches with leaves
      if(modNode.getChildCount()==1) {
        Object m=((DefaultMutableTreeNode)modNode.getFirstChild()).getUserObject();
        load_modules.add(new DefaultMutableTreeNode(m));
      }
      else load_modules.add(modNode);
    }
  }
	
	public JTree getModuleTree() {
		File dir = getModuleDir();
		
		// get the names of all the classes that are confirmed to be modules
		Vector names = getModuleClasses(dir);
		Enumeration classes = names.elements();
		
		// create the root node
		DefaultMutableTreeNode root = new DefaultMutableTreeNode("Analysis Module Manager");
		
		// create root children
		load_modules = new DefaultMutableTreeNode("Available Modules");
		
		loadNodeString = "Find Module";
		DefaultMutableTreeNode add_modules = new DefaultMutableTreeNode(loadNodeString);
		
		DefaultMutableTreeNode meth1 = null;
		
		// iterate over the class names and create a node for each
		while (classes.hasMoreElements()) {
			try	{
				// create each ModuleClass node using an instantiation of the ModuleClass
        addClassToTree((Class)classes.nextElement());
//				Class moduleClass = ;
//				if (installedModules.add(moduleClass)) {
//					modNode  = new DefaultMutableTreeNode(new ModuleClassContainer(moduleClass));
//
//          Class[] desiredParams ={new DataLayer().getClass()};
//
//          // get all this classes public methods
//          Method[] methods = methods = moduleClass.getMethods();
//
//          // iterate over the methods to see if they match what we want
//          for (int a=0;a<methods.length;a++) {
//            Class[] params = methods[a].getParameterTypes();
//            // if they match create a new node with that method
//            if (Arrays.equals(params,desiredParams)) {
//              ModuleMethod m=new ModuleMethod(moduleClass,methods[a]);
//              if(methods[a].getName()=="run") m.setName(modNode.getUserObject().toString()); // rename "run" to parent name 
//              modNode.add(new DefaultMutableTreeNode(m));
//            }
//          }
//          
//          // Replace single leaf branches with leaves
//          if(modNode.getChildCount()==1) {
//            Object m=((DefaultMutableTreeNode)modNode.getFirstChild()).getUserObject();
//            load_modules.add(new DefaultMutableTreeNode(m));
//          }
//          else load_modules.add(modNode);
//				}
			}
			catch (Throwable e) {
				System.out.println("Error in creating class node");
			}
		}

    root.add(load_modules);
		root.add(add_modules);
    
		treeModel = new DefaultTreeModel(root);
		
		moduleTree = new JTree(treeModel);
		moduleTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		
		moduleTree.addMouseListener(new TreeHandler());
		
		moduleTree.setFocusable(false);
		
		// expand the modules path
		moduleTree.expandPath(moduleTree.getPathForRow(1));
		return moduleTree;
	}

  /** Adds a new node to the Module subtree
	 * @param newNode The new node representing a new module.
	 */
	public void addModuleToTree(DefaultMutableTreeNode newNode) {
//		treeModel.insertNodeInto(newNode,load_modules,load_modules.getChildCount());
//		moduleTree.expandPath(moduleTree.getPathForRow(1));
	}

	/** Removes a node from the Module subtree
	 * @param newNode The node to be removed.
	 */
	public void removeModuleFromTree(DefaultMutableTreeNode newNode) {
		treeModel.removeNodeFromParent(newNode) ;
		treeModel.reload();
	}

	/**
	 * Action object that can be used to remove a module from the ModuleTree
	 */
	class RemoveModuleAction extends AbstractAction {
		DefaultMutableTreeNode removeNode;
		
		RemoveModuleAction(TreePath path) {
			removeNode = (DefaultMutableTreeNode)path.getLastPathComponent();
		}
		
		public void actionPerformed(ActionEvent e) {
      Object o=removeNode.getUserObject();
      if(o instanceof ModuleMethod) installedModules.remove(((ModuleMethod)o).getModClass());
      else if(o instanceof ModuleClassContainer) installedModules.remove(((ModuleClassContainer)o).returnClass());
      else System.err.println("Don't know how to delete class for "+o.getClass()); 
			removeModuleFromTree(removeNode);
			moduleTree.expandPath(moduleTree.getPathForRow(1));
		}
	}

// now add in the action listener to enable module method loading.
	public class TreeHandler extends MouseAdapter {
    private void showPopupMenu(MouseEvent e) {
      TreePath selPath = moduleTree.getPathForLocation(e.getX(), e.getY());
      if (selPath != null) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)selPath.getLastPathComponent();
        Object nodeObj = node.getUserObject();
        
        if ((nodeObj instanceof ModuleClassContainer)||
                (nodeObj instanceof ModuleMethod)
        ) {
          JPopupMenu popup = new JPopupMenu();
          TreePath removePath = moduleTree.getPathForLocation(e.getX(), e.getY());
          JMenuItem menuItem = new JMenuItem(new RemoveModuleAction(removePath));
          menuItem.setText("Remove Module");
          popup.add(menuItem);
          popup.show(e.getComponent(), e.getX(), e.getY());
        }
      }
    }
    public void mouseReleased(MouseEvent e) {
      if (e.isPopupTrigger()) showPopupMenu(e);
    }
		public void mousePressed(MouseEvent e) {
      if (e.isPopupTrigger()) showPopupMenu(e);
		}
    
		public void mouseClicked(MouseEvent e) {
			int selRow = moduleTree.getRowForLocation(e.getX(), e.getY());
			TreePath selPath = moduleTree.getPathForLocation(e.getX(), e.getY());
			if(selRow != -1) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode)selPath.getLastPathComponent();
				Object nodeObj = node.getUserObject();
					
				if(e.getClickCount() == 2)	{
          if (nodeObj instanceof ModuleMethod) {
						DataLayer parameter = CreateGui.currentPNMLData();
            if(parameter!=null)((ModuleMethod)nodeObj).execute(parameter);
					} else if (nodeObj == loadNodeString) {
						DefaultMutableTreeNode newNode ;
						
						//Create a file chooser
						JFileChooser fc = new JFileChooser();
						fc.setFileFilter(new ExtensionFilter(
									PROPERTY_FILE_EXTENSION, PROPERTY_FILE_DESC));
						//In response to a button click:
						int returnVal = fc.showOpenDialog(parent);
						File moduleProp;
						Class newModuleClass;
						if(returnVal == JFileChooser.APPROVE_OPTION) {
							moduleProp = fc.getSelectedFile();
							newModuleClass  = ModuleLoader.importModule(moduleProp);
							
							if (newModuleClass != null) {
                addClassToTree(newModuleClass);
                treeModel.reload();
                moduleTree.expandPath(moduleTree.getPathForRow(1));
							} else
								JOptionPane.showMessageDialog(parent,"Invalid file selected.\n Please ensure the class implements the Module interface and is on the CLASSPATH.","File Selection Error", JOptionPane.ERROR_MESSAGE);
						}
					}
				}
			}
		}
	}
}