/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.adaptive.controller;

import java.util.Enumeration;
import java.util.Vector;
import org.jikesrvm.VM;
import org.jikesrvm.Callbacks;
import org.jikesrvm.adaptive.OSROrganizerThread;
import org.jikesrvm.adaptive.database.AOSDatabase;
import org.jikesrvm.adaptive.database.callgraph.PartialCallGraph;
import org.jikesrvm.adaptive.database.methodsamples.MethodCountData;
import org.jikesrvm.adaptive.measurements.RuntimeMeasurements;
import org.jikesrvm.adaptive.measurements.instrumentation.Instrumentation;
import org.jikesrvm.adaptive.measurements.organizers.Organizer;
import org.jikesrvm.adaptive.recompilation.CompilationThread;
import org.jikesrvm.adaptive.recompilation.instrumentation.CounterBasedSampling;
import org.jikesrvm.adaptive.util.AOSLogging;
import org.jikesrvm.adaptive.util.AOSOptions;
import org.jikesrvm.adaptive.util.BlockingPriorityQueue;
import org.jikesrvm.compilers.baseline.EdgeCounts;
import org.jikesrvm.compilers.common.RecompilationManager;
import org.jikesrvm.scheduler.greenthreads.GreenProcessor;

/**
 * This class contains top level adaptive compilation subsystem functions.
 */
