"""Galois Field GF(256) arithmetic for QR code Reed-Solomon.

QR codes use GF(256) with primitive polynomial 0x11D (x^8 + x^4 + x^3 + x^2 + 1)
and generator alpha = 2 (0x02).
"""

PRIM_POLY = 0x11D

# exp[i] = alpha^i, log[alpha^i] = i
GF_EXP = [0] * 512  # doubled for convenience in multiplication
GF_LOG = [0] * 256


def _init_tables() -> None:
    """Build the exp and log lookup tables."""
    x = 1
    for i in range(255):
        GF_EXP[i] = x
        GF_LOG[x] = i
        x <<= 1
        if x & 0x100:
            x ^= PRIM_POLY
    # Fill the second half for wrap-around convenience
    for i in range(255, 512):
        GF_EXP[i] = GF_EXP[i - 255]


_init_tables()


def gf_mul(a: int, b: int) -> int:
    """Multiply two GF(256) elements."""
    if a == 0 or b == 0:
        return 0
    return GF_EXP[GF_LOG[a] + GF_LOG[b]]


def gf_div(a: int, b: int) -> int:
    """Divide two GF(256) elements. b must be non-zero."""
    if b == 0:
        raise ZeroDivisionError("Division by zero in GF(256)")
    if a == 0:
        return 0
    return GF_EXP[(GF_LOG[a] - GF_LOG[b]) % 255]


def gf_pow(a: int, power: int) -> int:
    """Raise a GF(256) element to a power."""
    if a == 0:
        return 0
    return GF_EXP[(GF_LOG[a] * power) % 255]


def gf_inverse(a: int) -> int:
    """Multiplicative inverse in GF(256)."""
    if a == 0:
        raise ZeroDivisionError("Zero has no inverse in GF(256)")
    return GF_EXP[255 - GF_LOG[a]]


def gf_poly_mul(p: list[int], q: list[int]) -> list[int]:
    """Multiply two polynomials over GF(256).

    Polynomials are represented as lists of coefficients,
    highest degree first: [c_n, c_{n-1}, ..., c_1, c_0].
    """
    result = [0] * (len(p) + len(q) - 1)
    for i, pi in enumerate(p):
        if pi == 0:
            continue
        for j, qj in enumerate(q):
            if qj == 0:
                continue
            result[i + j] ^= gf_mul(pi, qj)
    return result


def gf_poly_eval(poly: list[int], x: int) -> int:
    """Evaluate a polynomial at x using Horner's method."""
    result = 0
    for coeff in poly:
        result = gf_mul(result, x) ^ coeff
    return result


def gf_poly_scale(poly: list[int], scalar: int) -> list[int]:
    """Multiply all coefficients of a polynomial by a scalar."""
    return [gf_mul(c, scalar) for c in poly]


def gf_poly_add(p: list[int], q: list[int]) -> list[int]:
    """Add two polynomials over GF(256) (same as XOR)."""
    # Pad shorter polynomial with leading zeros
    diff = len(p) - len(q)
    if diff > 0:
        q = [0] * diff + q
    elif diff < 0:
        p = [0] * (-diff) + p
    return [a ^ b for a, b in zip(p, q)]
