package geomExtension;

import java.awt.geom.*;
import java.text.*;
    
public class Matrix2D {
    double[][] matrix={{0.0,0.0,0.0},{0.0,0.0,0.0},{0.0,0.0,1.0}};
    static double eps=1.0e-12;
    public Matrix2D(){
        this.matrix[0][0]=1.0;
        this.matrix[1][0]=0.0;
        this.matrix[0][1]=0.0;
        this.matrix[1][1]=1.0;
        this.matrix[0][2]=0.0;
        this.matrix[1][2]=0.0;
    }
    
    public Matrix2D(double[] elm){
        this.matrix[0][0]=elm[0];
        this.matrix[1][0]=elm[1];
        this.matrix[0][1]=elm[2];
        this.matrix[1][1]=elm[3];
        this.matrix[0][2]=elm[4];
        this.matrix[1][2]=elm[5];
    }
	
	public boolean isIdentity() {
		boolean identity=Math.abs(this.matrix[0][0]-1.0)<Matrix2D.eps&&Math.abs(this.matrix[1][0])<Matrix2D.eps
				&&Math.abs(this.matrix[0][1])<Matrix2D.eps&&Math.abs(this.matrix[1][1]-1.0)<Matrix2D.eps
				&&Math.abs(this.matrix[0][2])<Matrix2D.eps&&Math.abs(this.matrix[1][2])<Matrix2D.eps;
		return identity;
	}
	
    public double[][] getElm(){
        return this.matrix;
    }
    
    public double getElm(int i, int j){
        return this.matrix[i][j];
    }
    
    public void setElm(int i, int j, double elm){
        this.matrix[i][j]=elm;
    }
    
    public void addElm(int i, int j, double add){
        this.matrix[i][j]+=add;
    }

    public AffineTransform getAffineTransform(){
        AffineTransform affine
        =new AffineTransform(this.matrix[0][0], this.matrix[1][0],
                             this.matrix[0][1], this.matrix[1][1],
                             this.matrix[0][2], this.matrix[1][2]);
        return affine;
    }
    
    public void setAffineTransform(AffineTransform affine){
        double[] elm=new double[6];
        affine.getMatrix(elm);
        this.matrix[0][0]=elm[0];
        this.matrix[1][0]=elm[1];
        this.matrix[0][1]=elm[2];
        this.matrix[1][1]=elm[3];
        this.matrix[0][2]=elm[4];
        this.matrix[1][2]=elm[5];
    }
	
    public void setAffineTransform(Vector2D Ex, Vector2D Ey, Vector2D P){
		Ex=Vector2D.unitVector(Ex);
		Ey=Vector2D.unitVector(Ey);
        this.matrix[0][0]=Ex.getX();
        this.matrix[1][0]=Ex.getY();
        this.matrix[0][1]=Ey.getX();
        this.matrix[1][1]=Ey.getY();
        this.matrix[0][2]=P.getX();
        this.matrix[1][2]=P.getY();
    }
	
	public Vector2D getEx(){
		Vector2D Ex = new Vector2D(this.matrix[0][0], this.matrix[1][0]);
		return Ex;
	}
	
    public Vector2D getEy() {
		Vector2D Ey = new Vector2D(this.matrix[0][1], this.matrix[1][1]);
		return Ey;
	}
	
    public Vector2D getP() {
		Vector2D P = new Vector2D(this.matrix[0][2], this.matrix[1][2]);
		return P;
	}
	
    public String toString(){
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumIntegerDigits(4);
        nf.setMinimumFractionDigits(4);
        String str="";
        for(int i=0;i<2;i++){
            for(int j=0;j<3;j++){
                str+=" m["+i+","+j+"]="+nf.format(this.getElm(i, j))+",";
            }
        }
        return str;
    }
	
