import java.awt.*;
import java.lang.*;
import Drawable;
import Point3D;
import Raster;

// I don't consider this a very object-oriented task.
// Bear with me.

public class Matrix3D 
{
	private float m[][];

	//
	// Constructors
	//

		// initialize with identity transform
	public Matrix3D()
	{
		m = new float[4][4];
		loadIdentity();
	}
	
		// initialize with copy of source
	public Matrix3D(Matrix3D copy)
	{
		m = new float[4][4];
      System.arraycopy(copy.m, 0, m, 0, m.length);
	}
	
		// initialize with a mapping from
		// canonical space to screen space
   public Matrix3D(Raster r)
   {
		m = new float[4][4];
		loadIdentity();
		int w = r.getWidth();
		int h = r.getHeight();
		int z = 2 << 14; //*** ???????
		compose( Translation(w/2.0f, h/2.0f, z/2.0f) );
		compose( Scale(w/2.0f, h/2.0f, z/2.0f) );
		//System.out.println("Raster matrix: \n" + toString());
   }


	//
	// Static Class Methods: (java constructors suck anyway)
	//
	
	public static Matrix3D Identity()
	{
		return (new Matrix3D());
	}

	
	public static Matrix3D Rotation(float ax, float ay, float az, float theta)
	{
		return Rotation(new Point3D(ax, ay, az), theta);
	}
	
	public static Matrix3D Rotation(Point3D ap, float theta)
	{
		ap.normalize();
		Matrix3D mr = Symmetric(ap);
		//mr.scalarMult((float)(1.0 - Math.cos(theta)));
		Matrix3D ms = Skew(ap);
		ms.scalarMult((float)Math.sin(theta));
		Matrix3D mi = Identity();
		mi.sub(mr);  //
		mi.scalarMult((float)Math.cos(theta));
		
		mr.add( ms );
		mr.add( mi );
		return (mr);
	}
	

	public static Matrix3D Translation(float dx, float dy, float dz)
	{
		Matrix3D mt = new Matrix3D();
		mt.set(3, 0, dx); mt.set(3, 1, dy); mt.set(3, 2, dz);
		return (mt);
	}
	
	public static Matrix3D Translation(Point3D d)
	{
		Matrix3D mt = new Matrix3D();
		mt.set(3, 0, d.x); mt.set(3, 1, d.y); mt.set(3, 2, d.z);
		return (mt);
	}
	

	public static Matrix3D Scale(float sx, float sy, float sz)
	{
		Matrix3D ms = new Matrix3D();
		ms.set(0, 0, sx);  ms.set(1, 1, sy);  ms.set(2, 2, sz);
		return (ms);
	}
	

	public static Matrix3D Shear(float kxy, float kxz, float kyz)
	{
		Matrix3D ms = new Matrix3D();
		ms.set(1, 0, kxy);  ms.set(2, 0, kxz);  ms.set(2, 1, kyz);
		return (ms);
	}
	

	public static Matrix3D Symmetric(Point3D p)
	{
		return Symmetric(p.getVec());
	}
		
	public static Matrix3D Symmetric(float v[])
	{
		Matrix3D ms = new Matrix3D();
		for (int j = 0; j < 3; j++)
		{
			for (int i = 0; i < 3; i++)
			{
				ms.set(i, j, v[i] * v[j]);
			}
		}
		return (ms);		
	}
	

	public static Matrix3D Skew(float a, float b, float c)
	{
		float v[] = {a, b, c, 1};
		return (Skew(v));
	}
	
	public static Matrix3D Skew(Point3D a)
	{
		return (Skew(a.getVec()));
	}
	
	public static Matrix3D Skew(float v[])
	{
		Matrix3D ms = new Matrix3D();
		for (int j = 0; j < 3; j++)
		{
			for (int i = 0; i < 3; i++)
			{
				if (i == j)
					ms.set(i, j, 0);
				else
					ms.set(i, j, v[3-(i+j)] * ((i == (j+1)%3) ? -1 : 1));
			}
		}
		return (ms);		
	}


	public static Matrix3D LookAt(float eyex, float eyey, float eyez,
	                              float atx,  float aty,  float atz,
	                              float upx,  float upy,  float upz)
	{
		return LookAt(new Point3D(eyex, eyey, eyez),
		              new Point3D(atx, aty, atz),
		              new Point3D(upx, upy, upz));
	}
	
	public static Matrix3D LookAt(Point3D eye, Point3D at, Point3D up)
	{
		at.sub(eye);
		at.normalize();

		Point3D r = at.cross(up);
		r.normalize();

		Point3D u = r.cross(at);
		u.normalize();

		Matrix3D mla = new Matrix3D();
		for (int i = 0; i < 3; i++)
		{
			mla.set(i, 0, r.get(i));
			mla.set(i, 1, u.get(i));
			mla.set(i, 2, -at.get(i));
		}
		mla.set(3, 0, -r.dot(eye));
		mla.set(3, 1, -u.dot(eye));
		mla.set(3, 2, at.dot(eye));
		
		return (mla);
	}


