/////////////////////
// Sidney Chang    //
// 6.837 Project 3 //
// TA: Jacob       //
// 11/8/98         //
/////////////////////

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import Point3D;
import Matrix3D;

public class TestMatrix extends Applet {
  final static int CHUNKSIZE = 100;
  Raster raster;
  Image screen;
  Point3D vertList[];
  Point3D worldList[];
  Point3D tranList[];
  int vertices;
  Matrix3D view;
  Matrix3D model;
  
  float eyex, eyey, eyez;
  float lookatx, lookaty, lookatz;
  float upx, upy, upz;
  float fov;

  Choice c;
  Button b;
  TextField xField;
  TextField yField;
  TextField zField;
  TestMatrixListener handler;
  ChoiceListener choiceHandler;

  public TestMatrix() {
    // set up gui controls
    handler = new TestMatrixListener(this);
    choiceHandler = new ChoiceListener(this);
    setLayout(new BorderLayout());
    Panel controlPanel = new Panel();
    controlPanel.setLayout(new BorderLayout());
    Panel topControlPanel = new Panel();
    topControlPanel.setLayout(new FlowLayout());
    c = new Choice();
    c.add("Perspective");
    c.add("Orthographic");
    c.add("----------");
    c.add("Translate");
    c.add("Scale");
    c.add("Skew");
    c.addItemListener(choiceHandler);
    topControlPanel.add(c);
    controlPanel.add("North", topControlPanel);
    Panel bottomControlPanel = new Panel();
    topControlPanel.setLayout(new FlowLayout());
    bottomControlPanel.add(new Label("x"));
    xField = new TextField("",1);
    bottomControlPanel.add(xField);
    bottomControlPanel.add(new Label("y"));
    yField = new TextField("",1);
    bottomControlPanel.add(yField);
    bottomControlPanel.add(new Label("z"));
    zField = new TextField("",1);
    bottomControlPanel.add(zField);
    b = new Button("Go");
    b.addActionListener(handler);
    b.setActionCommand("Go");
    bottomControlPanel.add(b);
    disableInput();
    controlPanel.add("South", bottomControlPanel);
    add("South", controlPanel);
  }

  public void init() {
    raster = new Raster(size().width, size().height);
    setBackground(Color.white);
    raster.fill(getBackground());
    screen = raster.toImage();
    
    eyex = 0;        eyey = 0;        eyez = -10;
    lookatx = 0;     lookaty = 0;     lookatz = 0;
    upx = 0;         upy = 1;         upz = 0;
    fov = 30;
    
    vertList = new Point3D[CHUNKSIZE];
    vertices = 0;
    
    String filename = getParameter("datafile");
    showStatus("Reading "+filename);
    InputStream is = null;
    try {
      is = new URL(getDocumentBase(), filename).openStream();
      showStatus("Reading input");
      ReadInput(is);
      is.close();
    } catch (IOException e) {
      System.err.println("Error reading "+filename);
    }
    
    worldList = new Point3D[vertList.length];
    tranList = new Point3D[vertList.length];
    for (int i = 0; i < vertices; i++) {
      worldList[i] = new Point3D();
      tranList[i] = new Point3D();
    }
    
    view = new Matrix3D(raster);
    float t = (float) Math.sin(Math.PI*(fov/2)/180);
    float s = (t*size().height)/size().width;
    
    view.perspective(-t, t, -s, s, -1, -200);
    view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
    
    model = new Matrix3D();
    long time = System.currentTimeMillis();
    model.transform(vertList, worldList, 0, vertices);
    view.transform(worldList, tranList, 0, vertices);
    DrawObject();
    time = System.currentTimeMillis() - time;
    showStatus("Time = "+time+" ms");
    screen = raster.toImage();
  }
  
