<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
/* 
 * This file is part of the KDM-UI plugin and just converted from JS to PHP from the original source.
 * Original Source: https://www.paigeniedringhaus.com/blog/change-svg-color-with-help-from-css-filter
 * 
 */
class KIRBColor {
    public $r;
    public $g;
    public $b;

    public function __construct($r, $g, $b) {
        $this->set($r, $g, $b);
    }

    public function __toString() {
        return "rgb(" . round($this->r) . ", " . round($this->g) . ", " . round($this->b) . ")";
    }

    public function set($r, $g, $b) {
        $this->r = $this->clamp($r);
        $this->g = $this->clamp($g);
        $this->b = $this->clamp($b);
    }

    public function hueRotate($angle = 0) {
        $angle = ($angle / 180) * M_PI;
        $sin = sin($angle);
        $cos = cos($angle);

        $this->multiply([
            0.213 + $cos * 0.787 - $sin * 0.213,
            0.715 - $cos * 0.715 - $sin * 0.715,
            0.072 - $cos * 0.072 + $sin * 0.928,
            0.213 - $cos * 0.213 + $sin * 0.143,
            0.715 + $cos * 0.285 + $sin * 0.14,
            0.072 - $cos * 0.072 - $sin * 0.283,
            0.213 - $cos * 0.213 - $sin * 0.787,
            0.715 - $cos * 0.715 + $sin * 0.715,
            0.072 + $cos * 0.928 + $sin * 0.072,
        ]);
    }

    public function grayscale($value = 1) {
        $this->multiply([
            0.2126 + 0.7874 * (1 - $value),
            0.7152 - 0.7152 * (1 - $value),
            0.0722 - 0.0722 * (1 - $value),
            0.2126 - 0.2126 * (1 - $value),
            0.7152 + 0.2848 * (1 - $value),
            0.0722 - 0.0722 * (1 - $value),
            0.2126 - 0.2126 * (1 - $value),
            0.7152 - 0.7152 * (1 - $value),
            0.0722 + 0.9278 * (1 - $value),
        ]);
    }

    public function sepia($value = 1) {
        $this->multiply([
            0.393 + 0.607 * (1 - $value),
            0.769 - 0.769 * (1 - $value),
            0.189 - 0.189 * (1 - $value),
            0.349 - 0.349 * (1 - $value),
            0.686 + 0.314 * (1 - $value),
            0.168 - 0.168 * (1 - $value),
            0.272 - 0.272 * (1 - $value),
            0.534 - 0.534 * (1 - $value),
            0.131 + 0.869 * (1 - $value),
        ]);
    }

    public function saturate($value = 1) {
        $this->multiply([
            0.213 + 0.787 * $value,
            0.715 - 0.715 * $value,
            0.072 - 0.072 * $value,
            0.213 - 0.213 * $value,
            0.715 + 0.285 * $value,
            0.072 - 0.072 * $value,
            0.213 - 0.213 * $value,
            0.715 - 0.715 * $value,
            0.072 + 0.928 * $value,
        ]);
    }

    public function multiply($matrix) {
        $newR = $this->clamp(
            $this->r * $matrix[0] + $this->g * $matrix[1] + $this->b * $matrix[2]
        );
        $newG = $this->clamp(
            $this->r * $matrix[3] + $this->g * $matrix[4] + $this->b * $matrix[5]
        );
        $newB = $this->clamp(
            $this->r * $matrix[6] + $this->g * $matrix[7] + $this->b * $matrix[8]
        );
        $this->r = $newR;
        $this->g = $newG;
        $this->b = $newB;
    }

    public function brightness($value = 1) {
        $this->linear($value);
    }

    public function contrast($value = 1) {
        $this->linear($value, -(0.5 * $value) + 0.5);
    }

    public function linear($slope = 1, $intercept = 0) {
        $this->r = $this->clamp($this->r * $slope + $intercept * 255);
        $this->g = $this->clamp($this->g * $slope + $intercept * 255);
        $this->b = $this->clamp($this->b * $slope + $intercept * 255);
    }

    public function invert($value = 1) {
        $this->r = $this->clamp(($value + ($this->r / 255) * (1 - 2 * $value)) * 255);
        $this->g = $this->clamp(($value + ($this->g / 255) * (1 - 2 * $value)) * 255);
        $this->b = $this->clamp(($value + ($this->b / 255) * (1 - 2 * $value)) * 255);
    }

    public function hsl() {
        $r = $this->r / 255;
        $g = $this->g / 255;
        $b = $this->b / 255;
        $max = max($r, $g, $b);
        $min = min($r, $g, $b);
        $l = ($max + $min) / 2;

        if ($max === $min) {
            $h = $s = 0;
        } else {
            $d = $max - $min;
            $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
            switch ($max) {
                case $r:
                    $h = ($g - $b) / $d + ($g < $b ? 6 : 0);
                    break;
                case $g:
                    $h = ($b - $r) / $d + 2;
                    break;
                case $b:
                    $h = ($r - $g) / $d + 4;
                    break;
            }
            $h /= 6;
        }

        return [
            'h' => $h * 100,
            's' => $s * 100,
            'l' => $l * 100,
        ];
    }

