package projectEQ2440.QRcode.ReedSolomon.GaloisField;

/**
 * <b>public class Linear</b><br/>
 * Class to apply the linear resolution of a linear system
 */
public class Linear {
	
	// Variable containing the equations
	private Galois[][] equations = null;
	// Variable which tell if the "equations" is ready
	private boolean good = false;
	// Variable containing the solutions
	private Galois[] solutions = null;
	// Variable which tell if the system is resolved
	private boolean resolved = false;
	
	private int nbr_equations;
	private int nbr_params;
	
	/**
	 * <b>public Linear(Galois[][] equations, Galois[] results)</b><br/>
	 * Constructor
	 * 
	 * @param equations : the equations in form of a square-matrix
	 * @param results : the results of the equations in an array of same length than equations
	 */
	public Linear(Galois[][] equations, Galois[] results) {
		// We check if the lengths are good
		int ne = equations.length;
		int nr = results.length;
		if (ne != nr) return;
		
		// initialization of the equations
		this.equations = new Galois[ne][];
		
		// finishing with initializations of equations
		int i, j, ntmp;
		for (i=0; i<ne; i++) {
			ntmp = equations.length;
			// If the length are not the same for every equation, we stop 
			if (ntmp != ne) return;
			
			this.equations[i] = new Galois[ne+1];
			for (j=0; j<ne; j++) {
				this.equations[i][j] = equations[i][j].clone();
			}
			// we don't forget to put the result at the end of the equations
			this.equations[i][ne] = results[i].clone();
		}
		
		// at the end we can mark the result as good and save sizes
		nbr_equations = ne;
		nbr_params = ne+1;
		good = true;
	}
	
	/**
	 * <b>public boolean isGood()</b><br/>
	 * Tell if the equations are good
	 * 
	 * @return A boolean
	 */
	public boolean isGood() {
		return good;
	}
	
	/**
	 * <b>public boolean resolve()</b><br/>
	 * apply the resolution of the system
	 * 
	 * @return A boolean if the function well-worked
	 */
	public boolean resolve() {
		if (!good) return false;
		
		int n, k;
		if (!resolved) n = pseudo_resolution();
		else n = solutions.length;
		
		solutions = new Galois[n];
		for (k=0; k<n; k++) {
			solutions[k] = Galois.div(equations[k][nbr_params-1], equations[k][k]);
		}
		resolved = true;
		
		return true;
	}
	
	/**
	 * <b>public boolean resolved()</b><br/>
	 * Tell if it's resolved or not
	 * 
	 * @return A boolean
	 */
	public boolean resolved() {
		return resolved;
	}
	
	/**
	 * <b>public int nbr_solutions()</b><br/>
	 * Gives the number of solutions
	 * 
	 * @return the number of solutions
	 */
	public int nbr_solutions() {
		return solutions.length;
	}
	
	/**
	 * <b>public Galois[] solutions()</b><br/>
	 * Return the solutions, it is null the system is not resolved
	 * 
	 * @return The solutions
	 */
	public Galois[] solutions() {
		return solutions;
	}
	
	/**
	 * <b>public void solutions(Galois[] sol)</b><br/>
	 * Put the solutions into the first value of <b>sol</b>
	 * 
	 * @param sol : array where putting solutions
	 */
	public void solutions(Galois[] sol) {
		int k, n;
		if (sol.length<solutions.length) n = sol.length;
		else n = solutions.length;
		
		for (k=0; k<n; k++) {
			sol[k] = solutions[k];
		}
	}
	
	/**
	 * <b>private int pseudo_resolution()</b><br/>
	 * Apply a pseudo resolution to resolve the system as good as possible and stop when it isn't resolvable
	 * 
	 * @return The size of the solutions 
	 */
	private int pseudo_resolution() {
		int i, j;
		
		for (j=0; j<nbr_params-1; j++) {
			i = findNonNull(j);
			if (i<j) return j;
			else if (i>j) switchRow(i,j);
			
			for (i=0; i<j; i++) {
				if (!equations[i][j].isNull()) addProdivRow(i, j, equations[i][j].log(), equations[j][j].log(), j);
			}
			for (i=j+1; i<nbr_equations; i++) {
				if (!equations[i][j].isNull()) addProdivRow(i, j, equations[i][j].log(), equations[j][j].log(), j);
			}
		}
		
		return nbr_equations;
	}
	
	/**
	 * <b>private void switchRow(int i1, int i2)</b><br/>
	 * Function to switch 2 rows
	 * 
	 * @param i1 : first row to switch
	 * @param i2 : second row to switch
	 */
	private void switchRow(int i1, int i2) {
		Galois[] tmp = equations[i1];
		equations[i1] = equations[i2];
		equations[i2] = tmp;
	}
	
	/**
	 * <b>private void addProdivRow(int i1, int i2, int factor1, int factor2, int j0)</b><br/>
	 * Function to add a multiplied and dived row to another :
	 * row(i1) = row(i1) + row(i2)*factor1/factor2
	 * 
	 * Be careful : who can choose to avoid to apply this to the first column using j0
	 * 
	 * @param i1 : The row which will be change
	 * @param i2 : The row which will be multiplied and dived and added
	 * @param factor1 : multiplication factor
	 * @param factor2 : division factor
	 * @param j0 : column after the algorithm is applied
	 */
	private void addProdivRow(int i1, int i2, int factor1, int factor2, int j0) {
		int j;
		for (j=j0; j<nbr_params; j++) {
			equations[i1][j].add(equations[i2][j].prodiv(factor1, factor2));
		}
	}
	
	/**
	 * <b>private int findNonNull(int j)</b><br/>
	 * Function to find a non-null value in a column UNDER the diagonal, return -1 if ther isn't
	 * 
	 * @param j : Column where to search
	 * @return The row where there is the non-null value
	 */
	private int findNonNull(int j) {
		int i;
		for (i=j; i<nbr_equations; i++) {
			if (!equations[i][j].isNull()) return i;
		}
		return -1;
	}
	
}
