import java.awt.*;
import java.net.*;
import java.util.*;
import java.lang.Math;

public class textures extends MISApplet
{
	private final double focus = 400;
	private final double EPSILON = 1.0 / 40.0;
	private final double RADIUS = 25;
	private final int EPSILON_INV = 40;
	
	private final int X = 0;
	private final int Y = 1;
	private final int Z = 2;

	private final int R = 0;
	private final int G = 1;
	private final int B = 2;
	
	private final int TOP = 0;
	private final int MID = 1;
	private final int BOT = 2;	
	
	private final int PRE_DRAW = 1;
	private final int POST_DRAW = -1;
	
//	private final int RGB = 255;
	
	ParamSphere Head = new ParamSphere();
	
	Matrix3D Matrix3D = new Matrix3D();
	double mStack[][][] = new double[100][4][4];
	double MatrixTemp[][] = new double[4][4];
	int mTop = 0;

	double dSurfaceNormal[][][];
	double dVertexNormal[][][];
	double dNormalized[][][];
	
	double u[] = new double[3];
	double v[] = new double[3];
	double t, dStartTime;
		
	VertexNode Triangle[] = new VertexNode[3];
	VertexNode Vertices[] = new VertexNode[4];
	VertexNode Trap1[] = new VertexNode[4];
	VertexNode Trap2[] = new VertexNode[4];
	VertexNode NewNode = new VertexNode();
	VertexNode LeftNode = new VertexNode();
	VertexNode RightNode = new VertexNode();

	Image textureImage;
	ImageBuffer imageBuffer;
	
	int iTopMidBot[];
	
	private int iHeight, iWidth;
	private int iX, iY;
	private int iNumLights = 0;
	private double iShine = 64;
	
	private int FrameBuffer[][];
	private double ZBuffer[][];
	private double dLight[][] = new double[1000][6];
	
	private double dAmbience[] = {.1,.1,.1};
	private double dDiffuse[] = {.7,.7,.7};
	private double dSpecularClr[] = {.8,.8,.8};

	private double dEye[] = {0,0,-1};
	
	double CameraX, CameraY;
	double dTheta;
	boolean bIsInited = false;
	boolean bCameraView = false;

	public void render(Graphics g, double dCurrentTime)
	{
		if (bIsInited == false)
		{	
			dStartTime = dCurrentTime;
			InitStuff();
			bIsInited = true;
		}

		//clear the FrameBuffer and the ZBuffer
		for (int i=0; i<iHeight; i++)
		{
			for (int j=0; j<iWidth; j++)
			{
				FrameBuffer[i][j] = pack(230,230,230);
				ZBuffer[i][j] = 2;
			}
		}

//		CameraView(PRE_DRAW, Head);
		DrawShade(Head);
//		CameraView(POST_DRAW, Head);
		
		computeImage(System.currentTimeMillis() - dStartTime, FrameBuffer);
	}
	
	public void InitStuff()
	{
		iHeight = bounds().height;
		iWidth = bounds().width;
		
		FrameBuffer = new int[iHeight+1][iWidth+1];
		ZBuffer = new double[iHeight+1][iWidth+1];
					
		CameraX = 0;
		CameraY = 0;
		
		Head = new ParamSphere(0,0,0,150, EPSILON, iHeight, iWidth);

		Matrix3D.IdentityMatrix(mStack[mTop]);
		Head.Translate(mStack[mTop], 1,1,-focus);
		Head.Rotate(mStack[mTop], Math.PI/2, Y);
		Head.Transform(mStack[mTop], Head.getShape());
		
		try
		{
			textureImage = getImage(new URL(getParameter("texture")));
		}
		catch (MalformedURLException e) { }
		
		imageBuffer = new ImageBuffer(textureImage, this);
							
		dSurfaceNormal = new double[iHeight][iWidth][3];
		dVertexNormal = new double[iHeight][iWidth][3];
		dNormalized = new double[iHeight][iWidth][3];

		AddLight(-2,2,1,1,1,1);
//		AddLight(2,-3,1, .8,.8,.8);
//		AddLight(1,1,1,1,1,1);

		for (int k=0; k<3; k++)
			Triangle[k] = new VertexNode();
		for (int k=0; k<4; k++)
			Vertices[k] = new VertexNode();
		for (int k=0; k<4; k++)
		{
			Trap1[k] = new VertexNode();
			Trap2[k] = new VertexNode();
		}
	}
	
