<?php

declare(strict_types=1);

namespace App\Domain\QrCode\Service;

use App\Domain\QrCode\ValueObject\MaskPattern;

class QrCodeMasker
{
    /**
     * Findet die beste Maske für die Matrix
     */
    public function findBestMask(array $matrix, int $size): MaskPattern
    {
        $lowestPenalty = PHP_INT_MAX;
        $bestMask = MaskPattern::PATTERN_0;

        foreach (MaskPattern::cases() as $mask) {
            $testMatrix = $matrix;
            $this->applyMask($testMatrix, $size, $mask);
            $penalty = $this->calculateMaskPenalty($testMatrix, $size);

            if ($penalty < $lowestPenalty) {
                $lowestPenalty = $penalty;
                $bestMask = $mask;
            }
        }

        return $bestMask;
    }

    /**
     * Wendet eine Maske auf die Matrix an
     */
    public function applyMask(array &$matrix, int $size, MaskPattern $maskPattern): void
    {
        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                if ($matrix[$row][$col] === null) {
                    continue;
                }

                if (!$this->isFunctionModule($row, $col, $size)) {
                    if ($maskPattern->shouldMask($row, $col)) {
                        $matrix[$row][$col] = !$matrix[$row][$col];
                    }
                }
            }
        }
    }

    /**
     * Berechnet die Strafpunkte für eine maskierte Matrix
     */
    public function calculateMaskPenalty(array $matrix, int $size): int
    {
        $penalty = 0;

        // Regel 1: Aufeinanderfolgende Module gleicher Farbe
        $penalty += $this->evaluateConsecutiveModulesPenalty($matrix, $size);

        // Regel 2: Blöcke gleicher Farbe
        $penalty += $this->evaluateSameColorBlocksPenalty($matrix, $size);

        // Regel 3: Muster, die Finder-Pattern ähneln
        $penalty += $this->evaluateFinderPatternLikePenalty($matrix, $size);

        // Regel 4: Ausgewogenheit schwarzer und weißer Module
        $penalty += $this->evaluateBalancePenalty($matrix, $size);

        return $penalty;
    }

    /**
     * Evaluiert die Strafe für aufeinanderfolgende Module gleicher Farbe
     */
    private function evaluateConsecutiveModulesPenalty(array $matrix, int $size): int
    {
        $penalty = 0;

        // Zeilen prüfen
        for ($row = 0; $row < $size; $row++) {
            $count = 1;
            for ($col = 1; $col < $size; $col++) {
                if ($matrix[$row][$col] === $matrix[$row][$col - 1]) {
                    $count++;
                } else {
                    if ($count >= 5) {
                        $penalty += 3 + ($count - 5);
                    }
                    $count = 1;
                }
            }
            if ($count >= 5) {
                $penalty += 3 + ($count - 5);
            }
        }

        // Spalten prüfen
        for ($col = 0; $col < $size; $col++) {
            $count = 1;
            for ($row = 1; $row < $size; $row++) {
                if ($matrix[$row][$col] === $matrix[$row - 1][$col]) {
                    $count++;
                } else {
                    if ($count >= 5) {
                        $penalty += 3 + ($count - 5);
                    }
                    $count = 1;
                }
            }
            if ($count >= 5) {
                $penalty += 3 + ($count - 5);
            }
        }

        return $penalty;
    }

    /**
     * Evaluiert die Strafe für 2x2 Blöcke gleicher Farbe
     */
    private function evaluateSameColorBlocksPenalty(array $matrix, int $size): int
    {
        $penalty = 0;

        for ($row = 0; $row < $size - 1; $row++) {
            for ($col = 0; $col < $size - 1; $col++) {
                $color = $matrix[$row][$col];
                if ($color === $matrix[$row][$col + 1] &&
                    $color === $matrix[$row + 1][$col] &&
                    $color === $matrix[$row + 1][$col + 1]) {
                    $penalty += 3;
                }
            }
        }

        return $penalty;
    }

    /**
     * Evaluiert die Strafe für Muster, die wie Finder-Pattern aussehen
     */
    private function evaluateFinderPatternLikePenalty(array $matrix, int $size): int
    {
        $penalty = 0;
        // Vereinfachte Implementation
        return $penalty;
    }

    /**
     * Evaluiert die Strafe für unausgewogene schwarze/weiße Module
     */
    private function evaluateBalancePenalty(array $matrix, int $size): int
    {
        $darkCount = 0;
        $totalCount = $size * $size;

        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                if ($matrix[$row][$col] === true) {
                    $darkCount++;
                }
            }
        }

        $darkPercentage = $darkCount * 100 / $totalCount;
        $deviation = abs($darkPercentage - 50) / 5;

        return (int) ($deviation * 10);
    }

    /**
     * Prüft, ob ein Modul ein Funktionsmodul ist (also nicht maskiert werden sollte)
     */
    private function isFunctionModule(int $row, int $col, int $size): bool
    {
        // Finder-Pattern und Separatoren
        if (($row < 9 && $col < 9) ||
            ($row < 9 && $col >= $size - 8) ||
            ($row >= $size - 8 && $col < 9)) {
            return true;
        }

        // Timing-Pattern
        if ($row === 6 || $col === 6) {
            return true;
        }

        // Format-Informationen
        if (($row < 9 && $col === 8) || ($row === 8 && $col < 9) ||
            ($row === 8 && $col >= $size - 8) || ($row >= $size - 8 && $col === 8)) {
            return true;
        }

        // Dark Module bei Version 1
        if ($row === 8 && $col === 13) {
            return true;
        }

        return false;
    }
}