public class Controller implements Callbacks.ExitMonitor,
                                      Callbacks.AppStartMonitor,
                                      Callbacks.AppCompleteMonitor,
                                      Callbacks.AppRunStartMonitor,
                                      Callbacks.AppRunCompleteMonitor,
                                      Callbacks.RecompileAllDynamicallyLoadedMethodsMonitor {

  /**
   * Signals when the options and (optional) logging mechanism are enabled
   */
  public static boolean enabled = false;

  /**
   * Controller subsystem control options
   */
  public static final AOSOptions options = new AOSOptions();

  /**
   * Deferred command line arguments for the opt compiler
   */
  private static String[] optCompilerOptions = new String[0];

  /**
   * Add a deferred command line argument
   */
  public static void addOptCompilerOption(String arg) {
    String[] tmp = new String[optCompilerOptions.length + 1];
    for (int i = 0; i < optCompilerOptions.length; i++) {
      tmp[i] = optCompilerOptions[i];
    }
    tmp[optCompilerOptions.length] = arg;
    optCompilerOptions = tmp;
  }

  /**
   * Get the deferred command line arguments
   */
  public static String[] getOptCompilerOptions() {return optCompilerOptions;}

  /**
   * The controller thread, it makes all the decisions
   * (the thread sets this field when it is created.)
   */
  public static ControllerThread controllerThread = null;

  /**
   * Thread that will perform opt-compilations as directed by the controller
   * (the thread sets this field when it is created.)
   */
  public static CompilationThread compilationThread = null;

  /**
   * Thread collecting osr request and pass it to controllerThread
   */
  public static OSROrganizerThread osrOrganizer = null;

  /**
   * Threads that will organize profile data as directed by the controller
   */
  public static final Vector<Organizer> organizers = new Vector<Organizer>();

  /**
   * A blocking priority queue where organizers place events to
   * be processed by the controller
   * (an input to the controller thread)
   */
  public static BlockingPriorityQueue controllerInputQueue;

  /**
   * A blocking priority queue where the controller will place methods
   * to be opt compiled
   * (an output of the controller thread)
   */
  public static BlockingPriorityQueue compilationQueue;

  /**
   * The strategy used to make recompilation decisions
   */
  public static RecompilationStrategy recompilationStrategy;

  /**
   * Controller virtual clock, ticked every taken yieldpoint.
   */
  public static int controllerClock = 0;

  /**
   * The main hot method raw data object.
   */
  public static MethodCountData methodSamples;

  /**
   * The dynamic call graph
   */
  public static PartialCallGraph dcg;

  /**
   * Used to shut down threads
   */
  private static final ThreadDeath threadDeath = new ThreadDeath();

  /**
   * Has the execution of boot completed successfully?
   */
  private static boolean booted = false;

  /**
   * Initialize the controller subsystem (called from VM.boot)
   * This method is called AFTER the command line options are processed.
   */
  public static void boot() {
    // Signal that the options and (optional) logging mechanism are set
    // RuntimeCompiler checks this flag
    enabled = true;

    // Initialize the controller input queue
    controllerInputQueue = new BlockingPriorityQueue(new BlockingPriorityQueue.CallBack() {
      public void aboutToWait() { controllerThread.aboutToWait(); }
      public void doneWaiting() { controllerThread.doneWaiting(); }
    });

    compilationQueue = new BlockingPriorityQueue();

    // Create the analytic model used to make cost/benefit decisions.
    recompilationStrategy = new MultiLevelAdaptiveModel();

    // boot the runtime measurement systems
    RuntimeMeasurements.boot();

    // Initialize subsystems, if being used
    AdaptiveInlining.boot(options);

    // boot any instrumentation options
    Instrumentation.boot(options);

    // boot the aos database
    AOSDatabase.boot(options);

    CounterBasedSampling.boot(options);

    createControllerThread();

    Controller controller = new Controller();
    Callbacks.addExitMonitor(controller);
    Callbacks.addAppStartMonitor(controller);
    Callbacks.addAppCompleteMonitor(controller);
    Callbacks.addAppRunStartMonitor(controller);
    Callbacks.addAppRunCompleteMonitor(controller);

    // make sure the user hasn't explicitly prohibited this functionality
    if (!options.DISABLE_RECOMPILE_ALL_METHODS) {
      Callbacks.addRecompileAllDynamicallyLoadedMethodsMonitor(controller);
    }

    booted = true;
  }

  /**
   * To be called when the VM is about to exit.
   * @param value the exit value
   */
  public void notifyExit(int value) {
    report();
  }

  /**
   * To be called when the application starts
   * @param app the application name
   */
  public void notifyAppStart(String app) {
    AOSLogging.appStart(app);
    AOSLogging.recordRecompAndThreadStats();
  }

  /**
   * To be called when the application completes
   * @param app the application name
   */
  public void notifyAppComplete(String app) {
    AOSLogging.appComplete(app);
    AOSLogging.recordRecompAndThreadStats();
  }

  /**
   * To be called when the application completes one of its run
   * @param app the application name
   * @param run the run number, i.e. what iteration of the app we have started
   */
  public void notifyAppRunStart(String app, int run) {
    AOSLogging.appRunStart(app, run);
    AOSLogging.recordRecompAndThreadStats();
  }

  /**
   * To be called when the application completes one of its run
   * @param app the application name
   * @param run the run number, i.e. what iteration of the app we have completed
   */
  public void notifyAppRunComplete(String app, int run) {
    AOSLogging.appRunComplete(app, run);
    AOSLogging.recordRecompAndThreadStats();
  }

  /**
   * Called when the application wants to recompile all dynamically
   *  loaded methods.  This can be expensive!
   */
  public void notifyRecompileAll() {
    AOSLogging.recompilingAllDynamicallyLoadedMethods();
    RecompilationManager.recompileAllDynamicallyLoadedMethods(false);
  }

  // Create the ControllerThread
  static void createControllerThread() {
    Object sentinel = new Object();
    ControllerThread tt = new ControllerThread(sentinel);
    tt.start();
    // wait until controller threads are up and running.
    try {
      synchronized (sentinel) {
        sentinel.wait();
      }
    } catch (Exception e) {
      e.printStackTrace();
      VM.sysFail("Failed to start up controller subsystem");
    }
  }

  /**
   * Process any command line arguments passed to the controller subsystem.
   * <p>
   * This method has the responsibility of creating the options object
   * if it does not already exist
   * <p>
   * NOTE: All command line argument processing should be handled via
   * the automatically generated code in AOSOptions.java.
   * Don't even think of adding handwritten stuff here! --dave
   *
   * @param arg the command line argument to be processed
   */
  public static void processCommandLineArg(String arg) {
    if (!options.processAsOption("-X:aos", arg)) {
      VM.sysWrite("vm: illegal adaptive configuration directive \"" + arg + "\" specified as -X:aos:" + arg + "\n");
      VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
    }
  }

  /**
   * This method is called when the VM is exiting to provide a hook to allow
   * the adpative optimization subsystem to generate a summary report.
   * It can also be called directly from driver programs to allow
   * reporting on a single run of a benchmark that the driver program
   * is executing in a loop (in which case the adaptive system isn't actually
   * exiting.....so some of the log messages may get a little wierd).
   */
  public static void report() {
    if (!booted) return;
    ControllerThread.report();
    RuntimeMeasurements.report();

    for (Enumeration<Organizer> e = organizers.elements(); e.hasMoreElements();) {
      Organizer organizer = e.nextElement();
      organizer.report();
    }

    if (options.FINAL_REPORT_LEVEL >= 2) {
      EdgeCounts.dumpCounts();
      dcg.dumpGraph();
    }

    if (options.REPORT_INTERRUPT_STATS) {
      VM.sysWriteln("Timer Interrupt and Listener Stats");
      VM.sysWriteln("\tTotal number of clock ticks ", GreenProcessor.timerTicks);
      VM.sysWriteln("\tReported clock ticks ", GreenProcessor.reportedTimerTicks);
      VM.sysWriteln("\tController clock ", controllerClock);
      VM.sysWriteln("\tNumber of method samples taken ", (int) methodSamples.getTotalNumberOfSamples());
    }

    AOSLogging.systemExiting();
  }

  /**
   * Stop all AOS threads and exit the adaptive system.
   * Can be used to assess code quality in a steady state by
   * allowing the adaptive system to run "for a while" and then
   * stoppping it
   */
  public static void stop() {
    if (!booted) return;

    VM.sysWriteln("AOS: Killing all adaptive system threads");
    for (Enumeration<Organizer> e = organizers.elements(); e.hasMoreElements();) {
      Organizer organizer = e.nextElement();
      organizer.kill(threadDeath, true);
    }
    compilationThread.kill(threadDeath, true);
    controllerThread.kill(threadDeath, true);
    RuntimeMeasurements.stop();
    report();
  }
}
