package projectEQ2440.QRcode.ReedSolomon.GaloisField;

/**
 * <b>public class Polynom</b><br/>
 * Class to manage Galois[] as polynoms
 */
public class Polynom {
	
	/**
	 * <b>public static boolean addSS(Galois[] p1, Galois[] p2, Galois a, int shift, int length_p2)</b><br/>
	 * Addition of 2 polynoms with a shift and a scalar coefficient : 
	 * <br/>
	 * P1 = P1 + P2*a*X^shift
	 * <br/>
	 * With length_p2, we can reduce the number of operation, we will only use values between 0 and p2_length-1 in p2
	 * <br/>
	 * Be Careful : p1 is changed in position and p1 doesn't change of size
	 *  
	 *  @param p1 : polynom to modify 
	 *  @param p2 : polynom added
	 *  @param a : multiplication factor
	 *  @param shift : shift to apply to <b>p2</b>
	 *  @param length_p2 : force the length of used values of <b>p2</b>
	 *  @return A boolean if it worked
	 */
	public static boolean addSS(Galois[] p1, Galois[] p2, Galois a, int shift, int length_p2) {

		int n1 = p1.length;
		int n2 = p2.length;
		if (length_p2<0 || length_p2>n2) length_p2 = n2;
		
		if (n2+shift<=0 || shift+n2-length_p2>=n1) return true;
		
		int m = Math.max(0, n2+shift-n1);
		int n = Math.min(length_p2, n2+shift);
		
		int k;
		for (k=m; k<n; k++) {
			if ( !p1[k-shift+n1-n2].add( Galois.prod(a, p2[k]) ) ) return false;
		}
		
		return true;
	}
	
	/**
	 * <b>public static boolean addSS(Galois[] p1, Galois[] p2, Galois a, int shift)</b><br/>
	 * Addition of 2 polynoms with a shift and a scalar coefficient : 
	 * <br/>
	 * P1 = P1 + P2*a*X^shift
	 * <br/>
	 * Be Careful : p1 is changed in position and p1 doesn't change of size
	 *  
	 *  @param p1 : polynom to modify 
	 *  @param p2 : polynom added
	 *  @param a : multiplication factor
	 *  @param shift : shift to apply to <b>p2</b>
	 *  @return A boolean if it worked
	 */
	public static boolean addSS(Galois[] p1, Galois[] p2, Galois a, int shift) {
		return addSS(p1, p2, a, shift, p2.length);
	}

	/**
	 * <b>public static boolean addSS(Galois[] p1, Galois[] p2, Galois a)</b><br/>
	 * Addition of 2 polynoms with a scalar coefficient : 
	 * <br/>
	 * P1 = P1 + P2*a*X
	 * <br/>
	 * Be Careful : p1 is changed in position and p1 doesn't change of size
	 *  
	 *  @param p1 : polynom to modify 
	 *  @param p2 : polynom added
	 *  @param a : multiplication factor
	 *  @return A boolean if it worked
	 */
	public static boolean addSS(Galois[] p1, Galois[] p2, Galois a) {
		return addSS(p1, p2, a, 0, p2.length);
	}
	
	/**
	 * <b>public static boolean copy(Galois[] copyP, Galois[] p, int length)</b><br/>
	 * Make a copy of the polynom p in copyP, and you can choose how many values to copy
	 * <br/><br/>
	 * Be careful : the size of copyP must be set in advance !!!
	 * 
	 * @param copyP : the copy of <b>p</b>
	 * @param p : polynom to copy
	 * @param length : number of values to copy (must be lower than the length of copyP)
	 * @return A boolean if it worked
	 */
	public static boolean copy(Galois[] copyP, Galois[] p, int length) {
		
		if (p.length<length) length = p.length;
		if (copyP.length<length) return false;
		
		int k;
		for (k=0; k<length; k++) {
			copyP[k] = p[k].clone();
		}
		
		return true;
	}
	
	/**
	 * <b>public static boolean copy(Galois[] copyP, Galois[] p)</b><br/>
	 * Make a copy of the polynom p in copyP
	 * <br/><br/>
	 * Be careful : the size of copyP must be set in advance !!!
	 * 
	 * @param copyP : the copy of <b>p</b>
	 * @param p : polynom to copy
	 * @return A boolean if it worked
	 */
	public static boolean copy(Galois[] copyP, Galois[] p) {
		return copy(copyP, p, p.length);
	}

	/**
	 * <b>public static Galois[] shift(Galois[] p, int shift)</b><br/>
	 * Shift of a polynom : Q = P*X^shift
	 * 
	 * @param p : The polynom to shift
	 * @param shift : value to shift
	 * @return A new polynom, the shifted of <b>p</b>
	 */
	public static Galois[] shift(Galois[] p, int shift) {
		
		int n = p.length;
		
		if (shift<0) return null;
		Galois[] q = new Galois[n+shift];
		
		int k;
		for (k=0; k<n; k++) {
			q[k] = p[k].clone();
		}
		for (k=n; k<n+shift; k++) {
			q[k] = new Galois((byte) 0x00);
		}
		
		return q;
	}

