namespace Gma.QrCodeNet.Encoding.ReedSolomon;

/// <summary>
/// Description of GaloisField256.
/// </summary>
internal sealed class GaloisField256
{
    internal GaloisField256(int primitive)
    {
        AntiLogTable = new int[256];
        LogTable = new int[256];

        Primitive = primitive;

        int gfx = 1;

        // Power cycle is from 0 to 254. 2^255 = 1 = 2^0
        // Value cycle is from 1 to 255. Thus there should not have Log(0).
        for (int powers = 0; powers < 256; powers++)
        {
            AntiLogTable[powers] = gfx;
            if (powers != 255)
            {
                LogTable[gfx] = powers;
            }

            gfx <<= 1; // gfx = gfx * 2 where alpha is 2.

            if (gfx > 255)
            {
                gfx ^= primitive;
            }
        }
    }

    private int[] AntiLogTable { get; }
    private int[] LogTable { get; }

    internal int Primitive { get; }

    internal static GaloisField256 QRCodeGaloisField => new(QRCodeConstantVariable.QRCodePrimitive);

    /// <returns>
    /// Powers of a in GF table. Where a = 2
    /// </returns>
    internal int Exponent(int powersOfa) => AntiLogTable[powersOfa];

    /// <returns>
    /// Log (power of a) in GF table. Where a = 2
    /// </returns>
    internal int Log(int gfValue)
    {
        if (gfValue == 0)
        {
            throw new ArgumentException("GaloisField value will not be equal to 0, Log method.");
        }

        return LogTable[gfValue];
    }

    internal int Inverse(int gfValue)
    {
        if (gfValue == 0)
        {
            throw new ArgumentException("GaloisField value will not be equal to 0, Inverse method.");
        }

        return Exponent(255 - Log(gfValue));
    }

    internal int Addition(int gfValueA, int gfValueB) => gfValueA ^ gfValueB;

    internal int Subtraction(int gfValueA, int gfValueB) => Addition(gfValueA, gfValueB); // Subtraction is same as addition.

    /// <returns>
    /// Product of two values.
    /// In other words. a multiply b
    /// </returns>
    internal int Product(int gfValueA, int gfValueB)
    {
        if (gfValueA == 0 || gfValueB == 0)
        {
            return 0;
        }
        if (gfValueA == 1)
        {
            return gfValueB;
        }
        if (gfValueB == 1)
        {
            return gfValueA;
        }

        return Exponent((Log(gfValueA) + Log(gfValueB)) % 255);
    }

    /// <returns>
    /// Quotient of two values.
    /// In other words. a divided b
    /// </returns>
    internal int Quotient(int gfValueA, int gfValueB)
    {
        if (gfValueA == 0)
        {
            return 0;
        }

        if (gfValueB == 0)
        {
            throw new ArgumentException($"{nameof(gfValueB)} cannot be zero.");
        }

        if (gfValueB == 1)
        {
            return gfValueA;
        }

        return Exponent(Math.Abs(Log(gfValueA) - Log(gfValueB)) % 255);
    }
}