import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import Vertex3D;
import Point3D;
import Triangle;
import Matrix3D;
import Light;
import Surface;
import ZRaster;

public class Pipeline extends Applet {
    final static int CHUNKSIZE = 100;
    ZRaster raster;
    Image screen;
    Vertex3D vertList[];
    Vertex3D worldList[];
    Vertex3D canonicalList[];
    int vertices;
    Triangle triList[];
    int triangles;
    Matrix3D view;
    Matrix3D model;
    Matrix3D project;
    boolean cull = true;
    
    String filename;

    Light lightList[];
    int lights;
    Surface surfaceList[];
    int surfaces;

    Point3D eye, lookat, up;
    Point3D initEye, initLookat;
    float fov;

    public void init( )
    {
        setSize(550, 480);
        
        raster = new ZRaster(size().width, size().height);
        screen = raster.toImage();

        // initialize viewing parameters to default
        // values in case they are not defined by the
        // input file
        eye = new Point3D(0, 0, -10);
        lookat = new Point3D(0, 0, 0);
        up = new Point3D(0, 1, 0);
        fov = 30;

        vertList = new Vertex3D[CHUNKSIZE];
        vertices = 0;
        triList = new Triangle[CHUNKSIZE];
        triangles = 0;
        lightList = new Light[CHUNKSIZE];
        lights = 0;
        surfaceList = new Surface[CHUNKSIZE];
        surfaces = 0;

        filename = "cow1.obj";
        handleFileInput();
    
		//{{INIT_CONTROLS
		setLayout(null);
		setSize(494,427);
		textFieldRotateX = new java.awt.TextField();
		textFieldRotateX.setBounds(12,24,24,22);
		add(textFieldRotateX);
		textFieldRotateY = new java.awt.TextField();
		textFieldRotateY.setBounds(36,24,24,22);
		add(textFieldRotateY);
		textFieldRotateZ = new java.awt.TextField();
		textFieldRotateZ.setBounds(60,24,24,22);
		add(textFieldRotateZ);
		textFieldRotateAngle = new java.awt.TextField();
		textFieldRotateAngle.setBounds(84,24,36,22);
		add(textFieldRotateAngle);
		buttonRotate = new java.awt.Button();
		buttonRotate.setLabel("Rotate");
		buttonRotate.setBounds(12,12,72,12);
		buttonRotate.setBackground(new Color(12632256));
		add(buttonRotate);
		textFieldScaleX = new java.awt.TextField();
		textFieldScaleX.setBounds(132,24,24,22);
		add(textFieldScaleX);
		textFieldScaleY = new java.awt.TextField();
		textFieldScaleY.setBounds(156,24,24,22);
		add(textFieldScaleY);
		textFieldScaleZ = new java.awt.TextField();
		textFieldScaleZ.setBounds(180,24,24,22);
		add(textFieldScaleZ);
		buttonScale = new java.awt.Button();
		buttonScale.setLabel("Scale");
		buttonScale.setBounds(132,12,72,12);
		buttonScale.setBackground(new Color(12632256));
		add(buttonScale);
		choiceImage = new java.awt.Choice();
		choiceImage.addItem("cow");
		choiceImage.addItem("cube");
		choiceImage.addItem("cow1");
		choiceImage.addItem("cow2");
		choiceImage.addItem("cow3");
		choiceImage.addItem("cube1");
		choiceImage.addItem("cube2");
		try {
			choiceImage.select(0);
		}
		catch (IllegalArgumentException e) { }
		add(choiceImage);
		choiceImage.setBounds(396,12,85,18);
		textFieldTranslateX = new java.awt.TextField();
		textFieldTranslateX.setBounds(216,24,24,22);
		add(textFieldTranslateX);
		textFieldTranslateY = new java.awt.TextField();
		textFieldTranslateY.setBounds(240,24,24,22);
		add(textFieldTranslateY);
		textFieldTranslateZ = new java.awt.TextField();
		textFieldTranslateZ.setBounds(264,24,24,22);
		add(textFieldTranslateZ);
		buttonTranslate = new java.awt.Button();
		buttonTranslate.setLabel("Translate");
		buttonTranslate.setBounds(216,12,72,12);
		buttonTranslate.setBackground(new Color(12632256));
		add(buttonTranslate);
		canvas1 = new java.awt.Canvas();
		canvas1.setBounds(0,0,492,48);
		add(canvas1);
		//}}
	
		//{{REGISTER_LISTENERS
		SymAction lSymAction = new SymAction();
		buttonRotate.addActionListener(lSymAction);
		buttonScale.addActionListener(lSymAction);
		buttonTranslate.addActionListener(lSymAction);
		SymItem lSymItem = new SymItem();
		choiceImage.addItemListener(lSymItem);
		//}}
	}
    
        
    public void handleFileInput(){   
        showStatus("Reading "+filename);
        InputStream is = null;
        try {
            is = new URL(getDocumentBase(), filename).openStream();
            ReadInput(is);
            is.close();
        } catch (IOException e) {
            showStatus("Error reading "+filename);
        }
        
        //if (getParameter("cull") != null)
        //    if (getParameter("cull").equals("false"))
        //        cull = false;
            
        canonicalList = new Vertex3D[vertList.length];
        worldList = new Vertex3D[vertList.length];
        for (int i = 0; i < vertices; i++) {
            canonicalList[i] = new Vertex3D();
            worldList[i] = new Vertex3D();
        }
        initEye = new Point3D(eye);
        initLookat = new Point3D(lookat);
        
        project = new Matrix3D(raster);
        view = new Matrix3D();
        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(eye.x, eye.y, eye.z, lookat.x, lookat.y, lookat.z, up.x, up.y, up.z);
        model = new Matrix3D();
        DrawObject();
    }

    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;
    }

    private void growList()
    {
        Triangle newList[] = new Triangle[triList.length+CHUNKSIZE];
        System.arraycopy(triList, 0, newList, 0, triList.length);
        triList = newList;
    }

    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) {
		                Vertex3D newList[] = new Vertex3D[vertList.length+CHUNKSIZE];
                        System.arraycopy(vertList, 0, newList, 0, vertList.length);
		                vertList = newList;
		            }
		            vertList[vertices++] = new Vertex3D(x, y, z);
		        } else
		        if (st.sval.equals("f")) {
		            int faceTris = 0;
                    int v0 = (int) getNumber(st);
		            int v1 = (int) getNumber(st);
		            while (st.nextToken() == StreamTokenizer.TT_NUMBER) {
		                st.pushBack();
		                int v2 = (int) getNumber(st);
		                if (v2 == v0) continue;
		                if (triangles == triList.length) growList();
		                triList[triangles] = new Triangle(v0, v1, v2);
                        if (surfaces == 0) {
                            surfaceList[surfaces] = new Surface(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 5.0f);
		                    surfaces += 1;
                        }
                        triList[triangles].setSurface(surfaceList[surfaces-1]);
		                v1 = v2;
		                faceTris += 1;
                        triangles += 1;
		            }
		            st.pushBack();
			    } else
			    if (st.sval.equals("eye")) {
                    eye.x = (float) getNumber(st);
                    eye.y = (float) getNumber(st);
		            eye.z = (float) getNumber(st);
			    } else
			    if (st.sval.equals("look")) {
                    lookat.x = (float) getNumber(st);
                    lookat.y = (float) getNumber(st);
		            lookat.z = (float) getNumber(st);
			    } else
			    if (st.sval.equals("up")) {
                    up.x = (float) getNumber(st);
                    up.y = (float) getNumber(st);
		            up.z = (float) getNumber(st);
			    } else
			    if (st.sval.equals("fov")) {
                    fov = (float) getNumber(st);
			    } else
			    if (st.sval.equals("la")) {             // ambient light source
                    float r = (float) getNumber(st);
                    float g = (float) getNumber(st);
		            float b = (float) getNumber(st);
		            lightList[lights] = new Light(Light.AMBIENT, 0, 0, 0, r, g, b);
		            lights += 1;
			    } else
			    if (st.sval.equals("ld")) {             // directional light source
                    float r = (float) getNumber(st);
                    float g = (float) getNumber(st);
		            float b = (float) getNumber(st);
		            float x = (float) getNumber(st);
		            float y = (float) getNumber(st);
		            float z = (float) getNumber(st);
		            lightList[lights] = new Light(Light.DIRECTIONAL, x, y, z, r, g, b);
		            lights += 1;
			    } else
			    if (st.sval.equals("lp")) {             // point light source
                    float r = (float) getNumber(st);
                    float g = (float) getNumber(st);
		            float b = (float) getNumber(st);
		            float x = (float) getNumber(st);
		            float y = (float) getNumber(st);
		            float z = (float) getNumber(st);
		            lightList[lights] = new Light(Light.POINT, x, y, z, r, g, b);
		            lights += 1;
			    } else
			    if (st.sval.equals("surf")) {
                    float r = (float) getNumber(st);
                    float g = (float) getNumber(st);
		            float b = (float) getNumber(st);
		            float ka = (float) getNumber(st);
		            float kd = (float) getNumber(st);
		            float ks = (float) getNumber(st);
		            float ns = (float) getNumber(st);
		            surfaceList[surfaces] = new Surface(r, g, b, ka, kd, ks, ns);
		            surfaces += 1;
			    } else {
                    System.err.println("ERROR: line "+st.lineno()+": unexpected token :"+st.sval);
                    break scan;
			    }
			    break;
	        }
	        if (triangles % 100 == 0) showStatus("triangles = "+triangles);
	    }
        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);
    }

    int oldx, oldy;

    public boolean mouseDown(Event e, int x, int y)
    {
        if (e.metaDown()) {
            System.out.println("Reset view matrix");
            eye.copy(initEye);
            lookat.copy(initLookat);
            view.loadIdentity();
            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(eye.x, eye.y, eye.z, lookat.x, lookat.y, lookat.z, up.x, up.y, up.z);
            DrawObject();
        }
        oldx = x;
        oldy = y;
	    return true;
    }
    
    
    public boolean mouseDrag(Event e, int x, int y)
    {
        if (e.metaDown()) {
            System.out.println("Resetting view matrix");
        } else {
            float ax = lookat.x - eye.x;
            float ay = lookat.y - eye.y;
            float az = lookat.z - eye.z;
            float t = (float) (getSize().width / (2*Math.tan(((fov * Math.PI)/180)/2)));
            Matrix3D r = new Matrix3D( );
            t = (float) (Math.atan((double)(x - oldx)/t));
            r.rotate(up.x, up.y, up.z, t);
            eye.x = eye.x + ax*(oldy - y)/size().height;
            eye.y = eye.y + ay*(oldy - y)/size().height;
            eye.z = eye.z + az*(oldy - y)/size().height;
            lookat.x = r.get(0,0)*ax + r.get(1,0)*ay + r.get(2,0)*az;
            lookat.y = r.get(0,1)*ax + r.get(1,1)*ay + r.get(2,1)*az;
            lookat.z = r.get(0,2)*ax + r.get(1,2)*ay + r.get(2,2)*az;
            lookat.x += eye.x;
            lookat.y += eye.y;
            lookat.z += eye.z;
            t = (float) Math.sin(Math.PI*(fov/2)/180);
            float s = (t*size().height)/size().width;
            view.loadIdentity();
            view.perspective(-t, t, -s, s, -1, -200);
            view.lookAt(eye.x, eye.y, eye.z, lookat.x, lookat.y, lookat.z, up.x, up.y, up.z);
            oldx = x;
            oldy = y;
            DrawObject();
        }
        return true;
    }

  


    void DrawObject()
    {
        // System.out.println("view:\n"+view.toString());
        // System.out.println("project:\n"+project.toString());
        long time = System.currentTimeMillis();
        showStatus("Drawing "+triangles+" triangles ...");
        
        /*
            ... cull and illuminate in world space ...
        */
        model.transform(vertList, worldList, vertices);
        triList[0].setVertexList(worldList);
        for (int i = 0; i < triangles; i++) {
            //triList[i].norm();
            triList[i].Illuminate(lightList, lights, eye, cull);
        }
        
        /*
            .... clip in canonical eye coordinates ...
        */
        view.transform(worldList, canonicalList, vertices);
        triList[0].setVertexList(canonicalList);
        raster.fill(Color.white);
        raster.resetz();
        for (int i = 0; i < triangles; i++) {
            // check if trivially rejected
            if (triList[i].isVisible()) {
                triList[i].ClipAndDraw(raster, project);
            }
        }
        time = System.currentTimeMillis() - time;
        showStatus("Time = "+time+" ms");
        screen = raster.toImage( );
        repaint();
    }

		//{{DECLARE_CONTROLS
	java.awt.TextField textFieldRotateX;
	java.awt.TextField textFieldRotateY;
	java.awt.TextField textFieldRotateZ;
	java.awt.TextField textFieldRotateAngle;
	java.awt.Button buttonRotate;
	java.awt.TextField textFieldScaleX;
	java.awt.TextField textFieldScaleY;
	java.awt.TextField textFieldScaleZ;
	java.awt.Button buttonScale;
	java.awt.Choice choiceImage;
	java.awt.TextField textFieldTranslateX;
	java.awt.TextField textFieldTranslateY;
	java.awt.TextField textFieldTranslateZ;
	java.awt.Button buttonTranslate;
	java.awt.Canvas canvas1;
	//}}

	class SymAction implements java.awt.event.ActionListener
	{
		public void actionPerformed(java.awt.event.ActionEvent event)
		{
			Object object = event.getSource();
			if (object == buttonRotate)
				buttonRotate_ActionPerformed(event);
			else if (object == buttonScale)
				buttonScale_ActionPerformed(event);
			else if (object == buttonTranslate)
				buttonTranslate_ActionPerformed(event);
		}
	}

	void buttonRotate_ActionPerformed(java.awt.event.ActionEvent event)
	{
		float angle;
      float ax, ay, az, n;
      String angleS, axS, ayS, azS;
      
      angleS= (textFieldRotateAngle.getText());
      axS   = (textFieldRotateX.getText());
      ayS   = (textFieldRotateY.getText());
      azS   = (textFieldRotateZ.getText());
      angle = (float)Math.PI * ((Float.valueOf(angleS)).floatValue() / 180.0f);
      ax    = (Float.valueOf(axS)).floatValue();
      ay    = (Float.valueOf(ayS)).floatValue();
      az    = (Float.valueOf(azS)).floatValue();
      
      project = new Matrix3D(raster);
      model.rotate(ax, ay, az, angle);
      DrawObject();
      screen = raster.toImage();
      repaint();
	}

	void buttonScale_ActionPerformed(java.awt.event.ActionEvent event)
	{
      float ax, ay, az, n;
      String axS, ayS, azS;
      
      axS   = (textFieldScaleX.getText());
      ayS   = (textFieldScaleY.getText());
      azS   = (textFieldScaleZ.getText());
      ax    = (Float.valueOf(axS)).floatValue();
      ay    = (Float.valueOf(ayS)).floatValue();
      az    = (Float.valueOf(azS)).floatValue();
      
      project = new Matrix3D(raster);
      model.scale(ax, ay, az);
      DrawObject();
      screen = raster.toImage();
      repaint();
	}

	void buttonTranslate_ActionPerformed(java.awt.event.ActionEvent event)
	{
		float ax, ay, az, n;
      String axS, ayS, azS;
      
      axS   = (textFieldScaleX.getText());
      ayS   = (textFieldScaleY.getText());
      azS   = (textFieldScaleZ.getText());
      ax    = (Float.valueOf(axS)).floatValue();
      ay    = (Float.valueOf(ayS)).floatValue();
      az    = (Float.valueOf(azS)).floatValue();
      
      project = new Matrix3D(raster);
      model.translate(ax, ay, az);
      DrawObject();
      screen = raster.toImage();
      repaint();
	}

	class SymItem implements java.awt.event.ItemListener
	{
		public void itemStateChanged(java.awt.event.ItemEvent event)
		{
			Object object = event.getSource();
			if (object == choiceImage)
				choiceImage_ItemStateChanged(event);
		}
	}

	void choiceImage_ItemStateChanged(java.awt.event.ItemEvent event)
	{
		filename = choiceImage.getSelectedItem()+".obj";
		//if (checkboxCompose.getState())
		//   handleFileInput();
	  	//else {
	  	  raster = new ZRaster(size().width, size().height);
        screen = raster.toImage();

        // initialize viewing parameters to default
        // values in case they are not defined by the
        // input file
        eye = new Point3D(0, 0, -10);
        lookat = new Point3D(0, 0, 0);
        up = new Point3D(0, 1, 0);
        fov = 30;

        vertList = new Vertex3D[CHUNKSIZE];
        vertices = 0;
        triList = new Triangle[CHUNKSIZE];
        triangles = 0;
        lightList = new Light[CHUNKSIZE];
        lights = 0;
        surfaceList = new Surface[CHUNKSIZE];
        surfaces = 0;

        handleFileInput();
        screen = raster.toImage();
        repaint();		
	}
}