package wonderlab.graphics.world;

import wonderlab.graphics.geometry.*;
import wonderlab.graphics.shape.*;
import wonderlab.graphics.renderer.*;
import wonderlab.graphics.renderer.Phong;
import wonderlab.graphics.camera.*;
import wonderlab.applet.MISApplet;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Iterator;

public class World3D extends MISApplet{

	protected Camera camera;									// Camera
	protected Viewport viewport;								// Viewport
	protected Phong phong;										// Phong model
	protected ArrayList shapes;									// array for all objects in world
	protected ArrayList clippingPlanes;							// array of clipping planes
	public ArrayList lights;									// array for all lights in world
	int zBuffer[][];											// zBuffer
	protected MatrixStack S;									// Transformation stack
	int bgColor;
	protected boolean animate;									// controls continuous animation
	protected boolean debug;									// controls debug mode
	protected boolean showLines;								// controls debug mode
	protected boolean mouseDrag;
	
	public double delta_theta = 0;								// rotate world around z axis by this much at each timestep
	public double delta_phi = 0;								// rotate world around x axis by this much at each timestep
	protected int last_mouse_x = 0;
	protected int last_mouse_y = 0;
	
	public World3D () {
		super();
		bgColor = pack(20,20,20);
		zBuffer = new int[600][400];
		S = new MatrixStack();		
 		shapes = new ArrayList();
 		lights = new ArrayList();
 		camera = new Camera(1.1);
		phong = new Phong(this);
		clippingPlanes = new ArrayList();
		//clippingPlanes.add(new ClippingPlane(new Vector3D(0,0,-1), new Vector3D(0,0,0.1)));
		debug = false;
		showLines = false;
		animate = true;
	}
			
	public void initFrame() { 													// INITIALIZE ONE ANIMATION FRAME
		for (Iterator curShape = shapes.iterator(); curShape.hasNext(); ) {
				drawShape((Shape3D)curShape.next());
		}
    }
	
	public void drawShape(Shape3D s) {
		Vertex A, B, C, D, temp;
		double m, t;
		int tempColor[];
		wonderlab.graphics.geometry.Polygon [] faceStack;
		wonderlab.graphics.geometry.Polygon [] triangleStack;
		wonderlab.graphics.geometry.Polygon curTriangle;
		faceStack = s.faces();													// get faces of shape as array of polygons
		for (int i = 0; i < faceStack.length; i++) {							// iterate over all faces and draw them
			triangleStack = faceStack[i].asTriangles();							// decompose polygon into an array of triangles
			for (int j = 0; j < triangleStack.length; j++) {
					curTriangle = triangleStack[j];
					if (curTriangle != null) {
						viewport.transform(curTriangle);						// apply viewport transform
						A = curTriangle.getVertex(0);							// begin scanline
						B = curTriangle.getVertex(1);
						C = curTriangle.getVertex(2);
						phong.shade(A);
						phong.shade(B);
						phong.shade(C);	
						if (B.py < A.py) {
							temp = B;
							B = A;
							A = temp;
						}
						if (C.py < B.py) {
							temp = C;
							C = B;
							B = temp;
						}
						if (B.py < A.py) {
							temp = B;
							B = A;
							A = temp;
						}
						if (A.py == B.py) {
							scanline(A, B, C, C);
						} else if (C.py == B.py) {
							scanline(A, A, B, C);
						} else if (A.py == C.py) {
							scanline(A, B, A, B);
						} else {
							D = new Vertex(B);
							t = ((float)(B.py-A.py))/((float)(C.py-A.py));
							D.px = lerp(A.px,C.px,t);
							D.py = B.py;
							D.pz = lerp(A.pz,C.pz,t);
							D.setColor(new Vector3D(lerp(A.red(),C.red(),t),lerp(A.green(),C.green(),t),lerp(A.blue(),C.blue(),t),0));
							scanline(A,A,B,D);
							scanline(B,D,C,C);						
						}
					}
			}
		}
	}
	
	public boolean isVisible(wonderlab.graphics.geometry.Polygon P) {
		
		return true; 		
	}
	