	/**
	 * <b>public static Galois[] zeros(int length)</b><br/>
	 * Return a null Polynom
	 * 
	 * @param length : length of the polynom (Galois[])
	 * @return The zero polynom
	 */
	public static Galois[] zeros(int length) {
		
		if (length<0) return null;
		Galois[] p = new Galois[length];
		
		int k;
		for (k=0; k<length; k++) {
			p[k] = new Galois((byte) 0x00);
		}
		
		return p;
	}
	
	/** 
	 * <b>public static Galois[] scalarProd(Galois[] p, Galois a)</b><br/>
	 * Product of a polynom with a scalar
	 * 
	 * @param p : Polynom to multiply
	 * @param a : Multiplication factor
	 * @return A new Polynom resulting of multiplication
	 */
	public static Galois[] scalarProd(Galois[] p, Galois a) {
		
		int n = p.length;
		Galois[] q = new Galois[n];
		
		int k;
		for (k=0; k<n; k++) {
			q[k] = Galois.prod(p[k], a);
		}
		
		return q;
	}
	
	/**
	 * <b>public static boolean scalarProd_OP(Galois[] p, Galois a)</b><br/>
	 * Transform a polynom into the multiplication with a factor
	 * 
	 * @param p : Polynom to modify
	 * @param a : Multiplication factor
	 * @return boolean if it worked
	 */
	public static boolean scalarProd_OP(Galois[] p, Galois a) {
		
		int k;
		for (k=0; k<p.length; k++) {
			if ( !(p[k].prod(a)) ) return false;
		}
		return true;
	}

	/** 
	 * <b>public static Galois[] scalarDiv(Galois[] p, Galois a)</b><br/>
	 * Division of a polynom with a scalar
	 * 
	 * @param p : Polynom to divide
	 * @param a : Division factor
	 * @return A new Polynom resulting of division
	 */
	public static Galois[] scalarDiv(Galois[] p, Galois a) {
		
		int n = p.length;
		Galois[] q = new Galois[n];
		
		if (a.isNull()) return null;
		
		int k;
		for (k=0; k<n; k++) {
			q[k] = Galois.div(p[k], a);
		}
		
		return q;
	}

	/**
	 * <b>public static boolean scalarDiv_OP(Galois[] p, Galois a)</b><br/>
	 * Transform a polynom into the division with a factor
	 * 
	 * @param p : Polynom to modify
	 * @param a : Division factor
	 * @return boolean if it worked
	 */
	public static boolean scalarDiv_OP(Galois[] p, Galois a) {
		
		if (a.isNull()) return false;
		
		int k;
		for (k=0; k<p.length; k++) {
			if ( !(p[k].div(a)) ) return false;
		}
		return true;
	}
	
	/** 
	 * <b>public static Galois[] add(Galois[] p1, Galois[] p2)</b><br/>
	 * Addition of 2 polynoms
	 * 
	 * @param p1 : First polynom
	 * @param p2 : Second polynom
	 * @return A new polynom resulting of the addition
	 */
	public static Galois[] add(Galois[] p1, Galois[] p2) {
		
		int n1 = p1.length;
		int n2 = p2.length;
		
		if (n1<n2) {
			Galois[] p = p1;
			p1 = p2;
			p2 = p;
			int n = n1;
			n1 = n2;
			n2 = n;
		}
		
		Galois[] p3 = new Galois[n1];
		
		int k;
		for (k=n1-1; k>=n1-n2; k--) {
			p3[k] = Galois.add(p1[k],p2[k-n1+n2]);
		}
		for (k=n1-n2-1; k>=0; k--) {
			p3[k] = p1[k].clone();
		}
		
		return p3;
		
	}
	
	/**
	 * <b>public static boolean add_OP(Galois[] p1, Galois[] p2)</b><br/>
	 * Modify a polynom into the addition with another
	 * 
	 * @param p1 : Polynom to modify
	 * @param p2 : Polynom to add
	 * @return A boolean if it worked
	 */
	public static boolean add_OP(Galois[] p1, Galois[] p2) {
		
		int n1 = p1.length;
		int n2 = p2.length;
		
		if (n1<n2) return false;
		
		int k;
		for (k=n1-1; k>=n1-n2; k--) {
			if ( !(p1[k].add(p2[k-n1+n2])) ) return false;
		}
		
		return true;
		
	}

	/** 
	 * <b>public static Galois[] prod(Galois[] p1, Galois[] p2)</b><br/>
	 * Multiplication of 2 polynoms
	 * 
	 * @param p1 : First polynom
	 * @param p2 : Second polynom
	 * @return A new polynom resulting of the multiplication
	 */
	public static Galois[] prod(Galois[] p1, Galois[] p2) {
		
		int n1 = p1.length;
		int n2 = p2.length;
		int n3 = n1+n2-1;
		Galois[] p3 = zeros(n3);
		
		int k;
		for (k=0; k<n2; k++) {
			addSS(p3, p1, p2[n2-1-k], k);
		}
		
		return p3;
		
	}
	
