package org.spongycastle.pqc.math.linearalgebra;


import java.util.Vector;


/**
 * This abstract class defines the finite field <i>GF(2<sup>n</sup>)</i>. It
 * holds the extension degree <i>n</i>, the characteristic, the irreducible
 * fieldpolynomial and conversion matrices. GF2nField is implemented by the
 * classes GF2nPolynomialField and GF2nONBField.
 *
 * @see GF2nONBField
 * @see GF2nPolynomialField
 */
public abstract class GF2nField
{

    /**
     * the degree of this field
     */
    protected int mDegree;

    /**
     * the irreducible fieldPolynomial stored in normal order (also for ONB)
     */
    protected GF2Polynomial fieldPolynomial;

    /**
     * holds a list of GF2nFields to which elements have been converted and thus
     * a COB-Matrix exists
     */
    protected Vector fields;

    /**
     * the COB matrices
     */
    protected Vector matrices;

    /**
     * Returns the degree <i>n</i> of this field.
     *
     * @return the degree <i>n</i> of this field
     */
    public final int getDegree()
    {
        return mDegree;
    }

    /**
     * Returns the fieldpolynomial as a new Bitstring.
     *
     * @return a copy of the fieldpolynomial as a new Bitstring
     */
    public final GF2Polynomial getFieldPolynomial()
    {
        if (fieldPolynomial == null)
        {
            computeFieldPolynomial();
        }
        return new GF2Polynomial(fieldPolynomial);
    }

    /**
     * Decides whether the given object <tt>other</tt> is the same as this
     * field.
     *
     * @param other another object
     * @return (this == other)
     */
    public final boolean equals(Object other)
    {
        if (other == null || !(other instanceof GF2nField))
        {
            return false;
        }

        GF2nField otherField = (GF2nField)other;

        if (otherField.mDegree != mDegree)
        {
            return false;
        }
        if (!fieldPolynomial.equals(otherField.fieldPolynomial))
        {
            return false;
        }
        if ((this instanceof GF2nPolynomialField)
            && !(otherField instanceof GF2nPolynomialField))
        {
            return false;
        }
        if ((this instanceof GF2nONBField)
            && !(otherField instanceof GF2nONBField))
        {
            return false;
        }
        return true;
    }

    /**
     * @return the hash code of this field
     */
    public int hashCode()
    {
        return mDegree + fieldPolynomial.hashCode();
    }

    /**
     * Computes a random root from the given irreducible fieldpolynomial
     * according to IEEE 1363 algorithm A.5.6. This cal take very long for big
     * degrees.
     *
     * @param B0FieldPolynomial the fieldpolynomial if the other basis as a Bitstring
     * @return a random root of BOFieldPolynomial in representation according to
     *         this field
     * @see "P1363 A.5.6, p103f"
     */
    protected abstract GF2nElement getRandomRoot(GF2Polynomial B0FieldPolynomial);

    /**
     * Computes the change-of-basis matrix for basis conversion according to
     * 1363. The result is stored in the lists fields and matrices.
     *
     * @param B1 the GF2nField to convert to
     * @see "P1363 A.7.3, p111ff"
     */
    protected abstract void computeCOBMatrix(GF2nField B1);

    /**
     * Computes the fieldpolynomial. This can take a long time for big degrees.
     */
    protected abstract void computeFieldPolynomial();

