package org.apache.avalon.phoenix.launcher;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.HashMap;

/**
 * @author sshort
 *
 */
public class JMXLauncher implements JMXLauncherMBean {
    private static final String MAIN_CLASS =
        "org.apache.avalon.phoenix.frontends.CLIMain";
    private static final String LOADER_JAR = "phoenix-loader.jar";

    private int 			state;                  // Service lifecycle state
    
    private String  		phoenixHome  		= "../../phoenix/";
    private String  		phoenixConfigFile	= "../../phoenix/conf/kernel.xml";
    private String  		appsPath     		= "../../phoenix/apps/";
    private String  		logFilename  		= "../../phoenix/logs/phoenix.log";
    private boolean 		debugPhoenix 		= false;

   	private Object c_frontend;
	private Method startupMethod;
    private Method shutdownMethod;
			
    /**
     * MBean accessor to set the MBean state as an int
     */
	public int getState()
    {
        return state;
    }

    /**
     * MBean accessor to set the MBean state as a String
     */
    public String getStateString()
    {
        return states[state];
    }

	
    /**
     * MBean accessor to set the Phoenix home directory
     */
    public void setPhoenixHome(String value)
    {
        phoenixHome = value;
    }

    /**
     * MBean accessor to return the Phoenix home directory
     */
    public String getPhoenixHome()
    {
        return phoenixHome;
    }

    /**
     * MBean accessor to set the Phoenix ConfigFile directory
     */
    public void setPhoenixConfigFile(String value)
    {
        phoenixConfigFile = value;
    }

    /**
     * MBean accessor to return the Phoenix ConfigFile directory
     */
    public String getPhoenixConfigFile()
    {
        return phoenixConfigFile;
    }

    /**
     * MBean accessor to set the Phoenix applications directory
     */
    public void setAppsPath(String value)
    {
        appsPath = value;
    }

    /**
     * MBean accessor to return the Phoenix applications directory
     */
    public String getAppsPath()
    {
        return appsPath;
    }

    /**
     * MBean accessor to set the name of the Phoenix log file
     */
    public void setLogFilename(String value)
    {
        logFilename = value;
    }

    /**
     * MBean accessor to return the name of the Phoenix log file
     */
    public String getLogFilename()
    {
        return logFilename;
    }

    /**
     * MBean accessor to set the Phoenix debug flag
     */
    public void setPhoenixDebug (String value)
    {
        debugPhoenix = new Boolean(value).booleanValue();
    }

    /**
     * MBean accessor to return the Phoenix debug flag
     */
    public String  getPhoenixDebug ()
    {
        return new Boolean(debugPhoenix).toString();
    }
    
    /**
     * Method to create this MBean
     */
    public void create() throws Exception
    {
    	state = STOPPED;
    }
    
    /**
     * Method to start this MBean
     */
    public void start() throws Exception
    {
    	state = STARTING;
    	
        final HashMap data = new HashMap();

        if( debugPhoenix )
        {
            System.out.println( "JMXLauncher: Starting up Phoenix" );
            
            File f = new File(phoenixHome);
            System.out.println("JMXLauncher setting phoenix home to " + f.getCanonicalPath());
            f = new File(phoenixConfigFile);
            System.out.println("JMXLauncher setting phoenix config file to " + f.getCanonicalPath());
            f = new File(appsPath);
            System.out.println("JMXLauncher setting phoenix apps path to " + f.getCanonicalPath());
            f = new File(logFilename);
            System.out.println("JMXLauncher setting phoenix log file to " + f.getCanonicalPath());
        }

        try
        {
	        String[] args;
	        
			if (debugPhoenix)
				args = new String[8];
			else
				args = new String[7];
				
	        int idx = 0;
	        
	        args[idx++] = "-f";
	        args[idx++] = phoenixConfigFile;
	        args[idx++] = "-a";
	        args[idx++] = appsPath;
	        args[idx++] = "-l";
	        args[idx++] = logFilename;
            args[idx++] = "--disable-hook";
			
			if (debugPhoenix) 
	            args[idx++] = "-d";	

	        System.setProperty("phoenix.home", phoenixHome);

			startPhoenix(args, data);
        	
	    	state = STARTED;
	    	
            if( debugPhoenix )
            {
                System.out.println( "JMXLauncher: Phoenix startup completed" );
            }
        }
        catch( final Exception e )
        {
	    	state = FAILED;
            System.out.println( "JMXLauncher: Failed to start Phoenix launcher thread" );
            e.printStackTrace();
        }
    }
    
    /**
     * Method to stop this MBean
     */
    public void stop()
    {
    	if (state != STOPPED && state != STOPPING) {
	    	state = STOPPING;
	        if( debugPhoenix )
    	    {
	    	    System.out.println( "JMXLauncher: Stopping Phoenix");
    	    }
	        
	        try 
	        {
		        shutdownPhoenix();
	        }
	        catch( final Exception e )
    	    {
	           	System.out.println( "JMXLauncher: Failed to stop Phoenix" );
	            e.printStackTrace();
    	    }
	        
        	state = STOPPED;
    	}
    }
    
    /**
     * Method to destroy this MBean
     */
    public void destroy()
    {
    }