	public static Matrix3D Perspective(float l, float r,
	                                   float b, float t,
	                                   float n, float f)
	{
		Matrix3D mp = new Matrix3D();
		mp.set(0, 0, (2*n)/(r-l)); ; mp.set(2, 0, -(r+l)/(r-l)); ;
		 ; mp.set(1, 1, (2*n)/(b-t)); mp.set(2, 1, -(b+t)/(b-t)); ;
		 ; ; mp.set(2, 2, (f+n)/(f-n)); mp.set(3, 2, -(2*f*n)/(f-n));
		 ; ; mp.set(2, 3, 1); mp.set(3, 3, 0);
		return (mp);
	}
	
	
	public static Matrix3D Orthographic(float l, float r,
	                                    float b, float t,
	                                    float n, float f)
	{
		Matrix3D mo = new Matrix3D();
		mo.set(0, 0, 2f/(r-l)); ; ; mo.set(3, 0, -(r+l)/(r-l));
		 ; mo.set(1, 1, 2f/(b-t)); ; mo.set(3, 1, -(b+t)/(b-t));
		 ; ; mo.set(2, 2, 2f/(f-n)); mo.set(2, 3, -(f+n)/(f-n));
		return (mo);
	}
	

	//
	// Useful matrix/vector methods
	//        

	public float[] vecMult(float[] v)
	{
		float res[] = new float[4];
		for (int j = 0; j < 4; j++)
		{
			res[j] = 0;
			for (int i = 0; i < 4; i++)
			{
				res[j] += get(i, j) * v[i];
			}
		}
		return (res);
	}

	// Assumes 1 for last coordinate. Divides by w'.
	public Point3D vecMult(Point3D v)
	{
		Point3D res = new Point3D(v.x*get(0,0) + v.y*get(1,0) + v.z*get(2,0) + get(3,0) ,
		                          v.x*get(0,1) + v.y*get(1,1) + v.z*get(2,1) + get(3,1) ,
		                          v.x*get(0,2) + v.y*get(1,2) + v.z*get(2,2) + get(3,2) ,
		                          v.argb);
		res.div(v.x*get(0,3) + v.y*get(1,3) + v.z*get(2,3) + get(3,3));
		return res;
	}
	

	// Assumes 1 for last coordinate. Divides by w'.
	public Point3D vecMultXY(Point3D v)
	{
		Point3D res = new Point3D(v.x*get(0,0) + v.y*get(1,0) + v.z*get(2,0) + get(3,0) ,
		                          v.x*get(0,1) + v.y*get(1,1) + v.z*get(2,1) + get(3,1) ,
		                          v.x*get(0,2) + v.y*get(1,2) + v.z*get(2,2) + get(3,2) ,
		                          v.argb);
		res.div(v.x*get(0,3) + v.y*get(1,3) + v.z*get(2,3) + get(3,3));
		//res.z = 1.0f/res.z;
		return res;
	}
	

	private void scalarMult(float a)
	{
		for (int j = 0; j < 3; j++)
		{
			for (int i = 0; i < 3; i++)
			{
				set(i, j, a * get(i, j));
			}
		}
		return;
	}
	

	private void scalarDiv(float a)
	{
		scalarMult(1.0f / a);
		return;
	}
	

	private void add(Matrix3D ma)
	{
		for (int j = 0; j < 3; j++)
		{
			for (int i = 0; i < 3; i++)
			{
				set(i, j, ma.get(i, j) + get(i, j));
			}
		}
		return;
	}
	
	private void sub(Matrix3D ma)
	{
		for (int j = 0; j < 3; j++)
		{
			for (int i = 0; i < 3; i++)
			{
				set(i, j, get(i, j) - ma.get(i, j));
			}
		}
		return;
	}
	
	
	//
	// General interface methods
	//        
	
		// set element [j][i] to value
	public void set(int i, int j, float value)
	{
		try {
			m[j][i] = value;
			return;
		} catch (ArrayIndexOutOfBoundsException e)
		{
			System.out.println("Tried to set invalid element: " + i + ", " + j);
			return;
		}
	}

		// return element [j][i]
	public float get(int i, int j)                
	{
		try {
			return (m[j][i]);
		} catch (ArrayIndexOutOfBoundsException e)
		{
			System.out.println("Tried to get invalid element: " + i + ", " + j);
			return (0);
		}
	}
	
	//
	// Transform points from the in array to the out array
	// using the current matrix. The subset of points transformed
	// begins at the start index and has the specified length
	//
	//    for (i = 0; i < length; i++)
	//        out[start+i] = this * in[start+i]
	//
	
