package java.lang;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import org.ovmj.java.OVMSignals;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
/**
 * Represents the Java Virtual Machine (JVM). This class defines the 
 * life cycle management of a JVM. It provides for the start-up and
 * initialisation of the JVM, and provides the means for terminating
 * a JVM.
 * <p>This class utilise the underlying OVM kernel services to actually 
 * provide the necessary start-up and shut-down functionality.
 * <p>This interface provides support for the JVM shutdown hook architecture
 * as defined in JDK 1.3. The VM terminates when either all user-level threads
 * have exited, the {@link #exit} or {@link #halt} methods are invoked or the
 * underlying OVM is terminated (presumably due to an external signal).
 * Except in the case of {@link #halt} the shutdown sequence causes all
 * registered shutdown hook threads to execute to completion. As per the JDK
 * if any shutdown hook threads fail to terminate the JVM may fail to
 * terminate. An invocation of {@link #halt} does not cause shutdown hook 
 * threads to execute, but abruptly terminates the Java virtual machine.
 *
 * <p>It is expected that an OVM thread created for this purpose, initialises
 * the Java Virtual Machine by executing it's {@link #run} method  and so 
 * becomes the primordial JVM thread. That thread is then free to do as it 
 * pleases to ensure the Java application is executed correctly. 
 * The termination of the Java Virtual Machine does not imply the
 * termination of the OVM itself. 
 * This allows for the implementation of Isolates
 * between the OVM and the Java Virtual Machine.
 *
 * <h3>Initialization</h3>
 * <p>The JVM is notionally a singleton, but initialization of that singleton
 * can't take place until class initialization is enabled, which can't take 
 * place until JVM initialization has reached a particular state. To deal with
 * this the JVM is initialized through the static {@link #init()}
 * method. Once initialization reaches the point where class initialization
 * is enabled then static initialization of the singleton instance will occur.
 * The singleton JVM instance can then use any static data that was
 * pre-initialized by the <tt>init</tt> method.
 *
 * <h3>Implementation Notes</h3>
 * <p>Both the {@link #exit} and {@link #halt} methods are defined never to 
 * return. We must ensure that we can still cleanup any threads that invoke 
 * these methods, even if used incorrectly. For example, the JDK allows the 
 * VM to hang on shutdown if a shutdown hook invokes <code>exit</code>. 
 * This typically occurs because an attempt is made to <code>join</code> all 
 * shutdown hooks, but the hook that calls <code>exit</code> won't terminate 
 * until after the thread waiting for it to terminate releases a lock. While 
 * this is okay in the JDK up to 1.4, it does not seem good enough for a 
 * world that includes Isolates.
 * <p>Relatedly, in the JDK <code>halt</code> can simply terminate the 
 * process to halt the virtual machine, but with Isolates this is not 
 * possible and we must do a lot more work to cleanly terminate the VM. 
 * This means that we need to deal with active user-threads when shutdown 
 * occurs. For a user-level thread implementation this should not be too 
 * difficult (remove them from the ready queue) but for native threads it is 
 * akin to setting an asynchronous termination.
 *
 * <h3>To-Do</h3>
 * <ul>
 * <li>Actually do finalization
 * <li>Ensure clean termination
 * </ul>
 * 
 * <p>Fix me: currently we have to make things public so the launcher can
 * access them.
 *
 * @see java.lang.Runtime#exit
 * @see java.lang.Runtime#halt
 * @see java.lang.Runtime#addShutdownHook
 * @see java.lang.Runtime#removeShutdownHook
 *
 * @author David Holmes
 */
public class JavaVirtualMachine {

    /* This is the entry point for the JVM at which time class initialization
       is disabled. Consequently this class can not have any static
       initialization requirements. Instead, all initialization is placed
       in the init method that will enable class initialization once it has
       initialized threading etc.
    */

    // This is the thread object that becomes the "current thread"
    static Thread.PrimordialThread main;

    /**
     * Arguments passed to the JVM. This doesn't include application 
     * arguments, just those args used to configure the JVM itself.
     */
    static String[] jvmArgs;

