package reedSolomon;

/**
 * This class provides library of basic methods for operations over matricies.
 * This matricies are by default considered to be over finite fields of size of
 * 2^16.
 * 
 * @author Marcel Duris
 * @see iField
 */
public class Matrix {

    /** Default exponent used for calculating size of finite field */
    private final int DEF_SIZE = 16;
    private iField f;

    /**
     * Default constructor creates matrix library working over finite fields
     * of size 2^16.
     */
    public Matrix() {
        f = new GaloisField(DEF_SIZE);
    }

    /**
     * This constructor allows user to specify field over which are operations
     * performed.
     *
     * @param f
     */
    public Matrix(iField f) {
        this.f = f;
    }

    /**
     * This method enables user to set field used for calculations.
     * 
     * @param ff field
     */
    public void setField(iField ff) {
        f = ff;
    }

    /**
     * This method performs gaussian elimination over given matrix and as result
     * returns inverse matrix A^(-1) such that A^(-1) * A = I, where I is
     * identity matrix.
     * 
     * @param A matrix
     * @return inverse matrix
     */
    public int[][] eliminate(int[][] A) {
        int A1[][] = new int[A.length][2 * A[0].length];
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < A[i].length; j++) {
                A1[i][j] = A[i][j];
            }
            A1[i][A.length + i] = 1;
        }
        int N = A.length;

        //elimination
        for (int i = 0; i < N; i++) {
            //pivot row
            int max = i;
            for (int j = i + 1; j < N; j++) {
                if (Math.abs(A1[j][i]) > Math.abs(A1[max][i])) {
                    max = j;
                }
            }
            int[] temp = A1[i];
            A1[i] = A1[max];
            A1[max] = temp;

            int alpha = f.divide(1, A1[i][i]);
            for (int k = 0; k < 2 * N; k++) {
                A1[i][k] = f.multiply(A1[i][k], alpha);
            }

            for (int j = 0; j < N; j++) {
                if (j == i) {
                    continue;
                }

                alpha = A1[j][i];
                for (int k = 0; k < 2 * N; k++) {
                    A1[j][k] = f.substract(A1[j][k], f.multiply(alpha, A1[i][k]));
                }
            }
        }

        int[][] result = new int[N][N];
        for (int i = 0; i < N; i++) {
            System.arraycopy(A1[i], N, result[i], 0, N);
        }
        return result;
    }

    /**
     * This method multiplies two matricies by standard algorithm. If sizes of
     * matricies do not match, result is undefined.
     * 
     * @param A matrix
     * @param B matrix
     * @return matrix C such that C = A * B
     */
    public int[][] multiply(int[][] A, int[][] B) {
        int M = A.length;
        int N = B.length;
        int K = B[0].length;
        int[][] result = new int[M][K];
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < K; j++) {
                int sum = 0;
                for (int l = 0; l < N; l++) {
                    sum = f.add(sum, f.multiply(A[i][l], B[l][j]));
                }
                result[i][j] = sum;
            }
        }
        return result;
    }

    /**
     * Calculates matrix A^T.
     *
     * @param A matrix
     * @return matrix
     */
    public int[][] transponate(int[][] A) {
        int[][] T = new int[A[0].length][A.length];
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < A[0].length; j++) {
                T[j][i] = A[i][j];
            }
        }

        return T;
    }

    /**
     * This method multiplies given matrix by single column matrix derived from
     * given vector. Lenght of vector and number of columns in matrix must
     * match.
     *
     * @param A matrix
     * @param v vector
     * @return matrix equal to A*v^T
     */
    public int[] multiplyByVector(int[][] A, int[] v) {
        int[] res = new int[A.length];
        for (int i = 0; i < A.length; i++) {
            int sum = 0;
            for (int j = 0; j < v.length; j++) {
                sum = f.add(sum, f.multiply(A[i][j], v[j]));
            }
            res[i] = sum;
        }
        return res;
    }

   /* public int[] multiplyByVectorTransponated(int[][] A, int[] v) {
        int[][] mv = new int[v.length][1];
        for (int i = 0; i < v.length; i++) {
            mv[i][0] = v[i];
        }
        int[][] m = multiply(A, mv);
        int[] result = new int[v.length];
        for (int i = 0; i < v.length; i++) {
            result[i] = m[i][0];
        }

        return result;
    }*/

    /*
     * Return special form of Vandermonde matrix.
     *
     * @param m
     * @param n
     * @return
     */
    /*public int[][] getVandermonde(int m, int n) {
        iField g = new GaloisField(16);
        int[][] res = new int[m][n];

        for (int i = 0; i < n; i++) {
            res[0][i] = 1;
        }

        for (int i = 0; i < n; i++) {
            res[1][i] = i + 1;
        }

        for (int i = 2; i < m; i++) {
            for (int j = 0; j < n; j++) {
                res[i][j] = f.multiply(res[i - 1][j], res[1][j]);
            }
        }

        return res;
    }*/
}


