package projectEQ2440.QRcode.decode;

import projectEQ2440.QRcode.info.Version;

/**
 * <b>public class Patterns</b><br/>
 * Class to manage the research of alignment pattern in a binarized picture
 */
public class Patterns {
	
	// Some constants for research of pattern
	public static final double RESEARCH_RADIUS = 3;
	public static final double RESEARCH_STEP = 0.5;
	
	// Constant to read patterns
	public static final int ROW = 0;
	public static final int COL = 1;
	
	// patterns values
	private double[][][] patterns;
	
	// number of pattern
	private int lengthPatterns;
	
	// The picture
	private boolean[][] picture;
	
	// The version
	private Version version;
	
	// The affine transformation
	private AffineTransform transform;
	
	// patterns localization
	private int[] localization;
	
	// boolean to tell if the patterns are found
	private boolean validPatterns;
	
	// number of patterns missed
	private int nbrMissed;
	
	/**
	 * <b>public Patterns(boolean[][] picture, Version version, AffineTransform transform)</b><br/>
	 * Constructor initializing and implementing the research of alignment patterns
	 * 
	 * @param picture : the binarized picture
	 * @param version : the version of the QR code
	 * @param transform : the affine transformation previously researched
	 */
	public Patterns(boolean[][] picture, Version version, AffineTransform transform) {
		this.picture = picture;
		this.version = version;
		this.transform = transform;
		
		if (version.version() == 0) validPatterns = false;
		else if (version.version() == 1) {
			lengthPatterns = 0;
			localization = new int[0];
			patterns = new double[0][0][0];
			
			validPatterns = true;
		} else {
			localization = version.patternsLocalization();
			lengthPatterns = localization.length;
			patterns = new double[lengthPatterns][lengthPatterns][2];
		
			validPatterns = initialization();
		}
	}
	
	// TODO comments
	public Patterns(Version version, AffineTransform transform) {
		this.picture = new boolean[transform.QRlength()][transform.QRlength()];
		this.version = version;
		this.transform = transform;
		
		if (version.version() == 0) validPatterns = false;
		else if (version.version() == 1) {
			lengthPatterns = 0;
			localization = new int[0];
			patterns = new double[0][0][0];
			
			validPatterns = false;
		} else {
			localization = version.patternsLocalization();
			lengthPatterns = localization.length;
			patterns = new double[lengthPatterns][lengthPatterns][2];
		
			validPatterns = false;
		}
	}
	
	/**
	 * <b>public int QRlength()</b><br/>
	 * Gives the length of the QR code in number of module
	 * 
	 * @return the length of the QR code in number of module
	 */
	public int QRlength() {
		return version.lengthQR(); 
	}
	
	/**
	 * <b>public Version getVersion()</b><br/>
	 * gives the Version of the QR code
	 * 
	 * @return the Version of the QR code
	 */
	public Version getVersion() {
		return version;
	}
	
	/**
	 * <b>public AffineTransform getAffineTransform()</b><br/>
	 * Get the affine transformation
	 * 
	 * @return the affine transformation link to the QR code 
	 */
	public AffineTransform getAffineTransform() {
		return transform;
	}
	
	/**
	 * <b>public int localization(int num)</b><br/>
	 * Get the localization of the patterns given by the standard
	 * 
	 * @param num : the number of the pattern
	 * @return the position of it
	 */
	public int localization(int num) {
		if (num>=0 && num<lengthPatterns) return localization[num];
		return -1;
	}
	
	/**
	 * <b>public int nbrPatterns()</b><br/>
	 * Get the number of patterns on one row (or column)
	 *  
	 * @return the number of patterns
	 */
	public int nbrPatterns() {
		return lengthPatterns;
	}
	
	/**
	 * <b>public boolean patternsFound()</b><br/>
	 * Tell if the patterns has been found
	 * 
	 * @return if they have been found
	 */
	public boolean patternsFound() {
		return validPatterns;
	}
	
	/**
	 * <b>public double pattern(int rowNum, int colNum, int param)</b><br/>
	 * Gives the position of the patterns in the picture
	 * 
	 * @param rowNum : the number of the pattern in row (from 0 to nbrPatterns)
	 * @param colNum : the number of the pattern in column (from 0 to nbrPatterns)
	 * @param param : the desired value : ROW or COL
	 * @return the information about the desired pattern
	 */
	public double pattern(int rowNum, int colNum, int param) {
		if (validPatterns && rowNum>=0 && rowNum<lengthPatterns && colNum>=0 && colNum<lengthPatterns && param>=0 && param<2)
			return patterns[rowNum][colNum][param];
		return -1;
	}
	
