import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;


public class LogoApplet extends Applet implements Runnable, ActionListener {
  LogoCanvas logoCanvas;
  TextArea subroutines;
  TextField command, speedFld;
  Button startBtn, stopBtn;
  boolean degrees;
  Thread turtleRunner;
  Command[] commands;
  double speed=1;
  int step=0;
  
  public void init() {
    startBtn = new Button("Start");
    stopBtn = new Button("Stop");
    logoCanvas = new LogoCanvas();
    //logoCanvas = new LogoCanvasWithCNC(new java.io.OutputStreamWriter(System.out));
    subroutines = new TextArea();
    speedFld = new TextField("1",5);
    command = new TextField(40);
    startBtn.addActionListener(this);
    stopBtn.addActionListener(this);
    command.addActionListener(this);
    speedFld.addKeyListener(new KeyAdapter() {
      public void keyTyped(KeyEvent evt) {
        try { speed = new Double(speedFld.getText()).doubleValue(); }
        catch (Exception e) { }
      }
    });
    setLayout(new GridLayout(1,2));
    add(logoCanvas);
    logoCanvas.setSize(logoCanvas.getSize());
    logoCanvas.autoRepaint = false;
    Panel p = new Panel(new BorderLayout());
    Panel bot = new Panel(new GridLayout(2,1));
    Panel bot1 = new Panel(new FlowLayout(FlowLayout.LEFT));
    Panel bot2 = new Panel(new FlowLayout(FlowLayout.RIGHT));
    bot1.add(command);
    bot2.add(startBtn);
    bot2.add(stopBtn);
    bot2.add(new Label("  Speed:"));
    bot2.add(speedFld);
    bot.add(bot1);
    bot.add(bot2);
    p.add(subroutines);
    p.add(bot, "South");
    add(p);
    String script = getParameter("script");
    if (script != null) subroutines.setText(script);
    String scriptFile = getParameter("scriptFile");
    if (scriptFile != null) {
      showStatus("Reading scripts...");
      try {
        URL scriptURL = new URL(getDocumentBase(), scriptFile);
        Reader in = new InputStreamReader(scriptURL.openStream());
        subroutines.append("\n\n");
        char[] c = new char[256];
        int r = in.read(c);
        while (r != -1) {
          subroutines.append(new String(c, 0, r));
          r = in.read(c);
        }
        subroutines.append("\n");
      }
      catch (IOException e) {
        showStatus("Error reading scripts");
        System.out.println("Error reading scripts: "+e);
      }
      showStatus("");
    }
    if (getParameter("command")!=null) command.setText(getParameter("command"));
  }
  
  public void actionPerformed(ActionEvent e) {
    logoCanvas.setSize(logoCanvas.getSize());
    if (turtleRunner != null && turtleRunner.isAlive()) turtleRunner.stop();
    if (e.getSource() == startBtn || e.getSource() == command) {
      turtleRunner = new Thread(this);
      turtleRunner.setPriority(Thread.currentThread().getPriority()-1);
      turtleRunner.setDaemon(true);
      turtleRunner.start();
      command.selectAll();
    }
  }
  
  public void run() {
    updateCommands();
    doCommand(command.getText());
    logoCanvas.repaint();
  }
  
