package projectEQ2440.QRcode.decode;

/**
 * <b>class AffineTransform</b><br/>
 * Create the affine transformation based on the finders
 */
public class AffineTransform {
	
	// The affine parameters for transformation
	// [T11, T21, T12, T22, K1, K2]
	private double[] affineParams;
	
	// The finders for the research
	private Finders finders;
	
	// Length of the QR code
	private int QRlength;

	/**
	 * <b>public AffineTransform(Finders finders)</b><br/>
	 * Constructor initializing the parameters of the transformation
	 * 
	 * @param finders : the finders used for the affine transformation
	 */
	public AffineTransform(Finders finders) {
		this.finders = finders;
		affineParams = new double[6];
		QRlength = 0;
		
		lengthInitialization();
		affineInitialization();
	}
	
	/**
	 * <b>public Finders finders()</b><br/>
	 * get the finders used by the transformation
	 * 
	 * @return the finders
	 */
	public Finders finders() {
		return finders;
	}
	
	/**
	 * <b>public int QRlength()</b><br/>
	 * get the length of the QR code calculated by the research
	 *  
	 * @return the length of the QR code
	 */
	public int QRlength() {
		return QRlength;
	}
	
	/**
	 * <b>public void newLength(int length)</b><br/>
	 * set a new length for the QR code
	 * 
	 * @param length : the new length for the QR code
	 */
	public void newLength(int length) {
		if (QRlength != length) {
			QRlength = length;
			affineInitialization();
		}
	}
	
	/**
	 * <b>public int rowTransform(int row, int col)</b><br/>
	 * give the row of a transformed point
	 * 
	 * @param row : the row of the point to transform
	 * @param col : the column of the point to transform
	 * @return the row of the transformed point
	 */
	public int rowTransform(int row, int col) {
		return Math.max(0, Math.min(finders.getHeight()-1,
				(int) Math.round(affineParams[0]*(row+0.5) + affineParams[2]*(col+0.5) + affineParams[4]) ));
	}
	
	/**
	 * <b>public int colTransform(int row, int col)</b><br/>
	 * give the column of a transformed point
	 * 
	 * @param row : the row of the point to transform
	 * @param col : the column of the point to transform
	 * @return the column of the transformed point
	 */
	public int colTransform(int row, int col) {
		return Math.max(0, Math.min(finders.getWidth()-1,
				(int) Math.round(affineParams[1]*(row+0.5) + affineParams[3]*(col+0.5) + affineParams[5]) ));
	}
	
	/**
	 * <b>public double rowLinear(double x, double y)</b><br/>
	 * calculate the row of the linear transformation of a given vector 
	 * 
	 * @param x : x parameter of the vector
	 * @param y : y parameter of the vector
	 * @return the row parameter of the transformed vector
	 */
	public double rowLinear(double x, double y) {
		return affineParams[0]*x + affineParams[2]*y;
	}
	
	/**
	 * <b>public double colLinear(double x, double y)</b><br/>
	 * calculate the column of the linear transformation of a given vector 
	 * 
	 * @param x : x parameter of the vector
	 * @param y : y parameter of the vector
	 * @return the column parameter of the transformed vector
	 */
	public double colLinear(double x, double y) {
		return affineParams[1]*x + affineParams[3]*y;
	}
	
	/**
	 * <b>public boolean copyParams(double[] params)</b><br/>
	 * copy the parameters of the transformation to a given double array.
	 * Return false if the length of <b>params</b> is not 6.
	 * 
	 * @param params : the array to full (must be length 6)
	 * @return if it succeed
	 */
	public boolean copyParams(double[] params) {
		if (params == null) return false;
		if (params.length != 6) return false;
		
		for (int k=0; k<6; k++)
			params[k] = affineParams[k];
		return true;
	}
	
	/**
	 * <b>private void lengthInitialization()</b><br/>
	 * initialize calculating the length of the QR code
	 */
	private void lengthInitialization() {
		double distance, cos, size, n1, n2;
		long N1, N2;
		
		// calculation of first length
		distance = Math.sqrt( Math.pow(finders.finder(Finders.BOTTOM_LEFT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW),2) +  
				              Math.pow(finders.finder(Finders.BOTTOM_LEFT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL),2) );
		cos = Math.max( Math.abs(finders.finder(Finders.BOTTOM_LEFT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW)) , 
				        Math.abs(finders.finder(Finders.BOTTOM_LEFT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL)) )/distance;
		size = ( finders.finder(Finders.TOP_LEFT,Finders.SIZE) + finders.finder(Finders.BOTTOM_LEFT,Finders.SIZE) )*cos/2;
		n1 = 7*distance/size + 7;
		N1 = Math.round((n1-1)/4)*4+1;

		// calculation of second length
		distance = Math.sqrt( Math.pow(finders.finder(Finders.TOP_RIGHT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW),2) +  
				              Math.pow(finders.finder(Finders.TOP_RIGHT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL),2) );
		cos = Math.max( Math.abs(finders.finder(Finders.TOP_RIGHT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW)) , 
				        Math.abs(finders.finder(Finders.TOP_RIGHT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL)) )/distance;
		size = ( finders.finder(Finders.TOP_LEFT,Finders.SIZE) + finders.finder(Finders.TOP_RIGHT,Finders.SIZE) )*cos/2;
		n2 = 7*distance/size + 7;
		N2 = Math.round((n2-1)/4)*4+1;
		
		// choice of the better length
		if (N1==N2) QRlength = (int) N1;
		else if (Math.abs(N1-n1) < Math.abs(N2-n2)) QRlength = (int) N1;
		else QRlength = (int) N2;
	}
	
	/**
	 * <b>private void affineInitialization()</b><br/>
	 * Calculation of the transformation's parameters 
	 */
	private void affineInitialization() {
		// calculation of the affine parameters
		affineParams[0] = (finders.finder(Finders.BOTTOM_LEFT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW))/(QRlength-7);
		affineParams[1] = (finders.finder(Finders.BOTTOM_LEFT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL))/(QRlength-7);
		affineParams[2] = (finders.finder(Finders.TOP_RIGHT,Finders.ROW)-finders.finder(Finders.TOP_LEFT,Finders.ROW))/(QRlength-7);
		affineParams[3] = (finders.finder(Finders.TOP_RIGHT,Finders.COL)-finders.finder(Finders.TOP_LEFT,Finders.COL))/(QRlength-7);
		affineParams[4] = finders.finder(Finders.TOP_LEFT,Finders.ROW) - 3.5*(affineParams[0]+affineParams[2]);
		affineParams[5] = finders.finder(Finders.TOP_LEFT,Finders.COL) - 3.5*(affineParams[1]+affineParams[3]);
	}
}
