import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import Vertex3D;
import Point3D;
import Triangle;
import Matrix3D;
import Light;
import Surface;

public class Pipeline extends Applet {
    final static int CHUNKSIZE = 100;
    Raster 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 Raster(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) {
            System.out.println("error with file");
            showStatus("Error reading "+filename);
        } catch(NullPointerException e) {
            System.out.println("error with file");
            showStatus("Error reading "+filename);
        }
        
       
        for (int i = 0; i < vertices; i++) {
            vertList[i].averageNormals(triList);
        }

        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);
                                vertList[v0].addTri(triangles);
                                vertList[v1].addTri(triangles);
                                vertList[v2].addTri(triangles);
                                float nx = (vertList[v1].z - vertList[v0].z) * (vertList[v2].y - vertList[v1].y)- (vertList[v1].y - vertList[v0].y) * (vertList[v2].z - vertList[v1].z);
                                float ny = (vertList[v1].x - vertList[v0].x) * (vertList[v2].z - vertList[v1].z)- (vertList[v1].z - vertList[v0].z) * (vertList[v2].x - vertList[v1].x);
                                float nz = (vertList[v1].y - vertList[v0].y) * (vertList[v2].x - vertList[v1].x)- (vertList[v1].x - vertList[v0].x) * (vertList[v2].y - vertList[v1].y);
                                if (faceTris == 0) 
                                {
                                    vertList[v0].setNormal(nx, ny, nz);
                                    vertList[v1].setNormal(nx, ny, nz);
                                }
                                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]);
                                triList[triangles].setNormal(nx,ny,nz);
                                vertList[v2].setNormal(nx, ny, nz);
                                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();
        } else if (e.controlDown()) {
	        model.rotate(up.x,up.y,up.z,0.31f);
	        DrawObject();
	    }
        oldx = x;
        oldy = y;
       
        return true;
    }

    public boolean mouseDrag(Event e, int x, int y)
    {
        
        if (e.shiftDown()) {
            if (y-oldy<0) {
    		    model.scale(.9f,.9f,.9f);
            } else {
                model.scale(1.1f,1.1f,1.1f);
            }
            DrawObject();
		}
		else {
		    Point3D right = new Point3D();
		    Point3D at = new Point3D();
		    at=lookat.minus(eye);
		    right=up.cross(at);
		    System.out.println("right is "+right);
		    if (oldx-x>1) {
    		    model.rotate(up.x,up.y,up.z,-.3f);
	        }else if (oldx-x<-1) {
	            model.rotate(up.x,up.y,up.z,.3f);
	        }
	        if (oldy-y>1) {
    		    model.rotate(right.x,right.y,right.z,.3f);
	        }else if (oldy-y<-1) {
	            model.rotate(right.x,right.y,right.z,-.3f);
	        }
		    
		    //model.rotate(up.x,up.y,up.z,((float)(x-oldx)/size().width)*3.1f);
		    //model.rotate(right.x,right.y,right.z,((float)(oldy-y)/size().height)*3.1f);
	        DrawObject();
            
        }
		oldx = x;
        oldy = y;
		return true;
       
    }

    void DrawObject()
    {
        long time = System.currentTimeMillis();
        showStatus("Drawing "+triangles+" triangles ...");
        
        /*
            ... cull and illuminate in world space ...
        */
        model.transform(vertList, worldList, vertices);
        model.transformNormal(vertList, worldList, vertices);
        triList[0].setVertexList(worldList);
        
        
        for (int i = 0; i < triangles; i++) {
            triList[i].Illuminate(lightList, lights, eye, cull);
        }
        
        /*
            .... clip in canonical eye coordinates ...
        */
        view.transform(worldList, canonicalList, vertices);
        view.transformNormal(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();
    }

}
