<?php

namespace DynamicOOOS\Mpdf\Color;

use DynamicOOOS\Mpdf\Mpdf;
class ColorConverter
{
    const MODE_GRAYSCALE = 1;
    const MODE_SPOT = 2;
    const MODE_RGB = 3;
    const MODE_CMYK = 4;
    const MODE_RGBA = 5;
    const MODE_CMYKA = 6;
    private $mpdf;
    private $colorModeConverter;
    private $colorSpaceRestrictor;
    private $cache;
    public function __construct(Mpdf $mpdf, ColorModeConverter $colorModeConverter, ColorSpaceRestrictor $colorSpaceRestrictor)
    {
        $this->mpdf = $mpdf;
        $this->colorModeConverter = $colorModeConverter;
        $this->colorSpaceRestrictor = $colorSpaceRestrictor;
        $this->cache = [];
    }
    public function convert($color, array &$PDFAXwarnings = [])
    {
        $color = \strtolower(\trim($color));
        if ($color === 'transparent' || $color === 'inherit') {
            return \false;
        }
        if (isset(NamedColors::$colors[$color])) {
            $color = NamedColors::$colors[$color];
        }
        if (!isset($this->cache[$color])) {
            $c = $this->convertPlain($color, $PDFAXwarnings);
            $cstr = '';
            if (\is_array($c)) {
                $c = \array_pad($c, 6, 0);
                $cstr = \pack('a1ccccc', $c[0], \round($c[1]) & 0xff, \round($c[2]) & 0xff, \round($c[3]) & 0xff, \round($c[4]) & 0xff, \round($c[5]) & 0xff);
            }
            $this->cache[$color] = $cstr;
        }
        return $this->cache[$color];
    }
    public function lighten($c)
    {
        $this->ensureBinaryColorFormat($c);
        if ($c[0] == static::MODE_RGB || $c[0] == static::MODE_RGBA) {
            list($h, $s, $l) = $this->colorModeConverter->rgb2hsl(\ord($c[1]) / 255, \ord($c[2]) / 255, \ord($c[3]) / 255);
            $l += (1 - $l) * 0.8;
            list($r, $g, $b) = $this->colorModeConverter->hsl2rgb($h, $s, $l);
            $ret = [3, $r, $g, $b];
        } elseif ($c[0] == static::MODE_CMYK || $c[0] == static::MODE_CMYKA) {
            $ret = [4, \max(0, \ord($c[1]) - 20), \max(0, \ord($c[2]) - 20), \max(0, \ord($c[3]) - 20), \max(0, \ord($c[4]) - 20)];
        } elseif ($c[0] == static::MODE_GRAYSCALE) {
            $ret = [1, \min(255, \ord($c[1]) + 32)];
        }
        $c = \array_pad($ret, 6, 0);
        $cstr = \pack('a1ccccc', $c[0], \round($c[1]) & 0xff, \round($c[2]) & 0xff, \round($c[3]) & 0xff, \round($c[4]) & 0xff, \round($c[5]) & 0xff);
        return $cstr;
    }
    public function darken($c)
    {
        $this->ensureBinaryColorFormat($c);
        if ($c[0] == static::MODE_RGB || $c[0] == static::MODE_RGBA) {
            list($h, $s, $l) = $this->colorModeConverter->rgb2hsl(\ord($c[1]) / 255, \ord($c[2]) / 255, \ord($c[3]) / 255);
            $s *= 0.25;
            $l *= 0.75;
            list($r, $g, $b) = $this->colorModeConverter->hsl2rgb($h, $s, $l);
            $ret = [3, $r, $g, $b];
        } elseif ($c[0] == static::MODE_CMYK || $c[0] == static::MODE_CMYKA) {
            $ret = [4, \min(100, \ord($c[1]) + 20), \min(100, \ord($c[2]) + 20), \min(100, \ord($c[3]) + 20), \min(100, \ord($c[4]) + 20)];
        } elseif ($c[0] == static::MODE_GRAYSCALE) {
            $ret = [1, \max(0, \ord($c[1]) - 32)];
        }
        $c = \array_pad($ret, 6, 0);
        $cstr = \pack('a1ccccc', $c[0], $c[1] & 0xff, $c[2] & 0xff, $c[3] & 0xff, $c[4] & 0xff, $c[5] & 0xff);
        return $cstr;
    }
    /**
     * @param string $c
     * @return float[]
     */
    public function invert($c)
    {
        $this->ensureBinaryColorFormat($c);
        if ($c[0] == static::MODE_RGB || $c[0] == static::MODE_RGBA) {
            return [3, 255 - \ord($c[1]), 255 - \ord($c[2]), 255 - \ord($c[3])];
        }
        if ($c[0] == static::MODE_CMYK || $c[0] == static::MODE_CMYKA) {
            return [4, 100 - \ord($c[1]), 100 - \ord($c[2]), 100 - \ord($c[3]), 100 - \ord($c[4])];
        }
        if ($c[0] == static::MODE_GRAYSCALE) {
            return [1, 255 - \ord($c[1])];
        }
        // Cannot cope with non-RGB colors at present
        throw new \DynamicOOOS\Mpdf\MpdfException('Trying to invert non-RGB color');
    }
    /**
     * @param string $c Binary color string
     *
     * @return string
     */
    public function colAtoString($c)
    {
        if ($c[0] == static::MODE_GRAYSCALE) {
            return 'rgb(' . \ord($c[1]) . ', ' . \ord($c[1]) . ', ' . \ord($c[1]) . ')';
        }
        if ($c[0] == static::MODE_SPOT) {
            return 'spot(' . \ord($c[1]) . ', ' . \ord($c[2]) . ')';
        }
        if ($c[0] == static::MODE_RGB) {
            return 'rgb(' . \ord($c[1]) . ', ' . \ord($c[2]) . ', ' . \ord($c[3]) . ')';
        }
        if ($c[0] == static::MODE_CMYK) {
            return 'cmyk(' . \ord($c[1]) . ', ' . \ord($c[2]) . ', ' . \ord($c[3]) . ', ' . \ord($c[4]) . ')';
        }
        if ($c[0] == static::MODE_RGBA) {
            return 'rgba(' . \ord($c[1]) . ', ' . \ord($c[2]) . ', ' . \ord($c[3]) . ', ' . \sprintf('%0.2F', \ord($c[4]) / 100) . ')';
        }
        if ($c[0] == static::MODE_CMYKA) {
            return 'cmyka(' . \ord($c[1]) . ', ' . \ord($c[2]) . ', ' . \ord($c[3]) . ', ' . \ord($c[4]) . ', ' . \sprintf('%0.2F', \ord($c[5]) / 100) . ')';
        }
        return '';
    }
    /**
     * @param string $color
     * @param string[] $PDFAXwarnings
     *
     * @return bool|float[]
     */
    private function convertPlain($color, array &$PDFAXwarnings = [])
    {
        $c = \false;
        if (\preg_match('/^[\\d]+$/', $color)) {
            $c = [static::MODE_GRAYSCALE, $color];
            // i.e. integer only
        } elseif (\strpos($color, '#') === 0) {
            // case of #nnnnnn or #nnn
            $c = $this->processHashColor($color);
        } elseif (\preg_match('/(rgba|rgb|device-cmyka|cmyka|device-cmyk|cmyk|hsla|hsl|spot)\\((.*?)\\)/', $color, $m)) {
            // quickfix for color containing CSS variable
            \preg_match('/var\\(--([a-z-_]+)\\)/i', $m[0], $var);
            if ($var) {
                $m[2] = '0, 0, 0, 100';
            }
            $c = $this->processModeColor($m[1], \explode(',', $m[2]));
        }
        if ($this->mpdf->PDFA || $this->mpdf->PDFX || $this->mpdf->restrictColorSpace) {
            $c = $this->restrictColorSpace($c, $color, $PDFAXwarnings);
        }
        return $c;
    }
    /**
     * @param string $color
     *
     * @return float[]
     */
    private function processHashColor($color)
    {
        // in case of Background: #CCC url() x-repeat etc.
        $cor = \preg_replace('/\\s+.*/', '', $color);
        // Turn #RGB into #RRGGBB
        if (\strlen($cor) === 4) {
            $cor = '#' . $cor[1] . $cor[1] . $cor[2] . $cor[2] . $cor[3] . $cor[3];
        }
        $r = self::safeHexDec(\substr($cor, 1, 2));
        $g = self::safeHexDec(\substr($cor, 3, 2));
        $b = self::safeHexDec(\substr($cor, 5, 2));
        return [3, $r, $g, $b];
    }
    /**
     * @param $mode
     * @param mixed[] $cores
     * @return bool|float[]
     */
    private function processModeColor($mode, array $cores)
    {
        $c = \false;
        $cores = $this->convertPercentCoreValues($mode, $cores);
        switch ($mode) {
            case 'rgb':
                return [static::MODE_RGB, $cores[0], $cores[1], $cores[2]];
            case 'rgba':
                return [static::MODE_RGBA, $cores[0], $cores[1], $cores[2], $cores[3] * 100];
            case 'cmyk':
            case 'device-cmyk':
                return [static::MODE_CMYK, $cores[0], $cores[1], $cores[2], $cores[3]];
            case 'cmyka':
            case 'device-cmyka':
                return [static::MODE_CMYKA, $cores[0], $cores[1], $cores[2], $cores[3], $cores[4] * 100];
            case 'hsl':
                $conv = $this->colorModeConverter->hsl2rgb($cores[0] / 360, $cores[1], $cores[2]);
                return [static::MODE_RGB, $conv[0], $conv[1], $conv[2]];
            case 'hsla':
                $conv = $this->colorModeConverter->hsl2rgb($cores[0] / 360, $cores[1], $cores[2]);
                return [static::MODE_RGBA, $conv[0], $conv[1], $conv[2], $cores[3] * 100];
            case 'spot':
                $name = \strtoupper(\trim($cores[0]));
                if (!isset($this->mpdf->spotColors[$name])) {
                    if (isset($cores[5])) {
                        $this->mpdf->AddSpotColor($cores[0], $cores[2], $cores[3], $cores[4], $cores[5]);
                    } else {
                        throw new \DynamicOOOS\Mpdf\MpdfException(\sprintf('Undefined spot color "%s"', $name));
                    }
                }
                return [static::MODE_SPOT, $this->mpdf->spotColors[$name]['i'], $cores[1]];
        }
        return $c;
    }
    /**
     * @param string $mode
     * @param mixed[] $cores
     *
     * @return float[]
     */
    private function convertPercentCoreValues($mode, array $cores)
    {
        $ncores = \count($cores);
        if (\strpos($cores[0], '%') !== \false) {
            $cores[0] = (float) $cores[0];
            if ($mode === 'rgb' || $mode === 'rgba') {
                $cores[0] = (int) ($cores[0] * 255 / 100);
            }
        }
        if ($ncores > 1 && \strpos($cores[1], '%') !== \false) {
            $cores[1] = (float) $cores[1];
            if ($mode === 'rgb' || $mode === 'rgba') {
                $cores[1] = (int) ($cores[1] * 255 / 100);
            }
            if ($mode === 'hsl' || $mode === 'hsla') {
                $cores[1] /= 100;
            }
        }
        if ($ncores > 2 && \strpos($cores[2], '%') !== \false) {
            $cores[2] = (float) $cores[2];
            if ($mode === 'rgb' || $mode === 'rgba') {
                $cores[2] = (int) ($cores[2] * 255 / 100);
            }
            if ($mode === 'hsl' || $mode === 'hsla') {
                $cores[2] /= 100;
            }
        }
        if ($ncores > 3 && \strpos($cores[3], '%') !== \false) {
            $cores[3] = (float) $cores[3];
        }
        return $cores;
    }
    /**
     * @param mixed $c
     * @param string $color
     * @param string[] $PDFAXwarnings
     *
     * @return float[]
     */
    private function restrictColorSpace($c, $color, &$PDFAXwarnings = [])
    {
        return $this->colorSpaceRestrictor->restrictColorSpace($c, $color, $PDFAXwarnings);
    }
    /**
     * @param string $color Binary color string
     */
    private function ensureBinaryColorFormat($color)
    {
        if (!\is_string($color)) {
            throw new \DynamicOOOS\Mpdf\MpdfException('Invalid color input, binary color string expected');
        }
        if (\strlen($color) !== 6) {
            throw new \DynamicOOOS\Mpdf\MpdfException('Invalid color input, binary color string expected');
        }
        if (!\in_array($color[0], [static::MODE_GRAYSCALE, static::MODE_SPOT, static::MODE_RGB, static::MODE_CMYK, static::MODE_RGBA, static::MODE_CMYKA])) {
            throw new \DynamicOOOS\Mpdf\MpdfException('Invalid color input, invalid color mode in binary color string');
        }
    }
    /**
     * Converts the given hexString to its decimal representation when all digits are hexadecimal
     *
     * @param string $hexString The hexadecimal string to convert
     * @return float|int The decimal representation of hexString or 0 if not all digits of hexString are hexadecimal
     */
    private function safeHexDec($hexString)
    {
        return \ctype_xdigit($hexString) ? \hexdec($hexString) : 0;
    }
}