    /**
     * The name of the application class as specified in the arguments
     * extacted by <tt>init</tt>
     */
    static String mainClassName;

    /**
     * The arguments to be passed to the application classes main method
     */
    static String[] appArgs;

    /** 
     * The final exit status of the VM. This is the value passed to a direct
     * call to <tt>exit</tt> or <tt>halt</tt>, or else a value determined
     * by the VM depending on why termination is occurring. We use a global
     * variable for simplicity as we'd need to use one anyway to communicate
     * with the shutdown thread used by the RT-JVM.
     */
    static volatile int exitStatus = 0;

    /**
     * Performs initialization of the <tt>JavaVirtualMachine</tt>
     * instance at a time when class initialization is disabled.
     * This method can not rely on any use of static information
     * in other classes until class initialization has been enabled.
     * The main objective of this method is to bind the primordial
     * thread so that any thread related actions in class initializers
     * will execute correctly.
     * <p>Once class initialization is enabled we complete the initialization
     * of the primordial thread, read in all the command-line parameters,
     * process the JVM parameters appropriately, and setup the application
     * parameters.
     *
     * <p><b>IMPORTANT:</b> No String literals can be accessed before class
     * initialization has been enabled. Any attempt to do so will result in
     * a VM crash due to a NullPointerException in String.intern that itself
     * causes a NPE in Throwable.<clinit> and which then causes a panic.
     * If we want to debug we'll have to use byte[].
     *
     * <p><b>Note:</b> we can't pass args as we can't create Strings until
     * after class initialization is enabled.
     */
    public static void init() {

        // prevent anyone from calling this after the launcher does
        if (main != null) {
            // this is safe because it's not called first time through
            throw new IllegalAccessError("This isn't for public use");
        }

        // this only partially initializes the primordial thread
        // object - the rest is done after class initialization
        // is enabled below. Note we can't pass the thread name yet.
        main = new Thread.PrimordialThread(Thread.NORM_PRIORITY,
                                           false, Thread.STARTED_THREAD);

        // bind the underlying VM thread to our current thread
        main.vmThread = LibraryImports.getCurrentVMThread();

        if (main.vmThread == null) {
            // this will crash but so what
            throw new Error("currentVMThread returned null");
        }
        LibraryImports.bindPrimordialJavaThread(main);

        // now enable class initialization
        LibraryImports.enableClassInitialization();

        // Now force System to initialize first
        Object nil = System.in;

        LibraryImports.printString("   >>> enabled class initialization\n");

        // now it is safe to do arbitrary synchronization so we can 
        // complete the initialization of the primordial thread:
        main.completeInitialization("main");

        LibraryImports.printString(">>> completed initialization of main - end of init\n");

	String[] args = LibraryImports.getCommandlineArgumentStringArray();


        /* The argument list is of the form:
             <user-domain and/or JVM args starting with - >
             <application-class-name>
             <application args>

           The user-domain should already have acted upon any args it is
           interested in. We only look for JVM args that we are interested in
           and pass everything else on to the application.
        */

        // First find the application class name, if given
	int mainClassNameIndex = args.length;
	for (int i = 0; i < args.length; i++) {
	    if (args[i].charAt(0) != '-') {
		mainClassNameIndex = i;
                mainClassName = args[i];
		break;
	    }
	}

        // now split up the JVM and application args

        int nJvmArgs = mainClassNameIndex;
        int nAppArgs = args.length - mainClassNameIndex - 1;
        if (nAppArgs < 0)
            nAppArgs = 0;

        // create separate arrays for the JVM (plus user-domain) args and
        // the application args.

        jvmArgs = new String[nJvmArgs];
        appArgs = new String[nAppArgs];

        // now copy the args across

        for (int i = 0; i < nJvmArgs; i++)
            jvmArgs[i] = args[i];

        for (int i = 0, j = mainClassNameIndex+1; i < nAppArgs; i++, j++)
            appArgs[i] = args[j];


        // process the JVM args
        processJVMArgs();

        // install the actual JVM instance now. This class would eventually
        // get statically initialized but we don't want to take any chances
        // with the timing because the RT JVM will overwrite this setting
        // when it has init() invoked.

        instance =  new JavaVirtualMachine();

    }


