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 void init( )

    {

        raster = new ZRaster(size().width, size().height);



        // 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;



        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);

        }

        

        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()

    {

        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].Illuminate(lightList, lights, eye, lookat, 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();

    }



}