  public void updateCommands() {
    commands = getCommands();
  }
  
  
  // in retrospect it would have probably been better to 
  // have commands and thier arguments in accessed in a 
  // single layer of the command stack
  
  
  public void doCommand(String s) throws NumberFormatException {
  
  
  
    Hashtable oldEnv, env = new Hashtable();
    Stack tmp2, tmp, commandStack = new Stack(), envStack = new Stack();
    String nextToken, curCommandName=null;
    Command curCommand;
    tmp = getTokens(s);
    
    try {
    
  
    while(!tmp.isEmpty()) commandStack.push(tmp.pop());
    while(!commandStack.isEmpty()) {
      curCommandName = (String)commandStack.pop();
      if      (curCommandName.charAt(0) == '[') {
        tmp = getTokens(curCommandName.substring(1, curCommandName.length()-1));
        while(!tmp.isEmpty()) commandStack.push(tmp.pop());
      }
      else if (curCommandName.equalsIgnoreCase("end")) {
        env = (Hashtable)envStack.pop();
      }
      else if (curCommandName.equalsIgnoreCase("stop")) {
        while(!((String)commandStack.pop()).equalsIgnoreCase("end"));
        env = (Hashtable)envStack.pop();
      }
      else if (curCommandName.equalsIgnoreCase("if")) {
        double leftSide = evaluate((String)commandStack.pop(), env);
        String compareType = (String)commandStack.pop();
        double rightSide = evaluate((String)commandStack.pop(), env);
        boolean satisfied=false;
        if (compareType.indexOf("=") != -1  && leftSide == rightSide) satisfied=true;
        if (compareType.indexOf(">") != -1  && leftSide >  rightSide) satisfied=true;
        if (compareType.indexOf("<") != -1  && leftSide <  rightSide) satisfied=true;
        if (!satisfied) {
          curCommandName = (String)commandStack.pop();
          if (curCommandName.charAt(0) != '[') {
            curCommand = getCommand(curCommandName);
            if (curCommand == null) return;
            for(int i=0; i<curCommand.args.length; i++) nextToken=(String)commandStack.pop();
          }
        }
      }
      else if (curCommandName.equalsIgnoreCase("repeat")) {
        double times = evaluate((String)commandStack.pop(), env);
        curCommandName = (String)commandStack.pop();
        tmp = new Stack();
        tmp.push(curCommandName);
        if (curCommandName.charAt(0) == '[') {
          if (times >= 1) commandStack.push(curCommandName);
          if (times >= 2) {
            commandStack.push(""+(times-1));
            commandStack.push("repeat");
            commandStack.push(curCommandName);
          }
        }
        else {
          curCommand = getCommand(curCommandName);
          if (curCommand == null) return;
          for(int i=0; i<curCommand.args.length; i++) tmp.push(commandStack.pop());
          if (times >= 1) {
            tmp2 = new Stack();
            while(!tmp.isEmpty()) {
              tmp2.push(tmp.peek());
              commandStack.push(tmp.pop());
            }
            if (times >= 2) {
              while(!tmp2.isEmpty()) tmp.push(tmp2.pop());
              commandStack.push(""+(times-1));
              commandStack.push("repeat");
              while(!tmp.isEmpty()) commandStack.push(tmp.pop());
            }
          }
        }
      }
      else {
        curCommand = getCommand(curCommandName);
        if (curCommand == null) return;
        if (curCommand.isPrimative()) {
          double arg0=0;
          if (curCommand.args.length >= 1) arg0 = evaluate((String)commandStack.pop(), env); // one arg most any of them take
          if      (curCommandName.equalsIgnoreCase("left"   )) logoCanvas.left   (arg0);
          else if (curCommandName.equalsIgnoreCase("right"  )) logoCanvas.right  (arg0);
          else if (curCommandName.equalsIgnoreCase("back"   )) logoCanvas.back   (arg0);
          else if (curCommandName.equalsIgnoreCase("forward")) logoCanvas.forward(arg0);
          else if (curCommandName.equalsIgnoreCase("penup"  )) logoCanvas.penUp   ();
          else if (curCommandName.equalsIgnoreCase("pendown")) logoCanvas.penDown ();
          else if (curCommandName.equalsIgnoreCase("clear"  )) logoCanvas.clear   ();
          else if (curCommandName.equalsIgnoreCase("make"   )) {
            String varName = (String)commandStack.pop();
            Double val = new Double(evaluate((String)commandStack.pop(), env));
            env.put(varName, val);
          }
          else if (curCommandName.equalsIgnoreCase("reset"  )) {
            logoCanvas.clear   ();
            logoCanvas.x = logoCanvas.getSize().width/2;
            logoCanvas.y = logoCanvas.getSize().height/2;
            logoCanvas.ang = 0;
          }
          else System.out.println(curCommandName+" is not primative.");
          if (speed < 1) {
            logoCanvas.repaint();
            try { Thread.sleep((int)(1/speed)); } catch (Exception e) { }
          }
          else if (step++ % ((int)speed) == 0) logoCanvas.repaint();
        }
        else {
          envStack.push(env);
          oldEnv = env;
          env = new Hashtable();
          for(int i=0; i<curCommand.args.length; i++) 
            env.put(curCommand.args[i], new Double(evaluate((String)commandStack.pop(), oldEnv)));
          tmp = getTokens(curCommand.body);
          commandStack.push("end");
          while(!tmp.isEmpty()) commandStack.push(tmp.pop());
        }
      }
    }
    
    }
    catch (Exception e) {
      e.printStackTrace();
      System.out.println("curCommandName = "+curCommandName);
      while(!commandStack.isEmpty()) System.out.println(commandStack.pop());
    }
    
    
  }
  
  public Stack getTokens(String s) {
    Stack tmp;
    StringTokenizer st = new StringTokenizer(s);
    String nextToken;
    tmp = new Stack();
    int bracketDepth=0;
    while(st.hasMoreElements()) {
      nextToken = (String)st.nextElement();
      if (bracketDepth == 0) tmp.push(nextToken);
      else tmp.push(tmp.pop()+" "+nextToken);
      if (nextToken.startsWith("[")) bracketDepth += 1;
      else if (nextToken.endsWith("]")) bracketDepth -= 1;
    }
    return tmp;
  }
  