    // FIXME: we don't process command-line args until after basic 
    // initialization has occurred. This means that properties like the
    // system class loader or character encoding to use can't be set from
    // the command-line as by the time we process them it is too late.

    private static void processJVMArgs() {

        // Look for explicitly defined properties via -Dxxx=yyyy or -Dxxx
        java.util.Properties p = System.getProperties();
        for (int i = 0; i < jvmArgs.length; i++) {
            String arg = jvmArgs[i];
            if (arg.length() > 2 && 
                arg.charAt(0) == '-' &&
                arg.charAt(1) == 'D' ) {
                int equalIndex = arg.indexOf('=');
                if (equalIndex != -1) {
                    if (equalIndex > 2) {
                        String key = arg.substring(2, equalIndex);
                        String val;
                        if (equalIndex == arg.length()-1) // -Dxxx=
                            val = "";
                        else
                            val = arg.substring(equalIndex+1, arg.length());
                        p.setProperty(key, val);
                    }
                    else {
                        continue;  // -D= is not valid
                    }
                }
                else {// -Dxxx ?
                    String key = arg.substring(2, arg.length());
                    p.setProperty(key, "");
                }
            }
        }
    }


    /** The singleton instance of this class. This instance may be
        replaced by a subclass instance.
     */
    static JavaVirtualMachine instance;

    /** 
     * Returns the singleton instance of this class 
     * @return the singleton instance of this class 
     *
     */
    public static JavaVirtualMachine getInstance() {
        return instance;
    }

    /** 
     * Flag indicating whether shutdown sequence has commenced 
     * Only read or write when synchronized on <tt>this</tt>.
     */
    protected boolean shutdownInitiated = false;

    /**
     * Object that is locked during shutdown so that only one thread can
     * perform the shutdown.
     */
    private final Object shutdownLock = new Object();

    /** The number of active user threads */
    private int userThreads = 0;

    /** The number of active daemon threads */
    private int daemonThreads = 0;

    /** Flag indicating whether finalizers are to be run on exit */
    private boolean runFinalizersOnExit = false;

    /** Flag indicating if finalizers are running as part of shutdown.
        This causes {@link #exit} to behave differently.
    */
    private volatile boolean runningFinalizers = false;


    /** The registered shutdown hook threads */
    // We actually want an identity hash set but we only have a map. 
    private Map hooks = null;

    /** The entry set view of the hooks map - eagerly initialized to avoid
        problems with lazy initialization and scoped memory.
    */
    private Set hookEntries = null;

    /** Direct construction of a JVM is not allowed */
    JavaVirtualMachine() {}

    private long startTime=0;

