var TYPE_LINE = 0;
var TYPE_SURFACE = 1;

var Canvas3D = function(obj, options) {
	this.obj = document.getElementById(obj);
	this.canvas = this.obj.getContext("2d");
	
	this.worldUp = [ 0.0, 1.0, 0.0 ];
	this.viewMoveMatrix = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.viewRotationMatrix = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.worldTransform = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.objMoveMatrix = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.objRotationMatrix = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.objTransform = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	this.combinedTransform = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
	
	this.focalLength = 320.0;
	this.objects = [ ];
	
	this._3dPov(0,0,0,0,0,0);
	this._3dSetObject(0,0,0,0,0,0);
	
}

Canvas3D.prototype = {
	createObject: function(objType, pointArr, lineWeight, lineColour, fillColour) {
		var obj = {
			objType: objType,
			camdist: 0,
			points: pointArr,
			lineWeight: lineWeight,
			lineColour: lineColour,
			fillColour: fillColour
		};
		
		this.objects.push(obj);
	
	},
	
	_3dPov: function(x, y, z, xAngle, yAngle, zAngle) {
		this.moveFill(this.viewMoveMatrix, -x, -y, -z);
		this.rotateFill(this.viewRotationMatrix, xAngle, yAngle, zAngle);
		this.multiplyMatrixMatrix(this.viewRotationMatrix, this.viewMoveMatrix, this.worldTransform);
	},
	
	_3dSetObject: function(x, y, z, xAngle, yAngle, zAngle) {
		this.moveFill(this.objMoveMatrix, x, y, z);
		this.rotateFill(this.objRotationMatrix, xAngle, yAngle, zAngle);
		this.multiplyMatrixMatrix(this.objMoveMatrix, this.objRotationMatrix, this.objTransform);
		this.multiplyMatrixMatrix(this.worldTransform, this.objTransform, this.combinedTransform);
	},
	
	moveFill: function(a, x, y, z) {
		a[0] = 1;   a[1] = 0;   a[2] = 0;   a[3] = x;
		a[4] = 0;   a[5] = 1;   a[6] = 0;   a[7] = y;
		a[8] = 0;   a[9] = 0;   a[10]= 1;   a[11]= z;
	},
	
	rotateFill: function(a, xAngle, yAngle, zAngle) {
		var x = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
		var y = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
		var z = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
		var tmp = [ 0,0,0,0,0,0,0,0,0,0,0,0 ];
		
		var cx = Math.cos(xAngle);
		var cy = Math.cos(yAngle);
		var cz = Math.cos(zAngle);
		
		var sx = Math.sin(xAngle);
		var sy = Math.sin(yAngle);
		var sz = Math.sin(zAngle);
		
		x[0]=1;     x[1]=0;     x[2] =0;     x[3] =0;
		x[4]=0;     x[5]=cx;    x[6] =-sx;   x[7] =0;
		x[8]=0;     x[9]=sx;    x[10]=cx;    x[11]=0;

		y[0]=cy;    y[1]=0;     y[2] =sy;    y[3] =0;
		y[4]=0;     y[5]=1;     y[6] =0;     y[7] =0;
		y[8]=-sy;   y[9]=0;     y[10]=cy;    y[11]=0;

		z[0]=cz;    z[1]=-sz;   z[2] =0;     z[3] =0;
		z[4]=sz;    z[5]=cz;    z[6] =0;     z[7] =0;
		z[8]=0;     z[9]=0;     z[10]=1;     z[11]=0;
		
	  this.multiplyMatrixMatrix(z, y, tmp);
		this.multiplyMatrixMatrix(tmp, x, a);
	},
	
	_3dAxisAngle: function(x, y, z, theta) {
		
		var vecLen = Math.sqrt(x*x + y*y + z*z);
		if(vecLen < 0.0001) {
			return;
		}
		
		x /= vecLen;
		y /= vecLen;
		z /= vecLen;
		
		var c = Math.cos(theta);
		var s = Math.sin(theta);
		var t = 1.0 - c;
		
		this.viewRotationMatrix[0] = t*x*x + c;
		this.viewRotationMatrix[1] = t*x*y - s*z;
		this.viewRotationMatrix[2] = t*x*z + s*y;
		this.viewRotationMatrix[3] = 0;

		this.viewRotationMatrix[4] = t*x*y + s*z;
   	this.viewRotationMatrix[5] = t*y*y + c;
   	this.viewRotationMatrix[6] = t*y*z - s*x;
		this.viewRotationMatrix[7] = 0;

   	this.viewRotationMatrix[8] = t*x*z - s*y;
		this.viewRotationMatrix[9] = t*y*z + s*x;
		this.viewRotationMatrix[10] = t*z*z + c;
		this.viewRotationMatrix[11] = 0;
		
		this.multiplyMatrixMatrix(this.viewRotationMatrix, this.viewMoveMatrix, this.worldTransform);
	
	},
	
	_3dAxisAngleObject: function(x, y, z, theta) {
		
		var vecLen = Math.sqrt(x*x + y*y + z*z);
		if(vecLen < 0.0001) {
			return;
		}
		
		x /= vecLen;
		y /= vecLen;
		z /= vecLen;
		
		var c = Math.cos(theta);
		var s = Math.sin(theta);
		var t = 1.0 - c;
		
		this.viewRotationMatrix[0] = t*x*x + c;
		this.viewRotationMatrix[1] = t*x*y - s*z;
		this.viewRotationMatrix[2] = t*x*z + s*y;
		this.viewRotationMatrix[3] = 0;

		this.viewRotationMatrix[4] = t*x*y + s*z;
   	this.viewRotationMatrix[5] = t*y*y + c;
   	this.viewRotationMatrix[6] = t*y*z - s*x;
		this.viewRotationMatrix[7] = 0;

   	this.viewRotationMatrix[8] = t*x*z - s*y;
		this.viewRotationMatrix[9] = t*y*z + s*x;
		this.viewRotationMatrix[10] = t*z*z + c;
		this.viewRotationMatrix[11] = 0;
		
		this.multiplyMatrixMatrix(this.objMoveMatrix, this.objRotationMatrix, this.objTransform);
		this.multiplyMatrixMatrix(this.worldTransform, this.objTransform, this.combinedTransform);
	
	},
	
	_3dGetView: function() {
		return [
			-this.viewMoveMatrix[3],
			-this.viewMoveMatrix[7],
			-this.viewMoveMatrix[11],
			this.viewRotationMatrix[8],
			this.viewRotationMatrix[9],
			this.viewRotationMatrix[10],
		];
	},
	
	_3dLookAt: function(x1, y1, z1, x2, y2, z2) {
		 /* Build a transform as if you were at a point (x1,y1,z1), and
				looking at a point (x2,y2,z2) */
	
		 var ViewOut = [ 0, 0, 0 ];      // the View or "new Z" vector
		 var ViewUp = [ 0, 0, 0 ];       // the Up or "new Y" vector
		 var ViewRight = [ 0, 0, 0 ];    // the Right or "new X" vector
	
		 var ViewMagnitude;   // for normalizing the View vector
		 var UpMagnitude;     // for normalizing the Up vector
		 var UpProjection;    // magnitude of projection of View Vector on World UP
	
		 // first, calculate and normalize the view vector
		 ViewOut[0] = x2-x1;
		 ViewOut[1] = y2-y1;
		 ViewOut[2] = z2-z1;
		 ViewMagnitude = Math.sqrt(ViewOut[0]*ViewOut[0] + ViewOut[1]*ViewOut[1]+
				ViewOut[2]*ViewOut[2]);
	
		 // invalid points (not far enough apart)
		 if (ViewMagnitude < 0.00001)
				return (-1);
	
		 // normalize. This is the unit vector in the "new Z" direction
		 ViewOut[0] = ViewOut[0]/ViewMagnitude;
		 ViewOut[1] = ViewOut[1]/ViewMagnitude;
		 ViewOut[2] = ViewOut[2]/ViewMagnitude;
	
		 // Now the hard part: The ViewUp or "new Y" vector
	
		 // dot product of ViewOut vector and World Up vector gives projection of
		 // of ViewOut on WorldUp
		 UpProjection = ViewOut[0]*this.worldUp[0] + ViewOut[1]*this.worldUp[1]+
		 ViewOut[2]*this.worldUp[2];
	
		 // first try at making a View Up vector: use World Up
		 ViewUp[0] = this.worldUp[0] - UpProjection*ViewOut[0];
		 ViewUp[1] = this.worldUp[1] - UpProjection*ViewOut[1];
		 ViewUp[2] = this.worldUp[2] - UpProjection*ViewOut[2];
	
		 // Check for validity:
		 UpMagnitude = ViewUp[0]*ViewUp[0] + ViewUp[1]*ViewUp[1] + ViewUp[2]*ViewUp[2];
	
		 if (UpMagnitude < .000001)
		 {
				//Second try at making a View Up vector: Use Y axis default  (0,1,0)
				ViewUp[0] = -ViewOut[1]*ViewOut[0];
				ViewUp[1] = 1-ViewOut[1]*ViewOut[1];
				ViewUp[2] = -ViewOut[1]*ViewOut[2];
	
				// Check for validity:
				UpMagnitude = ViewUp[0]*ViewUp[0] + ViewUp[1]*ViewUp[1] + ViewUp[2]*ViewUp[2];
	
				if (UpMagnitude < .000001)
				{
						//Final try at making a View Up vector: Use Z axis default  (0,0,1)
						ViewUp[0] = -ViewOut[2]*ViewOut[0];
						ViewUp[1] = -ViewOut[2]*ViewOut[1];
						ViewUp[2] = 1-ViewOut[2]*ViewOut[2];
	
						// Check for validity:
						UpMagnitude = ViewUp[0]*ViewUp[0] + ViewUp[1]*ViewUp[1] + ViewUp[2]*ViewUp[2];
	
						if (UpMagnitude < .0000001)
								return (-1);
				}
		 }
	
		 // normalize the Up Vector
		 UpMagnitude = sqrt(UpMagnitude);
		 ViewUp[0] = ViewUp[0]/UpMagnitude;
		 ViewUp[1] = ViewUp[1]/UpMagnitude;
		 ViewUp[2] = ViewUp[2]/UpMagnitude;
	
		 // Calculate the Right Vector. Use cross product of Out and Up.
		 ViewRight[0] = -ViewOut[1]*ViewUp[2] + ViewOut[2]*ViewUp[1];
		 ViewRight[1] = -ViewOut[2]*ViewUp[0] + ViewOut[0]*ViewUp[2];
		 ViewRight[2] = -ViewOut[0]*ViewUp[1] + ViewOut[1]*ViewUp[0];
	
		 // Plug values into rotation matrix R
		 this.viewRotationMatrix[0]=ViewRight[0];
		 this.viewRotationMatrix[1]=ViewRight[1];
		 this.viewRotationMatrix[2]=ViewRight[2];
		 this.viewRotationMatrix[3]=0;
	
		 this.viewRotationMatrix[4]=ViewUp[0];
		 this.viewRotationMatrix[5]=ViewUp[1];
		 this.viewRotationMatrix[6]=ViewUp[2];
		 this.viewRotationMatrix[7]=0;
	
		 this.viewRotationMatrix[8]=ViewOut[0];
		 this.viewRotationMatrix[9]=ViewOut[1];
		 this.viewRotationMatrix[10]=ViewOut[2];
		 this.viewRotationMatrix[11]=0;
	
		 // Plug values into translation matrix T
		 this.moveFill(this.viewMoveMatrix,-x1,-y1,-z1);
	
		 // build the World Transform
		 this.multiplyMatrixMatrix(this.viewRotationMatrix,this.viewMoveMatrix,this.worldTransform);
	
		 return(0);
	},
	
	_3dMove: function(x, y, z) {
		 this.viewMoveMatrix[3] = -x;
		 this.viewMoveMatrix[7] = -y;
		 this.viewMoveMatrix[11]= -z;

   // build the transform
   	this.multiplyMatrixMatrix(this.viewRotationMatrix,this.viewMoveMatrix,this.worldTransform);
	},
	
	multiplyMatrixMatrix: function(A, B, C) {
		
		C[0] = A[0]*B[0] + A[1]*B[4] + A[2]*B[8];
		C[1] = A[0]*B[1] + A[1]*B[5] + A[2]*B[9];
		C[2] = A[0]*B[2] + A[1]*B[6] + A[2]*B[10];
		C[3] = A[0]*B[3] + A[1]*B[7] + A[2]*B[11] + A[3];

		C[4] = A[4]*B[0] + A[5]*B[4] + A[6]*B[8];
		C[5] = A[4]*B[1] + A[5]*B[5] + A[6]*B[9];
		C[6] = A[4]*B[2] + A[5]*B[6] + A[6]*B[10];
		C[7] = A[4]*B[3] + A[5]*B[7] + A[6]*B[11] + A[7];

		C[8] = A[8]*B[0] + A[9]*B[4] + A[10]*B[8];
		C[9] = A[8]*B[1] + A[9]*B[5] + A[10]*B[9];
		C[10] = A[8]*B[2] + A[9]*B[6] + A[10]*B[10];
		C[11] = A[8]*B[3] + A[9]*B[7] + A[10]*B[11] + A[11];
		
	},
	
	multiplyMatrixVector: function(a, b) {
		var c = [
			b[0] * a[0] + b[1] * a[4] + b[2] * a[8],
			b[0] * a[1] + b[1] * a[5] + b[2] * a[9],
			b[0] * a[2] + b[1] * a[6] + b[2] * a[10]
		];
		
		return c;
	},
	
// use the painter's algorithm to sort 
// our polygons.
	zSort: function() {
		var a, b, tmp, axmin, axmax, bxmin, bxmax, aymin, aymax, bymin, bymax;
		for(var i=0; i<this.objects.length; i++) {
			a = this.objects[i];
			axmin = a.points[0][0];
			for(var j=0; j<this.objects.length; j++) {
				b = this.objects[j];
			}
		}
	},
	
	paint: function() {
		var c = this.canvas;
		var o = this.obj;
		var hw = (o.width / 2) + 10; // this rendering method doesn't centre it perfectly
		var hh = (o.height / 2) + 10;
		var f = this.focalLength;
		c.fillStyle = "rgb(255,255,255)";
		c.fillRect(0, 0, o.width, o.height);
		
		var obj, point, camdist, xsum, ysum, zsum;
		
		for(var i=0; i<this.objects.length; i++) {
			
			obj = this.objects[i];
			xsum = ysum = zsum = 0;
			c.strokeStyle = obj.lineColour;
			c.fillStyle = obj.fillColour;
			c.beginPath();
			
			for(var j=0; j < obj.points.length; j++) {
				if (j == 0) {
					point = this.multiplyMatrixVector(this.combinedTransform, obj.points[j][0]);
					
					c.moveTo(hw + point[0] / (1-(point[2] / f)), hh + point[1] / (1-(point[2] / f)));
					xsum += point[0];
					ysum += point[1];
					zsum += point[2];
				}
				
				point = this.multiplyMatrixVector(this.combinedTransform, obj.points[j][1]);
				c.lineTo(hw + point[0] / (1-(point[2] / f)), hh + point[1] / (1-(point[2]/f)));
				xsum += point[0];
				ysum += point[1];
				zsum += point[2];

			}
			
			obj.camdist = Math.sqrt(
				Math.pow((xsum)/j, 2) + 
				Math.pow((ysum)/j, 2) +
				Math.pow(f-((zsum)/j), 2)
			);
			
			if(obj.objType == TYPE_SURFACE) {
				c.fill();
			} else {
				c.stroke();
			}
		}
		
// TODO this needs not-garbage Z sorting
		this.objects.sort(function(a,b){return (a.camdist > b.camdist) ? -1 : 1});
	}
};

