
package s3.core;

import ovm.core.Executive;
import ovm.core.domain.DomainDirectory;
import ovm.core.execution.Context;
import ovm.core.execution.Native;
import ovm.core.execution.NativeConstants;
import ovm.core.execution.NativeInterface;
import ovm.core.execution.OVMSignals;
import ovm.core.services.events.EventManager;
import ovm.core.services.io.BasicIO;
import ovm.core.services.memory.MemoryManager;
import ovm.core.services.memory.MemoryPolicy;
import ovm.core.services.threads.OVMDispatcher;
import ovm.core.services.timer.TimerManager;
import ovm.core.stitcher.EventServicesFactory;
import ovm.core.stitcher.IOServiceConfigurator;
import ovm.core.stitcher.ServiceConfiguratorBase;
import ovm.core.stitcher.ServiceFactory;
import ovm.core.stitcher.SignalServicesFactory;
import ovm.core.stitcher.ThreadDispatchServicesFactory;
import ovm.core.stitcher.ThreadServiceConfigurator;
import ovm.core.stitcher.TimerServicesFactory;
import ovm.services.ServiceInstance;
import ovm.services.events.SignalMonitor;
import ovm.util.CommandLine;
import s3.core.domain.S3JavaUserDomain;
import s3.services.java.ulv1.JavaMonitorImpl;
import s3.services.transactions.Transaction;
import s3.util.PragmaAtomic;

public class S3Executive extends S3Base implements Executive.Interface {


    // hook to the C code to set the native thread priority
    static final class NativeScheduler implements NativeInterface {
        static native int setPriority(int prio);
    }

    private volatile CommandLine processCommandLine;
    protected final String executionMode;

    /**
     * In the future the executionMode will (possibly) change from
     * OVM invocation to invocation, currently it determined at image build
     * time.<P> 
     * Used reflectively.
     * @param executionMode could be either null or nonnull :-)
    */ 
    public S3Executive(String executionMode) {
	this.executionMode = executionMode;
    }
    
    /** The configured dispatcher for the threading system */
    OVMDispatcher dispatcher;

    /** The configured event manager */
    EventManager em;


    /** All of the currently configured service instances - we assume/require that
        services are not added/removed dynamically
    */
    ServiceInstance[] services;

    private int calcNServices(ServiceConfiguratorBase config) {
        int nServices = 0;
        ServiceFactory[] factories = config.getServiceFactories();
        for (int i = 0; i < factories.length; i++) {
            ServiceInstance[] sa = factories[i].getServiceInstances();
            for (int j = 0; j < sa.length; j++) {
                if (sa[j] == null) {
                    continue;
                }
                
                nServices++;
            }
        }
        return nServices;
    }
    
    private int populateServices(int curIndex,
                                 ServiceConfiguratorBase config) {
        ServiceFactory[] factories = config.getServiceFactories();
        for (int i = 0; i < factories.length; i++) {
            ServiceInstance[] sa = factories[i].getServiceInstances();
            for (int j = 0; j < sa.length; j++) {
                if (sa[j] == null) {
                    continue;
                }
                
                services[curIndex++] = sa[j];
            }
        }
        return curIndex;
    }