    // invoked by the OVM runtime to start the JVM
    public void run() {
        // prevent anyone from calling this after the launcher does
        if (hooks != null) {
            throw new IllegalAccessError("This isn't for public use");
        }

        // This is the code for the main thread
        Runnable mainCode = new Runnable() {
                // NOTE: Any exceptions that propagate out of run will be
                // caught by the Thread code and dealt with correctly.
                public void run() {

                    // initialize all VM internal threads - this is overridden
                    // by subclasses as needed. We need to do this first
                    // to ensure any errors below can be dealt with eg. if
                    // we initiate shutdown due to an exception.

                    initializeVMThreads();

                    if (mainClassName == null) {
                        System.err.println(
                            "Error: No class specified for OVM to execute");
                        System.exit(-1);
                    }

                    ClassLoader scl = ClassLoader.systemClassLoader;
                    Class mainClass = null;
                    try {
                        mainClass = scl.loadClass(mainClassName, true);
                    }
                    catch (ClassNotFoundException ex) {
                        System.err.println("Exception in thread " + 
                                           Thread.currentThread().getName() + 
                                           ": " + ex + " for " + mainClassName);
                        System.exit(-1);
                    }

                    Method main = null;
                    try {
                        main = 
                            mainClass.getDeclaredMethod("main", 
                                                        new Class[] {
                                                            String[].class});
                    }
                    catch(NoSuchMethodException e) {
                        System.err.println("Exception in thread " + 
                                           Thread.currentThread().getName() + 
                                           ": " + e);
                        System.exit(-1);
                    }

                    // check main is public static void
                    int mods = main.getModifiers();
                    if (! (Modifier.isStatic(mods) &&
                           Modifier.isPublic(mods) &&
                           (main.getReturnType() == void.class)) ) {
                        System.err.println("No such method: public static void main(String[] args)");
                        System.exit(-1);
                    }

                    try {
                        try {
			    startTime=LibraryImports.getCurrentTime();
                            main.invoke(null, new Object[] {appArgs});
                        }
                        catch(InvocationTargetException ex) {
                            throw ex.getCause();
                        }
                    }
                    catch(Throwable t) {
                        System.err.println("Exception in thread " + 
                                           Thread.currentThread().getName() + 
                                           ": " + t);
                        t.printStackTrace();
                        System.exit(-1);
                    }
                }
            };
        
        try {
            main.setRunnable(mainCode);

            hooks = new IdentityHashMap(); // finish construction
            hookEntries = hooks.entrySet(); // eager construction otherwise
                                            // lazy construct can fail in scope

            // add main thread to the JVM
            addThread(main);

            // now we emulate the execution of the main thread by invoking
            // runThread directly
            try {
                main.runThread(); // should never return, or throw exception
            }
            finally {
                // something has gone very wrong
                LibraryImports.printString("FATAL Error: main.runThread returned\n");
                LibraryImports.halt(-1);
            }
        }
        catch (Throwable t) {
            // something went badly wrong and we don't know what we can use
            // safely
            LibraryImports.printString("FATAL Error during JVM.run()\n");
            try {
                LibraryImports.printString(t.toString());
                LibraryImports.printString("\n");
            }
            catch(Throwable t2) {
                LibraryImports.printString("Unable to print exception info\n");
            }
        }
        finally {
            LibraryImports.halt(-1);
        }
    }

    /** initializes any threads needed by the JVM itself. Subclasses can
        override to have initialization of their own threads.
    */
    void initializeVMThreads() {
        // install the Ctrl-C/SIGINT handler thread
        new SIGINT_Handler().start();

        // install the finalizer thread
	new Thread() {
	    {
		setDaemon(true);
		setPriorityInternal(LibraryImports.getMaximumPriority());
		setName("finalization-thread");
	    }
	    public void run() {
		LibraryImports.runFinalization();
	    }
	}.start();
    }


    /**
     * Called by the dispatcher to register an about-to-be started thread 
     * with the JVM.
     * <p>Note that this can be called while shutdown is in progress.
     *
     * @param t the thread that is being started
     */
    synchronized void addThread(Thread t) {
        // simply update the thread counts. We could add to a global thread
        // list if needed
        if (t.isDaemon()) {
            daemonThreads++;
        }
        else {
            userThreads++;
        }
    }

    /**
     * Called by the dispatcher to unregister a terminating thread.
     * If this thread is the last non-daemon thread to be removed, and
     * the shutdown sequence is not in progress, then the shutdown
     * sequence is initiated and this method never returns.
     * <p>Note that shutdown hook threads will always invoke this when
     * shutdown has been initiated.
     *
     * @param t the thread that is terminating
     */
    void removeThread(Thread t) {
        synchronized(this) {
            if (t.isDaemon()) { // simple case
                daemonThreads--;
                return;
            }
            else {
                userThreads--;
                if (userThreads > 0 || shutdownInitiated) {
                    return;
                }
                else {
                    shutdownInitiated = true;
                }
            }
        }

        performShutdown();
    }

    
    /**
     * Used to specify whether the JVM should run the finalizers for
     * all finalizable objects during the shutdown sequence. If shutdown
     * is already in progress then the setting is left unchanged.
     *
     * @param enabled if <tt>true</tt> then finalizers will be executed,
     * otherwise they won't.
     *
     * @see Runtime#runFinalizersOnExit
     */
    synchronized void runFinalizersOnExit(boolean enabled) {
        // don't let it change once shutdown has commenced
        if (!shutdownInitiated) {
            runFinalizersOnExit = enabled;
        }
    }