	public void scanline(Vertex A, Vertex B, Vertex C, Vertex D)
	{
		int h, w;
		int x1, x2, z1, z2, x,y,z;
		double t1, t2;
		double r1,r2,g1,g2,b1,b2,r,g,b;
		double dZ, dR, dG, dB;
		Vertex temp;
		int rgb[] = new int[3];

		// rename A,B,C,D if necessary so that vertices are arranged as follows
		//      A ___________ B
		//		 /			 \
		//	   C/_____________\ D
		//
		if (B.px < A.px) {
			temp = B;
			B = A;
			A = temp;
		}
		if (D.px < C.px) {
			temp = D;
			D = C;
			C = temp;
		}

		h = C.py - A.py;
		//if (h != (D.py - B.py)) System.out.println("Problem, trapezoid edges are not horizontal.");
		//if ( h < 0 ) System.out.println("Warning trapezoid is upside down.");
		for (y = A.py; y <= C.py; y++) {
			t1 = (double)(y-A.py)/(double)h;
			x1 = (int)Math.floor(lerp(A.px,C.px,t1));
			x2 = (int)Math.ceil(lerp(B.px,D.px,t1));
			z1 = lerp(A.pz,C.pz,t1);
			z2 = lerp(B.pz,D.pz,t1);
			r1 = lerp(A.red(),C.red(),t1); 
			r2 = lerp(B.red(),D.red(),t1);
			g1 = lerp(A.green(),C.green(),t1); 
			g2 = lerp(B.green(),D.green(),t1);
			b1 = lerp(A.blue(),C.blue(),t1); 
			b2 = lerp(B.blue(),D.blue(),t1);
			w = x2 - x1;

			// deltas
			if (x1 == x2) {
				dZ = 0;
				dR = 0;
				dG = 0;
				dB = 0;
			} else {
				dZ = (double)(z2 - z1)/(double)(w);
				dR = (double)(r2 - r1)/(double)(w);
				dG = (double)(g2 - g1)/(double)(w);
				dB = (double)(b2 - b1)/(double)(w);
			}
			//if ( (x2 - x1) < 0 ) System.out.println("Warning left side of trapezoid is on the right side.");
			for (x = x1; x <= x2; x++) {
				if ((showLines) && ((x == x1) || (x == x2) || (y == A.py) || (y == C.py))) {
					rgb[0] = 255;
					rgb[1] = 255;
					rgb[2] = 255;
				} else {
					rgb[0] = (int)(r1*255);
					rgb[1] = (int)(g1*255);
					rgb[2] = (int)(b1*255);
				}
				if ((x >= 0) && (x < W) && (y >=0) && (y < H)) {
					if (z1 > zBuffer[x][y]) {
						int i = y*W + x;
						pix[y*W + x] = pack(rgb[0],rgb[1],rgb[2]);
						zBuffer[x][y] = z1;
					}
				}
				z1 += dZ;
				r1 += dR;
				g1 += dG;
				b1 += dB;
			}
		}
	}

	public double lerp(double A, double B, double t) {
			return ((1-t)*A + t*B);
	}

	public int lerp(int A, int B, double t) {
			return ((int)(Math.round((1-t)*A) + Math.round(t*B)));
	}
	
	public void resetScreen() {
		int color = bgColor;
		for (int i = 0; i < W; i++) {
			for (int j = 0; j < H; j++) {
				pix[j*W + i] = color;
				zBuffer[i][j] = -2147483648;
			}
		}
	}
	
	public void add(Light L) {
		addLightToWorld(L);
	}
	
	public void add(Shape3D S) {
		addShapeToWorld(S);
	}
	
	private void addShapeToWorld(Shape3D S) {
		shapes.add(S);
	}
	
	private void addLightToWorld(Light L) {
		lights.add(L);
		phong.reset();
	}
	
	public ArrayList lights() {
		return lights;
	}

	public Camera camera() {
		return camera;
	}
	
	public double time() {
		return System.currentTimeMillis() / 1000.0;
	}
	
	
	////////////////////////////////// USER INTERFACE CONTROLS ///////////////////////////////////
	
	public boolean mouseUp(Event e, int x, int y) {
		last_mouse_x = x;
		last_mouse_y = y;
      	return true;
   	}

	public boolean mouseDown(Event e, int x, int y) {
		last_mouse_x = x;
		last_mouse_y = y;
      	return true;
   	}
   
   	public boolean mouseDrag(Event e, int x, int y) {
   		if (mouseDrag) {
			delta_theta += 	((x - last_mouse_x)/(float)W)*Math.PI;
			delta_phi += 	((y - last_mouse_y)/(float)H)*Math.PI*Math.cos(delta_theta)/(Math.abs(Math.cos(delta_theta)));
			last_mouse_x = x;
			last_mouse_y = y;
		}
	 	return true;
   	}

}