	public void DrawShade(ParamSurface Surface)
		//should send this a matrix of the xyz coords instead of the actual shape
		//this will help generalize it
		//also, change the shapes so that they store xyz in a 2D matrix
	{
		for (int i=1; i<EPSILON_INV; i++)
			//get the surface normals of the polygons
		{
			for (int j=0; j<EPSILON_INV; j++)
			{
				u[X] = Surface.getShape()[(i-1)%EPSILON_INV][j][X] - Surface.getShape()[i][j][X];
				u[Y] = Surface.getShape()[(i-1)%EPSILON_INV][j][Y] - Surface.getShape()[i][j][Y];
				u[Z] = Surface.getShape()[(i-1)%EPSILON_INV][j][Z] - Surface.getShape()[i][j][Z];
								
				v[X] = Surface.getShape()[i][(j+1)%EPSILON_INV][X] - Surface.getShape()[i][j][X];
				v[Y] = Surface.getShape()[i][(j+1)%EPSILON_INV][Y] - Surface.getShape()[i][j][Y];
				v[Z] = Surface.getShape()[i][(j+1)%EPSILON_INV][Z] - Surface.getShape()[i][j][Z];

				dSurfaceNormal[i-1][j] = CrossProduct(u,v);
			}
		}
			//the last row		
			for (int j=0; j<EPSILON_INV; j++)
			{
				u[X] = Surface.getShape()[EPSILON_INV-1][j][X] - Surface.getShape()[EPSILON_INV][j][X];
				u[Y] = Surface.getShape()[EPSILON_INV-1][j][Y] - Surface.getShape()[EPSILON_INV][j][Y];
				u[Z] = Surface.getShape()[EPSILON_INV-1][j][Z] - Surface.getShape()[EPSILON_INV][j][Z];
								
				v[X] = Surface.getShape()[EPSILON_INV-1][(j+1)%EPSILON_INV][X] - Surface.getShape()[EPSILON_INV][j][X];
				v[Y] = Surface.getShape()[EPSILON_INV-1][(j+1)%EPSILON_INV][Y] - Surface.getShape()[EPSILON_INV][j][Y];
				v[Z] = Surface.getShape()[EPSILON_INV-1][(j+1)%EPSILON_INV][Z] - Surface.getShape()[EPSILON_INV][j][Z];

				dSurfaceNormal[EPSILON_INV-1][j] = CrossProduct(u,v);
			}



		for (int i=1; i<EPSILON_INV; i++)
			//get the surface normals of the vertices by getting the average
			//of the surface normals of the polygons surrounding it
			//normalize the surface normals
		{
			for (int j=0; j<EPSILON_INV; j++)
			{
				dVertexNormal[i][j][X] = dSurfaceNormal[i-1][j][X] + 
										 dSurfaceNormal[i-1][Mod((j-1),EPSILON_INV)][X] +
										 dSurfaceNormal[i][Mod((j-1),EPSILON_INV)][X] + 
										 dSurfaceNormal[i][j][X];
													
				dVertexNormal[i][j][Y] = dSurfaceNormal[i-1][j][Y] + 
										 dSurfaceNormal[i-1][Mod((j-1),EPSILON_INV)][Y] +
										 dSurfaceNormal[i][Mod((j-1),EPSILON_INV)][Y] + 
										 dSurfaceNormal[i][j][Y];
													
				dVertexNormal[i][j][Z] = dSurfaceNormal[i-1][j][Z] + 
										 dSurfaceNormal[i-1][Mod((j-1),EPSILON_INV)][Z] +
										 dSurfaceNormal[i][Mod((j-1),EPSILON_INV)][Z] + 
										 dSurfaceNormal[i][j][Z];

				dNormalized[i][j] = Normalize(dVertexNormal[i][j]);
			}
		}

		for (int j=0; j<EPSILON_INV; j++)
		{
			dNormalized[0][j][X] = 0;
			dNormalized[0][j][Y] = 1;
			dNormalized[0][j][Z] = 0;
			
			dNormalized[EPSILON_INV][j][X] =  0;
			dNormalized[EPSILON_INV][j][Y] = -1;
			dNormalized[EPSILON_INV][j][Z] =  0;
		}

		for (int i=1; i<(EPSILON_INV+1); i++)
			//for each polygon
		{
			for (int j=0; j<(EPSILON_INV); j++)
			{
				
				Vertices[0].setXYZ(Surface.getShape()[i-1][j]);
				Vertices[0].setRGB(Shade(dNormalized[i-1][j]));
				Vertices[0].setdi((double)(i-1)/(double)EPSILON_INV);
				Vertices[0].setdj((double)(j)/(double)EPSILON_INV);
				
				Vertices[1].setXYZ(Surface.getShape()[i-1][Mod((j-1),EPSILON_INV)]);
				Vertices[1].setRGB(Shade(dNormalized[i-1][Mod((j-1),EPSILON_INV)]));
				Vertices[1].setdi((double)(i-1)/(double)EPSILON_INV);
				Vertices[1].setdj((double)(j-1)/(double)EPSILON_INV);
				
				Vertices[2].setXYZ(Surface.getShape()[i][Mod((j-1),EPSILON_INV)]);
				Vertices[2].setRGB(Shade(dNormalized[i][Mod((j-1),EPSILON_INV)]));
				Vertices[2].setdi((double)(i)/(double)EPSILON_INV);
				Vertices[2].setdj((double)(j-1)/(double)EPSILON_INV);
				
				Vertices[3].setXYZ(Surface.getShape()[i][j]);
				Vertices[3].setRGB(Shade(dNormalized[i][j]));
				Vertices[3].setdi((double)(i)/(double)EPSILON_INV);
				Vertices[3].setdj((double)(j)/(double)EPSILON_INV);
				
				for (int k=0; k<4; k++)
				{
					double dTemp[] = new double[3];
				
					//get px, py, pz
//					dTemp[X] = (focus * Vertices[k].getXYZ()[X]) / (focus - Vertices[k].getXYZ()[Z]);
//					dTemp[Y] = (focus * Vertices[k].getXYZ()[Y]) / (focus - Vertices[k].getXYZ()[Z]);

					dTemp[X] = (focus * Vertices[k].getXYZ()[X]) / (Vertices[k].getXYZ()[Z]);
					dTemp[Y] = (focus * Vertices[k].getXYZ()[Y]) / (Vertices[k].getXYZ()[Z]);
					dTemp[Z] = 1 / Vertices[k].getXYZ()[Z];
					
					//viewport
					dTemp[X] = dTemp[X] + iWidth/2;
					dTemp[Y] = dTemp[Y] + iHeight/2;
					
					//these vertices contain RGB values and px, py, pz
					Vertices[k].setXYZ(dTemp);
				}
				
				//first triangle
				Triangle[0].Copy(Vertices[0]);
				Triangle[1].Copy(Vertices[1]);
				Triangle[2].Copy(Vertices[2]);
				
				ScanConvert(Triangle);
				
				Triangle[0].Copy(Vertices[0]);
				Triangle[1].Copy(Vertices[2]);
				Triangle[2].Copy(Vertices[3]);
								
				ScanConvert(Triangle);
			}
		}
	}
		
