/*
 * Created on Sep 20, 2006
 *
 * Copyright Alok Saldnaha, all rights reserved.
 */
package edu.stanford.genetics.treeview.core;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.swing.JOptionPane;

import edu.stanford.genetics.treeview.*;

public class PluginManager {
	private static String s_pluginclassfile = "tv_plugins.cd";
	/** holds global plugin manager 
	 * I hate static variables, but this one looks necessary.
	 * */
	private static PluginManager pluginManager= new PluginManager();
	public static PluginManager getPluginManager() {
		return pluginManager;
	}

	/**
	 * holds list of all plugin factories
	 */
	private java.util.Vector pluginFactories = new Vector();

	public File[] readdir(String s_dir) {
		File f_dir = new File(s_dir);
		FileFilter fileFilter = new FileFilter() {
			public boolean accept(File file) {
				return file.getName().endsWith("jar");
			}
		};
		return f_dir.listFiles(fileFilter);
	}

	/*
	 * EFFECTS: Reads the file <s_pluginclassfile> from a jar RETURNS: string
	 * array of all classes to be declared NOTE: This method should probably be
	 * moved into a PluginManager class
	 */
	private ArrayList getClassDeclarations(JarFile jf)
			throws NullPointerException, IOException {

		ZipEntry ze = null;
		try {
			/*
			 * See if class declarations file exists
			 */
			Enumeration e = jf.entries();
			JarEntry je = null;
			String classfile = null;
			for (; e.hasMoreElements();) {
				je = (JarEntry) e.nextElement();
				if (je.toString().indexOf(s_pluginclassfile) >= 0) {
					classfile = je.toString();
				}
			}
			ze = jf.getEntry(classfile);
		} catch (NullPointerException e) {
			LogBuffer.println("JarFile has no tv_plugins.cd" +jf.getName());
			throw e;
		} catch (RuntimeException e) {
			e.printStackTrace();
		}

		/*
		 * Classfile exists (otherwise exception thrown) Read classes into array
		 * list
		 */
		BufferedReader br = new BufferedReader(new InputStreamReader(jf
				.getInputStream(ze)));
		ArrayList al = new ArrayList();
		String s = null;
		while ((s = br.readLine()) != null) {
			al.add(s);
		}
		return al;
	}

	public void loadPlugins(File[] f_jars, boolean showPopup) {
		String s_loadedPlugins = "";
		String s_notloadedPlugins = "";

		for (int i = 0; i < f_jars.length; i++) {
			
			try {
				URL jarURL = new URL("jar:"+f_jars[i].toURI().toURL().toString()+"!/");

				boolean b_loadedPlugin = loadPlugin(jarURL);
				
				if (b_loadedPlugin) {
					s_loadedPlugins += "<li>" + f_jars[i].getName() + "</li>";
				} else {
					s_loadedPlugins += "<li>" + f_jars[i].getName() + "*</li>";
				}
			} catch (NullPointerException e) {
				e.printStackTrace();
				s_notloadedPlugins += "<li>" + f_jars[i].getName() + "</li>";
				Debug.print(e);
			} catch (MalformedURLException e) {
				Debug.print(e);
			} 
		}
		if (showPopup) {
		JOptionPane.showMessageDialog(null,
				"<html> Found jar files: <ol>" + s_loadedPlugins
						+ "<br>* Already loaded</ol> <p>Unable to load: <ol>"
						+ s_notloadedPlugins + "</ol></html>");
		}
	}
	
