package logo.lang;

import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;

import java.awt.Color;

import java.lang.Thread;

import logo.lang.VarContext;
import logo.lang.rLogoPrimitive;
import logo.lang.rLogoHelp;
import logo.lang.rLogoException;

public abstract class Interpreter extends Thread {

   VarContext vc; //holds any variables & their values
   SubTable st;   //holds any subroutines & their definitions

   rLogoPrimitive r = new rLogoPrimitive(); //holds all of the rLogo primitives
   Vector queue = new Vector(); //holds pending commands

   private boolean rLogoBreak=false;
   //This could have imported logo.ui.TurtleGraphics, and supplied the
   //default behavior for these functions, but TurtleControlPanel needs
   //to override most of them to reflect state changes (for instance,
   //setColor changes the state of the ColorChooser, HideTurtle changes
   //the state of the "Turtle is Hidden" button, etc.) It seemed
   //there would be a slight performance gain in TurtleControlPanel if
   //calls to 'super.someMethod()' were avoided in the overridden functions,
   //so they are declared as 'abstract' and are fully implemented in
   //TurtleControlPanel. This also serves to better separate the 'language'
   //objects from the 'graphical' objects.

   public final static String rLOGO_BATCH = "_rLogo_Batch_";
   public final static String rLOGO_DUMP = "_rLogo_Dump_";

   public abstract void wrapString(String s);
   public abstract void forward(int i);
   public abstract void backward(int i);
   public abstract void turnLeft(int i);
   public abstract void turnRight(int i);
   public abstract void penUp();
   public abstract void penDown();
   public abstract void showTurtle();
   public abstract void hideTurtle();
   public abstract void clearScreen();
   public abstract void home();
   public abstract void setColor(Color c);
   public abstract void setBackColor(Color c);
   public abstract void edit(LogoSub s);
   public abstract void resetMode();
   public abstract void drawString(String s);
   public abstract void setRepaint(boolean b);
   public abstract void paint();

   public Interpreter() {
      vc=new VarContext();
      st=new SubTable();
   }

   public String popString(LogoList l) throws rLogoException {
     String result = l.pop();
     if (result==null) {
        throw(new rLogoException("Error: Not enough parameters!"));
     }
     return result;
   }

   public int popInt(LogoList l) throws rLogoException {
      Expression e = new Expression();
      Integer result = new Integer(0);
      String s = l.pop();
      if ( s==null ) {
        throw(new rLogoException("Error: Not enough parameters!"));
      } else { try {
          result = new Integer(e.evaluate(s,vc));
        } catch (NumberFormatException x) {
            throw(new rLogoException("Error: Bad Number"));
          }
      }
      return result.intValue();
   }

   public void queue(String command) {
      //queues a command for later processing
      queue.addElement(command);
   }

   public void run() {
     while (true) {
      if ( !queue.isEmpty() ) {
        try {
          execute( (String)queue.firstElement() );
        } catch (rLogoBreakException x) {
            wrapString( x.getMessage() );
            queue.removeAllElements();
        } catch (rLogoException x) {
            wrapString( x.getMessage() );
        }
        if (!queue.isEmpty()) { queue.removeElementAt(0); }
      } else {
        if (rLogoBreak) rLogoBreak=false;
      }
      try {
        sleep(100);
      } catch (InterruptedException e) {;}
     }
   }

   public final void Break() {
      rLogoBreak=true;
   }

