// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.ag;

import nachos.machine.*;
import nachos.security.*;
import nachos.threads.*;
import java.util.Hashtable;
import java.util.StringTokenizer;

/**
 * The default autograder. Loads the kernel, and then tests it using
 * <tt>Kernel.selfTest()</tt>.
 */
public class AutoGrader
{
  /**
   * Allocate a new autograder.
   */
  public AutoGrader ()
  {
  }
  
  /**
   * Start this autograder. Extract the <tt>-#</tt> arguments, call
   * <tt>init()</tt>, load and initialize the kernel, and call <tt>run()</tt>.
   * 
   * @param privilege
   *          encapsulates privileged access to the Nachos machine.
   */
  public void start (Privilege privilege)
  {
    Lib.assertTrue(this.privilege == null, "start() called multiple times");
    this.privilege = privilege;
    
    String[] args = Machine.getCommandLineArguments();
    
    extractArguments(args);
    
    System.out.print(" grader");
    
    init();
    
    System.out.print("\n");
    
    kernel = (Kernel)Lib.constructObject(Config.getString("Kernel.kernel"));
    kernel.initialize(args);
    
    run();
  }
  
  private void extractArguments (String[] args)
  {
    String testArgsString = Config.getString("AutoGrader.testArgs");
    if (testArgsString == null)
    {
      testArgsString = "";
    }
    
    for (int i = 0; i < args.length;)
    {
      String arg = args[i++];
      if (arg.length() > 0 && arg.charAt(0) == '-')
      {
        if (arg.equals("-#"))
        {
          Lib.assertTrue(i < args.length, "-# switch missing argument");
          testArgsString = args[i++];
        }
      }
    }
    
    StringTokenizer st = new StringTokenizer(testArgsString, ",\n\t\f\r");
    
    while (st.hasMoreTokens())
    {
      StringTokenizer pair = new StringTokenizer(st.nextToken(), "=");
      
      Lib.assertTrue(pair.hasMoreTokens(), "test argument missing key");
      String key = pair.nextToken();
      
      Lib.assertTrue(pair.hasMoreTokens(), "test argument missing value");
      String value = pair.nextToken();
      
      testArgs.put(key, value);
    }
  }
  
  String getStringArgument (String key)
  {
    String value = (String)testArgs.get(key);
    Lib.assertTrue(value != null, "getStringArgument(" + key
      + ") failed to find key");
    return value;
  }
  
  int getIntegerArgument (String key)
  {
    try
    {
      return Integer.parseInt(getStringArgument(key));
    }
    catch (NumberFormatException e)
    {
      Lib.assertNotReached("getIntegerArgument(" + key + ") failed: "
        + "value is not an integer");
      return 0;
    }
  }
  
  boolean getBooleanArgument (String key)
  {
    String value = getStringArgument(key);
    
    if (value.equals("1") || value.toLowerCase().equals("true"))
    {
      return true;
    }
    else if (value.equals("0") || value.toLowerCase().equals("false"))
    {
      return false;
    }
    else
    {
      Lib.assertNotReached("getBooleanArgument(" + key + ") failed: "
        + "value is not a boolean");
      return false;
    }
  }
  
  long getTime ()
  {
    return privilege.stats.totalTicks;
  }
  
  void targetLevel (int targetLevel)
  {
    this.targetLevel = targetLevel;
  }
  
  void level (int level)
  {
    this.level++;
    Lib.assertTrue(level == this.level,
      "level() advanced more than one step: test jumped ahead");
    
    if (level == targetLevel)
      done();
  }
  
  private int level = 0, targetLevel = 0;
  
  void done ()
  {
    System.out.print("\nsuccess\n");
    privilege.exit(162);
  }
  
  private Hashtable<String, String> testArgs = new Hashtable<String, String>();
  
  void init ()
  {
  }
  
  void run ()
  {
    kernel.selfTest();
    kernel.run();
    kernel.terminate();
  }
  
  Privilege privilege = null;
  Kernel kernel;
  
  /**
   * Notify the autograder that the specified thread is the idle thread.
   * <tt>KThread.createIdleThread()</tt> <i>must</i> call this method before
   * forking the idle thread.
   * 
   * @param idleThread
   *          the idle thread.
   */
  public void setIdleThread (KThread idleThread)
  {
  }
  
  /**
   * Notify the autograder that the specified thread has moved to the ready
   * state. <tt>KThread.ready()</tt> <i>must</i> call this method before
   * returning.
   * 
   * @param thread
   *          the thread that has been added to the ready set.
   */
  public void readyThread (KThread thread)
  {
  }
  
  /**
   * Notify the autograder that the specified thread is now running.
   * <tt>KThread.restoreState()</tt> <i>must</i> call this method before
   * returning.
   * 
   * @param thread
   *          the thread that is now running.
   */
  public void runningThread (KThread thread)
  {
    privilege.tcb.associateThread(thread);
    currentThread = thread;
  }
  
  /**
   * Notify the autograder that the current thread has finished.
   * <tt>KThread.finish()</tt> <i>must</i> call this method before putting the
   * thread to sleep and scheduling its TCB to be destroyed.
   */
  public void finishingCurrentThread ()
  {
    privilege.tcb.authorizeDestroy(currentThread);
  }
  
  /**
   * Notify the autograder that a timer interrupt occurred and was handled by
   * software if a timer interrupt handler was installed. Called by the hardware
   * timer.
   * 
   * @param privilege
   *          proves the authenticity of this call.
   * @param time
   *          the actual time at which the timer interrupt was issued.
   */
  public void timerInterrupt (Privilege privilege, long time)
  {
    Lib.assertTrue(privilege == this.privilege, "security violation");
  }
  
  /**
   * Notify the autograder that a user program executed a syscall instruction.
   * 
   * @param privilege
   *          proves the authenticity of this call.
   * @return <tt>true</tt> if the kernel exception handler should be called.
   */
  public boolean exceptionHandler (Privilege privilege)
  {
    Lib.assertTrue(privilege == this.privilege, "security violation");
    return true;
  }
  
  /**
   * Notify the autograder that <tt>Processor.run()</tt> was invoked. This can
   * be used to simulate user programs.
   * 
   * @param privilege
   *          proves the authenticity of this call.
   */
  public void runProcessor (Privilege privilege)
  {
    Lib.assertTrue(privilege == this.privilege, "security violation");
  }
  
  /**
   * Notify the autograder that a COFF loader is being constructed for the
   * specified file. The autograder can use this to provide its own COFF loader,
   * or return <tt>null</tt> to use the default loader.
   * 
   * @param file
   *          the executable file being loaded.
   * @return a loader to use in loading the file, or <tt>null</tt> to use the
   *         default.
   */
  public Coff createLoader (OpenFile file)
  {
    return null;
  }
  
  /**
   * Request permission to send a packet. The autograder can use this to drop
   * packets very selectively.
   * 
   * @param privilege
   *          proves the authenticity of this call.
   * @return <tt>true</tt> if the packet should be sent.
   */
  public boolean canSendPacket (Privilege privilege)
  {
    Lib.assertTrue(privilege == this.privilege, "security violation");
    return true;
  }
  
  /**
   * Request permission to receive a packet. The autograder can use this to drop
   * packets very selectively.
   * 
   * @param privilege
   *          proves the authenticity of this call.
   * @return <tt>true</tt> if the packet should be delivered to the kernel.
   */
  public boolean canReceivePacket (Privilege privilege)
  {
    Lib.assertTrue(privilege == this.privilege, "security violation");
    return true;
  }
  
  boolean hasArgument (String key)
  {
    return testArgs.get(key) != null;
  }
  
  private KThread currentThread;
}