    private function clamp($value) {
        if ($value > 255) {
            $value = 255;
        } else if ($value < 0) {
            $value = 0;
        }
        return $value;
    }
}

class KIRBColorSolver {
    private $target;
    private $targetHSL;
    private $reusedColor;

    public function __construct($target) {
        $this->target = $target;
        $this->targetHSL = $target->hsl();
        $this->reusedColor = new KIRBColor(0, 0, 0);
    }

    public function solve() {
        $result = $this->solveNarrow($this->solveWide());
        return [
            'values' => $result['values'],
            'loss' => $result['loss'],
            'filter' => $this->css($result['values']),
        ];
    }

    private function solveWide() {
        $A = 5;
        $c = 15;
        $a = [60, 180, 18000, 600, 1.2, 1.2];

        $best = ['loss' => INF];
        for ($i = 0; $best['loss'] > 25 && $i < 3; $i++) {
            $initial = [50, 20, 3750, 50, 100, 100];
            $result = $this->spsa($A, $a, $c, $initial, 1000);
            if ($result['loss'] < $best['loss']) {
                $best = $result;
            }
        }
        return $best;
    }

    private function solveNarrow($wide) {
        $A = $wide['loss'];
        $c = 2;
        $A1 = $A + 1;
        $a = [0.25 * $A1, 0.25 * $A1, $A1, 0.25 * $A1, 0.2 * $A1, 0.2 * $A1];
        return $this->spsa($A, $a, $c, $wide['values'], 500);
    }

    private function spsa($A, $a, $c, $values, $iters) {
        $alpha = 1;
        $gamma = 0.16666666666666666;

        $best = null;
        $bestLoss = INF;
        $deltas = array_fill(0, 6, 0);
        $highArgs = array_fill(0, 6, 0);
        $lowArgs = array_fill(0, 6, 0);

        for ($k = 0; $k < $iters; $k++) {
            $ck = $c / pow($k + 1, $gamma);
            for ($i = 0; $i < 6; $i++) {
                $deltas[$i] = wp_rand(0, 1) > 0.5 ? 1 : -1;
                $highArgs[$i] = $values[$i] + $ck * $deltas[$i];
                $lowArgs[$i] = $values[$i] - $ck * $deltas[$i];
            }

            $lossDiff = $this->loss($highArgs) - $this->loss($lowArgs);
            for ($i = 0; $i < 6; $i++) {
                $g = ($lossDiff / (2 * $ck)) * $deltas[$i];
                $ak = $a[$i] / pow($A + $k + 1, $alpha);
                $values[$i] = $this->fix($values[$i] - $ak * $g, $i);
            }

            $loss = $this->loss($values);
            if ($loss < $bestLoss) {
                $best = $values;
                $bestLoss = $loss;
            }
        }
        return ['values' => $best, 'loss' => $bestLoss];
    }

    private function fix($value, $idx) {
        $max = 100;
        if ($idx === 2) {
            $max = 7500;
        } else if ($idx === 4 || $idx === 5) {
            $max = 200;
        }

        if ($idx === 3) {
            if ($value > $max) {
                $value = (int) $value % (int) $max;
            } else if ($value < 0) {
                $value = (int) $max + ((int) $value % (int) $max);
            }
        } else if ($value < 0) {
            $value = 0;
        } else if ($value > $max) {
            $value = $max;
        }
        return $value;
    }

    private function loss($filters) {
        $color = $this->reusedColor;
        $color->set(0, 0, 0);

        $color->invert($filters[0] / 100);
        $color->sepia($filters[1] / 100);
        $color->saturate($filters[2] / 100);
        $color->hueRotate($filters[3] * 3.6);
        $color->brightness($filters[4] / 100);
        $color->contrast($filters[5] / 100);

        $colorHSL = $color->hsl();
        return (
            abs($color->r - $this->target->r) +
            abs($color->g - $this->target->g) +
            abs($color->b - $this->target->b) +
            abs($colorHSL['h'] - $this->targetHSL['h']) +
            abs($colorHSL['s'] - $this->targetHSL['s']) +
            abs($colorHSL['l'] - $this->targetHSL['l'])
        );
    }

    private function css($filters) {
        return sprintf(
            "invert(%d%%) sepia(%d%%) saturate(%d%%) hue-rotate(%ddeg) brightness(%d%%) contrast(%d%%)",
            round($filters[0]),
            round($filters[1]),
            round($filters[2]),
            round($filters[3] * 3.6),
            round($filters[4]),
            round($filters[5])
        );
    }
}
?>
