import java.applet.*;
import java.awt.*;
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;

  public void init( )
    {
      raster = new Raster(size().width, size().height);
      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);

      if (false) {
	System.out.println();
	System.out.println("view constructor yields ");
	view.print();
      }

      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);

      if (false) {
	System.out.println("view.perspective yields ");
	view.print();
      }

      // view.orthographic(-t, t, -s, s, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);

      if (false) {
	System.out.println("view.lookAt yields ");
	view.print();
      }	      

      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();
      }
      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());
      boolean drewSomething = false;
      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) {
	  drewSomething = true;
	  raster.setPixel(0xffa000a0, ix, iy);
	} else {
	  // System.out.println("("+ix+","+iy+")");
	}
      }
      // if (drewSomething) System.out.println("DrawObject drew something");
    }

}