	public void ScanConvert(VertexNode[] Triangle)
		//this takes a triangle, splits it into trapezoids
		//lerps to get individual pixel stuffs, etc, you know
	{
		//iTemp[0] has the max, iTemp[1] has the mid, iTemp[2] has the min
		iTopMidBot = MaxMin(Triangle);
		
		//we have three separate thingers so we have to make a new vertexnode
		t = (Triangle[iTopMidBot[MID]].getXYZ()[Y] - Triangle[iTopMidBot[BOT]].getXYZ()[Y]) / 
				(Triangle[iTopMidBot[TOP]].getXYZ()[Y] - Triangle[iTopMidBot[BOT]].getXYZ()[Y]);

		NewNode.Copy(VertexLerp(t, Triangle[iTopMidBot[BOT]], Triangle[iTopMidBot[TOP]]));
		
		if (NewNode.getXYZ()[X] < Triangle[iTopMidBot[MID]].getXYZ()[X])
		{
			Trap1[0].Copy(Triangle[iTopMidBot[TOP]]);
			Trap1[1].Copy(NewNode);
			Trap1[2].Copy(Triangle[iTopMidBot[MID]]);
			Trap1[3].Copy(Triangle[iTopMidBot[TOP]]);

			Trap2[0].Copy(NewNode);
			Trap2[1].Copy(Triangle[iTopMidBot[BOT]]);
			Trap2[2].Copy(Triangle[iTopMidBot[BOT]]);
			Trap2[3].Copy(Triangle[iTopMidBot[MID]]);
		}
		else
		{
			Trap1[0].Copy(Triangle[iTopMidBot[TOP]]);
			Trap1[1].Copy(Triangle[iTopMidBot[MID]]);
			Trap1[2].Copy(NewNode);
			Trap1[3].Copy(Triangle[iTopMidBot[TOP]]);

			Trap2[0].Copy(Triangle[iTopMidBot[MID]]);
			Trap2[1].Copy(Triangle[iTopMidBot[BOT]]);
			Trap2[2].Copy(Triangle[iTopMidBot[BOT]]);
			Trap2[3].Copy(NewNode);
		}				

		DrawTrapezoid(Trap1);
		DrawTrapezoid(Trap2);
	}
	