    public void startup() {
        
        Native.turnOffBuffering();
        
        try {
            // This MUST always be the first call
            MemoryManager.the().boot();

            // this must be initialized ASAP otherwise we'll crash trying to
            // process errors in the rest of the init code
            ovm.core.services.io.BasicIO.init();

	    Context.getCurrentContext().bootPrimordialContext();

            // start the executive domain
            DomainDirectory.getExecutiveDomain().startup();

            // tell the memory manager we're ready
	    MemoryManager.the().fullyBootedVM();

        } catch(Throwable t) { // typically to catch configuration errors
            // NOTE: depending on the problem it may be impossible
            //       to actually generate and throw an exception. If we
            //       try and fail we should get a recursive exception panic
	    throw Executive.panicOnException(t, "During boot process");
        }


        // get the dispatcher from the configuration
        try {
            ThreadDispatchServicesFactory tdsf = 
                (ThreadDispatchServicesFactory)ThreadServiceConfigurator.
                config.getServiceFactory(ThreadDispatchServicesFactory.name);
            dispatcher = tdsf.getThreadDispatcher();
        }
        catch(Throwable t) {
            throw Executive.panicOnException(t, "Locating Dispatcher");
        }


        // extract all the service instances
        try {
            services = 
                new ServiceInstance[calcNServices(ThreadServiceConfigurator.config) + calcNServices(IOServiceConfigurator.config)];
            
            populateServices(populateServices(0,
                                              ThreadServiceConfigurator.config),
                             IOServiceConfigurator.config);

            ThreadServiceConfigurator.config.printConfiguration();
            IOServiceConfigurator.config.printConfiguration();
        }
        catch(Throwable t) {
            throw Executive.panicOnException(t, "Reading service instances");
        }

        // initialise all the service instances - inter service dependencies
        // mean some services will be initialized before we get to them
        try {
            for( int i = 0; i < services.length; i++) {
                if (!services[i].isInited()) {
                    //d("Initializing service: " + services[i]);
                    services[i].init();
                }
            }
        }
        catch(Throwable t) {
            throw Executive.panicOnException(t, "Initializing service instances");
        }

	// get the event manager
	em = (EventManager)((EventServicesFactory)IOServiceConfigurator.config.
                            getServiceFactory(EventServicesFactory.name)).
                            getEventManager();


        // configure services with command-line options. It's not clear
        // whether S3Executive should take responsibility for understanding
        // command-line options and what services use them, or whether each
        // service should have access to the command-line and parse it itself.

        // read timer interrupt period in microseconds
        String timerPeriod = getCommandLine().consumeOption("period");
        if (timerPeriod != null) {
            int period = 0;
            try {
                period = Integer.parseInt(timerPeriod);
//                 BasicIO.out.print("Read timer interrupt period of ");
//                 BasicIO.out.println(period);
                TimerServicesFactory tsf = (TimerServicesFactory)ThreadServiceConfigurator.config.getServiceFactory(TimerServicesFactory.name);
                TimerManager tm = tsf.getTimerManager();
                if (tm.canSetTimerInterruptPeriod()) {
                    if (!tm.setTimerInterruptPeriod(period * 1000L))
                        BasicIO.err.println("ERROR: Invalid value on -period option");
                }
            }
            catch(NumberFormatException ex) {
                BasicIO.err.println("ERROR: Invalid value on -period option");
            }
        }

        // read native thread priority
        String priorityVal = getCommandLine().consumeOption("priority");
        if (priorityVal != null) {
            int prio = 0;
            try {
                prio = Integer.parseInt(priorityVal);
                int ret = NativeScheduler.setPriority(prio);
                if (ret != 0) {
                    switch (ret) {
                    case NativeConstants.ENOSYS:
                        BasicIO.err.println("ERROR: -priority option not supported due to no priority scheduling in the OS");
                        break;
                    case NativeConstants.EINVAL:
                        BasicIO.err.println("ERROR: -priority value out of range - using default");
                        break;
                    default:
                        BasicIO.err.println("ERROR: unable to set native priority: errno = " + ret);
                    }
                }
                else {
                    d("Native thread priority set to " + prio);
                }
            }
            catch(NumberFormatException ex) {
                BasicIO.err.println("ERROR: Invalid value on -priority option - using default");
            }
        }


        // start all the service instances - again some will be started by
        // other services
        try {
            for( int i = 0; i < services.length; i++) {
                if (!services[i].isStarted()) {
                    //d("Starting service: " + services[i]);
                    services[i].start();
                }
            }
        }
        catch(Throwable t) {
            throw Executive.panicOnException(t, "starting service instances");
        }


	// If you need to test something without reference to threading
	// or synchronization then do it here - but make sure you
	// catch any exceptions.

	// Initialise the threading subsystem and 'run' the VM
	
	try {
            dispatcher.initializeThreading();
	    try {
		run(); // no user-domain exceptions possible
	    } 
            catch (Throwable e) {
		BasicIO.err.print(
                    "Uncaught exception in S3Executive.startup(): ");
                BasicIO.err.println(e);
		e.printStackTrace();
		shutdown(1);
	    } 
            finally {
                // never returns - terminates OVM if last kernel thread
                try {
                    dispatcher.terminateCurrentThread(); 
                } 
                catch (Throwable t) {
                    panicOnException(t, "terminating current thread");
                }
		panic("unexpected return from terminateCurrentThread");
	    }
	} 
        catch (Throwable t) {
            panicOnException(t, "Initializing threading system");
        } 
    }

