<?php

namespace SVG\Fonts;

class FontRegistry
{
    private $fontFiles = [];

    public function addFont(string $filePath): void
    {
        $ttfFile = TrueTypeFontFile::read($filePath);
        if ($ttfFile === null) {
            throw new \RuntimeException('Font file "' . $filePath . '" is not a valid TrueType font.');
        }
        $this->fontFiles[] = $ttfFile;
    }

    public function findMatchingFont(?string $family, ?string $style, float $weight): ?FontFile
    {
        if (empty($this->fontFiles)) {
            return null;
        }

        // TODO implement generic families ('serif', 'sans-serif', 'monospace', etc.)

        // Check whether the requested font family is available, or whether we don't have to bother checking the family
        // in the following loops.
        $anyFontFamily = true;
        foreach ($this->fontFiles as $font) {
            if ($family === $font->getFamily()) {
                $anyFontFamily = false;
            }
        }

        // Attempt to find the closest-weight match with correct family and cursiveness.
        $match = $this->closestMatchBasedOnWeight(function (FontFile $font) use ($family, $anyFontFamily, $style) {
            $result = ($anyFontFamily || $font->getFamily() === $family);
            $isItalic = $font->isItalic();
            $isOblique = $font->isOblique();
            switch ($style) {
                case 'italic':
                    return $result && ($isItalic || $isOblique);
                case 'oblique':
                    return $result && ($isOblique || $isItalic);
                default:
                    return $result && !($isItalic || $isOblique);
            }
        }, $weight);

        // Attempt to match just based on the font family.
        $match = $match ?? $this->closestMatchBasedOnWeight(function (FontFile $font) use ($family, $anyFontFamily) {
            return $anyFontFamily || $font->getFamily() === $family;
        }, $weight);

        // Return any font at all, if possible.
        return $match ?? $this->fontFiles[0];
    }

    private function closestMatchBasedOnWeight(callable $filter, float $targetWeight): ?FontFile
    {
        $bestMatch = null;
        foreach ($this->fontFiles as $font) {
            if (!$filter($font)) {
                continue;
            }
            if ($bestMatch === null) {
                $bestMatch = $font;
                continue;
            }
            if (abs($targetWeight - $font->getWeight()) < abs($targetWeight - $bestMatch->getWeight())) {
                $bestMatch = $font;
            }
        }
        return $bestMatch;
    }
}
