package projectEQ2440.QRcode.decode;

import projectEQ2440.utils.CircleMemory;

/**
 * <b>class Finders</b><br/>
 * Class which automatically search the 3 finders in the given binarized picture
 */
public class Finders {
	
	// Constant for the read of the finders
	public final static int TOP_LEFT = 0;
	public final static int BOTTOM_LEFT = 1;
	public final static int TOP_RIGHT = 2;
	
	public final static int ROW = 0;
	public final static int COL = 1;
	public final static int SIZE = 2;
	public final static int NBR_VALID_SHAPE = 3;
	
	// The binarized picture
	private boolean[][] picture;
	// height
	private int height;
	// width
	private int width;
	
	// limitations
	private int istart, jstart, iend, jend;
	
	// Values already treated
	private boolean[][] done;
	
	// the finders : lentgh->[3][3] (row,column,size,number of point)
	private double[][] finders;
	
	// The memories for saving length of the measured black&white
	private CircleMemory memo;
	
	// boolean value which tell if the finders are found
	private boolean validFinders;
	
	/**
	 * <b>public Finders(boolean[][] picture)</b><br/>
	 * Initialized the research of the finders without any limitations
	 * 
	 * @param picture : the binarized picture
	 */
	public Finders(boolean[][] picture) {
		this.picture = picture;
		memo = new CircleMemory(5);
		finders = new double[3][4];
		validFinders = false;
		
		height = picture.length;
		width = picture[0].length;
		
		istart = 0;
		jstart = 0;
		iend = height;
		jend = width;
		
		research();
		sorting();
	}
	
	/**
	 * <b>public Finders(boolean[][] picture, int rowStart, int colStart, int rowEnd, int colEnd)</b><br/>
	 * Initialized the research of the finders with limitations
	 * 
	 * @param picture : the binarized picture
	 * @param rowStart : row to begin
	 * @param colStart :column to begin
	 * @param rowEnd : row to end
	 * @param colEnd : column to end
	 */
	public Finders(boolean[][] picture, int rowStart, int colStart, int rowEnd, int colEnd) {
		this.picture = picture;
		memo = new CircleMemory(5);
		finders = new double[3][4];
		validFinders = false;
		
		height = picture.length;
		width = picture[0].length;
		
		if (rowStart>=0 && rowStart<height)	istart = rowStart;
		else istart = 0;
		if (colStart>=0 && colStart<width) jstart = colStart;
		else jstart = 0;
		if (rowEnd>istart && rowEnd<=height) iend = rowEnd;
		else iend = height;
		if (colEnd>jstart && colEnd<=width) jend = colEnd;
		else jend = width;
		
		research();
		sorting();
	}
	
	/**
	 * <b>public int getWidth()</b><br/>
	 * Return the width of the picture
	 * 
	 * @return the width
	 */
	public int getWidth() {
		return width;
	}
	
	/**
	 * <b>getHeight()</b><br/>
	 * Return the height of the picture
	 * 
	 * @return the height
	 */
	public int getHeight() {
		return height;
	}
	
	/**
	 * <b>public boolean findersFound()</b><br/>
	 * tell if the finders are found or not
	 * 
	 * @return If the finders are found or not
	 */
	public boolean findersFound() {
		return validFinders;
	}
	
	/**
	 * <b>public double finder(int num, int param)</b><br/>
	 * Return information about the finder <br/>
	 * <b>number of finder : </b><br/>
	 * 0 : top left<br/>
	 * 1 : bottom left<br/>
	 * 2 : top right<br/>
	 * <b>parameter: </b><br/>
	 * 0 : row of the center<br/>
	 * 1 : column of the center<br/>
	 * 2 : average size<br/>
	 * 3 : number of point<br/>
	 * Return -1 if not good
	 *
	 * @param num : number of the finder
	 * @param param : parameter
	 * @return the value of the finder
	 */
	public double finder(int num, int param) {
		if (validFinders && num>=0 && num<3 && param<4 && param>=0) return finders[num][param];
		return -1;
	}
	