  private double getNumber(StreamTokenizer st) throws IOException {
    if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
      System.err.println("ERROR: line "+st.lineno()+": expected number");
      throw new IOException(st.toString());
    }
    return st.nval;
  }
  
  public void ReadInput(InputStream is) throws IOException {
    StreamTokenizer st = new StreamTokenizer(is);
    st.commentChar('#');
  scan: while (true) {
    switch (st.nextToken()) {
    default:
      break scan;
    case StreamTokenizer.TT_WORD:
      if (st.sval.equals("v")) {
	float x = (float) getNumber(st);
	float y = (float) getNumber(st);
	float z = (float) getNumber(st);
	if (vertices == vertList.length) {
	  Point3D newList[] = new Point3D[vertList.length+CHUNKSIZE];
	  System.arraycopy(vertList, 0, newList, 0, vertList.length);
	  vertList = newList;
	}
	vertList[vertices++] = new Point3D(x, y, z);
      } else
	if (st.sval.equals("eye")) {
	  eyex = (float) getNumber(st);
	  eyey = (float) getNumber(st);
	  eyez = (float) getNumber(st);
	} 
	else
	  if (st.sval.equals("look")) {
	    lookatx = (float) getNumber(st);
	    lookaty = (float) getNumber(st);
	    lookatz = (float) getNumber(st);
	  } 
	  else
	    if (st.sval.equals("up")) {
	      upx = (float) getNumber(st);
	      upy = (float) getNumber(st);
	      upz = (float) getNumber(st);
	    } 
	    else
	      if (st.sval.equals("fov")) {
		fov = (float) getNumber(st);
	      } 
	      else {
		System.err.println("ERROR: line "+st.lineno()+": unexpected token :"+st.sval);
		break scan;
	      }
      break;
    }
    if (vertices % 100 == 0) showStatus("vertices = " + vertices);
  }
  is.close();
  if (st.ttype != StreamTokenizer.TT_EOF) {
    throw new IOException(st.toString());
  }
  }
  
  public void paint(Graphics g) {
    g.drawImage(screen, 0, 0, this);
  }
  
  public void update(Graphics g) {
    paint(g);
  }
  
  float v0x, v0y, v0z;

  public boolean mouseDown(Event e, int x, int y) {
    if (e.metaDown()) {
      showStatus("Resetting model matrix");
      model.loadIdentity();
    }
    else {
      v0x = (float) (x - (size().width / 2));
      v0y = (float) ((size().height / 2) - y);
      v0z = (float) size().width;
      float l0 = (float) (1 / Math.sqrt(v0x*v0x + v0y*v0y + v0z*v0z));
      v0x *= l0;
      v0y *= l0;
      v0z *= l0;
    }
    return true;
  }

  public boolean mouseDrag(Event e, int x, int y) {
    if (e.metaDown()) {
      showStatus("Resetting model matrix");
    }
    else {
      float v1x = (float) (x - (size().width / 2));
      float v1y = (float) ((size().height / 2) - y);
      float v1z = (float) size().width;
      float l = (float) (1 / Math.sqrt(v1x*v1x + v1y*v1y + v1z*v1z));
      v1x *= l;
      v1y *= l;
      v1z *= l;
      
      float ax = v0y*v1z - v0z*v1y;
      float ay = v0z*v1x - v0x*v1z;
      float az = v0x*v1y - v0y*v1x;
      l = (float) Math.sqrt(ax*ax + ay*ay + az*az);
      float theta = (float) Math.asin(l);
      if (v0x*v1x + v0y*v1y + v0z*v1z < 0)
	theta += (float) Math.PI / 2;
      model.rotate(ax, ay, az, theta);
      v0x = v1x;
      v0y = v1y;
      v0z = v1z;
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();
    }
    screen = raster.toImage();
    repaint();
    return true;
  }
  
  void DrawObject() {
    int ix, iy;
    int w, h;
    w = raster.width;
    h = raster.height;
    raster.fill(getBackground());
    for (int i = 0; i < vertices; i++) {
      ix = (int) tranList[i].x;
      iy = (int) tranList[i].y;
      if (ix >= 0 && iy >= 0 && ix < w && iy < h)
	raster.setPixel(0xffa000a0, ix, iy);
    }
  }

  void onGo() {
    if (c.getSelectedItem().equals("Perspective")) {
      view = new Matrix3D(raster);
      float t = (float) Math.sin(Math.PI*(fov/2)/180);
      float s = (t*size().height)/size().width;
      
      view.perspective(-t, t, -s, s, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
      
      model = new Matrix3D();
      long time = System.currentTimeMillis();
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();
      time = System.currentTimeMillis() - time;
      showStatus("Time = "+time+" ms");
      screen = raster.toImage();
    }
    else if (c.getSelectedItem().equals("Orthographic")) {
      view = new Matrix3D(raster);
      float t = (float) Math.sin(Math.PI*(fov/2)/180);
      float s = (t*size().height)/size().width;
      
      view.orthographic(-t*9, t*9, -s*9, s*9, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
      
      //      model = new Matrix3D();
      long time = System.currentTimeMillis();
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();
      time = System.currentTimeMillis() - time;
      showStatus("Time = "+time+" ms");
      screen = raster.toImage();
    }
    else if (c.getSelectedItem().equals("Translate")) {
      float x_input, y_input, z_input;
      try {
	x_input = Float.valueOf(xField.getText()).floatValue();
	y_input = Float.valueOf(yField.getText()).floatValue();
	z_input = Float.valueOf(zField.getText()).floatValue();
      }
      catch (NumberFormatException e) {
	showStatus("Numbers only please!");
	return;
      }
      model.translate(x_input, y_input, z_input);
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();      
    }
    else if (c.getSelectedItem().equals("Scale")) {
      float x_input, y_input, z_input;
      try {
	x_input = Float.valueOf(xField.getText()).floatValue();
	y_input = Float.valueOf(yField.getText()).floatValue();
	z_input = Float.valueOf(zField.getText()).floatValue();
      }
      catch (NumberFormatException e) {
	showStatus("Numbers only please!");
	return;
      }
      model.scale(x_input, y_input, z_input);
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();      
    }
    else if (c.getSelectedItem().equals("Skew")) {
      float x_input, y_input, z_input;
      try {
	x_input = Float.valueOf(xField.getText()).floatValue();
	y_input = Float.valueOf(yField.getText()).floatValue();
	z_input = Float.valueOf(zField.getText()).floatValue();
      }
      catch (NumberFormatException e) {
	showStatus("Numbers only please!");
	return;
      }
      model.skew(x_input, y_input, z_input);
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();      
    }
    screen = raster.toImage();
    repaint();
  }

  void enableInput() {
    xField.setEditable(true);
    yField.setEditable(true);
    zField.setEditable(true);
  }

  void disableInput() {
    xField.setEditable(false);
    yField.setEditable(false);
    zField.setEditable(false);
  }

  void enableGo() {
    b.setEnabled(true);
  }

  void disableGo() {
    b.setEnabled(false);
  }
}

class TestMatrixListener implements ActionListener {

  private TestMatrix parent;

  TestMatrixListener(TestMatrix p) {
    parent = p;
  }

  public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("Go"))
      parent.onGo();
  }
}