	public void DrawTrapezoid(VertexNode[] Trap)
	{
		int top = (int)Math.ceil(Trap[0].getXYZ()[Y]);
		int bottom = (int)Math.ceil(Trap[1].getXYZ()[Y]);
		
		double ti, tj;
		for (int i = bottom; i < top; i++)
		{
			//find t between absolute top and current scanline
			//interpolate to get left and right
			ti = (i - Trap[1].getXYZ()[Y]) / (Trap[0].getXYZ()[Y] - Trap[1].getXYZ()[Y]);
			
			LeftNode.Copy(VertexLerp(ti, Trap[1], Trap[0]));
			RightNode.Copy(VertexLerp(ti, Trap[2], Trap[3]));
			//do it again between left and right
			
			int left = (int)Math.ceil(LeftNode.getXYZ()[X]);
			int right = (int)Math.ceil(RightNode.getXYZ()[X]);
			
			for (int j=left; j<right; j++)
			{
				int lerpi = 0;
				int lerpj = 0;
				VertexNode PixelNode = new VertexNode();
				tj = (j - LeftNode.getXYZ()[X]) / (RightNode.getXYZ()[X] - LeftNode.getXYZ()[X]);
					
				PixelNode.Copy(VertexLerp(tj, LeftNode, RightNode));
				
				lerpi = imageBuffer.getHeight() - (int)(PixelNode.di() * imageBuffer.getHeight());
				lerpj = imageBuffer.getWidth() - (int)(PixelNode.dj() * imageBuffer.getWidth());

				double r, g, b;
				r = Math.min(1, PixelNode.getRGB()[R] + ((double)unpack(imageBuffer.get(lerpi, lerpj), 0)/255));
				g = Math.min(1, PixelNode.getRGB()[G] + ((double)unpack(imageBuffer.get(lerpi, lerpj), 1)/255));
				b = Math.min(1, PixelNode.getRGB()[B] + ((double)unpack(imageBuffer.get(lerpi, lerpj), 2)/255));
				
				if ((i >= 0) && (i<iHeight) && (j>=0) && (j<iWidth))
					if ((PixelNode.getXYZ()[Z] < ZBuffer[i][j]))
					{
						FrameBuffer[i][j] = pack((int)(255*r), (int)(255*g), (int)(255*b));
//						FrameBuffer[i][j] = imageBuffer.get(lerpi, lerpj);
						ZBuffer[i][j] = PixelNode.getXYZ()[Z];
					}
			}			
		}		
	}
	
	public VertexNode VertexLerp(double t, VertexNode A, VertexNode B)
	{
		VertexNode NewNode = new VertexNode();
		double XYZ[] = new double[3];
		double RGB[] = new double[3];

		XYZ[X] = lerp(t, A.getXYZ()[X], B.getXYZ()[X]);
		XYZ[Y] = lerp(t, A.getXYZ()[Y], B.getXYZ()[Y]);
		XYZ[Z] = lerp(t, A.getXYZ()[Z], B.getXYZ()[Z]);
		
		RGB[R] = lerp(t, A.getRGB()[R], B.getRGB()[R]);
		RGB[G] = lerp(t, A.getRGB()[G], B.getRGB()[G]);
		RGB[this.B] = lerp(t, A.getRGB()[this.B], B.getRGB()[this.B]);
		
		NewNode.setXYZ(XYZ);
		NewNode.setRGB(RGB);
		
		NewNode.setdi(lerp(t, A.di(), B.di()));
		NewNode.setdj(lerp(t, A.dj(), B.dj()));
		
		return NewNode;	
	}
	