	/**
	 * <b>private void research()</b><br/>
	 * Load the research
	 * 
	 */
	private void research() {
		done = new boolean[height][width];
		
		int i, j, k, l, n;
		double alpha1, alpha2, beta1, beta2, gamma1, gamma2, u1, u2, v1, v2;
		double a1, a2, b1, b2, c1, c2;
		double xmin, xmax, ymin, ymax, x, y, det;
		double size;
		double[] detection = new double[3];
		double[] validation = new double[3];
		double[] tmp;
		
		for (i=istart; i<iend; i+=2) {
			j = jstart;
			while (rowDetection(i,j,detection)) {
				j = (int) Math.round(detection[1]);
				if (validCol(j,i,validation)) {
					
					alpha1 = detection[0]*detection[0];
					beta1  = detection[1]*detection[1];
					gamma1 = detection[0]*detection[1];
					
					u1 = detection[0];
					v1 = detection[1];
					
					alpha2 = validation[0]*validation[0];
					beta2  = validation[1]*validation[1];
					gamma2 = validation[0]*validation[1];
					
					u2 = validation[0];
					v2 = validation[1];
					
					// Detection of the shape in the central finder
					k = (int) Math.round(validation[0]);
					size = detection[2] + validation[2];
					n = 2;
					for (l=i-1; picture[l][j] && l>0; l--) {
						if (validRow(l,j,validation)) {
							alpha1 += validation[0]*validation[0];
							beta1  += validation[1]*validation[1];
							gamma1 += validation[0]*validation[1];
							u1 += validation[0];
							v1 += validation[1];
							size += validation[2];
							n++;
						}
					}
					xmin = l;
					for (l=i+1; picture[l][j] && l<height-1; l++) {
						if (validRow(l,j,validation)) {
							alpha1 += validation[0]*validation[0];
							beta1  += validation[1]*validation[1];
							gamma1 += validation[0]*validation[1];
							u1 += validation[0];
							v1 += validation[1];
							size += validation[2];
							n++;
						}
					}
					xmax = l;
					for (l=j-1; picture[k][l] && l>0; l--) {
						if (validCol(l,k,validation)) {
							alpha2 += validation[0]*validation[0];
							beta2  += validation[1]*validation[1];
							gamma2 += validation[0]*validation[1];
							u2 += validation[0];
							v2 += validation[1];
							size += validation[2];
							n++;
						}
					}
					ymin = l;
					for (l=j+1; picture[k][l] && l<width-1; l++) {
						if (validCol(l,k,validation)) {
							alpha2 += validation[0]*validation[0];
							beta2  += validation[1]*validation[1];
							gamma2 += validation[0]*validation[1];
							u2 += validation[0];
							v2 += validation[1];
							size += validation[2];
							n++;
						}
					}
					ymax = l;
					
					// calculation of the lines parameters
					a1 = beta1*u1-gamma1*v1;
					a2 = beta2*u2-gamma2*v2;
					b1 = alpha1*v1-gamma1*u1;
					b2 = alpha2*v2-gamma2*u2;
					c1 = alpha1*beta1-gamma1*gamma1;
					c2 = alpha2*beta2-gamma2*gamma2;
					det = a1*b2-a2*b1;
					
					// calculation and validation of the cross
					if (det != 0) {
						x = (b2*c1-b1*c2)/det;
						y = (a1*c2-a2*c1)/det;
						if ((x>xmin)&&(x<xmax)&&(y>ymin)&&(y<ymax)) {
							if (n>finders[2][3]) {
								finders[2][0] = x;
								finders[2][1] = y;
								finders[2][2] = size/n;
								finders[2][3] = n;
							}
							if (n>finders[0][3]) {
								tmp = finders[0];
								finders[0] = finders[2];
								finders[2] = finders[1];
								finders[1] = tmp;
							} else if (n>finders[1][3]) {
								tmp = finders[1];
								finders[1] = finders[2];
								finders[2] = tmp;
							}
						}
					}
					
				}
				j = j+1;
			}
		}
	}
	
	/**
	 * <b>private boolean rowDetection(int row, int colBegin, int[] result)</b><br/>
	 * Find the first good shape in a row and return if it's done 
	 * 
	 * @param row : the row to search
	 * @param colBegin : where to begin
	 * @return if good shape is found
	 */
	private boolean rowDetection(int row, int colBegin, double[] result) {
		int k, n=0;
		boolean tmp = picture[row][colBegin];
		int sum, length = 1;
		memo.raz();
		for (k=colBegin+1; k<jend-1; k++) {
			if (tmp == picture[row][k]) {
				length++;
			} else if (done[row][k]) {
				length = 0;
				tmp = picture[row][k+1];
				n = 0;
			} else if (n<5) {
				memo.writeRight(length);
				n++;
				length = 1;
				tmp = picture[row][k];
			} else {
				memo.writeRight(length);
				if (tmp) {
					sum = memo.sum();
					if (!done[row][(int) Math.round(k-sum/2.0)]) if (validShape()) {
						result[0] = row;
						result[1] = k - sum/2.0;
						result[2] = sum;
						return true;
					}
				}
				length = 1;
				tmp = picture[row][k];
			}
		}
		
		return false;
	}
	
	/**
	 * <b>private boolean validRow(int row, int colCenter, int[] result)</b><br/>
	 * Check if there is the good shape on the row at the given point
	 * 
	 * @param row : the row where make the test
	 * @param colCenter : the column of the center point
	 * @param result : an int[] to save the result
	 * @return if there is the good shape
	 */
	private boolean validRow(int row, int colCenter, double[] result) {
		int[] shape = new int[5];
		
		int k = colCenter;
		int length = 0;
		while (picture[row][k]) {
			done[row][k] = true;
			length++;
			k--;
			if (k<jstart) return false;
		}
		shape[2] = length;
		
		length = 0;
		while (!picture[row][k]) {
			done[row][k] = true;
			length++;
			k--;
			if (k<jstart) return false;
		}
		shape[1] = length;
		
		length = 0;
		while (picture[row][k]) {
			length++;
			k--;
			if (k<jstart) return false;
		}
		shape[0] = length;
		int side = k;
		
		length = 0;
		k = colCenter + 1;
		while (picture[row][k]) {
			done[row][k] = true;
			length++;
			k++;
			if (k>=jend) return false;
		}
		shape[2] += length;
		
		length = 0;
		while (!picture[row][k]) {
			done[row][k] = true;
			length++;
			k++;
			if (k>=jend) return false;
		}
		shape[3] = length;
		
		length = 0;
		while (picture[row][k]) {
			length++;
			k++;
			if (k>=jend) return false;
		}
		shape[4] = length;
		
		if (validShape(shape)) {
			result[0] = row;
			result[1] = (k+side)/2.0;
			result[2] = k-side-1;
			return true;
		}
		return false;
	}

