import java.util.Vector;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.geometry.*;


/**
 * This class walks through an L-system rule, as a Logo turtle would.
 * The turtle draws line segments (cylinders) behind it.
 * (called by: Lsystem.java, Interpreter.java)
 *
 * @author: Scott Teresi, April 1999, www.teresi.us
 */


public class Interpreter {

  State s = new State();
  Vector stateStack = new Vector();
  Appearance branchApp = new Appearance();
  Appearance leafApp = new Appearance();
  int depth;
  String rule;
  int cylEdges = 3;
  double cylRadius = 0.1;
  double mult = 0.01;
  boolean debug = false;;


  public Interpreter () {
    // set the default Appearances
    Material mat1 = new Material();
    mat1.setDiffuseColor(.9f, .2f, .05f);
    mat1.setAmbientColor(.18f, .05f, 0f);
    branchApp.setMaterial(mat1);
    Material mat2 = new Material();
    mat2.setDiffuseColor(0f, .7f, .05f);
    mat2.setAmbientColor(0f, .3f, 0f);
    leafApp.setMaterial(mat2);
  }


  public void setState (State state) {
    s = state;
  }


  public void setAppearance (Appearance app1, Appearance app2) {
    branchApp = app1;
    leafApp = app2;
  }


  public void initDraw (double rad, double m, int edges, boolean dbg) {
    cylRadius = rad;
    mult = m;
    cylEdges = edges;
    debug = dbg;
  }


  public BranchGroup execute(int d, String r) {

    depth = d;
    rule = r;

    BranchGroup bg = new BranchGroup();

    if (depth == 0)
      return bg;

    if (debug) {
      for (int i = 0; i < (8-depth); i ++)
	System.out.print("  ");
      System.out.println("Interpreting: " + rule);
    }

    int i = 0;
    while (i < rule.length()) {

      // System.out.println(rule.charAt(i) + "  " + new Point3f(s.curPos) +
      //		 "  " + s.angle);
      switch (rule.charAt(i)) {
        case ' ':
        case '=':
	  break;
        case '+':
	  // turn the cursor left
	  if (!s.swapDirections)
	    s.angle += s.angleInc;   // turn left
	  else
	    s.angle -= s.angleInc;   // turn right
	  if (s.angle > 360.0)
	    s.angle -= 360.0;
	  if (s.angle < -360.0)
	    s.angle += 360.0;
	  break;
        case '-':
	  // turn the cursor right
	  if (!s.swapDirections)
	    s.angle -= s.angleInc;   // turn right
	  else
	    s.angle += s.angleInc;   // turn left
	  if (s.angle > 360.0)
	    s.angle -= 360.0;
	  if (s.angle < -360.0)
	    s.angle += 360.0;
	  break;
        case '!':
	  // swap directions of all following cursor turns
	  s.swapDirections = !s.swapDirections;
	  break;
        case 'f':
	  // move forward one line length (actually DRAW something!)
	  double x = s.lineLength*Math.cos(Math.PI * s.angle/180.0) +
	             s.curPos.x;
	  double y = s.lineLength * Math.sin(Math.PI * s.angle/180.0) +
	             s.curPos.y;
	  double z = s.curPos.z;
	  CylinderCreator cylinder = new CylinderCreator();
	  cylinder.setResolution(cylEdges);
	  Appearance app;
	  if (depth > 1)
	    app = branchApp;
	  else
	    app = leafApp;
	  // make a branch
	  bg.addChild(cylinder.create(s.curPos, new Point3d(x, y, z),
	       cylRadius + mult * s.lineLength, app));
	  // cap the end of the branch
	  Transform3D transMatrix = new Transform3D();
	  transMatrix.setTranslation(new Vector3d(x, y, z));
	  TransformGroup tg = new TransformGroup(transMatrix);
	  Sphere sphere = new Sphere((float) (cylRadius + mult*s.lineLength),
				     Sphere.GENERATE_NORMALS, app);
	  tg.addChild(sphere);
	  bg.addChild(tg);

	  s.curPos = new Point3d(x, y, z);

	  break;
        case '@':
	  // scale the line length up or down
	  i = readScaleValue(i+1);
	  break;
        case 'z':
        case 'y':
        case 'x':
        case 'w':
        case 'v':
	  // interpret the rule for the variable encountered
	  Interpreter interpreter = new Interpreter();
	  interpreter.setState(s);
	  interpreter.setAppearance(branchApp, leafApp);
	  interpreter.initDraw (cylRadius, mult, cylEdges, debug);
	  bg.addChild(interpreter.execute(depth - 1,
					  Rules.getRule(rule.charAt(i))));
	  break;
        case '[':
	  // remember the current graphics state
	  stateStack.addElement(new State(s));
	  break;
        case ']':
	  // recall the previous graphics state
	  if (stateStack.size() > 0)
	    s = (State) stateStack.remove(stateStack.size() - 1);
	  else
	    System.out.println("Missing ] in line: " + rule);
	  break;
        default:
	  break;
      }
      i ++;
    }

    bg.compile();
    return bg;
  }



  int readScaleValue(int i) {

    StringBuffer valString = new StringBuffer();

    while (i < rule.length()) {

      switch (rule.charAt(i)) {
        case '.':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '0':
	  valString.append(rule.charAt(i));
	  break;
        default:
	  double scale = 0;
	  try {
	    scale = (new Double(valString.toString())).doubleValue();
	  } catch (Exception ex) {
	    System.out.println("Bad line length scale value, position " + i);
	    System.out.println("Line: " + rule);
	    System.exit(1);
	  }
	  s.lineLength *= scale;
	  // if (debug)
	  //  System.out.println("new line length: " + s.lineLength);
	  return i-1;
      }
      i ++;
    }

    return i;
  }

}


