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;

	int currentTool;  //0 = Rotate
					  //1 = Scale
					  //2 = Shear
					  //3 = Skew
					  //4 = Translate

	int pointsNeeded;
	int pointsHave;

	float v0x, v0y, v0z;
	float v1x, v1y, v1z;
	float v2x, v2y, v2z;
	float v3x, v3y, v3z;

	int WOFFSET = 20;
	int HOFFSET = 20;

    public void init( )
    {
		currentTool = 0;
		pointsNeeded = 2;
		pointsHave = 0;

		Button rotate = new Button("Rotate");
		add(rotate);

		Button scale = new Button("Scale");
		add(scale);
		
		Button shear = new Button("Shear");
		add(shear);
		
		Button skew = new Button("Skew");
		add(skew);
		
		Button translate = new Button("Translate");
		add(translate);

		Button ortho = new Button("Ortho");
		add(ortho);
		
        raster = new Raster(size().width - WOFFSET, size().height - HOFFSET);
        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");
//		String filename = "cow.obj";
        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 - HOFFSET))/ (size().width - WOFFSET);
                
        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, WOFFSET, HOFFSET, this);
    }

    public void update(Graphics g)
    {
        paint(g);
    }

    public boolean action(Event event, Object obj)
    {
        // if this action sprung from a button...
        Object oTarget = event.target;
        if (oTarget instanceof Button)
        {
            // ...and if the button label is the same
            // as "our button"
            Button buttonTarget = (Button)oTarget;
            String sButtonString = buttonTarget.getLabel();
            if (sButtonString.compareTo("Rotate") == 0)
            {
				currentTool = 0;
				pointsNeeded = 2;
				pointsHave = 0;
				showStatus("Rotate tool: click and drag");
                return true;
			}
            if (sButtonString.compareTo("Scale") == 0)
            {
				currentTool = 1;
				pointsNeeded = 1;
				pointsHave = 0;
				showStatus("Scale tool: click about center");
                return true;
			}
            if (sButtonString.compareTo("Shear") == 0)
            {
				currentTool = 2;
				pointsNeeded = 1;
				pointsHave = 0;
				showStatus("Shear tool: click about center");
                return true;
			}
            if (sButtonString.compareTo("Skew") == 0)
            {
				currentTool = 3;
				pointsNeeded = 1;
				pointsHave = 0;
				showStatus("Skew tool: click about center");
                return true;
			}
            if (sButtonString.compareTo("Translate") == 0)
            {
				currentTool = 4;
				pointsNeeded = 1;
				pointsHave = 0;
				showStatus("Translate tool: click about center");
                return true;
			}
			if (sButtonString.compareTo("Ortho") == 0)
			{
		        view = new Matrix3D(raster);
				float t = (float) Math.sin(Math.PI*(fov/2)/180);
				float s = (t*(size().height - HOFFSET))/ (size().width - WOFFSET);
                
				view.orthographic((float) (-t), (float)(t), 
								  (float) (-s), (float)(s), 
								  -1, -200);
				view.lookAt(eyex, eyey, eyez+10, 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();

				buttonTarget.setLabel("Persp");
				repaint();
				showStatus("Now in orthographic view");
				return true;
			}
			if (sButtonString.compareTo("Persp") == 0)
			{
		        view = new Matrix3D(raster);
				float t = (float) Math.sin(Math.PI*(fov/2)/180);
				float s = (t*(size().height - HOFFSET))/ (size().width - WOFFSET);
                
				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();

				buttonTarget.setLabel("Ortho");
				repaint();
				showStatus("Now in perspective view (broken)");
				return true;
			}

        
		
		
		}
        return false;
    }



    public boolean mouseDown(Event e, int x, int y)
    {
        if (e.metaDown()) {
            showStatus("Resetting model matrix");
            model.loadIdentity();
			model.transform(vertList, worldList, 0, vertices);
			view.transform(worldList, tranList, 0, vertices);
			DrawObject();
			screen = raster.toImage();
			repaint();
			return true;
        }
        v0x = (float) (x - ((size().width - WOFFSET) / 2));
        v0y = (float) (((size().height - HOFFSET) / 2) - y);
//        v0z = (float) size().width;
		v0z = 10;
        float l0 = (float) (1 / Math.sqrt(v0x*v0x + v0y*v0y + v0z*v0z));
        v0x *= l0;
        v0y *= l0;
        v0z *= l0;
		if (currentTool != 0) {
			if (v0x > 0) {
				v0x += (float) 0.35;
			} else {
				v0x -= (float) 0.35;
			}
			if (v0y > 0) {
				v0y += (float) 0.35;
			} else {
				v0y -= (float) 0.35;
			}
			v0z = 1;
		}

		switch (currentTool) {
		case 0:		return true;
		case 1:		model.scale(v0x, v0y, v0z);		break;
		case 2:		model.shear(v0x, v0y, v0z);		break;
		case 3:		model.skew(v0x, v0y, v0z);		break;
		case 4:		model.translate(v0x, v0y, v0z);	break;
		}
        model.transform(vertList, worldList, 0, vertices);
        view.transform(worldList, tranList, 0, vertices);
        DrawObject();
        screen = raster.toImage();
        repaint();

        return true;
    }

    public boolean mouseDrag(Event e, int x, int y)
    {
        if (e.metaDown()) {
            showStatus("Resetting model matrix");
			model.loadIdentity();
			model.transform(vertList, worldList, 0, vertices);
			view.transform(worldList, tranList, 0, vertices);
			DrawObject();
			screen = raster.toImage();
			repaint();
			return true;
        } else {
            v1x = (float) (x - ((size().width - WOFFSET) / 2));
            v1y = (float) (((size().height - HOFFSET) / 2) - y);
//            float v1z = (float) size().width;
			v1z = 10;
            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;
			if (currentTool == 0) {
				model.rotate(ax, ay, az, theta);
	//			model.rotate(0, 1, 0, (float).5);
			}
            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].getX();
            iy = (int) tranList[i].getY();
            if (ix >= 0 && iy >= 0 && ix < w && iy < h)
                raster.setPixel(0xffa000a0, ix, iy);
        }
    }

}