    /**
     * Registers a shutdown hook thread in this JVM. A thread can only be
     * registered prior to the shutdown sequence commencing and must not have
     * been started, or already registered.
     *
     * @param   hook  An initialized but unstarted {@link Thread} object
     *
     * @throws  IllegalArgumentException
     *          If the specified hook has already been registered,
     *          or if it can be determined that the hook is already running or
     *          has already been run
     *
     * @throws  IllegalStateException
     *          If the virtual machine is already in the process
     *          of shutting down
     *
     * @see java.lang.Runtime#addShutdownHook
     */
   synchronized void addShutdownHook(Thread hook) {
        if (shutdownInitiated) {
            throw new IllegalStateException("Shutdown in progress");
        }
        if ((hook.state & Thread.STARTED_THREAD) != 0) {
            throw new IllegalArgumentException("Hook already started");
        }
        if (hooks.containsKey(hook)) {
            throw new IllegalArgumentException("Hook already registered");
        }
        hooks.put(hook, hook);
    }

    /**
     * Removes a previously registered hook
     * @param hook the hook to remove
     * @return <code>true</code> if the specified hook had previously been
     * registered and was successfully de-registered, and <code>false</code>
     * otherwise.
     *
     * @throws  IllegalStateException
     *          If the virtual machine is already in the process of shutting
     *          down
     *
     * @see java.lang.Runtime#removeShutdownHook
     */
    synchronized boolean removeShutdownHook(Thread hook) {
        if (shutdownInitiated) {
            throw new IllegalStateException("Shutdown in progress");
        }
        if (hooks.containsKey(hook)) {
            hooks.remove(hook);
            return true;
        }
        else {
            return false;
        }
    }


    /**
     * Performs the shutdown sequence and then halts the JVM. The
     * <tt>shutdownInitiated</tt> flag should already have been set
     * by the caller. The first thread to gain the shutdown-lock will
     * perform the shutdown. Any other thread will hang trying to get
     * a lock that will never be released.
     */
    void performShutdown() {
        synchronized(shutdownLock) {
            try {
                long time;

                time=LibraryImports.getCurrentTime()-startTime;
                LibraryImports.printString("OVM shutdown initiated by ");
                LibraryImports.printString(Thread.currentThread().getName());
                LibraryImports.printString("\n");
                shutdown();
                if (startTime!=0) {
                    LibraryImports.printString("\n\n\n");

		    LibraryImports.printString("Nanosecs from the Epoch right before the beginning of main(): ");
	            LibraryImports.printLong(startTime);
		    LibraryImports.printString("\n\n");

		    LibraryImports.printString("Time used by the main method (excluding finalization and shutdown hooks): ");
	            LibraryImports.printLong((time)/60000000000L);
	            LibraryImports.printString("m");
	            LibraryImports.printLong((time%60000000000L)/1000000000L);
	            LibraryImports.printString(".");
	            LibraryImports.printLong(((time%60000000000L)/1000000L)%1000);
	            LibraryImports.printString("s\n(");
	            LibraryImports.printLong(time);
	            LibraryImports.printString("ns)\n");
                }
            }
            finally {
                halt(exitStatus);
            }
        }
    }