class ChoiceListener implements ItemListener {

  private TestMatrix parent;
  
  ChoiceListener(TestMatrix p) {
    parent = p;
  }

  public void itemStateChanged(ItemEvent e) {
    if (((String)(e.getItem())).equals("----------") &&
	e.getStateChange() == ItemEvent.SELECTED) {
      parent.disableInput();
      parent.disableGo();
    }
    else if (((String)(e.getItem())).equals("Perspective") &&
	 e.getStateChange() == ItemEvent.SELECTED) {
      parent.disableInput();
      parent.enableGo();
    }
    else if (((String)(e.getItem())).equals("Orthographic") &&
	 e.getStateChange() == ItemEvent.SELECTED) {
      parent.disableInput();
      parent.enableGo();
    }

    else if (((String)(e.getItem())).equals("Translate") &&
	 e.getStateChange() == ItemEvent.SELECTED) {
      parent.enableInput();
      parent.enableGo();
    }
    else if (((String)(e.getItem())).equals("Scale") &&
	 e.getStateChange() == ItemEvent.SELECTED) {
      parent.enableInput();
      parent.enableGo();
    }
    else if (((String)(e.getItem())).equals("Skew") &&
	 e.getStateChange() == ItemEvent.SELECTED) {
      parent.enableInput();
      parent.enableGo();
    }
  }
}





