// Wendy Chien
// Project 3
// Recitation F11

// This is mostly the same code as what was provided in class.  
// It was extended to include testing of scaling, translating, and skew.

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 implements ItemListener {
    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;

    Panel MethodPanel;
    Choice MethodChooser;
    Panel ProjPanel;
    Choice ProjChooser;

    int mChoice, pChoice;
  
    private static final int SCALEX = 0;
    private static final int SCALEY = 1;
    private static final int SCALEZ = 2;
    private static final int TRANSLATE = 3;
    private static final int ROTATE = 4;
    private static final int SKEW = 5;


    public void init( )
    {
        setLayout(new BorderLayout());
	MethodPanel = new Panel();
        MethodChooser = new Choice();
	MethodChooser.add("Scale X");
	MethodChooser.add("Scale Y");
	MethodChooser.add("Scale Z");
	MethodChooser.add("Translate");
	MethodChooser.add("Rotate");
	MethodChooser.add("Skew");
	MethodPanel.add(new Label("Choose a transformation "));
	MethodPanel.add(MethodChooser);
	add("South",MethodPanel);
	MethodChooser.addItemListener(this);

	ProjPanel = new Panel();
	ProjChooser = new Choice();
        ProjChooser.add("Perspective");
	ProjChooser.add("Orthographic");
	ProjPanel.add(new Label("Choose a Projection "));
	ProjPanel.add(ProjChooser);
	add("North",ProjPanel);
	ProjChooser.addItemListener(this);

	raster = new Raster(size().width, size().height);
        raster.fill(getBackground());
        screen = raster.toImage();

        eyex = -10;        eyey = 0;        eyez = 0;
        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);
        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);
        //view.lookAt(eyex, 10, 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();

    }

  public void itemStateChanged(ItemEvent e) {
    if (mChoice != MethodChooser.getSelectedIndex()) {
      mChoice = MethodChooser.getSelectedIndex();
    }
    else if (pChoice != ProjChooser.getSelectedIndex()) {
      pChoice = ProjChooser.getSelectedIndex();
      changeProjection();
    }
  }
   
  public void changeProjection() {
    if (pChoice == 1) {
      view = new Matrix3D(raster);
      float t = (float) Math.sin(Math.PI*(fov/2)/180);
      float s = (t*size().height)/size().width;
      
      view.orthographic(-10*t, 10*t, -10*s, 10*s, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
      
      model = new Matrix3D();
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();
      screen = raster.toImage();
      repaint();
    }
    else {
      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();
      model.transform(vertList, worldList, 0, vertices);
      view.transform(worldList, tranList, 0, vertices);
      DrawObject();
      screen = raster.toImage();
      repaint();
    }
  }

    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;
    float s0x, s0y;
    float s1x, s1y;

    public boolean mouseDown(Event e, int x, int y)
    {
      
        if (e.metaDown()) {
          showStatus("Resetting model matrix");
	  model.loadIdentity();
        }
	
	if ((mChoice >= SCALEX) && (mChoice <= SCALEZ)) {
	  s0x = x;
	  s0y = y;
	}
	if (mChoice == TRANSLATE) {
	  s0x = x;
	  s0y = y;
	}
	if (mChoice == ROTATE) {
	  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;
	}
	if (mChoice == SKEW) {
	  s0x = x;
	  s0y = y;
	}
	return true;
    }

    public boolean mouseDrag(Event e, int x, int y)
    {
      float z;

        if (e.metaDown()) {
            showStatus("Resetting model matrix");
        } else {
	  if (mChoice == SCALEX) {
	    s1x = x;
	    model.scale(Math.abs(s1x-s0x)/20,1,1);
	  }
	  if (mChoice == SCALEY) {
	    s1y = y;
	    model.scale(1,Math.abs(s1y-s0y)/20,1);
	  }
	  if (mChoice == SCALEZ) {
	    s1x = x;
	    s1y = y;
	    z = (float) ((s1x-s0x)+(s1y-s0y))/2;
	    model.scale(1,1,z/20);
	  }
	  if (mChoice == TRANSLATE) {
	    s1x = x;
	    s1y = y;
	    z = (float) ((s1x-s0x)+(s1y-s0y))/2;
	    model.translate((s1x-s0x)/10,(s1y-s0y)/10,z/10);
	  }
	  if (mChoice == ROTATE) {
            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;
	  }
	  if (mChoice == SKEW) {
	    s1x = x;
	    s1y = y;
	    z = (float) ((s1x-s0x)+(s1y-s0y))/2;
	    model.skew((s1x-s0x)/10,(s1y-s0y)/10,z/10);
	  }
	  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);
        }
    }

}