  public Command getCommand(String s) throws Exception {
    int i=0;
    for(i=0; i<commands.length; i++) if (s.equalsIgnoreCase(commands[i].name)) break;
    if (i==commands.length) {
      throw new Exception("Don't know command "+s);
    }
    else return commands[i];
  }
  
  public double evaluate(String s, Hashtable vals) throws NumberFormatException{
  
    if (s.indexOf('+') != -1)
      return evaluate(s.substring(0, s.indexOf('+')), vals) + 
        evaluate(s.substring(s.indexOf('+') + 1), vals);
    
    else if (s.indexOf('-',1) != -1)
      return evaluate(s.substring(0, s.indexOf('-',1)), vals) - 
        evaluate(s.substring(s.indexOf('-',1) + 1), vals);
    
    else if (s.indexOf('*') != -1)
      return evaluate(s.substring(0, s.indexOf('*')), vals) * 
        evaluate(s.substring(s.indexOf('*') + 1), vals);
    
    else if (s.indexOf('/') != -1)
      return evaluate(s.substring(0, s.indexOf('/')), vals) / 
        evaluate(s.substring(s.indexOf('/') + 1), vals);
    
    else if (s.indexOf('^') != -1)
      return Math.pow(evaluate(s.substring(0, s.indexOf('^')), vals) , 
        evaluate(s.substring(s.indexOf('^') + 1), vals) );
    
    else if (s.indexOf('%') != -1)
      return evaluate(s.substring(0, s.indexOf('%')), vals) % 
        evaluate(s.substring(s.indexOf('%') + 1), vals);
    
    else if (vals.get(s) != null) return ((Double)vals.get(s)).doubleValue();
    else return (new Double(s)).doubleValue();
    
  }
  
  public Command[] getPrimatives() {
    Command[] primatives = { new Command("left"  , 1, null), 
                             new Command("right" , 1, null), 
                             new Command("forward",1, null),
                             new Command("back"  , 1, null), 
                             new Command("penup" , 0, null),
                             new Command("pendown",0, null),
                             new Command("clear" , 0, null),
                             new Command("make"  , 0, null),
                             new Command("reset" , 0, null),
                             new Command("stop"  , 0, null)  } ;
    return primatives;
  }
  
  public Command[] getCommands() {
    Vector args, commands = new Vector();
    Command[] primatives = getPrimatives();
    for(int i=0; i<primatives.length; i++) commands.addElement(primatives[i]);
    String curName=null, curBody, tmp, s = subroutines.getText();
    String[] curArgs;
    StringTokenizer st = new StringTokenizer(s," \n\t\r", true);
    
    //try {
    
    while(st.hasMoreElements()) {
      tmp = st.nextToken();
      while(st.hasMoreElements() && !tmp.equals("to") && !tmp.equals("on")) tmp = st.nextToken();
      if (!st.hasMoreElements()) continue;
      tmp = st.nextToken();
      curName = st.nextToken();
      args = new Vector();
      while(st.nextToken().equals(" ")) {
        args.addElement(st.nextToken());
      }
      curArgs = new String[args.size()];
      for(int i=0; i<curArgs.length; i++) curArgs[i] = (String)args.elementAt(i);
      curBody = st.nextToken();
      while(!tmp.equals("end")) {
        curBody = curBody + tmp;
        tmp = st.nextToken();
      }
      commands.addElement(new Command(curName, curArgs, curBody));
    }
    Command[] commandsArray = new Command[commands.size()];
    for(int i=0; i<commandsArray.length; i++) commandsArray[i] = (Command)commands.elementAt(i);
    return commandsArray;
    
    /*
    }
    catch (Exception e) {
      e.printStackTrace();
      System.out.println(curName);
      return null;
    }
    */
    
  }
  
  
  class Command {
    String name;
    String[] args;
    String body;
    public Command(String name, String[] args, String body) {
      this.name = name;
      this.args = args;
      this.body = body;
    }
    public Command(String name, int argCount, String body) {
      args = new String[argCount];
      for(int i=0; i<argCount; i++) { args[i] = "primative_arg"+i; }
      this.name = name;
      this.body = body;
    }
    public boolean isPrimative() { return body == null; }
    public String toString() {
      StringBuffer sb = new StringBuffer(name);
      sb.append('(');
      if (args.length > 0) sb.append(args[0]);
      for(int i=1; i<args.length; i++) {
        sb.append(", ");
        sb.append(args[i]);
      }
      sb.append(") {\n");
      sb.append(body);
      sb.append("\n}");
      return sb.toString();
    }
  }
  
  
}