<?php

declare(strict_types=1);

namespace App\Domain\QrCode\ValueObject;

/**
 * Repräsentiert ein Galois-Feld GF(2^8) für Reed-Solomon-Berechnungen
 */
class GaloisField
{
    // Generator-Polynom für GF(2^8)
    private const PRIMITIVE_POLY = 0x11D; // x^8 + x^4 + x^3 + x^2 + 1

    // Lookup-Tabellen für Multiplikation und Division
    private array $expTable = [];
    private array $logTable = [];

    public function __construct()
    {
        $this->initTables();
    }

    /**
     * Initialisiert die Lookup-Tabellen für schnelle Berechnungen
     */
    private function initTables(): void
    {
        // Setze Tabellen-Größe
        $this->expTable = array_fill(0, 256, 0);
        $this->logTable = array_fill(0, 256, 0);

        // Berechne die Exponentiation und Logarithmus-Tabellen
        $x = 1;
        for ($i = 0; $i < 255; $i++) {
            $this->expTable[$i] = $x;
            $this->logTable[$x] = $i;

            // Multiplikation mit x in GF(2^8)
            $x = ($x << 1) ^ ($x & 0x80 ? self::PRIMITIVE_POLY : 0);
            $x &= 0xFF;
        }

        // Setze expTable[255] = expTable[0]
        $this->expTable[255] = $this->expTable[0];
    }

    /**
     * Addiert zwei Elemente im Galois-Feld
     */
    public function add(int $a, int $b): int
    {
        return $a ^ $b; // In GF(2^8) ist Addition = XOR
    }

    /**
     * Subtrahiert zwei Elemente im Galois-Feld
     * In GF(2^8) ist Subtraktion = Addition = XOR
     */
    public function subtract(int $a, int $b): int
    {
        return $this->add($a, $b);
    }

    /**
     * Multipliziert zwei Elemente im Galois-Feld
     */
    public function multiply(int $a, int $b): int
    {
        // Spezialfall: Null-Multiplikation
        if ($a === 0 || $b === 0) {
            return 0;
        }

        // Benutze Log-Tabellen für Multiplikation
        $logA = $this->logTable[$a];
        $logB = $this->logTable[$b];
        $sumLog = ($logA + $logB) % 255;

        return $this->expTable[$sumLog];
    }

    /**
     * Dividiert zwei Elemente im Galois-Feld
     */
    public function divide(int $a, int $b): int
    {
        if ($b === 0) {
            throw new \DivisionByZeroError('Division by zero in Galois field');
        }

        if ($a === 0) {
            return 0;
        }

        // Benutze Log-Tabellen für Division
        $logA = $this->logTable[$a];
        $logB = $this->logTable[$b];
        $diffLog = ($logA - $logB + 255) % 255;

        return $this->expTable[$diffLog];
    }

    /**
     * Berechnet die Potenz eines Elements im Galois-Feld
     */
    public function power(int $a, int $power): int
    {
        if ($a === 0) {
            return 0;
        }

        if ($power === 0) {
            return 1;
        }

        $logA = $this->logTable[$a];
        $resultLog = ($logA * $power) % 255;

        return $this->expTable[$resultLog];
    }

    /**
     * Liefert die Logarithmus-Tabelle
     */
    public function getLogTable(): array
    {
        return $this->logTable;
    }

    /**
     * Liefert die Exponentiations-Tabelle
     */
    public function getExpTable(): array
    {
        return $this->expTable;
    }
}
