package projectEQ2440.QRcode.info;

import projectEQ2440.QRcode.mask.*;

/**
 * <b>public class Format extends FormatConst</b><br/>
 * Class managing the Format of the QR code
 */
public class Format extends FormatConst {
	
	// Format saved
	private boolean[] actualFormat;
	
	// Distance of the actual format with the table
	private int actualDistance;
	
	/**
	 * <b>public Format()</b><br/>
	 * Basic constructor
	 */
	public Format() {
		actualFormat = null;
		actualDistance = 15; 
	}
	
	/**
	 * <b>public Format(boolean[] format)</b><br/>
	 * Constructor with the format brute
	 * 
	 * @param format : format brute
	 */
	public Format(boolean[] format) {
		if (format.length == 15) {
			insert(format);
			actualDistance = decode(actualFormat);
		} else {
			actualFormat = null;
			actualDistance = 15; 
		}
	}
	
	/**
	 * <b>public Format(boolean[] format, boolean spread)</b><br/>
	 * Constructor with the format brute and with the possibility to apply the spread sequence
	 * 
	 * @param format : format brute
	 * @param spread : apply or not the spread sequence
	 */
	public Format(boolean[] format, boolean spread) {
		if (format.length == 15) {
			insert(format);
			if (spread) applySpread(actualFormat);
			actualDistance = decode(actualFormat);
		} else {
			actualFormat = null;
			actualDistance = 15; 
		}
	}
	
	/**
	 * <b>public Format(int correctionLevel, int maskPattern)</b><br/>
	 * Constructor for encoding
	 * 
	 * @param correctionLevel : the correction level -> L:1, M:2, Q:3, H:4
	 * @param maskPattern : The mask pattern (between 0 and 7)
	 */
	public Format(int correctionLevel, int maskPattern) {
		int tmp = 0;
		if (correctionLevel == 1) tmp = 8;
		if (correctionLevel == 3) tmp = 24;
		if (correctionLevel == 4) tmp = 16;
		tmp += maskPattern;
		insert(tableFormat[tmp]);
		actualDistance = 0;
	}
	
	/**
	 * <b>public boolean[] codeWord()</b><br/>
	 * Return a copy of the actual format
	 * 
	 * @return a copy of the actual format
	 */
	public boolean[] codeWord() {
		if (actualFormat == null) return null;
		boolean[] format = new boolean[15];
		int k;
		for (k=0; k<15; k++) format[k] = actualFormat[k];
		return format;
	}
	
	/**
	 * <b>public boolean[] formatSpread()</b><br/>
	 * Return the format after applying the spread sequence
	 * 
	 * @return Format XOR SpreadSequence
	 */
	public boolean[] codeWordSpread() {
		boolean[] format = codeWord();
		applySpread(format);
		return format;
	}
	
	/**
	 * <b>public int correctionLevel()</b><br/>
	 * Return the correction level in the format :<br/>
	 * L : 1<br/>
	 * M : 2<br/>
	 * Q : 3<br/>
	 * H : 4
	 * 
	 * @return The correction level
	 */
	public int correctionLevel() {
		if (actualFormat == null) return -1;
		int tmp = 0;
		if (actualFormat[0]) tmp = 2;
		if (actualFormat[1]) tmp++;
		if (tmp == 0) return 2;
		if (tmp == 2) return 4;
		return tmp;
	}
	
	/**
	 * <b>public int maskPattern()</b><br/>
	 * Return the mask pattern in the format
	 * 
	 * @return the mask pattern
	 */
	public int maskPattern() {
		if (actualFormat == null) return -1;
		int tmp = 0;
		if (actualFormat[2]) tmp = 4;
		if (actualFormat[3]) tmp += 2;
		if (actualFormat[4]) tmp++;
		return tmp;
	}
	
	/**
	 * <b>public Mask mask()</b><br/>
	 * Return the mask in a Mask class
	 * 
	 * @return Mask
	 */
	public Mask mask() {
		return Mask.generate(maskPattern());
	}
	
	/**
	 * <b>public boolean compare(boolean[] format)</b><br/>
	 * Compare the actual format with a given new one and keep the less damaged<br/>
	 * return true if it keeps the new one or false for the old one
	 * 
	 * @param format : New format to compare
	 * @return If it keeps the new format
	 */
	public boolean compare(boolean[] format) {
		return compare(format, false);
	}
	
	/**
	 * <b>public boolean compare(boolean[] format, boolean spread)</b><br/>
	 * Compare the actual format with a given new one and keep the less damaged<br/>
	 * You can decide to apply the spread sequence<br/>
	 * return true if it keeps the new one or false for the old one
	 * 
	 * @param format : New format to compare
	 * @param spread : Apply the spread sequence or not
	 * @return If it keeps the new format
	 */
	public boolean compare(boolean[] format, boolean spread) {
		if (actualDistance == 0) return false;
		if (format.length != 15) return false;
		boolean[] tmp = actualFormat;
		insert(format);
		if (spread) applySpread(actualFormat);
		int distance = decode(actualFormat);
		if (distance < actualDistance) {
			actualDistance = distance;
			return true;
		}
		actualFormat = tmp;
		return false;
	}
	
	/**
	 * <b>public static void applySpread(boolean[] format)</b><br/>
	 * Static function to apply spread sequence to a format
	 * 
	 * @param format : The format to apply the spread sequence
	 */
	public static void applySpread(boolean[] format) {
		if (format.length != 15) return;
		int k;
		if (format.length == 15) {
			for (k=0; k<15; k++) {
				format[k] ^= spreadSequence[k];
			}
		}
	}
	
	/**
	 * <b>private void insert(boolean[] format)</b><br/>
	 * make a copy of a given format and save it as actual format<br/>
	 * The length of the input must be 15
	 * 
	 * @param format : Format to insert
	 */
	private void insert(boolean[] format) {
		int k;
		actualFormat = new boolean[15];
		for (k=0; k<15; k++) actualFormat[k] = format[k];
	}
	
	/**
	 * <b>private static int decode(boolean[] format)</b><br/>
	 * decode the format transforming directly the values and return the distance with the table <br/>
	 * The length of the input must be 15
	 * 
	 * @param format : boolean array representing the format to decode
	 * @return the distance between the input format and final format
	 */
	private static int decode(boolean[] format) {
		int minDist = distance(format, tableFormat[0]);
		int minInd = 0;		
	    int distance, k;
		
		for (k=1; k<32; k++) {
			distance = distance(format, tableFormat[k]);
			if (distance<minDist) {
				minDist = distance;
				minInd = k;
			}
		}
		
		if (minDist>0) {
			for (k=0; k<15; k++) {
				format[k] = tableFormat[minInd][k];
			}
		}
		
		return minDist;
	}
	
	/**
	 * <b>private int distance(boolean[] b1, boolean[] b2)</b><br/>
	 * Function returning the distance between to boolean array<br/>
	 * The length of the input must be 15
	 * 
	 * @param b1 : First boolean Array
	 * @param b2 : Second boolean Array
	 * @return the distance between two
	 */
	private static int distance(boolean[] b1, boolean[] b2) {
		int k, distance = 0;
		
		for (k=0; k<15; k++) {
			if (b1[k]^b2[k]) distance++;
		}
		
		return distance;
	}
	
}