    /** 
     * The actual implementation of the shutdown sequence.
     * <p>In phase 1 all hooks are started in sequence. If any hook fails to
     * terminate (eg. if it invokes exit) then phase 1 never terminates and
     * shutdown never completes and {@link #halt} must be invoked to cause
     * termination. This is as designed.
     * <p> In phase 2 finalizers are run if we have been set to run
     * finalizers on exit. Unlike the JDK we always run finalizers even if
     * exit is called with a non-zero status. If finalizers are being used for
     * resource cleanup then that should occur under all exit conditions.
     * <p>If a finalizer never returns then phase 2 will never complete and
     * again {@link #halt} will have to be invoked to cause termination.
     * Note however, that if a finalizer invokes {@link #exit} with a non-zero
     * status, then we must invoke {@link #halt} straight-away. 
     * <p>The actual calling of {@link #halt} is arranged by our caller. 
     */
    void shutdown() {
        // assert: shutdownInitiated

        // note only 1 thread can ever execute this method and that hooks
        // can no longer be modified.

        // Phase 1: run all hooks.

        // start
        for (Iterator i = hookEntries.iterator(); i.hasNext();) {
            try {
                Map.Entry e = (Map.Entry) i.next();
                ((Thread)e.getKey()).start();
            }
            catch(IllegalThreadStateException ex) {
                // ignore - someone started the hook after they
                // registered it, so drop it from the set
                i.remove();
            }
        }
        // and join
        for (Iterator i = hookEntries.iterator(); i.hasNext();) {
            // if someone interrupts the thread doing the shutdown
            // we ignore it completely and check that all hooks terminate.
            // In contrast the JDK skips the hook that it was joining on
            // when the interrupt occurred.
            Map.Entry e = (Map.Entry) i.next();
            Thread t = (Thread)e.getKey();
            while (true) {
                try {
                    t.join();
                    break; // get out of infinite loop
                } catch (InterruptedException ex) {
                    continue;
                }
            }
        }

        // just in case we're keeping things alive that might interfere
        // with running finalizers, or at least, affect it.
        hooks.clear();

        // phase 2: run finalizers

        if (runFinalizersOnExit) {
            // note that we are executing finalizers
            runningFinalizers = true;
            // TODO: actually execute finalizers
        }

    }

    /**
     * Terminates the currently running Java virtual machine by initiating its
     * shutdown sequence, as per the specification of 
     * {@link java.lang.Runtime#exit}. This method never returns.
     *
     * @param status the value to be passed back to the host executing 
     * environment
     */
    void exit(int status) {
        boolean haltJVM = false;

        // use main lock to figure out what state we are in but don't
        // hold during the actual shutdown
        synchronized(this) {
            if (shutdownInitiated) {
                // if finalizers are being run and status is non-zero we
                // must halt
                if (runningFinalizers && status != 0) {
                    haltJVM = true;
                }
                // else nothing for this thread to do so we'll 'hang'
                // acquiring the shutdown lock
            }
            else {
                shutdownInitiated = true;
		exitStatus = status;
            }
        }
        
        if (haltJVM) {
            halt(status);  // never returns
            throw new InternalError("halt returned");
        }

        performShutdown();
    }

    /**
     * Terminates the currently running Java virtual machine immediately as
     * per the specification of {@link java.lang.Runtime#halt}. 
     * This method never returns.
     *
     * @param status the value to be passed back to the host 
     * executing environment
     */
    void halt(int status) {
        // Note that we could get here while shutdown is in progress. In
        // that case we still forcefully halt the VM.
        // It's not clear exactly what that will involve.
        // we don't try to acquire any locks here as that would disallow an
        // immediate shutdown. So the kernel services must deal with the
        // potential for concurrent invocations
        LibraryImports.halt(status);
    }


    /**
     * Invoke the GC. This is defined as a method on the JVM instance so that
     * we can override for a Real-time JVM to deal with no-heap entities
     */
    void gc() {
        LibraryImports.gc();
    }


    /**
     * Thread subclass for waiting upon ctrl-C and initiating the shutdown
     * sequence. This is a non-static nested class as it accesses the current
     * JVM object.
     */
    class SIGINT_Handler extends Thread {
        SIGINT_Handler() {
            this.setDaemon(true);
            this.setPriorityInternal(LibraryImports.getMaximumPriority());
            this.setName("SIGINT-handler");
        }

        public void run() {
            if (LibraryImports.waitForSignal(OVMSignals.OVM_SIGINT) == 0) {
                throw new Error("SIGINT handler unable to wait for SIGINT");
            }

            synchronized(JavaVirtualMachine.this) {
                shutdownInitiated = true;
            }

            exitStatus = -1;
            JavaVirtualMachine.this.performShutdown();
        }
    }

            
}