   public void execute(String command) throws rLogoException {
      LogoList l;
      l = new LogoList(command);
      Expression e = new Expression();

      String cmdString;
      int condition;
      Color c;

      while ( !l.empty() ) {
         if (rLogoBreak) { rLogoBreak=false; throw new rLogoBreakException("interrupted"); }
         String nextCommand = popString(l);
         nextCommand=nextCommand.toLowerCase();

         if ( nextCommand.startsWith("[") ) {  //(command block)
            execute(nextCommand.substring(1,nextCommand.length()-1));
         } else
         switch ( r.lookup(nextCommand) ) {
          case rLogoPrimitive.COMMAND_NOT_FOUND : //perhaps it's a subroutine.
              LogoSub subroutine=st.findSub(nextCommand);
              if (subroutine!=null) {
                cmdString = subroutine.getValue();
                int param=0;
                while ( param<subroutine.paramCount() ) {
                    execute("make "+subroutine.getParam(param++)+" "+popString(l));
                }
                execute(cmdString);
              } else {
                  wrapString("I don't know how to "+nextCommand);
                  return;
              }
              break;
          case rLogoPrimitive.SHOW : //shows the value of an expression
                wrapString( e.evaluate(popString(l),vc));
              break;
          case rLogoPrimitive.RUN : //executes a list (e.g. run [fd 100])
              cmdString = popString(l);
              execute(cmdString.substring(1,cmdString.length()-1));
              break;
          case rLogoPrimitive.REPEAT : //repeat n [list]
              int count=popInt(l);
              cmdString = popString(l);
              for (int i=0; i<count; i++) {
                execute(cmdString);
              }
              break;
          case rLogoPrimitive.WHILE : //while (conditon) [list]
              String condString = popString(l);
              cmdString = popString(l);
                while ( new Integer(e.evaluate(condString,vc)).intValue()!=0 ) {
                    execute(cmdString);
                }
              break;
          case rLogoPrimitive.IF_ELSE : //if (conditon) [true_list] [false_list]
              condition=popInt(l);
              cmdString = popString(l);
              String elseCmdString = popString(l);
              if (condition==0) { //false
                execute(elseCmdString);
              } else {
                execute(cmdString);
              }
              break;
          case rLogoPrimitive.MAKE : //make variable (expression)
              String var = popString(l);
              int value = popInt(l);
              if ( var.startsWith(":") ) {
                wrapString("Error: illegal variable name");
                return;
              } else vc.setValue(var,value);
              break;
          case rLogoPrimitive.TO : //to (procedure_name)
              String subName = popString(l);
              if ( subName.length()>0 ) {
                if ( r.lookup(subName.toLowerCase())==rLogoPrimitive.COMMAND_NOT_FOUND ) {
                  LogoSub sub=st.findSub(subName);
                  if ( sub==null ) {
                    sub=new LogoSub(subName,"");
                    st.setObject(sub);
                  }
                  edit(sub);
                } else {
                  wrapString("Error: Can't redefine primitives");
                  return;
                }
              } else {
                wrapString("('to' must be followed by a name)");
                return;
              }
              break;
          case rLogoPrimitive.FORWARD : //forward (expression)
              forward(popInt(l));
              break;
          case rLogoPrimitive.BACK : //back (expression)
              backward(popInt(l));
              break;
          case rLogoPrimitive.RIGHT : //right (expression)
              turnRight(popInt(l));
              break;
          case rLogoPrimitive.LEFT : //left (expression)
              turnLeft(popInt(l));
              break;
          case rLogoPrimitive.PEN_UP : //Lifts the Turtle's Pen
              penUp();
              break;
          case rLogoPrimitive.PEN_DOWN : //Lowers the Turtle's Pen
              penDown();
              break;
          case rLogoPrimitive.SHOW_TURTLE : //Exposes the Turtle
              showTurtle();
              break;
          case rLogoPrimitive.HIDE_TURTLE : //Makes the Turtle Disappear
              hideTurtle();
              break;
          case rLogoPrimitive.CLEAR_SCREEN :  //Erases the Screen
              clearScreen();
              break;
          case rLogoPrimitive.AUTO : //resumes Automatic repaints
              setRepaint(true);
              paint();
              break;
          case rLogoPrimitive.MANUAL : // Disables automatic screen refreshes
              setRepaint(false);
              break;
          case rLogoPrimitive.PAINT :   // Force a Screen Refresh
              paint();
              break;
          case rLogoPrimitive.HOME :    // Returns the Turtle to its Home Position
              home();
              break;
          case rLogoPrimitive.SET_PEN_COLOR : // Changes the Drawing Color
              c = new Color(popInt(l),popInt(l),popInt(l));
              setColor(c);
              break;
          case rLogoPrimitive.SET_BACK_COLOR :  // Changes the Background (Screen) Color
              c = new Color(popInt(l),popInt(l),popInt(l));
              setBackColor(c);
              break;
          case rLogoPrimitive.WRITE :   // Writes a message to the Screen
              drawString(e.evaluate(popString(l),vc));
              break;
          case rLogoPrimitive.PAUSE :   // Pauses the specified number of seconds
              try {
                sleep(popInt(l));
              } catch (InterruptedException exception) {;}
              break;
          case rLogoPrimitive.BATCH : // Batch Loader
              LogoSub batch = new LogoSub(rLOGO_BATCH,"");
              edit(batch);
              break;
          case rLogoPrimitive.DUMP :  // Batch Dump (i.e. Save)
              LogoSub dump = new LogoSub(rLOGO_DUMP,"");
              dump.setValue(dump());
              edit(dump);
              break;
          case rLogoPrimitive.PARAMS : // Ignore this and the rest of the line
              //ignore it.
              popString(l);
              break;
          case rLogoPrimitive.END :
              //do nothing; (most likely, the user pasted the output
              //of 'dump()' into a new function.)
              break;
          case rLogoPrimitive.HELP : // Request for on-line help
              if (l.empty()) wrapString(getHelp(""));
              else wrapString(getHelp(popString(l)));
              break;
          default :
              wrapString("I don't know how to "+nextCommand);
              return;
         }

      }
   }

