/*
	CommandInterpreter.java
	by Lewis Baumstark

Purpose
--------------

Takes a string formatted with the JBOT language as input and parses it for
calling appropriate graphics routines, calculations, etc.


Algorithm
--------------
	
1. pre-passes through an input string and creates a table of all lines
with "LABEL" command on them (for reference when input script is actually
run).

2. Tokenizes an input string into lines delimited by ";"'s.

3. Tokenizes each line, in succession, into the command followed by its
operators.

4. Each parsed line calls the appropriate method to perform calculations,
call graphics routines, etc.  Also writes tracing information to a
StringBuffer object.

*/


import java.io.*;
import java.lang.*;
import java.util.*;

public class CommandInterpreter {

      //public variables/objects

      public static String script; //string containing the entire text of the input script
      public StringTokenizer scriptLines; //each token here is one line of the input script
      public static String name = new String("default");

      //graphics objects
      public base b = new base();
      public robot r = new robot();

      private static int MAX_CODE = 100; //specifies the size of codeMem[]
      public static String codeMem[] = new String [MAX_CODE]; //a "virtual address space" for lines of code
      int lineNum = 0; //index variable for codeMem[]
      int totalLines =  0; //will hold the total number of lines
      int n; //counter variable.  no other major significance.  poor insignificant little n.

    //all label references begin with "L", ie, "LABEL L14" accesses the 14th element
    private static int MAX_LABELS = 25; //specifies size of the above
    public static int labelTable[] = new int [MAX_LABELS]; //lookup table for labelled lines

    //all register references begin with "V" followed by an integer, ie
    // "V5" refers to the data in register 5, which is varList[5]
    private static int MAX_VAR = 10; //specifies size of the above
    public static int varList[] = new int [MAX_VAR]; // list of "virtual data registers"

    //Status register definition
    private static Register SR = new Register();

    //place to send tracing information
    public StringBuffer tracer = new StringBuffer();

  //public constructor method
    public CommandInterpreter()
    {
	    this.loadScript("");
    }

    //loads the input string into the command parser
    public void loadScript(String s)
    {
	    this.script = s;
    }

  //reset all major variables back to initial values
    public void reset()
    {
      this.script = "";
      this.name = "";
      this.lineNum = 0;
      this.totalLines = 0;
      SR.set();
      this.tracer.setLength(0);
      r.reset();
      b.reset();

      for(n=0;n<=MAX_VAR;n++){varList[n]=0;}
      for(n=0;n<=MAX_LABELS;n++){labelTable[n]=0;}
    }


  //main looping routing to go through all the lines of the input script
    public void parseScript()
    {
	    String s = new String(this.script);

	    this.scriptLines = new StringTokenizer(this.script, ";");
	    
	    //put the full source at the beginning of the trace stream
	    this.tracer.append("\nUser Input\n----------\n");
	    this.tracer.append(this.script+"\n\n\n");
	    this.tracer.append("+---Begin Trace---+\n\n");

	    //"loads" the lines of code into the "virtual memory"
	    while(this.scriptLines.hasMoreTokens())
	    {
		    CommandInterpreter.codeMem[this.lineNum] = this.scriptLines.nextToken();
		    this.lineNum++;
	    }

	    this.totalLines = this.lineNum;	

	    this.lineNum = 0; //needs to be reset for later

	    //pre pass the script and find all the labels
	    this.findLabel1(codeMem);

	    //print a table of labels into the tracer
	    for(n=0;n<=MAX_LABELS-1;n++)
	    {
		    this.tracer.append("L"+n+" = "+labelTable[n]+"\n");
	    }	



	    //the graphics routines accessed
	    while(this.lineNum < this.totalLines)
	    {
		    this.processLine(codeMem[this.lineNum]);
		    this.lineNum++;
	    }

	    //System.out.print(this.tracer.toString());

	    this.lineNum = 0; //reset so we overwrite codeMem instead of appending!
    }


