/*******************************************************************************
* matrix_t invert_matrix(matrix_t A)
*
* Invert Matrix function based on LUP decomposition and then forward and
* backward substitution.
*******************************************************************************/
matrix_t invert_matrix(matrix_t A){
	int i,j,k,m;
	matrix_t L,U,P,D,temp;
	matrix_t out = create_empty_matrix();
	if(!A.initialized){
		printf("ERROR: matrix not initialized yet\n");
		return out;
	}
	if(A.cols != A.rows){
		printf("ERROR: matrix is not square\n");
		return out;
	}
	if(matrix_determinant(A) == 0){
		printf("ERROR: matrix is singular, not invertible\n");
		return out;
	}
	m = A.cols;
	LUP_decomposition(A,&L,&U,&P);
	D    = create_identity_matrix(m);
	temp = create_square_matrix(m);

	for(j=0;j<m;j++){
		for(i=0;i<m;i++){
			for(k=0;k<i;k++){
				D.data[i][j] -= L.data[i][k] * D.data[k][j];
			}
		}
		for(i=m-1;i>=0;i--){				// backwards.. last to first
			temp.data[i][j] = D.data[i][j];
			for(k=i+1;k<m;k++){	
				temp.data[i][j] -= U.data[i][k] * temp.data[k][j];
			}
			temp.data[i][j] = temp.data[i][j] / U.data[i][i];
		}
	}
	// multiply by permutation matrix
	out = multiply_matrices(temp, P);		
	// free allocation	
	destroy_matrix(&temp);	
	destroy_matrix(&L);		
	destroy_matrix(&U);
	destroy_matrix(&P);
	destroy_matrix(&D);
	return out;
}
int LUP_decomposition__alloc(int length, double **A, double ***LU, int **p) {
	*p = (int*)malloc(sizeof(int) * length);
	new_matrix(LU, length, length);
	return LUP_decomposition(length, A, *LU, *p);
}
int main(){
	printf("Let's test some linear algebra functions....\n\n");
	
	// create a random nxn matrix for later use
	matrix_t A = create_random_matrix(DIM,DIM);
	printf("New Random Matrix A:\n");
	print_matrix(A);
	
	// also create random vector
	vector_t b = create_random_vector(DIM);
	printf("\nNew Random Vector b:\n");
	print_vector(b);
	
	// do an LUP decomposition on A
	matrix_t L,U,P;
	LUP_decomposition(A,&L,&U,&P);
	printf("\nL:\n");
	print_matrix(L);
	printf("U:\n");
	print_matrix(U);
	printf("P:\n");
	print_matrix(P);
	
	// do a QR decomposition on A
	matrix_t Q,R;
	QR_decomposition(A,&Q,&R);
	printf("\nQR Decomposition of A\n");
	printf("Q:\n");
	print_matrix(Q);
	printf("R:\n");
	print_matrix(R);
	
	// get determinant of A
	float det = matrix_determinant(A);
	printf("\nDeterminant of A : %8.4f\n", det);
	
	// get an inverse for A
	matrix_t Ainv = invert_matrix(A);
	if(A.initialized != 1) return -1;
	printf("\nAinverse\n");
	print_matrix(Ainv);
	
	// multiply A times A inverse
	matrix_t AA = multiply_matrices(A,Ainv);
	if(AA.initialized!=1) return -1;
	printf("\nA * Ainverse:\n");
	print_matrix(AA);
	
	// solve a square linear system
	vector_t x = lin_system_solve(A, b);
	printf("\nGaussian Elimination solution x to the equation Ax=b:\n");
	print_vector(x);
	
	// now do again but with qr decomposition method
	vector_t xqr = lin_system_solve_qr(A, b);
	printf("\nQR solution x to the equation Ax=b:\n");
	print_vector(xqr);
	
	// If b are the coefficients of a polynomial, get the coefficients of the
	// new polynomial b^2
	vector_t bb = poly_power(b,2);
	printf("\nCoefficients of polynomial b times itself\n");
	print_vector(bb);
	
	// clean up all the allocated memory. This isn't strictly necessary since
	// we are already at the end of the program, but good practice to do.
	destroy_matrix(&A);
	destroy_matrix(&AA);
	destroy_vector(&b);
	destroy_vector(&bb);
	destroy_vector(&x);
	destroy_vector(&xqr);
	destroy_matrix(&Q);
	destroy_matrix(&R);

	printf("DONE\n");
	return 0;
}