package projectEQ2440.QRcode.info;

/**
 * <b>public class Version extends VersionConst</b><br/>
 * Class managing the Version of the QR code
 */
public class Version extends VersionConst {

	// Version saved
	private int actualVersion;
	
	// Distance of the actual Version with the table
	private int actualDistance;
	
	/**
	 * <b>public Version()</b><br/>
	 * Basic Constructor
	 */
	public Version() {
		actualVersion = 0;
		actualDistance = 18;
	}
	
	/**
	 * <b>public Version(int version)</b><br/>
	 * Constructor given the version
	 * 
	 * @param version : the version needed
	 */
	public Version(int version) {
		if (version >= 1 && version <= 40) {
			actualVersion = version;
			actualDistance = 0;
		} else {
			actualVersion = 0;
			actualDistance = 18;
		}
	}
	
	/**
	 * <b>public Version(boolean[] codeWord)</b><br/>
	 * Constructor given the code word
	 * 
	 * @param codeWord : the code word of the version
	 */
	public Version(boolean[] codeWord) {
		if (codeWord.length == 18) {
			boolean[] tmp = new boolean[18];
			int k;
			for (k=0; k<18; k++) tmp[k] = codeWord[k];
			actualDistance = decode(tmp);
			insert(tmp);
		} else {
			actualVersion = 0;
			actualDistance = 18;
		}
	}

	/**
	 * <b>public int version()</b><br/>
	 * return the actual version of this Version
	 * 
	 * @return the actual version
	 */
	public int version() {
		return actualVersion;
	}
	
	/**
	 * <b>public int lengthQR()</b>br/>
	 * Give the length of th QR code link to this version
	 * 
	 * @return the length of the QR code with this version
	 */
	public int lengthQR() {
		return actualVersion*4+17;
	}
	
	/**
	 * <b>public boolean[] codeWord()</b><br/>
	 * return the code word link to the version
	 * 
	 * @return the linked code word
	 */
	public boolean[] codeWord() {
		if (actualVersion < 7) return null;
		boolean[] tmp = new boolean[18];
		int k;
		for (k=0; k<18; k++) tmp[k] = tableVersion[actualVersion-7][k];
		return tmp;
	}
	
	/**
	 * <b>public boolean compare(boolean[] codeWord)</b><br/>
	 * Compare the actual code word version 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 codeWord : New code word version to compare
	 * @return If it keeps the new version
	 */
	public boolean compare(boolean[] codeWord) {
		if (actualDistance == 0) return false;
		if (codeWord.length != 18) return false;
		boolean[] tmp = new boolean[18];
		int k;
		for (k=0; k<18; k++) tmp[k] = codeWord[k];
		int distance = decode(tmp);
		if (distance < actualDistance) {
			actualDistance = distance;
			insert(tmp);
			return true;
		}
		return false;
	}
	
	/**
	 * <b>public int[] patternsLocalization()</b><br/>
	 * Return the patterns localization as in the QR code papersheet
	 * 
	 * @return patterns Localization in an array
	 */
	public int[] patternsLocalization() {
		if (actualVersion == 0) return null;
		return patternsLocalization[actualVersion-1].clone();
	}
	
	/**
	 * <b>public int messageLength()</b><br/>
	 * return the total message length
	 * 
	 * @return message length
	 */
	public int messageLength() {
		if (actualVersion == 0) return 0;
		return messageLength[actualVersion-1]-3;
	}
	
	/**
	 * <b>public int nbrInterleavingBlock(int correctionLevel)</b><br/>
	 * Return the number of interleaving block message in function of the error correction level
	 * 
	 * @param correctionLevel : the correction level
	 * @return number of interleaving block message
	 */
	public int nbrInterleavingBlock(int correctionLevel) {
		if (actualVersion == 0 || correctionLevel < 1 || correctionLevel > 4) return 0;
		return nbrInterleavingBlock[actualVersion-1][correctionLevel-1];
	}
	
	/**
	 * <b>public int nbrCorrectionByte(int correctionLevel)</b><br/>
	 * Return the number of correction byte in function of the error correction level
	 * 
	 * @param correctionLevel : the correction level
	 * @return the number of correction byte
	 */
	public int nbrCorrectionByte(int correctionLevel) {
		if (actualVersion == 0 || correctionLevel < 1 || correctionLevel > 4) return 0;
		return nbrCorrectionByte[actualVersion-1][correctionLevel-1];
	}
	
	/**
	 * <b>public int[] nbrEachInterleavingBlock(int correctionLevel)</b><br/>
	 * return a length 2 integer array with the number of the two length of Interleaving block
	 * 
	 * @param correctionLevel : the correction level
	 * @return a length 2 integer
	 */
	public int[] nbrEachInterleavingBlock(int correctionLevel) {
		int[] result = new int[2];
		int nib = nbrInterleavingBlock(correctionLevel);
		result[1] = messageLength() % nib;
		result[0] = nib - result[1];
		return result;
	}
	
	/**
	 * <b>private void insert(boolean[] codeWord)</b><br/>
	 * make a copy of a given code word and save it as actual version<br/>
	 * The length of the input must be 18 (at least 6)
	 * 
	 * @param format : code word to insert
	 */
	private void insert(boolean[] codeWord) {
		int k;
		actualVersion = 0;
		int tmp = 1;
		for (k=5; k>=0; k--) { 
			if (codeWord[k]) actualVersion += tmp;
			tmp *= 2;
		}
		if (actualVersion > 40 || actualDistance > 6) actualVersion = 0;
	}

	/**
	 * <b>private static int decode(boolean[] codeWord)</b><br/>
	 * decode the code word transforming directly the values and return the distance with the table <br/>
	 * The length of the input must be 18
	 * 
	 * @param codeWord : boolean array representing the code word to decode
	 * @return the distance between the input code word and final code word
	 */
	private static int decode(boolean[] codeWord) {
		int minDist = distance(codeWord, tableVersion[0]);
		int minInd = 0;		
	    int distance, k;
		
		for (k=1; k<34; k++) {
			distance = distance(codeWord, tableVersion[k]);
			if (distance<minDist) {
				minDist = distance;
				minInd = k;
			}
		}
		
		if (minDist>0) {
			for (k=0; k<18; k++) {
				codeWord[k] = tableVersion[minInd][k];
			}
		}
		
		return minDist;
	}

	/**
	 * <b>private int distance(boolean[] code1, boolean[] code2)</b><br/>
	 * Function returning the distance between to boolean array<br/>
	 * The length of the input must be 18
	 * 
	 * @param code1 : First boolean Array
	 * @param code2 : Second boolean Array
	 * @return the distance between two
	 */
	private static int distance(boolean[] code1, boolean[] code2) {
		int k, distance = 0;
		
		for (k=0; k<18; k++) {
			if (code1[k]^code2[k]) distance++;
		}
		
		return distance;
	}
	
}