      //findLabel1() and findLabel2() are used to establish a list of labelled lines
      //this is done on the first pass of the interpreter
    private void findLabel1(String s[])
	{
	  int lineNum=0;

	    for(lineNum=0;lineNum<=this.totalLines-1;lineNum++)
	    {
	      this.findLabel2(s[lineNum],lineNum);
	    }
	}

    private void findLabel2(String s, int lnum)
	{
	  int a;
	  StringTokenizer t = new StringTokenizer(s);
	  if((t.nextToken()).equals("LABEL"))
	    {
	      this.labelTable[this.getLabelIndex(t.nextToken())] = lnum;
	    }
	}

  //parse out the individual lines of code
    public void processLine(String line)
    {
	    String firstWord;

	    StringTokenizer command = new StringTokenizer(line);

	    this.tracer.append(line+";\n"); //tell which line we're tracing

	    firstWord = command.nextToken(); //ie, firstWord = the keyword

    	    //route to the appropriate method to process the keyword
	    if(firstWord.equalsIgnoreCase("EQ")) this.EQ(command.nextToken(), command.nextToken());
	    if(firstWord.equalsIgnoreCase("ADD")) 
	    {
		    this.ADD(command.nextToken(), command.nextToken());
	    }
	    if(firstWord.equalsIgnoreCase("SENSOR")) this.SENSOR(); 
	    if(firstWord.equalsIgnoreCase("COPEN")) this.COPEN(command.nextToken());
	    if(firstWord.equalsIgnoreCase("CCLOSE")) this.CCLOSE(command.nextToken());
	    if(firstWord.equalsIgnoreCase("ROTATE")) this.ROTATE(command.nextToken(),command.nextToken(),command.nextToken());
	    if(firstWord.equalsIgnoreCase("BRT")) this.BRT(command.nextToken());
	    if(firstWord.equalsIgnoreCase("BNT")) this.BNT(command.nextToken());
	    if(firstWord.equalsIgnoreCase("LABEL")) this.LABEL(command.nextToken());
	    if(firstWord.equalsIgnoreCase("END"))
	    {
		    this.tracer.append("Program finished.\n");
		    this.tracer.append("\n+---End Trace---+\n\n");
		    return;
	    }
    }

    //decides if an operator is a register name or a numeric value
    //if a register, returns the value in the register, else it
    //returns the value of the integer
    public int getVarOrInt(String s)
    {
	    if(s.startsWith("V")) return this.varList[Integer.parseInt(s.substring(1))];
	    else return Integer.parseInt(s);
    }

    //returns the index of the label reference.  if an error occurs, will simply return the next line.
    private int getLabelIndex(String s)
    {
	    if(s.startsWith("L")) return Integer.parseInt(s.substring(1));
	    else return 0;
    }

    //returns the value of the label reference.
    public int getLabel(String s)
    {
	    if(s.startsWith("L")) return this.labelTable[Integer.parseInt(s.substring(1))];
	    else return ++this.lineNum;
    }

    //returns true if op1 == op2, else false, sets the status register accordingly
    public boolean EQ(String op1, String op2)
    {
	    int a;
	    int b;
	    a = this.getVarOrInt(op1);
	    b = this.getVarOrInt(op2);
	    if(a == b) SR.reset();
	    else SR.set();
	    this.tracer.append("op1 = "+a+" op2 = "+b+"\nresult: "+SR.check()+"\n\n");
	    return SR.check();
    }

    //adds two values and places the result in the first operand
    //the first operand must be a variable name
    public int ADD(String op1, String op2)
    {
	    int a;
	    int b;
	    int c;
	    int result;

	    a = this.getVarOrInt(op1);

	    b = this.getVarOrInt(op2);

	    c = Integer.parseInt(op1.substring(1));

	    result = a + b;
	    this.varList[c] = result;

	    //write tracing info
	    this.tracer.append("op1 + op2 -> V"+c+"\n"+a+" + "+b+" -> "+result+"\n\n");	

	    return result;    
    }

