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

public class TestApplet extends Applet implements ActionListener, ItemListener, MouseMotionListener, MouseListener
{
  // Declarations
  final static int CHUNKSIZE = 100;
  Raster raster;
  Panel bPanel;
  Image screen;
  Point3D vertList[];
  Point3D worldList[];
  Point3D tranList[];
  int vertices;
  Matrix3D view;
  Matrix3D model;
  Label v1, v2, v3;
  TextField tf1, tf2, tf3;
  List comList;
  Checkbox c;
  boolean orth;

  float eyex, eyey, eyez;
  float lookatx, lookaty, lookatz;
  float upx, upy, upz;
  float fov;
  
  public void init( )
  {
    orth = false;
    bPanel = new Panel();
    setLayout(new BorderLayout());

    add("South", bPanel);
    doControls();

    addMouseMotionListener(this);
    addMouseListener(this);
    
    raster = new Raster(400, 400);
    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();
      ReadInput(is);
      is.close();
    } catch (IOException e) {
      showStatus("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);
    model = new Matrix3D();

    float t = (float) Math.sin(Math.PI*(fov/2)/180);
    float s = t;
    
    view.perspective(-t, t, -s, s, -1, -200);

    view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
    //System.out.println("View after lookat: " + view.toString());

    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(new InputStreamReader(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 void mousePressed(MouseEvent e)
  {
    if (e.isMetaDown()) {
      showStatus("Resetting model matrix");
      model.loadIdentity();
    }
    v0x = (float) (e.getX() - (400 / 2));
    v0y = (float) ((400 / 2) - e.getY());
    v0z = (float) 400;
    float l0 = (float) (1 / Math.sqrt(v0x*v0x + v0y*v0y + v0z*v0z));
    v0x *= l0;
    v0y *= l0;
    v0z *= l0;
  }

  public void mouseMoved(MouseEvent e)
  {
    //System.err.println("In Matrix3D:translate: src = " + src);    
  }

  public void mouseDragged(MouseEvent e)
  {
    if (e.isMetaDown()) {
      showStatus("Resetting model matrix");
    } else {
      float v1x = (float) (e.getX() - (400 / 2));
      float v1y = (float) ((400 / 2) - e.getY());
      float v1z = (float) 400;
      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();
  }
  
  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);
    }
  }

  private void doControls()
  {
    bPanel.setLayout(new GridLayout(5, 2));
    bPanel.setSize(400, 200);
    // Add a list box for control selection
    Label l1 = new Label("Command:");
    
    comList = new List(2, false);
    comList.addItemListener(this);
    comList.setSize(50, comList.getSize().height);
    bPanel.add(l1);
    bPanel.add(comList);
    
    comList.add("Scale");
    comList.add("LoadIdentity (Reset) ");
    comList.add("Translate");
    comList.add("Skew");
    
    // Add text boxes for data input
    v1 = new Label("");
    tf1 = new TextField();
    bPanel.add(v1);
    bPanel.add(tf1);
    
    v2 = new Label("");
    tf2 = new TextField();
    bPanel.add(v2);
    bPanel.add(tf2);
    
    v3 = new Label("");
    tf3 = new TextField();
    bPanel.add(v3);
    bPanel.add(tf3);

    // Add a checkbox
    c = new Checkbox("Orthographic");
    c.addItemListener(this);
    bPanel.add(c);

    // Add an execute button
    Button b = new Button("Execute Command");
    b.setActionCommand("Execute");
    b.addActionListener(this);
    bPanel.add(b);
  }
  
  public void actionPerformed(ActionEvent e)
  {
    String s;
    if(e.getActionCommand() == "Execute")
      {
	s = comList.getSelectedItem();
	if(s != null)
	  {
	    showStatus("Executing command: " + comList.getSelectedItem());
	    if(s.equals("Scale"))
	      {
		float sx = new Float(tf1.getText()).floatValue();
		float sy = new Float(tf2.getText()).floatValue();
		float sz = new Float(tf3.getText()).floatValue();
		
		model.scale(sx, sy, sz);
	      }
	    else if(s.equals("Skew"))
	      {
		float kxy = new Float(tf1.getText()).floatValue();
		float kxz = new Float(tf2.getText()).floatValue();
		float kyz = new Float(tf3.getText()).floatValue();
		
		model.skew(kxy, kxz, kyz);
	      }
	    else if(s.equals("Translate"))
	      {
		float tx = new Float(tf1.getText()).floatValue();
		float ty = new Float(tf2.getText()).floatValue();
		float tz = new Float(tf3.getText()).floatValue();
		
		model.translate(tx, ty, tz);
	      }
	    else if(s.equals("LoadIdentity (Reset) "))
	      {
		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();
	    repaint();
	  }
      }
  }
  
  public void itemStateChanged(ItemEvent e)
  {
    String s;
    if(c.getState() != orth)
      {
	orth = c.getState();
	if(orth == true){
	  showStatus("Changing to orthographic projection.");
	  
	  view = new Matrix3D(raster);
	  view.orthographic(-8, 8, -5, 5, -5, 5);
	  view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, 
		      upx, upy, upz);
    	}
	else{
	  showStatus("Changing to perspective projection.");

	  float t = (float) Math.sin(Math.PI*(fov/2)/180);
	  float s1 = t;
	  
	  view = new Matrix3D(raster);
	  view.perspective(-t, t, -s1, s1, -1, -200);	  
	  view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, 
		      upx, upy, upz);
	}

	long time = System.currentTimeMillis();
	view.transform(worldList, tranList, 0, vertices);
	DrawObject();
	time = System.currentTimeMillis() - time;
	showStatus("Time = "+time+" ms");
	screen = raster.toImage();
	repaint();
      }
    else
      {
	s = comList.getSelectedItem();
	
	if(s.equals("Scale"))
	  {
	    v1.setText("X scale factor");
	    v2.setText("Y scale factor");
	    v3.setText("Z scale factor");
	  }
	else if(s.equals("Skew"))
	  {
	    v1.setText("XY skew factor");
	    v2.setText("XZ skew factor");
	    v3.setText("YZ skew factor");
	  }
	else if(s.equals("Translate"))
	  {
	    v1.setText("X translate factor");
	    v2.setText("Y translate factor");
	    v3.setText("Z translate factor"); 
	  }
	else if(s.equals("LoadIdentity (Reset) "))
	  {
	    v1.setText("");
	    v2.setText("");
	    v3.setText(""); 
	  }
      }
  }
  
  public void mouseExited(MouseEvent e)
  {
  }
  public void mouseClicked(MouseEvent e)
  {
  }
  public void mouseReleased(MouseEvent e)
  {
  }
  public void mouseEntered(MouseEvent e)
  {
  }
}