	/**
	 * <b>private boolean initialization() </b><br/>
	 * Initialize the research of patterns
	 * 
	 * @return if it succeeded
	 */
	private boolean initialization() {
		
		if (version.version()==1) return true;
		
		patterns[0][0][0] = transform.rowTransform(6,6);
		patterns[0][0][1] = transform.colTransform(6,6);

		patterns[lengthPatterns-1][0][0] = transform.rowTransform(version.lengthQR()-7,6);
		patterns[lengthPatterns-1][0][1] = transform.colTransform(version.lengthQR()-7,6);

		patterns[0][lengthPatterns-1][0] = transform.rowTransform(6,version.lengthQR()-7);
		patterns[0][lengthPatterns-1][1] = transform.colTransform(6,version.lengthQR()-7);
		
		int ii, jj;
		double[] middle = new double[2];
		nbrMissed = 0;
		
		for (ii=1; ii<lengthPatterns-1; ii++) {
			middle[0] = transform.rowTransform(localization[ii],6);
			middle[1] = transform.colTransform(localization[ii],6);
			if (!research(middle,patterns[ii][0])) return false;
			
			middle[0] = transform.rowTransform(6,localization[ii]);
			middle[1] = transform.colTransform(6,localization[ii]);
			if (!research(middle,patterns[0][ii])) return false;
		}
		
		for (ii=1; ii<lengthPatterns; ii++) { 
			for (jj=1; jj<lengthPatterns; jj++) {
				middle[0] = patterns[ii][jj-1][0] - patterns[ii-1][jj-1][0] + patterns[ii-1][jj][0];
				middle[1] = patterns[ii][jj-1][1] - patterns[ii-1][jj-1][1] + patterns[ii-1][jj][1];
				if (!research(middle,patterns[ii][jj])) return false;
			}
		}
		
		return true;
	}
	
	/**
	 * <b>private boolean research(double[] middle, double[] result)</b><br/>
	 * Implement the research of one pattern in a local area
	 * 
	 * @param middle : the middle of the local area
	 * @param result : array to put the final information
	 * @return if the pattern is found
	 */
	private boolean research(double[] middle, double[] result) {
		double rad = RESEARCH_RADIUS;
		double step = RESEARCH_STEP; 
		
		double i, j;
		double row, col;
		
		int n = 0;
		double rowSum = 0;
		double colSum = 0;
		
		for (i=-rad; i<=rad; i+=step) {
			for (j=-rad; j<=rad; j+=step) {
				row = middle[0] + transform.rowLinear(i,j);
				col = middle[1] + transform.colLinear(i,j);
				
				if (checkAround(row,col)) {
					rowSum += row;
					colSum += col;
					n++;
				}
				
			}
		}
		
		if (n==0) {
			if (nbrMissed > (lengthPatterns*lengthPatterns-3)/4) return false;
			nbrMissed++;
			result[0] = middle[0];
			result[1] = middle[1];
		} else {
			result[0] = rowSum/n;
			result[1] = colSum/n;
		}
		
		return true;
		
	}
	
	/**
	 * <b>private boolean checkAround(double row, double col)</b><br/>
	 * Check if the given point is an alignment pattern or not
	 * 
	 * @param row : the row of the point
	 * @param col : the column of the point
	 * @return if it is or not a pattern
	 */
	private boolean checkAround(double row, double col) {
		int rowInt = (int) Math.round(row);
		int colInt = (int) Math.round(col);
		
		if (!checkInPicture(rowInt,colInt)) return false;
		if (!picture[rowInt][colInt]) return false;
		
		int i, j;
		for(i=-1; i<=1; i++) {
			for (j=-1; j<=1; j++) {
				if (i!=0 || j!=0) {
					rowInt = (int) Math.round(row + transform.rowLinear(i,j));
					colInt = (int) Math.round(col + transform.colLinear(i,j));
					
					if (!checkInPicture(rowInt,colInt)) return false;
					if (picture[rowInt][colInt]) return false;
					
					rowInt = (int) Math.round(row + transform.rowLinear(2*i,2*j));
					colInt = (int) Math.round(col + transform.colLinear(2*i,2*j));
					
					if (!checkInPicture(rowInt,colInt)) return false;
					if (!picture[rowInt][colInt]) return false;
				}
			}
		}
		
		return true;
	}
	
	/**
	 * <b>private boolean checkInPicture(int row, int col)</b><br/>
	 * Check if a point is in the picture
	 * 
	 * @param row : the row of the point
	 * @param col : the column of the point
	 * @return if it's in the picture or not
	 */
	private boolean checkInPicture(int row, int col) {
		return row>=0 && row<picture.length && col>=0 && col<picture[0].length;
	}
}