	public static boolean isIdentity(AffineTransform affine) {
        double[] elm=new double[6];
		affine.getMatrix(elm);
		boolean identity=Math.abs(elm[0]-1.0)<Matrix2D.eps&&Math.abs(elm[1])<Matrix2D.eps
				&&Math.abs(elm[2])<Matrix2D.eps&&Math.abs(elm[3]-1.0)<Matrix2D.eps
				&&Math.abs(elm[4])<Matrix2D.eps&&Math.abs(elm[5])<Matrix2D.eps;
		return identity;
	}
	
    public static Vector2D getEx(AffineTransform affine) {
        double[] elm=new double[6];
        affine.getMatrix(elm);
		Vector2D Ex = new Vector2D(elm[0], elm[1]);
		return Ex;
	}

	public static Vector2D getEy(AffineTransform affine) {
        double[] elm=new double[6];
        affine.getMatrix(elm);
		Vector2D Ey = new Vector2D(elm[2], elm[3]);
		return Ey;
	}

	public static Vector2D getP(AffineTransform affine) {
        double[] elm=new double[6];
        affine.getMatrix(elm);
		Vector2D P = new Vector2D(elm[4], elm[5]);
		return P;
	}
	
    public AffineTransform getAffineTransform(Vector2D Ex, Vector2D Ey, Vector2D P){
		Ex=Vector2D.unitVector(Ex);
		Ey=Vector2D.unitVector(Ey);
		AffineTransform affine=new AffineTransform(Ex.getX(), Ex.getY(), Ey.getX(), 
				Ey.getY(), P.getX(), P.getY());
		return affine;
    }
	
    public static Vector2D transForm(Matrix2D M, Vector2D vec){
        double X=M.getElm(0,0)*vec.getX()+M.getElm(0,1)*vec.getY()+M.getElm(0,2);
        double Y=M.getElm(1,0)*vec.getX()+M.getElm(1,1)*vec.getY()+M.getElm(1,2);
        return new Vector2D(X,Y);
    }
    
    public static Point2D transForm(Matrix2D M, Point2D p){
        double X=M.getElm(0,0)*p.getX()+M.getElm(0,1)*p.getY()+M.getElm(0,2);
        double Y=M.getElm(1,0)*p.getX()+M.getElm(1,1)*p.getY()+M.getElm(1,2);
        return new Point2D.Double(X,Y);
    }
    
