// Matrix.java
// This class implements the basic matrix operations for 3D graphics
package wonderlab.graphics.geometry;


public class Matrix {
	
	double[][] data;
	Matrix temp;
	int m;
	int n;

	// fcn to test Matrix on command-line
	public static void main (String argv[]) {

		Matrix A = new Matrix(5, 5);
		Matrix B = new Matrix(5, 5);
		Matrix C = new Matrix(1, 5);
		Matrix D = new Matrix(5, 1);
		double[][] e = {{0.1,0.2,0.3,0.4,0.5}};
		Matrix E = new Matrix(e, 1, 5);
		
		A.identity();
		B.identity();
		C.set(0,1,5);
		D.set(1,0,5);
		A.print("A as identity");
		B.print("B as identity");
		A.multiply(B);
		A.print("AB");		
		C.print("C is");
		C.multiply(A);
		C.print("CA");
		C.multiply(D);
		D.print("D is");
		C.print("CD");
		E.print("E is");
		E.transpose();
		E.print("E tranposed is");
		B.transpose();
		B.print("B transposed is");
	}
	
	public Matrix(int m, int n) {
		this.m = m;
		this.n = n;
		data = new double[m][n];
	}

	public Matrix(Matrix src) {
		m = src.m;
		n = src.n;
		
		data = new double[m][n];
		
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				data[i][j] = src.get(i,j);
			}
		}
	}

	public Matrix(double[][] src, int m, int n) {
		this.m = m;
		this.n = n;
		
		data = src;		
	}
	
	public void zeroMatrix() {
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				data[i][j] = 0;
			}
		}
	}
	
	public void identityMatrix() {
		if (m != n) {
			error("cannot make identity from non-square matrices.");
		} else {
			zeroMatrix();
			for (int i = 0; i < m; i++) {
					data[i][i] = 1;
			}
		}
	}
	
	public void identity() {
		this.identityMatrix();
	}
		
	public void set(int i, int j, double a) { 
		if ((i >=0) && (i <= (m-1)) && (j >=0) && (j<=n)) {
			data[i][j] = a;
		} else {
			error("dimension violation, attempted to set element out-of-range.");
		}
	}

	public double get(int i, int j) { 
		if ((i >=0) && (i <= (m-1)) && (j >=0) && (j<=n)) {
			return data[i][j]; 
		} else {
			error("dimension violation, attempted to read element out-of-range.");
			return 0.0;
		}
	}
	
	public void copy(Matrix src) {
		if ((src.m != this.m) || (src.n != this.n)) {
			error("cannot copy matrices of different dimensions");
		}
		
		for (int i = 0 ; i < m ; i++) {
			for (int j = 0 ; j < n ; j++) {
	 			set(i,j, src.get(i,j));
	 		}
	 	}
	}
   
   	public void add(Matrix src) {
		for (int i = 0 ; i < m ; i++) {
			for (int j = 0 ; j < n ; j++) {
	 			set(i,j, this.get(i,j) + src.get(i,j));
	 		}
	 	}
	}
	
	public void multiply(Matrix B) {
		if (this.n == B.m) {
			temp = new Matrix(this);
			n = B.n;
			data = new double[m][n];
			for (int i = 0 ; i < m ; i++) {
				for (int j = 0 ; j < n ; j++) {
					double sum = 0;
					for (int k = 0 ; k < B.m ; k++) {
						sum += temp.get(i,k) * B.get(k,j);
					}
					data[i][j] = sum;
				}
			}
		} else {
			error("Cannot multiply matrices with different inner dimensions");
		}
	}
	
	public void transpose() {
		temp = new Matrix(this);
		m = temp.n;
		n = temp.m;
		data = new double[m][n];
		
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				data[i][j] = temp.get(j,i);
			}
		}
	}
	
	public void inverse() {
		/*
		The code for this fcn was adapted from a post on gamedev.net; It is basically a written-out
		version of finding the inverse by Cramer's rule for a 4x4 matrix.  The orig. post may still 
		be avail. here:  http://www.gamedev.net/community/forums/topic.asp?topic_id=412090&whichpage=1&#2744731

					[ a b c d ]
					[ e f g h ]
			data = 	[ i j k l ]
					[ m n o p ]

		*/
		
		if ((m == 4) && (n==4)) {
			double a = data[0][0];
			double b = data[0][1];
			double c = data[0][2];
			double d = data[0][3];
			double e = data[1][0];
			double f = data[1][1];
			double g = data[1][2];
			double h = data[1][3];
			double i = data[2][0];
			double j = data[2][1];
			double k = data[2][2];
			double l = data[2][3];
			double m = data[3][0];
			double n = data[3][1];
			double o = data[3][2];
			double p = data[3][3];
		
		
			data[0][0] = g*l*n + h*j*o - f*l*o - g*j*p + f*k*p - h*k*n;
			data[0][1] = d*k*n - c*l*n - d*j*o + b*l*o + c*j*p - b*k*p;
			data[0][2] = c*h*n + d*f*o - b*h*o - c*f*p + b*g*p - d*g*n;
			data[0][3] = d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l;
			
			data[1][0] = h*k*m - g*l*m - h*i*o + e*l*o + g*i*p - e*k*p;
			data[1][1] = c*l*m + d*i*o - a*l*o - c*i*p + a*k*p - d*k*m;
			data[1][2] = d*g*m - c*h*m - d*e*o + a*h*o + c*e*p - a*g*p;
			data[1][3] = c*h*i + d*e*k - a*h*k - c*e*l + a*g*l - d*g*i; 
			
			data[2][0] = f*l*m + h*i*n - e*l*n - f*i*p + e*j*p - h*j*m;
			data[2][1] = d*j*m - b*l*m - d*i*n + a*l*n + b*i*p - a*j*p;
			data[2][2] = b*h*m + d*e*n - a*h*n - b*e*p + a*f*p - d*f*m;
			data[2][3] = d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l;
			
			data[3][0] = g*j*m - f*k*m - g*i*n + e*k*n + f*i*o - e*j*o;
			data[3][1] = b*k*m + c*i*n - a*k*n - b*i*o + a*j*o - c*j*m;
			data[3][2] = c*f*m - b*g*m - c*e*n + a*g*n + b*e*o - a*f*o;
			data[3][3] = b*g*i + c*e*j - a*g*j - b*e*k + a*f*k - c*f*i;
	
			// actually this is 1 over the determinant
			double det = 	1/(d*g*j*m - c*h*j*m - d*f*k*m + b*h*k*m + c*f*l*m - b*g*l*m -
					d*g*i*n + c*h*i*n + d*e*k*n - a*h*k*n - c*e*l*n + a*g*l*n +
					d*f*i*o - b*h*i*o - d*e*j*o + a*h*j*o + b*e*l*o - a*f*l*o -
					c*f*i*p + b*g*i*p + c*e*j*p - a*g*j*p - b*e*k*p + a*f*k*p);
			
			int x, y;
			for (x = 0; x < 4; x++) {
				for (y = 0; y < 4; y++) {
					data[x][y] *= det;
				}
			}
		} else {
			error("Can only invert 4x4 matrices (sorry!).");
		}
	}
	
	public boolean square() {
		return (this.m == this.n);
	}
	
	public void random() {
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				data[i][j] = Math.random();
			}
		}
	
	}
	
	public void print(String s) {
		System.out.println(s);

		s = "";		
		
		for (int i = 0; i < this.m; i++) {
			s = "";
			for (int j = 0; j < n; j++) {
				s += this.get(i,j) + "\t";
			}
			System.out.println(s);
		}
		System.out.println();
	}

	public void print() {
		String s = "";
		this.print(s);
	}
	
	public void error(String err) {
		System.out.println("Class Matrix: " + err);
	}
		
}

