package reedSolomon;

import java.util.Arrays;

/**
 * This class provides way to perform arithmetics over finite fields. Basic
 * operations supported are addition, substraction, multiplication and division.
 *
 * This is implementation of Galois fields of size 2^w. Therefore addition and
 * substraction of two numbers are implemented as xoring of two numbers.
 *
 * To multiply and divide numbers logarithmic tables are used. Size of this
 * tables is equal to number of elements in field and directly affects memory
 * requirements of class.
 *
 * @author Marcel Duris
 * @see reedSolomon.iField
 */
public class GaloisField implements iField{
    private int[] gflog;
    private int[] gfilog;
    private long gpoly;
    private int gpoly4 = 023;
    private int gpoly8 = 0435;
    private int gpoly16 = 0210013;
    private long gpoly32 = 040020000006l;
    private int gpoly64;// = 023;
    private int w ;
    //private int  gpoly = 0435;
    //int w = 8; //gpoly and w are dependent on each other

    /**
     * Constructor creates Galois filed of size 2^w were w is passed as
     * argument. Logarithmic tables are precomputed.
     * 
     * @param w parameter used to determine size of field.
     */
    public GaloisField(int w){
        this.w = w;
        int prime_to_w = 1 << w; //2^8
        switch(w){
            case 4: gpoly = gpoly4;break;
            case 8: gpoly = gpoly8;break;
            case 16: gpoly = gpoly16;break;
            case 32: gpoly = gpoly32;break;
            case 64: throw(new UnsupportedOperationException("Galois Field size not supported."));
            //gpoly = gpoly64;break;
            default:
                throw(new IllegalArgumentException("Galois Field size of 2^" + w + " not supported."));
        }
        gflog = new int[prime_to_w];
        gfilog = new int[prime_to_w];

        int num = 1;
        for (int log = 0; log < prime_to_w - 1; log++){
            gflog[num] = log;
            gfilog[log] = num;
            num = num << 1;
            if ((num & prime_to_w) != 0) num = (int)(num ^ gpoly);
        }
    }

    /**
     * Prints logarithmic tables.
     */
    public void printTables(){
        //for (int i = 0; i < w; i++){
            System.out.println("gflog: " + Arrays.toString(gflog));
            System.out.println("gfilog: " + Arrays.toString(gfilog));
        //}
    }

    /**
     * Performs addtion of two numbers.
     *
     * @param a first number
     * @param b second number
     * @return result of a+b
     */
    public int add(int a, int b){
        return a^b;
    }

    /**
     * Performs substraction of two numbers.
     *
     * @param a first number
     * @param b second number
     * @return result of a-b
     */
    public int substract(int a, int b){
        return a^b;
    }

    /**
     * Performs multiplication of two numbers.
     *
     * @param a first number
     * @param b second number
     * @return result of a*b
     */
    public int multiply(int a, int b){
        if (a == 0 || b == 0){
            return 0;
        }
        return gfilog[mod((gflog[a] + gflog[b]), ((1 << w) - 1))];
    }

    /**
     * Performs division on two numbers.
     *
     * @param a first number
     * @param b second number
     * @return result of a/b
     */
    public int divide(int a, int b){
        if (a == 0) return 0;
        return gfilog[mod((gflog[a] - gflog[b]), ((1 << w) - 1))];
    }

    private static int mod(int a, int b){
        int result = a%b;
        if (result < 0) {
            result = b + result;
        }
        return result;
    }

}