    // this is called as part of general termination (such as from a JVM's
    // System.exit and so multiple threads could still be using these services
    // as we shut them down. That would be a "Bad Thing" so we ensure this
    // method is executed atomically.
    public void shutdown(int reason) throws PragmaAtomic  {
	JavaMonitorImpl.dumpMonitorStatistics();//FIXME
	if (Transaction.the().gatherStatistics()) s3.services.transactions.S3TransactionWithStats.dumpStats();
	// run whatever cleanups are necessary
        try {
            for( int i = 0; i < services.length; i++) {
                try {
                    services[i].aboutToShutdown();
                }
                catch(Throwable t) {
                    BasicIO.err.println("Exception during S3Executive pre-shutdown hook");
                    BasicIO.err.println(t);
                    try {
                        t.printStackTrace();
                    }
                    catch (Throwable t2) {
                        BasicIO.err.println(" - error printing stack trace");
                    }
                }
            }
            for( int i = 0; i < services.length; i++) {
                try {
                    services[i].stop();
                    services[i].destroy();
                }
                catch(Throwable t) {
                    BasicIO.err.println("Exception during S3Executive shutdown");
                    BasicIO.err.println(t);
                    try {
                        t.printStackTrace();
                    }
                    catch (Throwable t2) {
                        BasicIO.err.println(" - error printing stack trace");
                    }
                }
            }
        }
        finally {
            // shutdown all the domains ...
            Native.exit_process(reason);
        }
    }

    public void shutdown() {
        shutdown(0);
    }

    public void panic(String message) {
        MemoryPolicy.the().enterExceptionSafeArea();
        try {
            BasicIO.err.print("PANIC: ");
            BasicIO.err.println(message);
        }
        finally {
            Native.abort();
        }
    }
    
    public void panicOnErrno(String message, int errno) {
        MemoryPolicy.the().enterExceptionSafeArea();
        try {
            BasicIO.err.print("PANIC: ");
            BasicIO.err.println(message);
            BasicIO.err.print("ERRNO: ");
            BasicIO.err.println(errno);
        }
        finally {
            Native.abort();
        }
    }

    public void panicOnException(Throwable t, String message) {
        MemoryPolicy.the().enterExceptionSafeArea();
        try {
            BasicIO.err.print("PANIC: ");
            BasicIO.err.println(message);
            BasicIO.err.println("Unhandled Exception:");
            try {
                BasicIO.err.println(t);
                t.printStackTrace();
            } catch (Throwable t2) {
                BasicIO.err.print("\n<error printing stack trace>\n");
            }
        }
        finally {
            Native.abort();
        }
    }
    
    public CommandLine getCommandLine() {
	if (processCommandLine == null) { // OK, a race - but benign
	    processCommandLine = parseCommandLine();
	}
	return processCommandLine;
    }

    private static CommandLine parseCommandLine() {
	String[] args = new String[Native.get_process_arg_count() - 1];
	byte[] buf = new byte[512];
	for (int i = 0; i < args.length; i++) {
	    // skip the process name (hence start from 1
	    int len = Native.get_process_arg(i + 1, buf, buf.length);
	    args[i] = new String(buf, 0, len);
	}
	return new CommandLine(args);
    }