	  public boolean loadPlugin(URL jarURL) {
		  boolean b_loadedPlugin = false;
		  try {
			  LogBuffer.println("Plugin Jar " + jarURL);
			  JarURLConnection conn = (JarURLConnection)jarURL.openConnection();
			  JarFile jarFile = conn.getJarFile();
			  ArrayList al_classnames = getClassDeclarations(jarFile);
			  for (int j = 0; j < al_classnames.size(); j++) {
				  Class thisClass = getClass();
				  URLClassLoader urlcl = new URLClassLoader(
						  new URL[] { jarURL }, thisClass.getClassLoader());
				  if (!pluginExists(
						  (String) al_classnames.get(j))) {
					  Class c = urlcl
					  .loadClass((String) al_classnames.get(j));
					  /*
					   * XXX: Supposedly, loadClass should call the static
					   * initializer for the class, but I'm finding it doesn't
					   * so I'm forcing an instantiation with new Instance.
					   */
					  @SuppressWarnings("unused")
					PluginFactory pp = (PluginFactory) c.newInstance();
					  b_loadedPlugin |= true;
				  } else {
					  b_loadedPlugin |= false;
				  }
			  }
		  } catch (ClassNotFoundException e) {
			  e.printStackTrace();
			  LogBuffer.println("ClassNotFound " + e);
		  } catch (InstantiationException e) {
			  Debug.print(e);
		  } catch (IllegalAccessException e) {
			  Debug.print(e);
		  } catch (IOException e) {
			e.printStackTrace();
		}
		  return b_loadedPlugin;
	}

	/**
	   * 
	   * remember, static methods cannot be overridden.
	   * @param pf
	   */
	public static void registerPlugin(PluginFactory pf) {
		LogBuffer.println("Registering Plugin " + pf.getPluginName());
		getPluginManager().pluginFactories.add(pf);
	}

	/**
	 * Assigns corresponding nodes from the global config to the appropriate
	 * plugin factories.
	 * 
	 * needs to be called whenever a plugin is loaded or 
	 * the global confignode changes.
	 *
	 */
	public void pluginAssignConfigNodes(ConfigNode node) {
		PluginFactory [] plugins =  getPluginFactories();
		for (int i = 0; i < plugins.length; i++) {
			ConfigNode pluginNode = null;
			ConfigNode [] pluginPresets = node.fetch("PluginGlobal");
			for (int j = 0; j < pluginPresets.length; j++) {
				// scan existing pluginPresets for correct name
				if (pluginPresets[j].getAttribute("name", "").equals(
						plugins[i].getPluginName())) {
					pluginNode = pluginPresets[j];
					break;
				}
			}
			if (pluginNode == null) {
				// no existing presets node for plugin, must create
				pluginNode = node.create("PluginGlobal");
				pluginNode.setAttribute("name", plugins[i].getPluginName(),"");
			}
			plugins[i].setGlobalNode(pluginNode);
		}
	}
	
	public PluginFactory [] getPluginFactories() {
		PluginFactory [] ret = new PluginFactory [pluginFactories.size()];
		Enumeration e = pluginFactories.elements();
		int i = 0;
		while (e.hasMoreElements())
			ret[i++] = (PluginFactory) e.nextElement();
		return ret;
	}
	public PluginFactory getPluginFactoryByName(String name) {
		Enumeration e = pluginFactories.elements();
		while (e.hasMoreElements()){
			PluginFactory factory = (PluginFactory) e.nextElement();
			if (name.equals(factory.getPluginName())) {
				return factory;
			}
		}
		return null;
	}
	
	public String [] getPluginNames() {
		String [] names = new String[pluginFactories.size()];
		for (int i =0; i < names.length; i++) {
			names[i] = ((PluginFactory) pluginFactories.elementAt(i)).getPluginName();
			LogBuffer.println("Pluginclassname ---- " + names[i]);
		}
		return names;
	}
	/**
	 * 
	 * @param i
	 * @return ith plugin, or null if no such plugin.
	 */
	public PluginFactory getPluginFactory(int i) {
		if ((i < pluginFactories.size()) &&
			(i >= 0)) {
			return (PluginFactory) pluginFactories.elementAt(i);
		} else {
			return null;
		}
	}
	public boolean pluginExists(String qualified_name) {
		Enumeration e = pluginFactories.elements();
		String s = null;
		while (e.hasMoreElements()) {
			s = ((PluginFactory) e.nextElement()).toString();
			/*
			 * The toString function on an (Object) always adds some funny strings
			 * to the end of the string, so we have to use "contains".
			 */
			if (s.indexOf(qualified_name) != -1) {
				return true;
			}
		}
		return false;
	}
}