    public static Matrix2D scalarMult(double mult, Matrix2D M){
        Matrix2D M0=new Matrix2D();
        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                double elm=M.getElm(i, j);
                M0.setElm(i, j, mult*elm);
            }
        }
        return M0;
    }
    
    public static Matrix2D multMatrix(Matrix2D M1, Matrix2D M2){
        Matrix2D M0=new Matrix2D();
        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                double elm=0.0;
                for(int k=0;k<3;k++) {
                    elm+=M1.getElm(i,k)*M2.getElm(k,j);
                    M0.setElm(i,j, elm);
                }
            }
        }
        return M0;
    }   
    
    public static Matrix2D InverseMatrix(Matrix2D M){
        double[][] M0=M.getElm();
        double[][] Minv=Matrix.InverseMatrix(M0);
        double[] elm=new double[6];
        for(int i=0;i<3;i++){
            elm[2*i]=Minv[0][i];
            elm[2*i+1]=Minv[1][i];
        }
        return new Matrix2D(elm);
    }
    
    public static Matrix2D getScaleMatrix(double scale){
        Matrix2D M=new Matrix2D();
        M.setElm(0,0,scale);
        M.setElm(0,1,0.0);
        M.setElm(0,2,0.0);
        M.setElm(1,0,0.0);
        M.setElm(1,1,scale);
        M.setElm(1,2,0.0);
        M.setElm(2,0,0.0);
        M.setElm(2,1,0.0);
        M.setElm(2,2,1.0);
        return M;
    }
    
    public static Matrix2D getResizeMatrix(Rectangle2D oldBox, Rectangle2D newBox){
        double x=oldBox.getX();
        double y=oldBox.getY();
        double X=newBox.getX();
        double Y=newBox.getY();
        double scaleX=newBox.getWidth()/oldBox.getWidth();
        double scaleY=newBox.getHeight()/oldBox.getHeight();
        Matrix2D M=new Matrix2D();
        M.setElm(0,0, scaleX);
        M.setElm(0,1, 0.0);
        M.setElm(0,2, X-x*scaleX);
        M.setElm(1,0, 0.0);
        M.setElm(1,1, scaleY);
        M.setElm(1,2, Y-y*scaleY);
        M.setElm(2,0, 0.0);
        M.setElm(2,1, 0.0);
        M.setElm(2,2, 1.0);
        return M;
    }

    public static Matrix2D getRotationMatrix(Point2D anchorP, Vector2D vec1, Vector2D vec2){
        double len1=Vector2D.length(vec1);
        double len2=Vector2D.length(vec2);
        Matrix2D M=new Matrix2D();
        if(len1<1e-10){
            System.err.println("*** Error in Matrix2D.getRotationMatrix: vec1=0");
            return null;
        } 
        double scale=len2/len1;
        Vector2D unitVec1=Vector2D.unitVector(vec1);
        Vector2D unitVec2=Vector2D.unitVector(vec2);
        double cos=Vector2D.sproduct(unitVec1, unitVec2);
        double sin=Vector2D.vproduct(unitVec1, unitVec2);
        double scaledCos=scale*cos;
        double scaledSin=scale*sin;
        M.setElm(0,0,scaledCos);
        M.setElm(1,0,scaledSin);
        M.setElm(2,0,0.0);
        M.setElm(0,1,-scaledSin);
        M.setElm(1,1,scaledCos);
        M.setElm(2,1,0.0);
        double m02=(1.0-scaledCos)*anchorP.getX()+scaledSin*anchorP.getY();
        double m12=-scaledSin*anchorP.getX()+(1.0-scaledCos)*anchorP.getY();
        M.setElm(0,2,m02);
        M.setElm(1,2,m12);
        M.setElm(2,2,1.0);
        return M;
    }
    
    public static Matrix2D getFlipMatrix(int direction, double value){
        Matrix2D M=new Matrix2D();
        if(direction==0){
            M.setElm(0,0,-1.0);
            M.setElm(1,0, 0.0);
            M.setElm(2,0, 0.0);
            M.setElm(0,1, 0.0);
            M.setElm(1,1, 1.0);
            M.setElm(2,1, 0.0);
            M.setElm(0,2, 2.0*value);
            M.setElm(1,2, 0.0);
            M.setElm(2,2, 1.0);
        } else{
            M.setElm(0,0, 1.0);
            M.setElm(1,0, 0.0);
            M.setElm(2,0, 0.0);
            M.setElm(0,1, 0.0);
            M.setElm(1,1,-1.0);
            M.setElm(2,1, 0.0);
            M.setElm(0,2, 0.0);
            M.setElm(1,2, 2.0*value);
            M.setElm(2,2, 1.0);
        }
        //print(M);
        return M;
    }
    
    public static AffineTransform multMatrix(AffineTransform M1, AffineTransform M2){
        double[] elm=new double[6];
        double[] elm1=new double[6];
        double[] elm2=new double[6];
       
        M1.getMatrix(elm1);
        M2.getMatrix(elm2);
        Matrix2D M01=new Matrix2D(elm1);
        Matrix2D M02=new Matrix2D(elm2);
        Matrix2D M=Matrix2D.multMatrix(M01, M02);
        elm[0]=M.getElm(0, 0);
        elm[1]=M.getElm(1, 0);
        elm[2]=M.getElm(0, 1);
        elm[3]=M.getElm(1, 1);
        elm[4]=M.getElm(0, 2);
        elm[5]=M.getElm(1, 2);
        return new AffineTransform(elm);
    } //End of multiplyMatrix 
    
    public static void print(Matrix2D M){
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumIntegerDigits(4);
        nf.setMinimumFractionDigits(4);
        for(int i=0;i<3;i++){
            String str="";
            for(int j=0;j<3;j++){
                str+=" Mat["+i+","+j+"]="+nf.format(M.getElm(i, j))+",";
            }
            System.out.println(str);
        }
    }

}