	public void transform(Point3D in[], Point3D out[], int start, int length)
	{
		//System.out.println("Transform: ");
		int begin = Math.max(start, 0);
		int end = Math.min(start+length, Math.min(out.length, in.length));
		for (int p = begin; p < end; p++)
		{
			out[p] = vecMult(in[p]);
			/*
			if (p < start + 6)
			{
				System.out.println("Made point: "+
				                   "("+ in[p].x +", "+ in[p].y +", "+ in[p].z +")"+
				                   " => "+
										 "("+ out[p].x +", "+ out[p].y +", "+ out[p].z +") : "+
										 out[p].colorString() );
			}
			*/
		}
		return;
	}
	

	public void transformDraw(Point3D in[], int inlen, Point3D tri[], int trilen, Raster r)
	{
		//System.out.println("TransformDraw: ");
		Triangle foo;
		
		for (int p = 0; p < inlen; p++)
		{
			(vecMultXY(in[p])).Draw(r);
			/*
			foo = new Point3D(v[0]/v[3], v[1]/v[3], v[2]/v[3], in[p].argb);
			foo.Draw(r);
			*/
			//r.setPixel(in[p].argb, (int)(v[0]/v[3]), (int)(v[1]/v[3]), (int)(in[p].z));
		}
		for (int t = 0; t < trilen; t += 3)
		{
			foo = new Triangle(vecMultXY(tri[t]), vecMultXY(tri[t+1]), vecMultXY(tri[t+2]));
			foo.Draw(r);
		}
		return;
	}
	

		// this = this * src
	public final void compose(Matrix3D src)
	{
		float nm[][] = new float[4][4];
		for (int j = 0; j < 4; j++)
		{
			for (int i = 0; i < 4; i++)
			{
				nm[j][i] = 0;
				for (int k = 0; k < 4; k++)
				{
					nm[j][i] += get(k, j) * src.get(i, k);
				}
			}
		}
		m = nm;
		return;
	}
	

		// this = src * this
	public final void composeLeft(Matrix3D src)
	{
		Matrix3D mtmp = new Matrix3D(src);
		mtmp.compose(this);
		m = mtmp.m;
		return;
	}


		// this = identity
	public void loadIdentity()
	{
		for (int j = 0; j < 4; j++)
		{
			for (int i = 0; i < 4; i++)
			{
				set(i, j, (i == j) ? 1 : 0);
			}
		}
		
		return;
	}


		// this = this * t
	public void translate(float tx, float ty, float tz)
	{
		compose( Translation(tx, ty, tz) );
		return;
	}


		// this = this * scale
	public void scale(float sx, float sy, float sz)
	{
		compose( Scale(sx, sy, sz) );
		return;
	}

	

		// this = this * shear
	public void shear(float kxy, float kxz, float kyz)
	{
		compose( Shear(kxy, kxz, kyz) );
		return;
	}
	

		// this = this * skew
	public void skew(float kxy, float kxz, float kyz)
	{
		compose( Skew(kxy, kxz, kyz) );
		return;
	}
	

		// this = this * rotate
	public void rotate(float ax, float ay, float az, float angle)
	{
		if (angle == 0.0f) return;
		compose( Rotation(ax, ay, az, angle) );
		return;
	}
	

		// this = this * lookat
		// Hmmm... Assume eye, at are points;
		//                up is a vector.
		// Could this be any less clear???
	public void lookAt(float eyex, float eyey, float eyez,
	                   float atx,  float aty,  float atz,
	                   float upx,  float upy,  float upz)
	{
		if ((eyex == atx && eyey == aty && eyez == atz) ||
		    (upx == atx && upy == aty && upz == atz)   )return;
		    
		compose( LookAt(eyex, eyey, eyez, 
		                atx,  aty,  atz, 
		                upx,  upy,  upz)	);
		return;
	}
	
	
	//
	// Assume the following projection transformations
	// transform points into the canonical viewing space
	//
	
	public void perspective(float left, float right,                // this = this * persp
	                        float bottom, float top,
	                        float near, float far)
	{
		compose( Perspective(left, right, bottom, top, near, far) );
		return;
	}
	
	public void orthographic(float left, float right,               // this = this * ortho
	                         float bottom, float top,
	                         float near, float far)
	{
		
		compose( Orthographic(left, right, bottom, top, near, far) );
		return;
	}
	
	public String toString()
	{
		String s  = "|\t" + m[0][0] + "\t" + m[0][1] + "\t" + m[0][2] + "\t" + m[0][3] + "\t|\n";
		       s += "|\t" + m[1][0] + "\t" + m[1][1] + "\t" + m[1][2] + "\t" + m[1][3] + "\t|\n";
		       s += "|\t" + m[2][0] + "\t" + m[2][1] + "\t" + m[2][2] + "\t" + m[2][3] + "\t|\n";
		       s += "|\t" + m[3][0] + "\t" + m[3][1] + "\t" + m[3][2] + "\t" + m[3][3] + "\t|\n";
		
		return (s);
	}
	
}

