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;

    Light lightList[];
    int lights;
    Surface surfaceList[];
    int surfaces;

    Point3D eye, lookat, up;
    Point3D initEye, initLookat;
    float fov;
    public static final boolean DEBUG       = false;
    public static final boolean CULLDEBUG   = false;
    public static final boolean ILLUMDEBUG  = false;
    public static final boolean CLIPDEBUG   = false;
    public static final boolean MATRIXDEBUG = false;

    int currentLight;
    String statusMsg = ""; long time;
	java.awt.Choice    lightChoice;
	java.awt.Label     lightLabel, redLabel, greenLabel, blueLabel;
	java.awt.TextField redLightTextField, greenLightTextField, blueLightTextField;
    java.awt.Button    setLightButton, restartButton;

    public void init( )
    {
        setLayout(null);
        //setSize(size().width,size().height);
        setSize(500, 350);
        setBackground(Color.white);

        ///////////////////////////////////////////////////////////////////
        // Step 1: Create raster and initialize view parameters
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step1: Creating ZRaster"); //debug
        //raster = new ZRaster(size().width, size().height);
        raster = new ZRaster(300, 300); //temporary

        //DEBUG
        //Matrix3D test1 = new Matrix3D();
        //test1.set(0,0, 3.0f);test1.set(0,1,-2.0f);test1.set(0,2, 2.0f);test1.set(1,0, 1.0f);test1.set(1,1, 2.0f);test1.set(1,2,-3.0f);test1.set(2,0, 4.0f);test1.set(2,1, 1.0f);test1.set(2,2, 2.0f);
        //float d = test1.det3();
        //System.out.println("d = "+d); //debug
        //System.out.println("test1:\n"+test1); //debug
        //System.out.println("ct:\n"+test1.compute_ct(d)); //debug

        // 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;

        ///////////////////////////////////////////////////////////////////
        // Step 2: Create vertList, triList, lightList, surfaceList
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step2: Creating vertList, triList, lightList, surfaceList"); //debug
        vertList = new Vertex3D[CHUNKSIZE];   vertices  = 0;
        triList = new Triangle[CHUNKSIZE];    triangles = 0;
        lightList = new Light[CHUNKSIZE];     lights    = 0;
        surfaceList = new Surface[CHUNKSIZE]; surfaces  = 0;

        ///////////////////////////////////////////////////////////////////
        // Step 3: Parse input file
        //         - Add vertices to vertList
        //         - Create surfaces, add them to surfaceList, and set
        //         - Change eye params
        //         - Add lights to lightList
        ///////////////////////////////////////////////////////////////////
        //String filename = getParameter("datafile");
        String filename = "cube.obj"; //temporary
        if (DEBUG) System.out.println("Step3: Parsing "+filename); //debug
        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;
        
        ///////////////////////////////////////////////////////////////////
        // Make options
        ///////////////////////////////////////////////////////////////////
        
        currentLight = 0;
        lightLabel = new java.awt.Label("Lights:");
        lightLabel.setBounds(310,10,40,25);
        add(lightLabel);
		lightChoice = new java.awt.Choice();
        // RED
        redLabel = new java.awt.Label("R:");
        redLabel.setBounds(310,40,12,24); add(redLabel);
        redLightTextField = new java.awt.TextField();
        redLightTextField.setText(""+lightList[1].ir);
        redLightTextField.setBounds(325,40,35,24); add(redLightTextField);
        // GREEN
        greenLabel = new java.awt.Label("G:");
        greenLabel.setBounds(365,40,12,24); add(greenLabel);
        greenLightTextField = new java.awt.TextField();
        greenLightTextField.setText(""+lightList[1].ig);
        greenLightTextField.setBounds(380,40,35,24); add(greenLightTextField);
        // BLUE
        blueLabel = new java.awt.Label("B:");
        blueLabel.setBounds(420,40,12,24); add(blueLabel);
        blueLightTextField = new java.awt.TextField();
        blueLightTextField.setText(""+lightList[1].ib);
        blueLightTextField.setBounds(435,40,35,24); add(blueLightTextField);
        // SET
        setLightButton = new java.awt.Button();
        setLightButton.setBackground(new Color(12632256));
        setLightButton.setLabel("Set");
        setLightButton.setBounds(475,40,35,24); add(setLightButton);

        for (int i = 0; i < lights; i++) {
            if (lightList[i].lightType == Light.AMBIENT) {
        		lightChoice.addItem("Light "+(i+1)+": AMBIENT");
        	} else if (lightList[i].lightType == Light.DIRECTIONAL) {
        		lightChoice.addItem("Light "+(i+1)+": DIRECTIONAL");
            }
		}
        add(lightChoice);
        lightChoice.setBounds(360,10,150,25);
        
        // Add listener
		java.awt.event.ItemListener   list  =  (java.awt.event.ItemListener) (new ChoiceListener());
		java.awt.event.ActionListener list2 =  (java.awt.event.ActionListener) (new ButtonListener());
		lightChoice.addItemListener(list);
		setLightButton.addActionListener(list2);
        
        ///////////////////////////////////////////////////////////////////
        // Step 4: Create canonicalList and worldList
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step 4: Creating canonicalList and worldList"); //debug
        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();
        }
        // Since eyepoint position and direction may change (you can move
        // around the scene), store initial eye position (eye) and
        // direction (lookAt) in case you want to reset to orig position.
        initEye = new Point3D(eye);
        initLookat = new Point3D(lookat);

        ///////////////////////////////////////////////////////////////////
        // Step 5: Initialize projection, view, and model matrices
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step 5: Initializing projection, view, and model matrices"); //debug
        project = new Matrix3D(raster);
        if (MATRIXDEBUG) System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@"); //debug
        if (MATRIXDEBUG) System.out.println("project created:"); //debug
        if (MATRIXDEBUG) System.out.println(project+"\n"); //debug
        if (MATRIXDEBUG) System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@"); //debug
        view = new Matrix3D();
        model = new Matrix3D();
        float t = (float) Math.sin(Math.PI*(fov/2)/180);
        float s = (t*size().height)/size().width;

        ///////////////////////////////////////////////////////////////////
        // Step 6: Tranform view from World Space -> Eye Space -> Canonical Space
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step 6: Tranforming view from World Space -> Eye Space -> Canonical Space"); //debug
        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);
        if (MATRIXDEBUG) System.out.println("###########################"); //debug
        if (MATRIXDEBUG) System.out.println("view after perspective and lookAt:"); //debug
        if (MATRIXDEBUG) System.out.println(view+"\n"); //debug
        if (MATRIXDEBUG) System.out.println("###########################"); //debug
        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")) {
        ///////////////////////////////////////////////////////////////////
        // Add vertices to vertList
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("- Add vertices to vertList"); //debug
                            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) {
        ///////////////////////////////////////////////////////////////////
        // Create and assign surfaces
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("- Creating and assigning surfaces"); // debug
                            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);
        g.setColor(Color.black);
        g.clearRect(0, 300, size().width, size().height);
        g.drawString(statusMsg, 0, 310);
    }

    public void update(Graphics g)
    {
        paint(g);
    }

    int oldx, oldy;

    public boolean mouseDown(Event e, int x, int y)
    {
        if (e.metaDown()) { // 2nd mouse button
            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()) { // 2nd mouse button
            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()
    {
        time = System.currentTimeMillis();
        showStatus("Drawing "+triangles+" triangles ...");

        ///////////////////////////////////////////////////////////////////
        // Step 7: Cull and Illuminate in world space
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step 7: Culling and Illuminating in world space"); //debug
        model.transform(vertList, worldList, vertices);
        triList[0].setVertexList(worldList);
        for (int i = 0; i < triangles; i++) {
            triList[i].Illuminate(lightList, lights, eye, cull);
        }

        ///////////////////////////////////////////////////////////////////
        // Step 8: Clip and draw in eye space
        ///////////////////////////////////////////////////////////////////
        if (DEBUG) System.out.println("Step 8: Cliping and drawing in eye space"); //debug
        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( );
        statusMsg = triangles+" triangles drawn in "+time+" ms";
        repaint();
    }


    class ChoiceListener implements  java.awt.event.ItemListener  {
        public void itemStateChanged(java.awt.event.ItemEvent event) {
			Object object = event.getSource();
			if (object == lightChoice) {
			    currentLight = lightChoice.getSelectedIndex();
			    //System.out.println("currentLight = "+currentLight); //debug
			    // Update text boxes
	            redLightTextField.setText(""+lightList[currentLight].ir);
	            greenLightTextField.setText(""+lightList[currentLight].ig);
	            blueLightTextField.setText(""+lightList[currentLight].ib);
			}
		}
    }
    
   class ButtonListener implements  java.awt.event.ActionListener  {
        public void actionPerformed(java.awt.event.ActionEvent event) {
			Object object = event.getSource();
            // setLightButton or restartButton;
			if (object == setLightButton) {
			    String str = "";
			    float intensity;
			    try {
			        str = redLightTextField.getText();
			        intensity = (new Float(str)).floatValue();
			    } catch (Exception e) {
			        signalError(str);
			        return;
			    }
			    // Make sure 0.0 <= intensity <= 1.0
			    if ((intensity > 1.0) || (intensity < 0.0)) {
			        signalError(str);
			        return;
			    }
			    lightList[currentLight].ir = intensity;
			    try {
			        str = greenLightTextField.getText();
			        intensity = (new Float(str)).floatValue();
			    } catch (Exception e) {
			        signalError(str);
			        return;
			    }
			    // Make sure 0.0 <= intensity <= 1.0
			    if ((intensity > 1.0) || (intensity < 0.0)) {
			        signalError(str);
			        return;
			    }
			    lightList[currentLight].ig = intensity;
			    try {
			        str = blueLightTextField.getText();
			        intensity = (new Float(str)).floatValue();
			    } catch (Exception e) {
			        signalError(str);
			        return;
			    }
			    // Make sure 0.0 <= intensity <= 1.0
			    if ((intensity > 1.0) || (intensity < 0.0)) {
			        signalError(str);
			        return;
			    }
			    lightList[currentLight].ib = intensity;
			    // Redraw
			    DrawObject();
    		}
        }
        public void signalError(String str) {
            // Display error msg
			showStatus("\""+str+"\" is an in an invalid intensity value");
			statusMsg = "\""+str+"\" is an in an invalid intensity value";
			// Recover old values
			redLightTextField.setText(""+lightList[currentLight].ir);
			greenLightTextField.setText(""+lightList[currentLight].ig);
			blueLightTextField.setText(""+lightList[currentLight].ib);
			repaint();
        }
   }
}
