<?php

namespace EstatebudConnect\Utilities;

if (!defined('WPINC')) {
    die();
}

class Color
{
    private $_hex;
    private $_hsl;
    private $_rgb;
    public const DEFAULT_ADJUST = 10;

    public function __construct(string $hex)
    {
        $color = self::sanitize_hex($hex);
        $this->_hex = $color;
        $this->_hsl = self::hex_to_hsl($color);
        $this->_rgb = self::hex_to_rgb($color);
    }

    public static function hex_to_hsl(string $color): array
    {
        $color = self::sanitize_hex($color);

        $R = hexdec($color[0] . $color[1]);
        $G = hexdec($color[2] . $color[3]);
        $B = hexdec($color[4] . $color[5]);

        $HSL = [];

        $var_R = $R / 255;
        $var_G = $G / 255;
        $var_B = $B / 255;

        $var_Min = min($var_R, $var_G, $var_B);
        $var_Max = max($var_R, $var_G, $var_B);
        $del_Max = $var_Max - $var_Min;

        $L = ($var_Max + $var_Min) / 2;

        if ($del_Max == 0) {
            $H = 0;
            $S = 0;
        } else {
            if ($L < 0.5) {
                $S = $del_Max / ($var_Max + $var_Min);
            } else {
                $S = $del_Max / (2 - $var_Max - $var_Min);
            }

            $del_R = (($var_Max - $var_R) / 6 + $del_Max / 2) / $del_Max;
            $del_G = (($var_Max - $var_G) / 6 + $del_Max / 2) / $del_Max;
            $del_B = (($var_Max - $var_B) / 6 + $del_Max / 2) / $del_Max;

            if ($var_R == $var_Max) {
                $H = $del_B - $del_G;
            } elseif ($var_G == $var_Max) {
                $H = 1 / 3 + $del_R - $del_B;
            } elseif ($var_B == $var_Max) {
                $H = 2 / 3 + $del_G - $del_R;
            }

            if ($H < 0) {
                $H++;
            }
            if ($H > 1) {
                $H--;
            }
        }

        $HSL['H'] = $H * 360;
        $HSL['S'] = $S;
        $HSL['L'] = $L;

        return $HSL;
    }

    public static function hsl_to_hex(array $hsl = []): string
    {
        if (empty($hsl) || !isset($hsl['H'], $hsl['S'], $hsl['L'])) {
            throw new Exception('Param was not an HSL array');
        }

        [$H, $S, $L] = [$hsl['H'] / 360, $hsl['S'], $hsl['L']];

        if ($S == 0) {
            $r = $L * 255;
            $g = $L * 255;
            $b = $L * 255;
        } else {
            if ($L < 0.5) {
                $var_2 = $L * (1 + $S);
            } else {
                $var_2 = $L + $S - $S * $L;
            }

            $var_1 = 2 * $L - $var_2;

            $r = 255 * self::hue_to_rgb($var_1, $var_2, $H + 1 / 3);
            $g = 255 * self::hue_to_rgb($var_1, $var_2, $H);
            $b = 255 * self::hue_to_rgb($var_1, $var_2, $H - 1 / 3);
        }

        $r = dechex(round($r));
        $g = dechex(round($g));
        $b = dechex(round($b));

        $r = strlen('' . $r) === 1 ? '0' . $r : $r;
        $g = strlen('' . $g) === 1 ? '0' . $g : $g;
        $b = strlen('' . $b) === 1 ? '0' . $b : $b;

        return $r . $g . $b;
    }

    public static function hex_to_rgb(string $color): array
    {
        $color = self::sanitize_hex($color);

        $R = hexdec($color[0] . $color[1]);
        $G = hexdec($color[2] . $color[3]);
        $B = hexdec($color[4] . $color[5]);

        $RGB['R'] = $R;
        $RGB['G'] = $G;
        $RGB['B'] = $B;

        return $RGB;
    }