	public double[] Shade(double[] dNormal)
	//only do specular or diffuse, not both
	{
		double RGB[] = {0,0,0};
		
//		RGB = Gouraud(RGB, dNormal);
		RGB = Phong(RGB, dNormal);

		return RGB;
	}
	
	public double[] Gouraud(double[] RGB, double[] dNormal)
	{
		return AddDiffuse(AddAmbience(RGB), dNormal);
	}
//	
	public double[] Phong(double[] RGB, double[] dNormal)
	{
		RGB = AddAmbience(RGB);
//		RGB = AddDiffuse(RGB, dNormal);
		RGB = AddSpecular(RGB, dNormal);
		return RGB;
	}
	
	public double[] AddAmbience(double[] RGB)
	{
		return VectorAddVector(RGB, dAmbience);
	}
	
	public double[] AddDiffuse(double[] RGB, double[] dNormal)
	{
		double dTempRGB[] = RGB;
		for (int i=0; i<iNumLights; i++)
			for (int j=0; j<3; j++)
				dTempRGB[j] += dDiffuse[j] * DotProduct(dNormal, (dLight[i]));
		return dTempRGB;
	}
	
	public double[] AddSpecular(double RGB[], double[] dNormal)
	//do this so it does diffuse as well
	{
		double dTempRGB[] = RGB;
		double dSpecular = 1;
		double dDiffusion;
		
		for(int i=0; i<iNumLights; i++)
		{
			dDiffusion = Math.min(1,DotProduct(dNormal, Normalize(dLight[i])));
							
			dSpecular = Math.min(0,DotProduct(getReflection(dNormal),Normalize(dLight[i])));
			dSpecular = Math.pow(dSpecular, 32);
			
			dTempRGB[R] += dLight[i][R+3] *( dDiffusion*dDiffuse[R] + dSpecular*dSpecularClr[R]);
			dTempRGB[G] += dLight[i][G+3] *( dDiffusion*dDiffuse[G] + dSpecular*dSpecularClr[G]);
			dTempRGB[B] += dLight[i][B+3] *( dDiffusion*dDiffuse[B] + dSpecular*dSpecularClr[B]);

		}
		return dTempRGB;
	}
	
	public double[] AddBoring (double[] RGB, double[] dNormal)
	{
		for(int i=0; i<RGB.length; i++)
			RGB[i] = 0.5 + 0.5 * dNormal[i];
		return RGB;
	}
	
	public void AddLight(double xNew, double yNew, double zNew, double rNew, double gNew, double bNew)
	{
		dLight[iNumLights][X] = xNew;
		dLight[iNumLights][Y] = yNew;
		dLight[iNumLights][Z] = zNew;
		
		dLight[iNumLights][3] = rNew;
		dLight[iNumLights][4] = gNew;
		dLight[iNumLights][5] = bNew;
		iNumLights++;
	}
	
	public double[] getReflection(double[] dNormal)
	{
		double dTemp[] = new double[3];
				
		dTemp[X] = (2 * DotProduct(dNormal, Normalize(dEye)) * dNormal[X] - Normalize(dEye)[X]);
		dTemp[Y] = (2 * DotProduct(dNormal, Normalize(dEye)) * dNormal[Y] - Normalize(dEye)[Y]);
		dTemp[Z] = (2 * DotProduct(dNormal, Normalize(dEye)) * dNormal[Z] - Normalize(dEye)[Z]);
		
		return dTemp;
	}
	
	public double[] VectorAddVector(double[] dVector1, double[] dVector2)
	{
		for (int i=0; i<3; i++)
			dVector1[i] += dVector2[i];
		return dVector1;
	}
	int i=0;
	
	public double[] VectorMultiplyConstant(double dMult, double[] dVector)
	{
		for (int i=0; i<3; i++)
			dVector[i] *= dMult;
		return dVector;
	}