	/**
	 * <b>public static Galois[] rest(Galois[] p1, Galois[] p2)</b><br/>
	 * Remainder of division of 2 polynoms 
	 * <br/><br/>
	 * BE CAREFUL : The second polynom must be unit : 1*X^n + a_n-1*X^n-1....
	 * 
	 * @param p1 : First polynom
	 * @param p2 : Dividend polynom
	 * @return A new polynom remainder of the Division
	 */
	public static Galois[] rest(Galois[] p1, Galois[] p2) {

		int n1 = p1.length;
		int n2 = p2.length;

		Galois[] p3 = new Galois[n1];
		copy(p3, p1);
		
		if (n1<n2) return p3;
		
		int k;
		for (k=0; k<=n1-n2; k++) {
			addSS(p3, p2, p3[k].clone(), n1-n2-k);
		}
		
		return p3;
		
	}
	
	/**
	 * <b>public static boolean rest_OP(Galois[] p1, Galois[] p2)</b><br/>
	 * Modify a polynom to have the remainder of division with another polynom 
	 * <br/><br/>
	 * BE CAREFUL : The second polynom must be unit : 1*X^n + a_n-1*X^n-1....
	 * 
	 * @param p1 : Polynom to modify
	 * @param p2 : Dividend polynom
	 * @return A boolean if it worked
	 */
	public static boolean rest_OP(Galois[] p1, Galois[] p2) {

		int n1 = p1.length;
		int n2 = p2.length;

		if (n1<n2) return true;
		
		int k;
		for (k=0; k<=n1-n2; k++) {
			if ( !(addSS(p1, p2, p1[k].clone(), n1-n2-k)) ) return false;
		}
		
		return true;
		
	}
	
	/**
	 * <b>public static Galois evaluation(Galois[] p, int log)</b><br/>
	 * Evaluation of a polynom to the value a^log : p(a^log)
	 * 
	 * @param p : polynom to evaluate
	 * @param log : power of the primitiv value
	 * @return the evaluation
	 */
	public static Galois evaluation(Galois[] p, int log) {
		int tmp = 0;
		Galois result = new Galois((byte) 0x00);
		
		int k;
		for (k=p.length-1; k>=0; k--) {
			if (!p[k].isNull()) result.add( Galois.pow( p[k].log() + tmp ) );
			tmp += log;
		}
		
		return result;
	}

	/**
	 * <b>public static Galois[] byte2gal(int[] pByte)</b><br/>
	 *  Convert byte[] to Galois[]
	 *  
	 *  @param pByte : byte array to convert
	 *  @return Galois array resulting
	 */
	public static Galois[] byte2gal(byte[] pByte) {
		return byte2gal(pByte,0,-1,0);
	}

	/**
	 * <b>public static Galois[] byte2gal(int[] pByte, int start, int length)</b><br/>
	 * Convert byte[] to Galois[] starting to read at start and read length element (until the end if to big)
	 *  
	 * @param pByte : byte array to convert
	 * @param start : starting point
	 * @param length : the length of the reading (-1 to read until the end)
	 * @return Galois array resulting
	 */
	public static Galois[] byte2gal(byte[] pByte, int start, int length) {
		return byte2gal(pByte,start,length,0);
	}

	/**
	 * <b>public static Galois[] shift(Byte[] p, int shift)</b><br/>
	 * Convert byte[] to Galois[] starting to read at start and read length element (until the end if to big) and add zero galois at the end
	 *  
	 * @param pByte : byte array to convert
	 * @param start : starting point
	 * @param length : the length of the reading (-1 to read until the end)
	 * @param padding : number of zero to add at the end
	 * @return Galois array resulting
	 */
	public static Galois[] byte2gal(byte[] pByte, int start, int length, int padding) {
		if (start < 0) start = 0;
		if (length < 0 || length > pByte.length-start) length = pByte.length-start;
		if (padding < 0) padding = 0;
		Galois[] pGal = new Galois[length+padding];
		
		int k;
		for (k=0; k<length; k++) {
			pGal[k] = new Galois(pByte[k+start]);
		}
		for (k=length; k<length+padding; k++) {
			pGal[k] = new Galois((byte) 0);
		}
		
		return pGal;
	}

	/**
	 * <b>public static byte[] gal2byte(Galois[] pGal)</b><br/>
	 * Convert Galois[] to byte[]
	 * 
	 * @param pGal : Galois array to convert
	 * @return byte array resulting
	 */
	public static byte[] gal2byte(Galois[] pGal) {
		
		int n = pGal.length; 
		byte[] pByte = new byte[n];
		
		int k;
		for (k=0; k<n; k++) {
			pByte[k] = pGal[k].toByte();
		}
		
		return pByte;
	}
	
	/**
	 * <b>public static String toString(Galois[] p)</b><br/>
	 * Return a String form of the polynom
	 * 
	 * @param p : Polynom to have string form
	 * @return String form
	 */
	public static String toString(Galois[] p) {

		String s = "";
		int n = p.length;
		
		int k;
		for (k=n-1; k>0; k--) {
			s = s + p[n-k-1].toString() + "X^" + k + " + ";
		}
		s = s + p[n-1].toString() + "\n";
				
		return s;
	}
	
}