    public static function rgb_to_hex(array $rgb = []): string
    {
        if (empty($rgb) || !isset($rgb['R'], $rgb['G'], $rgb['B'])) {
            throw new Exception('Param was not an RGB array');
        }

        $hex[0] = str_pad(dechex((int) $rgb['R']), 2, '0', STR_PAD_LEFT);
        $hex[1] = str_pad(dechex((int) $rgb['G']), 2, '0', STR_PAD_LEFT);
        $hex[2] = str_pad(dechex((int) $rgb['B']), 2, '0', STR_PAD_LEFT);

        $hex[0] = strlen($hex[0]) === 1 ? '0' . $hex[0] : $hex[0];
        $hex[1] = strlen($hex[1]) === 1 ? '0' . $hex[1] : $hex[1];
        $hex[2] = strlen($hex[2]) === 1 ? '0' . $hex[2] : $hex[2];

        return implode('', $hex);
    }

    public static function rgb_to_string(array $rgb = []): string
    {
        if (empty($rgb) || !isset($rgb['R'], $rgb['G'], $rgb['B'])) {
            throw new Exception('Param was not an RGB array');
        }

        return 'rgb(' . $rgb['R'] . ', ' . $rgb['G'] . ', ' . $rgb['B'] . ')';
    }

    public function darken(int $amount = self::DEFAULT_ADJUST): string
    {
        $darkerHSL = $this->darken_hsl($this->_hsl, $amount);

        return self::hsl_to_hex($darkerHSL);
    }

    public function lighten(int $amount = self::DEFAULT_ADJUST): string
    {
        $lighterHSL = $this->lighten_hsl($this->_hsl, $amount);

        return self::hsl_to_hex($lighterHSL);
    }

    public function mix(string $hex2, int $amount = 0): string
    {
        $rgb2 = self::hex_to_rgb($hex2);
        $mixed = $this->mix_rgb($this->_rgb, $rgb2, $amount);

        return self::rgb_to_hex($mixed);
    }

    public function make_gradient(int $amount = self::DEFAULT_ADJUST): array
    {
        if ($this->isLight()) {
            $lightColor = $this->_hex;
            $darkColor = $this->darken($amount);
        } else {
            $lightColor = $this->lighten($amount);
            $darkColor = $this->_hex;
        }

        return ['light' => $lightColor, 'dark' => $darkColor];
    }

    public function isLight($color = false, int $lighterThan = 130): bool
    {
        $color = $color ? $color : $this->_hex;

        $r = hexdec($color[0] . $color[1]);
        $g = hexdec($color[2] . $color[3]);
        $b = hexdec($color[4] . $color[5]);

        return ($r * 299 + $g * 587 + $b * 114) / 1000 > $lighterThan;
    }

    public function is_dark($color = false, int $darkerThan = 130): bool
    {
        $color = $color ? $color : $this->_hex;

        $r = hexdec($color[0] . $color[1]);
        $g = hexdec($color[2] . $color[3]);
        $b = hexdec($color[4] . $color[5]);

        return ($r * 299 + $g * 587 + $b * 114) / 1000 <= $darkerThan;
    }

    public function complementary(): string
    {
        $hsl = $this->_hsl;
        $hsl['H'] += $hsl['H'] > 180 ? -180 : 180;

        return self::hsl_to_hex($hsl);
    }

    public function get_hsl(): array
    {
        return $this->_hsl;
    }

    public function get_hex(): string
    {
        return $this->_hex;
    }

    public function get_rgb(): array
    {
        return $this->_rgb;
    }

    private function darken_hsl(array $hsl, int $amount = self::DEFAULT_ADJUST): array
    {
        if ($amount) {
            $hsl['L'] = $hsl['L'] * 100 - $amount;
            $hsl['L'] = $hsl['L'] < 0 ? 0 : $hsl['L'] / 100;
        } else {
            $hsl['L'] /= 2;
        }

        return $hsl;
    }