    //sets the status register to true if the claw's sensor detects an object
    //false otherwise
    public boolean SENSOR()
    {
    /*	if("Claw Object".sensor)SR.set(); //needs to be coordinator with
    Andy
	    else SR.reset();
    */
	    //write tracing info
	    this.tracer.append("Sensor status: "+SR.check()+"\n\n");

	    return SR.check();
    }

    //calls the claw open routines with a percentage
    //claw will open to that percentage, NOT that much more percentage 
    public static int percent = 0;
    public int COPEN(String op)
    {
	    int a;
	    a = this.getVarOrInt(op);
	    //percent = percent + a;
	    //test if claw is already open this much
	    //if(percent >= 100) percent = 99;

    	    r.moveControl(0, 0, a);
    
	    //write tracing info
	    this.tracer.append("\tClaw Position is "+percent+" %\n\n");

	    return percent;
    }

    public int CCLOSE(String op)
    {
	    int a;
	    a = this.getVarOrInt(op);
	    //    percent = percent - a;

	    //test if claw isn't closed
	    //if(percent <= 0) percent = 0;
    
	    r.moveControl(0, 0, -a);
   
	    //write tracing info
	    this.tracer.append("Claw position is "+percent+" %\n\n");

	    return percent;
    }

    // "branch on true"
    public void BRT(String op)
    {

	    if(SR.check())
	    {
		    this.lineNum = this.getLabel(op);
		    //write tracing info
		    this.tracer.append("Branching to label "+op+"\n\n");
		    //send command to Andy's stuff here
		    this.SR.set();
	    }
	    else this.tracer.append("Condition not true; no branch.\n\n");
	    //this.lineNum--; //necessary b/c of the ++ in the while loop
    }

    //"branch on not true"
    public void BNT(String op)
    {
	    if(!SR.check())
	    {
		    this.lineNum = this.getLabel(op);
		    //write  tracing info
		    this.tracer.append("Brancing to label "+op+"\n\n");
		    //send message to Andy's stuff here
		    this.SR.set();
	    }
	    else this.tracer.append("Test true; no branch.\n\n");
    }

    //label a line
    public void LABEL(String op)
    {
      //this.labelTable[this.getLabel(op)] = this.lineNum;
	    //write tracing info
	    this.tracer.append(op+" = "+this.lineNum+"\n\n");
    }

    //rotate subroutines and static var's to accompany
    private static int Apos;
    private static int Bpos;
    private static int Cpos;
    public void ROTATE(String a1, String b1, String c1) {

	    int Adeg;
	    int Bdeg;
	    int Cdeg;

	    Adeg = getVarOrInt(a1);
	    Bdeg = getVarOrInt(b1);
	    Cdeg = getVarOrInt(c1);

	    //determine relative orientation of joint A and range-check
	    Apos = Apos + Adeg;
	    if(Apos <= 0) Apos = 0;
	    if(Apos >= 180) Apos = 180;

	    //determine relative orientation of joint B and range-check
	    Bpos = Bpos + Bdeg;
	    if(Bpos <= 0) Bpos = 0;
	    if(Bpos >= 90) Bpos = 90;

	    //Andy's arm movement
	    r.moveControl(Adeg, Bdeg, 0);

	    //determine relative orientation of joint C and range-check
	    Cpos = Cpos + Cdeg;
	    if(Cpos <= 0) Cpos = Cpos + 360;
	    if(Cpos >= 360) Cpos = Cpos - 360;
	    b.move(Cdeg);

	    //write tracing info
	    this.tracer.append("A moved "+Adeg+" deg.\n");
	    this.tracer.append("B moved "+Bdeg+" deg.\n");
	    this.tracer.append("C moved "+Cdeg+" deg.\n");
    }

}