package math.matrix;

import math.Maths;
import math.equation.Polynomial;
import exception.ComplexException;
import exception.IllegalDimensionException;
import exception.NoSquareException;

/**
 * 3D matrix object m<sub>ij</sub>&isin;&#x211D;
 * with 3 rows and n columns
 * @author Daniel Weschke
 */
public class Matrix3D extends Matrix{
	/**
	 * UID
	 */
	private static final long serialVersionUID = 2505785127150028971L;

	/**
	 * Create an null matrix.
	 */
	public Matrix3D(){
		super();
	}

	/**
	 * Create an 3-by-n matrix with zeros.
	 * @param n number of columns 
	 */
	public Matrix3D(int n){
		super(3,n);
	}

	/**
	 * Create an 3-by-3 matrix based on components.
	 * @param a11
	 * @param a12
	 * @param a13
	 * @param a21
	 * @param a22
	 * @param a23
	 * @param a31
	 * @param a32
	 * @param a33
	 */
	public Matrix3D(double a11, double a12, double a13,
			double a21, double a22, double a23,
			double a31, double a32, double a33){
		super(3,3);
		set(0,0,a11);
		set(0,1,a12);
		set(0,2,a13);
		set(1,0,a21);
		set(1,1,a22);
		set(1,2,a23);
		set(2,0,a31);
		set(2,1,a32);
		set(2,2,a33);
	}

	/**
	 * Create an 3-by-3 matrix based on a 2d array.
	 * @param a 3d double array
	 */
	public Matrix3D(double[][] a){
		super(a);
	}
	
	public Matrix3D(Matrix B){
		super(B);
	}

	/**
	 * 3d rotation around unit vector u by ϑ degrees counterclockwise or
	 * clockwise with negative degrees for a right-hand system.
	 * @param ϑ in degrees
	 * @param u unit vector
	 * @return 3d rotated matrix
	 * @throws IllegalDimensionException
	 */
	public Matrix3D rotate(double ϑ, double[] u) throws IllegalDimensionException{
		if(!isR3() && u.length == 3) throw new IllegalDimensionException("Illegal matrix dimensions.");
		// TODO: verify
		double u1 = u[0], u2 = u[1], u3 = u[2];
		return (Matrix3D) new Matrix3D(new double[][]{
				{u1*u1*(1-Maths.cosd(ϑ))+Maths.cosd(ϑ),
					u1*u2*(1-Maths.cosd(ϑ))-u3*Maths.sind(ϑ),
						u1*u3*(1-Maths.cosd(ϑ))+u2*Maths.sind(ϑ)},
				{u2*u1*(1-Maths.cosd(ϑ))+u3*Maths.sind(ϑ),
					u2*u2*(1-Maths.cosd(ϑ))+Maths.cosd(ϑ),
						u2*u3*(1-Maths.cosd(ϑ))-u1*Maths.sind(ϑ)},
				{u3*u1*(1-Maths.cosd(ϑ))-u2*Maths.sind(ϑ),
					u3*u2*(1-Maths.cosd(ϑ))+u1*Maths.sind(ϑ),
						u3*u3*(1-Maths.cosd(ϑ))+Maths.cosd(ϑ)}}).times(this);
	}

	/**
	 * Rotation about the x axis by φ degrees counterclockwise for a right-hand system.
	 * @param φ in degrees
	 * @return 3d rotated matrix
	 * @throws IllegalDimensionException
	 */
	public Matrix3D rotateX(double φ) throws IllegalDimensionException{
		if(!isR3()) throw new IllegalDimensionException("Illegal matrix dimensions.");
		return (Matrix3D) new Matrix3D(new double[][]{
				{1,0,0},
				{0,Maths.cosd(φ),Maths.sind(φ)},
				{0,-Maths.sind(φ),Maths.cosd(φ)}}).times(this);
	}

	/**
	 * Rotation about the y axis by ϑ degrees counterclockwise for a right-hand system.
	 * @param ϑ in degrees
	 * @return 3d rotated matrix
	 * @throws IllegalDimensionException
	 */
	public Matrix3D rotateY(double ϑ) throws IllegalDimensionException{
		if(!isR3()) throw new IllegalDimensionException("Illegal matrix dimensions.");
		return (Matrix3D) new Matrix3D(new double[][]{
				{Maths.cosd(ϑ),0,-Maths.sind(ϑ)},
				{0,1,0},
				{Maths.sind(ϑ),0,Maths.cosd(ϑ)}}).times(this);
	}

	/**
	 * Rotation about the z axis by ψ degrees counterclockwise for a right-hand system.
	 * @param ψ in degrees
	 * @return 3d rotated matrix
	 * @throws IllegalDimensionException
	 */
	public Matrix3D rotateZ(double ψ) throws IllegalDimensionException{
		if(!isR3()) throw new IllegalDimensionException("Illegal matrix dimensions.");
		return (Matrix3D) new Matrix3D(new double[][]{
				{Maths.cosd(ψ),Maths.sind(ψ),0},
				{-Maths.sind(ψ),Maths.cosd(ψ),0},
				{0,0,1}}).times(this);
	}

	/**
	 * 3d rotation by φ around x axis, ϑ around y axis and ψ degrees around z axis counterclockwise or
	 * clockwise with negative degrees for a right-hand system.
	 * @param φ in degrees
	 * @param ϑ in degrees
	 * @param ψ in degrees
	 * @return 3d rotated matrix
	 * @throws IllegalDimensionException
	 */
	public Matrix3D rotateXYZ(double φ, double ϑ, double ψ) throws IllegalDimensionException{
		if(!isR3()) throw new IllegalDimensionException("Illegal matrix dimensions.");
		return (Matrix3D) rotateX(φ).times(rotateY(ϑ)).times(rotateZ(ψ)).times(this);
	}

	/**
	 * Projection onto the x-y plane
	 * @return matrix projected onto the x-y plane
	 * @throws IllegalDimensionException
	 */
	public Matrix3D projectionToXY() throws IllegalDimensionException{
		if(!isR3()) throw new IllegalDimensionException("Illegal matrix dimensions.");
		return (Matrix3D) new Matrix(new double[][]{{1,0,0},{0,1,0},{0,0,0}}).times(this);
	}
	
    /**
     * Real eigenvalue calculated with the root formula of second and third degree.
     * @return eigenvalues λ<sub>i</sub>
     * @throws NoSquareException 
     * @throws IllegalDimensionException 
     * @throws ComplexException 
     */
	public Vector3D eigenvaluesRe(int analytic) throws NoSquareException, IllegalDimensionException, ComplexException{
		if(m!=n && m!=3) throw new IllegalDimensionException("Illegal matrix dimensions.");
		Vector3D lambda = new Vector3D(m);
		// matrix diagonal?
		double a12 = get(0, 1);
		double a13 = get(0, 2);
		double a23 = get(1, 2);
		double d = a12*a12 + a13*a13 + a23*a23;
		if (d == 0){ // A is diagonal.
			lambda = new Vector3D(get(0,0),get(1,1),get(2,2));
			lambda.sort(); // don't have to but ...
		} else {
			Polynomial p = new Polynomial(1,
					   -trace(),
					   -((this.times(this)).trace()-trace()*trace())/2,
					   -det(analytic));
			lambda = new Vector3D(p.rootsRe());
		}
		return lambda;
	}

	public static void main(String[] args) {
		Matrix3D a = new Matrix3D(0,0,1,0,1,0,1,0,0);
		System.out.println(a);
	}

}