    private function lighten_hsl(array $hsl, int $amount = self::DEFAULT_ADJUST): array
    {
        if ($amount) {
            $hsl['L'] = $hsl['L'] * 100 + $amount;
            $hsl['L'] = $hsl['L'] > 100 ? 1 : $hsl['L'] / 100;
        } else {
            $hsl['L'] += (1 - $hsl['L']) / 2;
        }

        return $hsl;
    }

    private function mix_rgb(array $rgb1, array $rgb2, int $amount = 0): array
    {
        $r1 = ($amount + 100) / 100;
        $r2 = 2 - $r1;

        $rmix = ($rgb1['R'] * $r1 + $rgb2['R'] * $r2) / 2;
        $gmix = ($rgb1['G'] * $r1 + $rgb2['G'] * $r2) / 2;
        $bmix = ($rgb1['B'] * $r1 + $rgb2['B'] * $r2) / 2;

        return ['R' => $rmix, 'G' => $gmix, 'B' => $bmix];
    }

    private static function hue_to_rgb(float $v1, float $v2, float $vH): float
    {
        if ($vH < 0) {
            ++$vH;
        }

        if ($vH > 1) {
            --$vH;
        }

        if (6 * $vH < 1) {
            return $v1 + ($v2 - $v1) * 6 * $vH;
        }

        if (2 * $vH < 1) {
            return $v2;
        }

        if (3 * $vH < 2) {
            return $v1 + ($v2 - $v1) * (2 / 3 - $vH) * 6;
        }

        return $v1;
    }

    private static function sanitize_hex(string $hex): string
    {
        $color = str_replace('#', '', $hex);

        if (!preg_match('/^[a-fA-F0-9]+$/', $color)) {
            throw new Exception('HEX color does not match format');
        }

        if (strlen($color) === 3) {
            $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
        } elseif (strlen($color) !== 6) {
            throw new Exception('HEX color needs to be 6 or 3 digits long');
        }

        return $color;
    }

    public function __to_string()
    {
        return '#' . $this->get_hex();
    }

    public function __get(string $name)
    {
        switch (strtolower($name)) {
            case 'red':
            case 'r':
                return $this->_rgb['R'];
            case 'green':
            case 'g':
                return $this->_rgb['G'];
            case 'blue':
            case 'b':
                return $this->_rgb['B'];
            case 'hue':
            case 'h':
                return $this->_hsl['H'];
            case 'saturation':
            case 's':
                return $this->_hsl['S'];
            case 'lightness':
            case 'l':
                return $this->_hsl['L'];
        }

        $trace = debug_backtrace();
        trigger_error(
            esc_html('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line']),
            E_USER_NOTICE,
        );
        return null;
    }

    public function __set(string $name, $value)
    {
        switch (strtolower($name)) {
            case 'red':
            case 'r':
                $this->_rgb['R'] = $value;
                $this->_hex = self::rgb_to_hex($this->_rgb);
                $this->_hsl = self::hex_to_hsl($this->_hex);
                break;
            case 'green':
            case 'g':
                $this->_rgb['G'] = $value;
                $this->_hex = self::rgb_to_hex($this->_rgb);
                $this->_hsl = self::hex_to_hsl($this->_hex);
                break;
            case 'blue':
            case 'b':
                $this->_rgb['B'] = $value;
                $this->_hex = self::rgb_to_hex($this->_rgb);
                $this->_hsl = self::hex_to_hsl($this->_hex);
                break;
            case 'hue':
            case 'h':
                $this->_hsl['H'] = $value;
                $this->_hex = self::hsl_to_hex($this->_hsl);
                $this->_rgb = self::hex_to_rgb($this->_hex);
                break;
            case 'saturation':
            case 's':
                $this->_hsl['S'] = $value;
                $this->_hex = self::hsl_to_hex($this->_hsl);
                $this->_rgb = self::hex_to_rgb($this->_hex);
                break;
            case 'lightness':
            case 'light':
            case 'l':
                $this->_hsl['L'] = $value;
                $this->_hex = self::hsl_to_hex($this->_hsl);
                $this->_rgb = self::hex_to_rgb($this->_hex);
                break;
        }
    }
}