	public double[] VectorMultiplyVector(double[] dVectorA, double[] dVectorB)
	{
		double[] dVectorC = new double[3];
		for (int i=0; i<3; i++)
		{
			dVectorC[i] = dVectorA[i]*dVectorB[0] + dVectorA[i]*dVectorB[1] + dVectorA[i]*dVectorB[2];
			System.out.println(dVectorA[i]);
		}	
		return dVectorC;
	}
	
	public double[] Normalize(double[] dVector)
	{
		double dLength = Math.sqrt(DotProduct(dVector, dVector));
		dVector[X] = dVector[X] / dLength;
		dVector[Y] = dVector[Y] / dLength;
		dVector[Z] = dVector[Z] / dLength;
		
		return dVector;
	}
	
	public double[] CrossProduct(double[] u, double[] v)
	{
		double dCross[] = new double[3];
		dCross[X] = u[Y]*v[Z] - u[Z]*v[Y];
		dCross[Y] = u[Z]*v[X] - u[X]*v[Z];
		dCross[Z] = u[X]*v[Y] - u[Y]*v[X];
		
		return dCross;
	}
	
	public double DotProduct(double[] u, double[] v)
	{
		return (u[X]*v[X] + u[Y]*v[Y] +u[Z]*v[Z]);
	}
	
	public double lerp(double t, double a, double b)
	{
		return a + t * (b - a);
	}
	
	public void CameraView(int iSign, ParamSurface Surface)
	{
		if (iSign == PRE_DRAW)
		{
			Matrix3D.IdentityMatrix(mStack[mTop]);
			push();	
				Surface.Rotate(mStack[mTop], -CameraX, Y);
				Surface.Transform(mStack[mTop], Surface.getShape());
			pop();
			push();
				Surface.Rotate(mStack[mTop], CameraY, X);
				Surface.Transform(mStack[mTop], Surface.getShape());
			pop();
		}
		else if (iSign == POST_DRAW)
		{
			Matrix3D.IdentityMatrix(MatrixTemp);
			push();
				Surface.Rotate(mStack[mTop], -CameraY, X);
				Surface.Transform(mStack[mTop], Surface.getShape());
			pop();
			push();
				Surface.Rotate(mStack[mTop], CameraX, Y);
				Surface.Transform(mStack[mTop], Surface.getShape());
			pop();
		}
	}
	
	public int[] MaxMin(VertexNode[] Triangle)
	{
		int iIndexMax = 0, iIndexMin = 0, iIndexMid = 0;
		double dMax, dMin;
		
		dMax = Math.max(Triangle[0].getXYZ()[Y], Triangle[1].getXYZ()[Y]);
		dMax = Math.max(dMax, Triangle[2].getXYZ()[Y]);

		dMin = Math.min(Triangle[0].getXYZ()[Y], Triangle[1].getXYZ()[Y]);
		dMin = Math.min(dMin, Triangle[2].getXYZ()[Y]);
		
		for (int i=0; i<Triangle.length; i++)
		{
			if (Triangle[i].getXYZ()[Y] == dMax)
				iIndexMax = i;
			else if (Triangle[i].getXYZ()[Y] == dMin)
				iIndexMin = i;
			else
				iIndexMid = i;
		}
		
		int iTemp[] = {iIndexMax, iIndexMid, iIndexMin};
		return iTemp;
	}

	public void push()
	{
		Matrix3D.Copy(mStack[mTop], mStack[mTop+1]);
		mTop++;
	}
	
	public void pop()
	{
		mTop--;
	}
	
	public int Mod(int iNum, int iMod)
	{
		if(iNum > 0)
			iNum = iNum%iMod;
		else
			while (iNum < 0)
				iNum += iMod;
		return iNum;		
	}

	public boolean mouseDrag(Event e, int x, int y)
	{
		
		x = iX - x;
		y = iY - y;
		CameraX = (CameraX + ((double)x / 360.0))%(2*Math.PI);
		CameraY = (CameraY + ((double)y / 360.0))%(2*Math.PI);
		
		return true;	
	}

	public boolean mouseUp(Event e, int x, int y)
	{
		return true;
	}
	
	public boolean mouseDown(Event e, int x, int y)
	{
		iX = x;
		iY = y;
		return true;			
	}
}
