<?php

declare(strict_types=1);

namespace App\Domain\QrCode\Service;

use App\Domain\QrCode\Pattern\AlignmentPattern;
use App\Domain\QrCode\Pattern\DarkModulePattern;
use App\Domain\QrCode\Pattern\FinderPattern;
use App\Domain\QrCode\Pattern\FormatInfoPattern;
use App\Domain\QrCode\Pattern\TimingPattern;
use App\Domain\QrCode\Pattern\VersionInfoPattern;
use App\Domain\QrCode\ValueObject\ErrorCorrectionLevel;
use App\Domain\QrCode\ValueObject\MaskPattern;
use App\Domain\QrCode\ValueObject\QrCodeMatrix;
use App\Domain\QrCode\ValueObject\QrCodeVersion;

class QrCodeRenderer
{
    public function __construct(
        private readonly QrCodeMasker $masker = new QrCodeMasker(),
        private readonly ReedSolomon $reedSolomon = new ReedSolomon()
    ) {
    }

    /**
     * Rendert einen QR-Code basierend auf den kodierten Daten
     */
    public function render(string $data, QrCodeVersion $version, ErrorCorrectionLevel $level): QrCodeMatrix
    {
        $size = $version->getSize();
        $matrix = array_fill(0, $size, array_fill(0, $size, null));

        // 1. Funktionsmuster hinzufügen
        $this->applyFunctionalPatterns($matrix, $version);

        // 2. Daten mit Error Correction platzieren
        $dataWithEcc = $this->reedSolomon->addErrorCorrection(
            $data,
            $version->getDataCapacity($level),
            $level
        );
        $this->placeData($matrix, $dataWithEcc, $size);

        // 3. Beste Maske finden und anwenden
        $bestMask = $this->masker->findBestMask($matrix, $size);
        $this->masker->applyMask($matrix, $size, $bestMask);

        // 4. Format-Informationen hinzufügen
        $formatPattern = new FormatInfoPattern($level, $bestMask->value);
        $formatPattern->apply($matrix, $size);

        // null-Werte durch false ersetzen
        for ($r = 0; $r < $size; $r++) {
            for ($c = 0; $c < $size; $c++) {
                if ($matrix[$r][$c] === null) {
                    $matrix[$r][$c] = false;
                }
            }
        }

        return new QrCodeMatrix($matrix);
    }

    /**
     * Wendet alle funktionalen Muster auf die Matrix an
     */
    private function applyFunctionalPatterns(array &$matrix, QrCodeVersion $version): void
    {
        $size = $version->getSize();
        $versionValue = $version->getValue();

        // 1. Finder-Muster
        $finderPatterns = FinderPattern::createAll($size);
        foreach ($finderPatterns as $pattern) {
            $pattern->apply($matrix, $size);
        }

        // 2. Timing-Muster
        $timingPattern = new TimingPattern();
        $timingPattern->apply($matrix, $size);

        // 3. Ausrichtungsmuster (für Versionen > 1)
        if ($versionValue > 1) {
            $alignmentPattern = new AlignmentPattern($versionValue);
            $alignmentPattern->apply($matrix, $size);
        }

        // 4. Dunkles Modul
        $darkModule = new DarkModulePattern($versionValue);
        $darkModule->apply($matrix, $size);

        // 5. Versions-Information (für Versionen >= 7)
        if ($versionValue >= 7) {
            $versionInfoPattern = new VersionInfoPattern($versionValue);
            $versionInfoPattern->apply($matrix, $size);
        }
    }

    /**
     * Platziert die kodierten Daten in der Matrix
     */
    private function placeData(array &$matrix, string $data, int $size): void
    {
        $dataIndex = 0;
        $direction = -1; // Starte mit Aufwärtsbewegung

        // Starte von unten rechts und bewege dich im Zickzack-Muster
        for ($col = $size - 1; $col > 0; $col -= 2) {
            // Timing-Spalte überspringen
            if ($col === 6) $col--;

            for ($count = 0; $count < $size; $count++) {
                $row = $direction === -1 ? $size - 1 - $count : $count;

                for ($c = 0; $c < 2; $c++) {
                    $currentCol = $col - $c;

                    // Nur in leere Zellen (null) Daten einfügen
                    if ($row >= 0 && $row < $size && $currentCol >= 0 && $currentCol < $size &&
                        $matrix[$row][$currentCol] === null) {

                        if ($dataIndex < strlen($data)) {
                            $matrix[$row][$currentCol] = ($data[$dataIndex] === '1');
                            $dataIndex++;
                        } else {
                            $matrix[$row][$currentCol] = false;
                        }
                    }
                }
            }
            $direction *= -1; // Richtung umkehren
        }
    }
}