	/**
	 * <b>private boolean validCol(int col, int rowCenter, int[] result)</b><br/>
	 * Check if there is the good shape on the col at the given point
	 * 
	 * @param col : the col where make the test
	 * @param rowCenter : the row of the center point
	 * @param result : an int[] to save the result
	 * @return if there is the good shape
	 */
	private boolean validCol(int col, int rowCenter, double[] result) {
		int[] shape = new int[5];
		
		int k = rowCenter;
		int length = 0;
		while (picture[k][col]) {
			done[k][col] = true;
			length++;
			k--;
			if (k<istart) return false;
		}
		shape[2] = length;
		
		length = 0;
		while (!picture[k][col]) {
			done[k][col] = true;
			length++;
			k--;
			if (k<istart) return false;
		}
		shape[1] = length;
		
		length = 0;
		while (picture[k][col]) {
			length++;
			k--;
			if (k<istart) return false;
		}
		shape[0] = length;
		int side = k;
		
		length = 0;
		k = rowCenter + 1;
		while (picture[k][col]) {
			done[k][col] = true;
			length++;
			k++;
			if (k>=iend) return false;
		}
		shape[2] += length;
		
		length = 0;
		while (!picture[k][col]) {
			done[k][col] = true;
			length++;
			k++;
			if (k>=iend) return false;
		}
		shape[3] = length;
		
		length = 0;
		while (picture[k][col]) {
			length++;
			k++;
			if (k>=iend) return false;
		}
		shape[4] = length;
		
		if (validShape(shape)) {
			result[0] = (k+side)/2.0;
			result[1] = col;
			result[2] = k-side-1;
			return true;
		}
		return false;
	}
	
	/**
	 * <b>private boolean validShape()</b><br/>
	 * Tell if the memory match the good shape : [1,1,3,1,1]
	 * 
	 * @return if it matches
	 */
	private boolean validShape() {
		double tmp, central = memo.read(2)/3.0;
		int k;
		for (k=0; k<5; k++) {
			tmp = Math.abs(central/memo.read(k)-1);
			if (k!=2 && tmp>=0.55) return false;
		}
		return true;
	}

	/**
	 * <b>private boolean validShape(int[] shape)</b><br/>
	 * Tell if the shape match the good shape : [1,1,3,1,1]
	 * 
	 * @param shape : the shape to test, must be length 5
	 * @return if it matches
	 */
	private boolean validShape(int[] shape) {
		double tmp, central = shape[2]/3.0;
		int k;
		for (k=0; k<5; k++) {
			tmp = Math.abs(central/shape[k]-1);
			if (k!=2 && tmp>=0.55) return false;
		}
		return true;
	}
	
	/**
	 * <b>private void sorting()</b><br/>
	 * Sort the finders to have them in the good order and valid the finders
	 * 
	 */
	private void sorting() {
		if (finders[2][3]==0) return;
		
		double[][] vector = 
			{	{ finders[2][0] - finders[1][0] , finders[2][1] - finders[1][1] },
				{ finders[0][0] - finders[2][0] , finders[0][1] - finders[2][1] },
				{ finders[1][0] - finders[0][0] , finders[1][1] - finders[0][1] } };
		double[] switcher;
		double tmp1, tmp2;
		int angle = 0;
		
		tmp1 = Math.abs( vector[1][0]*vector[2][0] + vector[1][1]*vector[2][1] );
		tmp2 = Math.abs( vector[2][0]*vector[0][0] + vector[2][1]*vector[0][1] );
		if ( tmp2 < tmp1 ) {
			tmp1 = tmp2;
			angle = 1;
		}
		tmp2 = Math.abs( vector[0][0]*vector[1][0] + vector[0][1]*vector[1][1] );
		if ( tmp2 < tmp1 ) {
			tmp1 = tmp2;
			angle = 2;
		}
		
		if (angle > 0) {
			switcher = finders[0];
			finders[0] = finders[angle];
			finders[angle] = switcher;
		}
		
		tmp1 = (finders[1][0]-finders[0][0])*(finders[2][1]-finders[0][1]) - (finders[1][1]-finders[0][1])*(finders[2][0]-finders[0][0]);  
		if (tmp1 == 0) return;
		if (tmp1 < 0) {
			switcher = finders[1];
			finders[1] = finders[2];
			finders[2] = switcher;
		}
		validFinders = true;
		
	}
}