    //The rLogoHelp class is relatively large (~10K),
    //so override this method for a leaner class:
    String getHelp(String topic) {
      rLogoHelp help = new rLogoHelp();
      return help.topic(topic);
    }

   public String dump() {
   //"dumps" all procedures and variables into the editor, in a
   //format that is suitable for later processing by "load()"
   //It would be a trivial task to make this save out to a
   //file in a non-secure application...
      String result=new String();
      LogoVar nextVar = vc.First();
      while ( nextVar!=null ) {
        result+="make "+nextVar.getName()+" "+nextVar.getValue()+"\n";
        nextVar=vc.Next();
      }
      LogoSub nextSub = st.First();
      while ( nextSub!=null ) {
        result+="to "+nextSub.getName()+"\n"+nextSub.getValue()+"\nend\n";
        nextSub=st.Next();
      }

      if ( result.length()==0 ) {
        result="Nothing to Dump.";
      }
      return result;
   }

   public int load(String file) {
   //"loads" a series of procedures and variables into the subTable
   //and varContext; returns the number of lines loaded.
   //Converting this to load from a file would be very simple (in an
   //application)
      int result=0;
      int stop;
      String nextLine;
      String nextSubName = null;
      String nextSub = null;
      while ( file.length()>0 ) {
          result++;
          stop=file.indexOf("\n");
          if (stop<0) stop=file.length();
          nextLine=file.substring(0,stop);
          nextLine.toLowerCase();
          nextLine.trim();
          if ( ++stop>file.length() )
            file="";
          else
            file=file.substring(stop);
          if ( nextSubName!=null ) { //we're in the middle of loading a subroutine
              if ( nextLine.startsWith("end") ) { //close it up...
                st.setValue(nextSubName,nextSub);
                nextSubName=null;
                nextSub=null;
              } else nextSub=nextSub.concat(nextLine+"\n"); //add another line
          } else if ( nextLine.startsWith("to ") ) { //a new subroutine...
              nextSubName=nextLine.substring(3);
              nextSub=new String();
          } else { //execute it...
              queue(nextLine);
          }
      }
      return result;
   }
}

class Expression {

   public final String SYMBOLS=new String("~*/+-<>=");

   //should this be a static method?
   //should this make use of class StringBuffer() ?
   public String replace(String expr, String oldString, String newString) {
   //replaces 1st occurrence of <oldString> in <expr> with <newString>
      String result;
      int start = expr.indexOf(oldString);
      if ( start>-1 ) {
         result = new String();
         result+=expr.substring(0,start);
         result+=newString;
         result+=expr.substring(start+oldString.length());
      } else result=expr;
      return result;
   }

   int getTerm(int i, Vector v) throws rLogoException { //extracts term #i from expression held in v
      int result=0;
      try {
         result=new Integer((String)v.elementAt(i)).intValue();
      } catch (NumberFormatException e) {
          throw new rLogoException("Syntax Error: Bad Number: "+(String)v.elementAt(i));
      } catch (ArrayIndexOutOfBoundsException e) {
          throw new rLogoException("Syntax Error: Unable to Evaluate Expression");
      }
      return result;
   }

   int getTerm1(int i, Vector v) throws rLogoException {
      return getTerm(i-1,v);
   }

   int getTerm2(int i, Vector v) throws rLogoException {
      return getTerm(i+1,v);
   }

