<?php declare(strict_types=1);

namespace App\Captcha\Images;

use App\Captcha\Config\ImageBody as ImageBodyConfig;
use App\Captcha\Contracts\ImageLines;
use App\Captcha\Dto\Coordinators;
use App\Captcha\Dto\Image as ImageDto;
use App\Captcha\Dto\ImageBody as ImageBodyDto;
use App\Captcha\Dto\Sector;
use App\Captcha\Dto\Sectors;
use App\Captcha\Dto\Symbols;
use App\Captcha\Contracts\ImageBody;
use App\Captcha\Enums\SymbolType;
use App\Captcha\Contracts\ImageManager;

final class Body implements ImageBody
{
    public function __construct(
        private readonly ImageManager $imageManager,
        private readonly ImageLines $imageLines
    ) { }

    public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto
    {
        $image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
        $image->insertBackground($config->randomBackground());

        $imageData = match ($symbols->getType()) {
            SymbolType::String => $this->processingString($image, $symbols, $config),
        };
        $image = $imageData['image'];

        if ($config->getNumberLines() > 0) {
            $this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
        }

        $image = new ImageDto(
            imageBase64: $image->encode(),
            width: $image->getWidth(),
            height: $image->getHeight()
        );

        return new ImageBodyDto(
            image: $image,
            coordinators: $imageData['coordinators']
        );
    }

    private function processingString(Image $image, Symbols $symbols, ImageBodyConfig $config): array
    {
        $sectors = $this->calculateSectors($symbols, $image->getWidth(), $image->getHeight());
        $coordinators = [];
        foreach ($symbols->getSuccess() as $number => $symbol) {
            $coordinators[] = $this->processingStringSymbol($image, $symbol, $config, $sectors->random());
        }

        if (!empty($symbols->getFakes())) {
            foreach ($symbols->getFakes() as $number => $symbol) {
                $this->processingStringSymbol($image, $symbol, $config, $sectors->random());
            }
        }

        return [
            'image' => $image,
            'coordinators' => $coordinators
        ];
    }

    private function processingStringSymbol(Image &$image, string|int $symbol, ImageBodyConfig $config, Sector $sector): Coordinators
    {
        $fontSize = $config->fontSize();
        if ($fontSize > $sector->getHeight()) {
            $fontSize = $sector->getHeight();
        }
        $fontPathFile = $config->randomFont();
        $fontColor = $config->randomFontColor();
        $angle = $config->randomAngle();
        $marginLeft = mt_rand($sector->getX(), $sector->getX() + $sector->getWidth() - $fontSize);
        $marginTop = mt_rand($sector->getY(), $sector->getY() + $sector->getHeight() - $fontSize);

        return $image->addText(
            text: $symbol,
            x: $marginLeft,
            y: $marginTop,
            size: $fontSize,
            angle: $angle,
            hexColor: $fontColor,
            fontName: $fontPathFile
        );
    }

    private function calculateSectors(Symbols $symbols, int $width, int $height): Sectors
    {
        $points = count($symbols->getSuccess()) + count($symbols->getFakes());
        $sumFloors = 3;
        $sumRooms = ceil($points / $sumFloors);

        $heightFloor = floor($height / $sumFloors);
        $widthFloor  = floor($width / $sumRooms);

        $sectors = new Sectors();
        for ($floor = 0; $floor < $sumFloors; $floor++) {
            $y = $heightFloor * $floor;
            for ($room = 0; $room < $sumRooms; $room++) {
                $sectors->add(
                    x: ($widthFloor * $room),
                    y: $y,
                    width:  $widthFloor,
                    height: $heightFloor
                );
            }
        }

        return $sectors;
    }
}