    /**
     * Runs the thread which launches James  
     */
    private void startPhoenix(String[] args, Map data)
    {
        int exitCode;
        
        try
        {
            //setup new Policy manager
            Policy.setPolicy( new FreeNEasyPolicy() );

			ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
			ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
			
            //Create common ClassLoader
            final URL[] libUrls = getLibClassPath();
            final URLClassLoader libClassLoader = new URLClassLoader( libUrls, contextClassLoader );

            //Create engine ClassLoader
            final URL[] engineUrls = getEngineClassPath();
            final URLClassLoader engineClassLoader = new URLClassLoader( engineUrls, libClassLoader );

            data.put( "common.classloader", libClassLoader );
            data.put( "container.classloader", engineClassLoader );
            data.put( "phoenix.home", new File( findPhoenixHome() ) );

            //Setup context classloader
            Thread.currentThread().setContextClassLoader( libClassLoader );

            //Create main launcher
            final Class clazz = engineClassLoader.loadClass( MAIN_CLASS );
            final Class[] paramTypes =
                new Class[]{args.getClass(), Map.class, Boolean.TYPE};
            final Method startupMethod = clazz.getMethod( "main", paramTypes );
            shutdownMethod = clazz.getMethod( "shutdown", new Class[] { } );
            c_frontend = clazz.newInstance();

            //kick the tires and light the fires....
            final Integer integer = (Integer)startupMethod.invoke(
                c_frontend, new Object[]{args, data, new Boolean( false )} );
            exitCode = integer.intValue();

            if( exitCode != 0 )
            {
            	throw new Exception("Result " + exitCode + " from main()");
            }
        }
        catch( final Exception e )
        {
            System.out.println("JMXLauncher: Failed to start Phoenix");
            e.printStackTrace();
        }
	}
	
    /**
     * Shutdown Phoenix
     */
    private void shutdownPhoenix() throws Exception
    {
        if (c_frontend != null && shutdownMethod != null) {
        	shutdownMethod.invoke( c_frontend, new Object[] { } );
        }
       
        startupMethod = null;
        shutdownMethod = null;
        c_frontend = null;
    }


    /**
     * Create a ClassPath for the engine.
     *
     * @return the set of URLs that engine uses to load
     * @throws Exception if unable to aquire classpath
     */
    private static URL[] getLibClassPath()
        throws Exception
    {
        final ArrayList urls = new ArrayList();

        final File dir = findLibDir();
        final File[] files = dir.listFiles();
        for( int i = 0; i < files.length; i++ )
        {
            final File file = files[ i ];
            if( file.getName().endsWith( ".jar" ) )
            {
                urls.add( file.toURL() );
            }
        }

        return (URL[])urls.toArray( new URL[ urls.size() ] );
    }

    /**
     * Find directory to load common libraries from.
     *
     * @return the lib dir
     * @throws Exception if unable to aquire directory
     */
    private static File findLibDir()
        throws Exception
    {
        final String phoenixHome = findPhoenixHome();
        final String libDir =
            phoenixHome + File.separator + "lib";
        final File dir = new File( libDir ).getCanonicalFile();
        if( !dir.exists() )
        {
            throw new Exception( "Unable to locate engine lib directory at " + dir.getCanonicalPath() );
        }
        return dir;
    }

    /**
     * Create a ClassPath for the engine.
     *
     * @return the set of URLs that engine uses to load
     * @throws Exception if unable to aquire classpath
     */
    private static URL[] getEngineClassPath()
        throws Exception
    {
        final ArrayList urls = new ArrayList();

        final File dir = findEngineLibDir();
        final File[] files = dir.listFiles();
        for( int i = 0; i < files.length; i++ )
        {
            final File file = files[ i ];
            if( file.getName().endsWith( ".jar" ) )
            {
                urls.add( file.toURL() );
            }
        }

        return (URL[])urls.toArray( new URL[ urls.size() ] );
    }

    /**
     * Find directory to load engine specific libraries from.
     *
     * @return the lib dir
     * @throws Exception if unable to aquire directory
     */
    private static File findEngineLibDir()
        throws Exception
    {
        final String phoenixHome = findPhoenixHome();
        final String engineLibDir =
            phoenixHome + File.separator + "bin" + File.separator + "lib";
        final File dir = new File( engineLibDir ).getCanonicalFile();
        if( !dir.exists() )
        {
            throw new Exception( "Unable to locate engine lib directory at " + dir.getCanonicalPath() );
        }
        return dir;
    }

    /**
     * Utility method to find the home directory
     * of Phoenix and make sure system property is
     * set to it.
     *
     * @return the location of phoenix directory
     * @throws Exception if unable to locate directory
     */
    private static String findPhoenixHome()
        throws Exception
    {
        String phoenixHome = System.getProperty( "phoenix.home", null );
        if( null == phoenixHome )
        {
        	throw new Exception("Unable to read system property \"phoenix.home\"");
            //final File loaderDir = findLoaderDir();
            //phoenixHome = loaderDir.getAbsoluteFile().getParentFile() + File.separator;
        }

        phoenixHome = (new File( phoenixHome )).getCanonicalFile().toString();
        System.setProperty( "phoenix.home", phoenixHome );
        return phoenixHome;
    }

    /**
     *  Finds the LOADER_JAR file in the Phoenix bin directory
     */
    private static final File findLoaderDir()
        throws Exception
    {
        final String classpath = System.getProperty( "java.class.path" );
        final String pathSeparator = System.getProperty( "path.separator" );
        final StringTokenizer tokenizer = new StringTokenizer( classpath, pathSeparator );

        while( tokenizer.hasMoreTokens() )
        {
            final String element = tokenizer.nextToken();

            if( element.endsWith( LOADER_JAR ) )
            {
                File file = (new File( element )).getCanonicalFile();
                file = file.getParentFile();
                return file;
            }
        }

        throw new Exception( "Unable to locate " + LOADER_JAR + " in classpath" );
    }

    /**
     * Default polic class to give every code base all permssions.
     * Will be replaced once the kernel loads.
     */
    private static class FreeNEasyPolicy
        extends Policy
    {
        public PermissionCollection getPermissions( final CodeSource codeSource )
        {
            final Permissions permissions = new Permissions();
            permissions.add( new java.security.AllPermission() );
            return permissions;
        }

        public void refresh()
        {
        }
    }
}