   public String evaluate(String expr, VarContext context) throws rLogoException {

      //Variable substitutions
      //Simplify equations
      //Precedence: () , *, /, +, -, <, >, =,

      if (expr==null) throw new rLogoException("Syntax Error: Bad Expression (received null)");
      expr.trim();
      if ( expr.length()==0 ) throw new rLogoException("Syntax Error: Received Empty Expression");

      //remove leading & trailing parentheses, if they exist (e.g. '(expr)' becomes 'expr')
      if ( expr.startsWith("(") && expr.endsWith(")") ) {
        expr=expr.substring(1,expr.length()-1);
      }
      //recursively evaluate all parenthetical sub-expressions:
      while (expr.indexOf("(")>-1) {
         String subExpr=extractParentheticalExpression(expr);
         expr=replace(expr,"("+subExpr+")",evaluate(subExpr,context));
      }

      Vector v = new Vector();

      StringTokenizer st = new StringTokenizer(expr,SYMBOLS,true);

      while (st.hasMoreTokens()) {
         v.addElement(st.nextToken());
      }

      //first, lookup values of any variables:
      for (int i=0; i<v.size(); i++) {
         String p = (String)v.elementAt(i);
         if (p.charAt(0)==':') {
            Integer substitute=new Integer(0);
            v.setElementAt(new Integer(context.getValue(p.substring(1))).toString(),i);
         }
      }

      //next, evalutate the -'s ("negatives", not subtraction):
      //A - is interpreted as (-1*nextTerm), if and only if
      //it is the first token encountered, or the token that
      //precedes it is one of expression SYMBOLS.
      for (int i=0; i<v.size(); i++) {
         if ( ((String)(v.elementAt(i))).equals("-") ) {
            if ( (i==0) || (SYMBOLS.indexOf((String)v.elementAt(i-1))>=0) ) {
              int term = getTerm2(i,v);
              v.setElementAt(Integer.toString(-1*term),i);
              v.removeElementAt(i+1);
            }
         }
      }

      //next, evaluate the ~'s (logical negation):
      for (int i=0; i<v.size(); i++) {
         if ( ((String)(v.elementAt(i))).equals("~") ) {
              int term = getTerm2(i,v);
              v.setElementAt( (term==0) ? "1":"0",i);
              v.removeElementAt(i+1);
         }
      }

      evaluateSubExpression(v,'*');
      evaluateSubExpression(v,'/');
      evaluateSubExpression(v,'+');
      evaluateSubExpression(v,'-');
      evaluateSubExpression(v,'>');
      evaluateSubExpression(v,'<');
      evaluateSubExpression(v,'=');

      if (v.size()>1) throw (new rLogoException("Syntax Error: Unable to Evaluate Expression"));

      return (String) v.elementAt(0);


   }

   private final void evaluateSubExpression(Vector v, char symbol) throws rLogoException {
     String nextString;
     for (int i=1; i<v.size(); i++) {
        nextString=(String)(v.elementAt(i));
        if ( nextString.length()==1 && nextString.charAt(0)==symbol ) {
          int term1=getTerm1(i,v);
          int term2=getTerm2(i,v);
          int result;
          switch ( symbol ) {
            case '*' :
              result=term1*term2;
              break;
            case '/' :
              result=term1/term2;
              break;
            case '+' :
              result=term1+term2;
              break;
            case '-' :
              result=term1-term2;
              break;
            case '>' :
              result=(term1>term2) ? 1 : 0;
              break;
            case '<' :
              result=(term1<term2) ? 1 : 0;
              break;
            case '=' :
              result=(term1==term2) ? 1 : 0;
              break;
            default :
              result=0;
          }
          v.setElementAt(result+"",i);
          v.removeElementAt(i+1);
          v.removeElementAt(i-1);
          i--; //reset the looping variable, since elements have been removed
        }
     }
   }

   public String extractParentheticalExpression(String expr) throws rLogoException {

   //returns the first *full* parenthetical expression from a string.
   //For example, "1+((2+3)*4)+(5+6)" returns "((2+3)*4)"
      String result = null;

      if ( expr.indexOf("(")>-1 ) {

         //Find the first OPEN-Paren
         int openCount=1;
         boolean found = false;
         int start = expr.indexOf("(");
         int end=start;
         for (int i=start+1;  i<expr.length(); i++) {
            end++;
            if ( expr.charAt(i)=='(' ) {
               openCount++;
            } else if ( expr.charAt(i)==')' ) {
               if (--openCount==0) {
                  found = true;
                  break;
               }
            }
         }
         if (found) {
            result = expr.substring(start+1,end);
         } else {
            throw(new rLogoException("Syntax Error: ')' not found."));
         }
      }
      return result;
   }
}

//A break is handled slightly differently than a regular
//exception:
class rLogoBreakException extends rLogoException {
  public rLogoBreakException(String msg) {
    super(msg);
  }
}