    private void run() {
        CommandLine cmd = getCommandLine();


        // Process all the command-line args.
        // FIXME: we need a more structured way to let services configure
        // themselves based on command-line args

        // SIGUSR1 causes thread stack dumps. In the future the signal to use
        // should be configurable.
	installDumpHandler(cmd.consumeOption("dumpAll")!=null,
			   cmd.consumeOption("dumpStacks")!=null,
			   cmd.consumeOption("dumpEvMan")!=null);

             
        // run the executive domain tests first. If something critical
        // fails here we may not get the user-domain up and running and may
        // not be able to see what's gone wrong if we ran the tests after the
        // user-domain code.
        // Additionally, until we have domain lifecycle management, when the
        // user-domain JVM terminates we'll invoke shutdown and so the call to
        // user-domain.run never returns

	if (cmd.consumeOption("dump-image-profile") != null)
	    dumpImageProfile();
	
///FIXEM::: if(!Transaction.the().transactionalMode())
///    if (cmd.consumeOption("doexectests") != null)  test.runtime.TestSuite.the().run();

	if (cmd.consumeOption("nouserdomain") != null)
	    return;

	// No engine-specific code is needed right now, but once we
	// can pass arguments into the userDomain, that may change.
	S3JavaUserDomain userDomain = (S3JavaUserDomain)
            DomainDirectory.getUserDomain(1);

	userDomain.startup();

	// Unparse the command line, producing a new command line that
	// does not contain the options we consumed.
        String[] udArgs = cmd.unparse();
	userDomain.run(udArgs);
    }

    void dumpImageProfile() {
	MemoryManager.the().dumpExtent(Native.getImageBaseAddress(),
				       Native.getImageEndAddress());
    }

    static final boolean DEBUG_DUMP = true;

    void installDumpHandler(final boolean dumpAll,
			    final boolean dumpStacks,
			    final boolean dumpEvMan) {
	if (dumpEvMan && !em.hasNonTrivialDumpStateHook()) {
	    BasicIO.out.println("-dumpEvMan makes no sense, because configured "+
				"event manager does not have any information "+
				"to dump.");
	    Native.exit_process(1);
	}
	
        SignalMonitor sigMon = null;
        SignalServicesFactory ssf = (SignalServicesFactory)
            ThreadServiceConfigurator.config.getServiceFactory(SignalServicesFactory.name);
        if (ssf != null) {
            sigMon = ssf.getSignalMonitor();
            if (sigMon != null) {
                SignalMonitor.SignalHandler handler =
                    new SignalMonitor.SignalHandler() {
                        public void fire(int sig, int count) {
                            // we have no idea the context in which we will
                            // execute so we have to be careful not to trample
                            // on the current thread
                            Object r1 =
				MemoryPolicy.the().enterExceptionSafeArea();
			    try {
				if (dumpAll || dumpStacks) {
				    try {
					dispatcher.dumpStacks();
				    }
				    catch (Throwable t) {
					BasicIO.err.println("Exception occurred "+
							    "trying to dump "+
							    "stacks");
					if (DEBUG_DUMP) {
					    try {
						t.printStackTrace();
					    }
					    catch(Throwable t2) {
						BasicIO.err
						    .println("Exception occurred "+
							     "trying to print "+
							     "exception stack "+
							     "trace");
					    }
					}
				    }
				}
				if (dumpAll || dumpEvMan) {
				    try {
					em.dumpStateHook();
				    } catch (Throwable t) {
					BasicIO.err.println("Exception occurred "+
							    "trying to dump "+
							    "event manager state");
				    }
				}
			    }
                            finally {
                                MemoryPolicy.the().leave(r1); 
                            }
                        }
                    };
                sigMon.addSignalHandler(handler, OVMSignals.OVM_SIGUSR1);
            }
        }
    }
}
