package JSSPF;

import java.io.File;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Provides plugin classes at runtime from a plugin directory and provides ways
 * to enumerate the plugins that are present.
 */
public class PluginClassManager
{
    // TODO: High priority - use this!
    //private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    // Stores the list of available plugin classes.
    private ArrayList<PluginClassDef> classes = new ArrayList<PluginClassDef>();

    /**
     * Creates and initialises the provider. The constructor searches the plugin
     * directory, creating DynamicClass objects from which we can create instances
     * later.
     */
    public PluginClassManager(String pluginDir, boolean verbose)
    {
        try
        {
            ArrayList<URL> lotsaURLs = new ArrayList<URL>();

            // HACK: Hack? Is this a safe assumption?
            URLClassLoader scl = (URLClassLoader) ClassLoader.getSystemClassLoader();
            URL[] baseURLs = scl.getURLs();
            for (URL cpURL : baseURLs)
            {
                if (verbose) System.out.println("Adding base URL: " + cpURL);
                lotsaURLs.add(cpURL);
            }
            
            // Examine the dir and find URLs and a list of classnames to try
            // loading. TODO: Keep list of dirs traversed and try classnames
            // from each subdir (need to add all paths too).
            ArrayList<String> classNames = new ArrayList<String>();

            try
            {
                File f = new File(pluginDir);
                String s = f.getCanonicalPath();
                lotsaURLs.add(f.toURI().toURL());

                ArrayList<File> files = new ArrayList<File>();
                //ArrayList<String> paths = new ArrayList<String>();

                files.add(f);

                while (files.size() > 0)
                {
                    File aFile = files.remove(0);

                    if (aFile.isDirectory())
                    {
                        try
                        {
                            files.addAll(Arrays.asList(aFile.listFiles()));
                        }
                        catch (Exception e)
                        {
                            if (verbose) System.out.println("Error accessing plugin directory: " + aFile.getPath() + " - " + e.getMessage());
                        }
                    }
                    else if (aFile.isFile())
                    {
                        try
                        {
                            String path = aFile.getCanonicalPath();
                            if (verbose) System.out.println("Examining file: " + path);

                            String fileName = path.replace((CharSequence) (s + File.separator), "");

                            if (fileName.endsWith(".class"))
                            {
                                if (verbose) System.out.println("Class file: " + fileName);
                                String className = toClassName(fileName);
                                classNames.add(className);
                            }
                            else if (fileName.endsWith(".jar"))
                            {
                                // TODO: Error handling.
                                if (verbose) System.out.println("Opening JAR: " + fileName);

                                JarFile jarFile = new JarFile(aFile);
                                lotsaURLs.add(aFile.toURI().toURL());

                                Enumeration<JarEntry> entries = jarFile.entries();
                                while (entries.hasMoreElements())
                                {
                                    JarEntry entry = entries.nextElement();
                                    String entryName = entry.getName();
                                    if (entryName.endsWith(".class"))
                                    {
                                        if (verbose) System.out.println("JAR Entry: " + entryName);
                                        String className = toClassName(entryName);
                                        classNames.add(className);
                                    }
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            if (verbose) System.out.println("Error processing plugin file: " + aFile.getPath() + " - " + e.toString());
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (verbose) System.out.println("Error accessing plugin directory: " + pluginDir + " - " + e.toString());
            }
            
            // Create the classloader that will handle all our classes.
            URLClassLoader cl = new URLClassLoader(lotsaURLs.toArray(baseURLs));

            // Try to load the classes we found.
            for (String className : classNames)
            {
                try
                {
                    Class dynamic = cl.loadClass(className);

                    try
                    {
                        PluginClassBase obj = (PluginClassBase) dynamic.newInstance();

                        String classType = obj.getType();
                        String typeName = obj.getName();

                        classes.add(new PluginClassDef(typeName, classType, dynamic));

                        System.out.println("Loaded plugin '" + typeName + "'");
                    }
                    catch (Exception e)
                    {
                        if (verbose) System.out.println("Not a plugin '" + className + "' - " + e.toString());
                    }
                }
                catch (Exception e)
                {
                    if (verbose) System.out.println("Not a class '" + className + "' - " + e.toString());
                }                
            }
        }
        catch (Exception e)
        {
            if (verbose) System.out.println("Unknown exception with plugin directory: " + pluginDir + " - " + e.getMessage());
        }
    }

    private String toClassName(String fileName)
    {
        String className = fileName.replace((CharSequence) ".class", "").replace((CharSequence) File.separator, ".").replace((CharSequence) "/", ".");
        return className;
    }

    /**
     * Creates and returns a plugin object given the name of the plugin.
     * @param pluginName The name of the plugin required.
     * @return The named class, as a DynamicBase, or null if no such plugin
     * exists.
     */
    public PluginClassBase getPlugin(String pluginName)
    {
        for (int x = 0; x < classes.size(); x++)
        {
            PluginClassDef clasS = classes.get(x);
            if (clasS.className.equals(pluginName)) return clasS.getInstance();
        }
        return null;
    }


    /**
     * Returns all the known DynamicClass objects allowing enumeration
     * of the available plugins.
     * @return A copy of the list of available plugin classes.
     */
    public ArrayList<PluginClassDef> getPluginList()
    {
        return new ArrayList<PluginClassDef>(classes);
    }

}