    /**
     * Inverts the given matrix represented as bitstrings.
     *
     * @param matrix the matrix to invert as a Bitstring[]
     * @return matrix^(-1)
     */
    protected final GF2Polynomial[] invertMatrix(GF2Polynomial[] matrix)
    {
        GF2Polynomial[] a = new GF2Polynomial[matrix.length];
        GF2Polynomial[] inv = new GF2Polynomial[matrix.length];
        GF2Polynomial dummy;
        int i, j;
        // initialize a as a copy of matrix and inv as E(inheitsmatrix)
        for (i = 0; i < mDegree; i++)
        {
            try
            {
                a[i] = new GF2Polynomial(matrix[i]);
                inv[i] = new GF2Polynomial(mDegree);
                inv[i].setBit(mDegree - 1 - i);
            }
            catch (RuntimeException BDNEExc)
            {
                BDNEExc.printStackTrace();
            }
        }
        // construct triangle matrix so that for each a[i] the first i bits are
        // zero
        for (i = 0; i < mDegree - 1; i++)
        {
            // find column where bit i is set
            j = i;
            while ((j < mDegree) && !a[j].testBit(mDegree - 1 - i))
            {
                j++;
            }
            if (j >= mDegree)
            {
                throw new RuntimeException(
                    "GF2nField.invertMatrix: Matrix cannot be inverted!");
            }
            if (i != j)
            { // swap a[i]/a[j] and inv[i]/inv[j]
                dummy = a[i];
                a[i] = a[j];
                a[j] = dummy;
                dummy = inv[i];
                inv[i] = inv[j];
                inv[j] = dummy;
            }
            for (j = i + 1; j < mDegree; j++)
            { // add column i to all columns>i
                // having their i-th bit set
                if (a[j].testBit(mDegree - 1 - i))
                {
                    a[j].addToThis(a[i]);
                    inv[j].addToThis(inv[i]);
                }
            }
        }
        // construct Einheitsmatrix from a
        for (i = mDegree - 1; i > 0; i--)
        {
            for (j = i - 1; j >= 0; j--)
            { // eliminate the i-th bit in all
                // columns < i
                if (a[j].testBit(mDegree - 1 - i))
                {
                    a[j].addToThis(a[i]);
                    inv[j].addToThis(inv[i]);
                }
            }
        }
        return inv;
    }

    /**
     * Converts the given element in representation according to this field to a
     * new element in representation according to B1 using the change-of-basis
     * matrix calculated by computeCOBMatrix.
     *
     * @param elem  the GF2nElement to convert
     * @param basis the basis to convert <tt>elem</tt> to
     * @return <tt>elem</tt> converted to a new element representation
     *         according to <tt>basis</tt>
     * @throws DifferentFieldsException if <tt>elem</tt> cannot be converted according to
     * <tt>basis</tt>.
     * @see GF2nField#computeCOBMatrix
     * @see GF2nField#getRandomRoot
     * @see GF2nPolynomial
     * @see "P1363 A.7 p109ff"
     */
    public final GF2nElement convert(GF2nElement elem, GF2nField basis)
        throws RuntimeException
    {
        if (basis == this)
        {
            return (GF2nElement)elem.clone();
        }
        if (fieldPolynomial.equals(basis.fieldPolynomial))
        {
            return (GF2nElement)elem.clone();
        }
        if (mDegree != basis.mDegree)
        {
            throw new RuntimeException("GF2nField.convert: B1 has a"
                + " different degree and thus cannot be coverted to!");
        }

        int i;
        GF2Polynomial[] COBMatrix;
        i = fields.indexOf(basis);
        if (i == -1)
        {
            computeCOBMatrix(basis);
            i = fields.indexOf(basis);
        }
        COBMatrix = (GF2Polynomial[])matrices.elementAt(i);

        GF2nElement elemCopy = (GF2nElement)elem.clone();
        if (elemCopy instanceof GF2nONBElement)
        {
            // remember: ONB treats its bits in reverse order
            ((GF2nONBElement)elemCopy).reverseOrder();
        }
        GF2Polynomial bs = new GF2Polynomial(mDegree, elemCopy.toFlexiBigInt());
        bs.expandN(mDegree);
        GF2Polynomial result = new GF2Polynomial(mDegree);
        for (i = 0; i < mDegree; i++)
        {
            if (bs.vectorMult(COBMatrix[i]))
            {
                result.setBit(mDegree - 1 - i);
            }
        }
        if (basis instanceof GF2nPolynomialField)
        {
            return new GF2nPolynomialElement((GF2nPolynomialField)basis,
                result);
        }
        else if (basis instanceof GF2nONBField)
        {
            GF2nONBElement res = new GF2nONBElement((GF2nONBField)basis,
                result.toFlexiBigInt());
            // TODO Remember: ONB treats its Bits in reverse order !!!
            res.reverseOrder();
            return res;
        }
        else
        {
            throw new RuntimeException(
                "GF2nField.convert: B1 must be an instance of "
                    + "GF2nPolynomialField or GF2nONBField!");
        }

    }

}
