package rebelsky.pal;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.util.Vector;

/**
 * A simple computer, intended to run PAL programs.  The normal
 * sequence of operations is:
 * <pre>
 * // Create a new computer
 * Computer hal = new Computer(max-bytes);
 * hal.setCode(program);
 * hal.run(false);
 * </pre>
 * 
 * @author Samuel A. Rebelsky
 * @version 1.0 of November 2002
 */
public class Computer 
{
  // +----------------+----------------------------------------------------
  // | Program Design |
  // +----------------+

/*
  A computer includes code, memory, program counter, stack pointer,
  frame pointer and memory.

  We repeatedly execute the current instruction (given by the program
  counter) until we reach a Halt instruction.

  Labels and Temporaries store their corresponding values.

  At some point, I should add debugging capabilities.
 */

  // +--------+------------------------------------------------------------
  // | Fields |
  // +--------+

  /** The instructions for the current program. */
  Vector code;

  /** The number of instructions for the current program. */
  int codesize;

  /** Memory. */
  int[] memory;

  /** The program counter. */
  int pc;

  /** The stack pointer. */
  int sp;

  /** The frame pointer. */
  int fp;

  /** The writer use for output. */
  PrintWriter out;

  /** The reader to use for input. */
  BufferedReader in;


  // +--------------+------------------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Build a new computer with a specified space for memory.
   */
  public Computer(int mem) {
    this.memory = new int[mem];
    out = new PrintWriter(System.out, true);
    in = new BufferedReader(new InputStreamReader(System.in));
  } // Computer()


  // +---------------+-----------------------------------------------------
  // | Local Helpers |
  // +---------------+

  /**
   * Flatten a tree of instructions onto the end of the code.
   */
  private void flatten(InstructionSequence seq)
  {
    int seqlen = seq.instructions.size();
    for (int i = 0; i < seqlen; i++) {
      Instruction command = (Instruction) seq.instructions.get(i);
      if (command instanceof InstructionSequence)
        flatten((InstructionSequence) command);
      else
        this.code.add(command);
    } // for
  } // flatten(InstructionSequence, int)

  /**
   * Print an instruction along with its line number.
   */
  private void printInstruction(int line, Instruction command)
  {
    // Print the instruction number with leading 0's.
    if (line < 10) out.print("000");
    else if (line < 100) out.print("00");
    else if (line < 1000) out.print("0");
    out.print(line);
    out.print(" ");

    // Print labels followed by colons.
    if (command instanceof Label)
      out.println(command.toString() + ":");
    // Print everything else normally.
    else
      out.println(command.toString());
  } // printInstruction(int,Instruction)

  /**
   * Scan the code, setting each label to its line number.
   */
  private void processLabels()
  {
    for (int line = 0; line < this.codesize; line++) {
      Instruction command = (Instruction) code.get(line);
      if (command instanceof Label)
        ((Label) command).address = line;
    } // for(int line)
  } // processLabels()


  // +---------+-----------------------------------------------------------
  // | Methods |
  // +---------+

  /**
   * Dump the program to the specified PrintWriter.
   */
  public void dump(PrintWriter out)
  {
    for (int line = 0; line < this.codesize; line++) {
       printInstruction(line, (Instruction) code.get(line));
    } // for(int i)
  } // dump(PrintWriter)

  /**
   * Run the program starting at instruction 0.  If the parameter is
   * true, prints out a trace of the program.
   *
   * @exception Exception
   *   If one of the instructions fails or we run out of instructions
   *   without hitting a stop.
   */
  public void run(boolean trace)
    throws Exception
  {
    Instruction current;
    // Set the program counter to 0.
    this.pc = 0;
    // Set the frame pointer and stack pointer to the end of memory.
    this.fp = memory.length;
    this.sp = memory.length;
    // Repeatedly get the current instruction and execute it.
    while (this.pc < this.codesize) {
      current = (Instruction) this.code.get(this.pc);
      if (trace) { printInstruction(this.pc, current); }
      this.pc++;
      if (current instanceof Halt) return;
      current.execute(this);
    }
    // Sanity check: Did we stop because of incorrect pc?
    throw new Exception("Invalid instruction number: " + this.pc);
  } // run()

  /**
   * Set the code in the computer to a new sequence of instructions.
   */
  public void setCode(InstructionSequence seq) 
  {
    this.code = new Vector();
    flatten(seq);
    this.codesize = this.code.size();
    processLabels();
  } // setCode(InstructionSequence seq)

} // class Computer

