#include "galois.h"
#include <stdexcept>
#include <cstring>

namespace paritypp {

uint8_t galois::exp_table[512];
uint8_t galois::log_table[256];
bool galois::initialized = false;

void galois::init() {
    if (initialized) return;

    // Primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 = 0x11d
    constexpr uint16_t primitive = 0x11d;

    uint16_t x = 1;
    for (int i = 0; i < 255; i++) {
        exp_table[i] = static_cast<uint8_t>(x);
        log_table[x] = static_cast<uint8_t>(i);
        x <<= 1;
        if (x & 0x100) {
            x ^= primitive;
        }
    }

    // Duplicate for wraparound in multiplication
    for (int i = 255; i < 512; i++) {
        exp_table[i] = exp_table[i - 255];
    }

    log_table[0] = 0; // Convention: log(0) unused, but set to 0
    initialized = true;
}

uint8_t galois::add(uint8_t a, uint8_t b) {
    return a ^ b;
}

uint8_t galois::sub(uint8_t a, uint8_t b) {
    return a ^ b; // Same as add in GF(2^8)
}

uint8_t galois::mul(uint8_t a, uint8_t b) {
    if (a == 0 || b == 0) return 0;
    return exp_table[log_table[a] + log_table[b]];
}

uint8_t galois::div(uint8_t a, uint8_t b) {
    if (b == 0) throw std::invalid_argument("Division by zero in GF(2^8)");
    if (a == 0) return 0;
    return exp_table[log_table[a] + 255 - log_table[b]];
}

uint8_t galois::pow(uint8_t a, uint8_t n) {
    if (n == 0) return 1;
    if (a == 0) return 0;
    int log_a = log_table[a];
    int result = (log_a * n) % 255;
    return exp_table[result];
}

uint8_t galois::inv(uint8_t a) {
    if (a == 0) throw std::invalid_argument("Inverse of zero in GF(2^8)");
    return exp_table[255 - log_table[a]];
}

void galois::mul_region(uint8_t* dst, const uint8_t* src, uint8_t coeff, size_t len) {
    if (coeff == 0) {
        std::memset(dst, 0, len);
        return;
    }
    if (coeff == 1) {
        std::memcpy(dst, src, len);
        return;
    }
    int log_c = log_table[coeff];
    for (size_t i = 0; i < len; i++) {
        dst[i] = src[i] == 0 ? 0 : exp_table[log_table[src[i]] + log_c];
    }
}

void galois::addmul_region(uint8_t* dst, const uint8_t* src, uint8_t coeff, size_t len) {
    if (coeff == 0) return;
    if (coeff == 1) {
        for (size_t i = 0; i < len; i++) {
            dst[i] ^= src[i];
        }
        return;
    }
    int log_c = log_table[coeff];
    for (size_t i = 0; i < len; i++) {
        if (src[i] != 0) {
            dst[i] ^= exp_table[log_table[src[i]] + log_c];
        }
    }
}

} // namespace paritypp
