<?php

declare(strict_types=1);

namespace Cin\Quickpurchase;

use Exception;

final class ColorMan
{
  public static function rgb2hsl($color)
  {
    $r = $color['red'] / 255;
    $g = $color['green'] / 255;
    $b = $color['blue'] / 255;

    $max = max($r, $g, $b);
    $min = min($r, $g, $b);

    $l = ($max + $min) / 2;
    $d = $max - $min;

    if (0 === $d) {
      $h = $s = 0; // achromatic
    } else {
      $s = $d / (1 - abs(2 * $l - 1));

      switch ($max) {
        case $r:
          $h = 60 * fmod((($g - $b) / $d), 6);
          if ($b > $g) {
            $h += 360;
          }

          break;

        case $g:
          $h = 60 * (($b - $r) / $d + 2);

          break;

        case $b:
          $h = 60 * (($r - $g) / $d + 4);

          break;
      }
    }

    return ['h' => round($h, 2), 's' => round($s, 2), 'l' => round($l, 2)];
  }

  public static function hsl2rgb($hsl)
  {
    $h = $hsl['h'];
    $s = $hsl['s'];
    $l = $hsl['l'];

    $c = (1 - abs(2 * $l - 1)) * $s;
    $x = $c * (1 - abs(fmod(($h / 60), 2) - 1));
    $m = $l - ($c / 2);

    if ($h < 60) {
      $r = $c;
      $g = $x;
      $b = 0;
    } elseif ($h < 120) {
      $r = $x;
      $g = $c;
      $b = 0;
    } elseif ($h < 180) {
      $r = 0;
      $g = $c;
      $b = $x;
    } elseif ($h < 240) {
      $r = 0;
      $g = $x;
      $b = $c;
    } elseif ($h < 300) {
      $r = $x;
      $g = 0;
      $b = $c;
    } else {
      $r = $c;
      $g = 0;
      $b = $x;
    }

    $r = abs(($r + $m) * 255);
    $g = abs(($g + $m) * 255);
    $b = abs(($b + $m) * 255);

    return ['red' => (int) floor($r), 'green' => (int) floor($g), 'blue' => (int) floor($b)];
  }

  public static function colorDistance($reference, $actual)
  {
    return ($reference['red'] - $actual['red']) ^ 2 + ($reference['green'] - $actual['green']) ^ 2 + ($reference['blue'] - $actual['blue']) ^ 2;
  }

  public static function darkestPixel($img)
  {
    $width        = imagesx($img);
    $height       = imagesy($img);
    $darkestFlag  = 1;
    $darkestPixel = [
      'color' => [
        'red'   => null,
        'green' => null,
        'blue'  => null
      ],
      'hsl' => [
        'h' => null,
        's' => null,
        'l' => null
      ],
      'pos' => [
        'x' => null,
        'y' => null
      ]
    ];

    for ($x = 0; $x < $width; ++$x) {
      for ($y = 0; $y < $height; ++$y) {
        $rgb   = imagecolorat($img, $x, $y);
        $color = imagecolorsforindex($img, $rgb);
        $hsl   = self::rgb2hsl($color);
        if ($hsl['l'] < $darkestFlag && 0 === $color['alpha']) {
          $darkestFlag              = $hsl['l'];
          $darkestPixel['color']    = $color;
          $darkestPixel['hsl']      = $hsl;
          $darkestPixel['pos']['x'] = $x;
          $darkestPixel['pos']['y'] = $y;
        }
      }
    }

    return $darkestPixel;
  }

  public static function brightestPixel($img)
  {
    $width          = imagesx($img);
    $height         = imagesy($img);
    $brightestFlag  = 0;
    $brightestPixel = [
      'color' => [
        'red'   => null,
        'green' => null,
        'blue'  => null
      ],
      'hsl' => [
        'h' => null,
        's' => null,
        'l' => null
      ],
      'pos' => [
        'x' => null,
        'y' => null
      ]
    ];

    for ($x = 0; $x < $width; ++$x) {
      for ($y = 0; $y < $height; ++$y) {
        $rgb   = imagecolorat($img, $x, $y);
        $color = imagecolorsforindex($img, $rgb);
        $hsl   = self::rgb2hsl($color);
        if ($hsl['l'] > $brightestFlag && 0 === $color['alpha']) {
          $brightestFlag              = $hsl['l'];
          $brightestPixel['color']    = $color;
          $brightestPixel['hsl']      = $hsl;
          $brightestPixel['pos']['x'] = $x;
          $brightestPixel['pos']['y'] = $y;
        }
      }
    }

    return $brightestPixel;
  }

  public static function colorizeKeepAlpha($imgSrc, $targetColor)
  {
    $width  = imagesx($imgSrc);
    $height = imagesy($imgSrc);
    $imgDst = imagecreatetruecolor($width, $height);

    $targetHSL = self::rgb2hsl($targetColor);

    imagefill($imgDst, 0, 0, \IMG_COLOR_TRANSPARENT);
    imagesavealpha($imgDst, true);
    imagealphablending($imgDst, true);

    for ($x = 0; $x < $width; ++$x) {
      for ($y = 0; $y < $height; ++$y) {
        $rgb             = imagecolorat($imgSrc, $x, $y);
        $colorCurrentRGB = imagecolorsforindex($imgSrc, $rgb);
        $alpha           = $colorCurrentRGB['alpha'];
        $colorCurrentHSL = self::rgb2hsl($colorCurrentRGB);
        $colorNewRGB     = self::hsl2rgb(['h' => $targetHSL['h'], 's' => $targetHSL['s'], 'l' => $colorCurrentHSL['l']]);
        $colorNew        = imagecolorallocatealpha($imgSrc, $colorNewRGB['red'], $colorNewRGB['green'], $colorNewRGB['blue'], $alpha);
        imagesetpixel($imgDst, $x, $y, $colorNew);
      }
    }

    return $imgDst;
  }

  public static function replaceColorKeepAlpha($imgSrc, $originColor, $targetColor)
  {
    $dims   = self::dims($imgSrc);
    $imgDst = imagecreatetruecolor($dims[0], $dims[1]);
    imagefill($imgDst, 0, 0, \IMG_COLOR_TRANSPARENT);
    imagesavealpha($imgDst, true);
    imagealphablending($imgDst, true);

    for ($x = 0; $x < $dims[0]; ++$x) {
      for ($y = 0; $y < $dims[1]; ++$y) {
        $rgb                  = imagecolorat($imgSrc, $x, $y);
        $colorCurrentRGB      = imagecolorsforindex($imgSrc, $rgb);
        $alpha                = $colorCurrentRGB['alpha'];
        $originColor['alpha'] = $alpha;
        $colorNewRGB          = imagecolorallocatealpha($imgSrc, $colorCurrentRGB['red'], $colorCurrentRGB['green'], $colorCurrentRGB['blue'], $alpha);
        if ($colorCurrentRGB === $originColor) {
          $colorNewRGB = imagecolorallocatealpha($imgSrc, $targetColor['red'], $targetColor['green'], $targetColor['blue'], $alpha);
        }
        imagesetpixel($imgDst, $x, $y, $colorNewRGB);
      }
    }

    return $imgDst;
  }

  public static function hex2rgb($hex)
  {
    $hex = str_replace('#', '', $hex);

    if (3 === mb_strlen($hex)) {
      $r = hexdec(mb_substr($hex, 0, 1).mb_substr($hex, 0, 1));
      $g = hexdec(mb_substr($hex, 1, 1).mb_substr($hex, 1, 1));
      $b = hexdec(mb_substr($hex, 2, 1).mb_substr($hex, 2, 1));
    } else {
      $r = hexdec(mb_substr($hex, 0, 2));
      $g = hexdec(mb_substr($hex, 2, 2));
      $b = hexdec(mb_substr($hex, 4, 2));
    }

    return ['red' => $r, 'green' => $g, 'blue' => $b];
  }

  public static function rgb2hex($rgb)
  {
    $hex = '#';
    $hex .= mb_str_pad(dechex($rgb['red']), 2, '0', \STR_PAD_LEFT);
    $hex .= mb_str_pad(dechex($rgb['green']), 2, '0', \STR_PAD_LEFT);
    $hex .= mb_str_pad(dechex($rgb['blue']), 2, '0', \STR_PAD_LEFT);

    return $hex;
  }

  public static function readimage($imagePath)
  {
    $imageType = exif_imagetype($imagePath);
    if ( ! $imageType === (\IMAGETYPE_PNG || \IMAGETYPE_JPEG || \IMAGETYPE_GIF)) {
      throw new Exception('Das ist kein Bild?!');
    }
    $image = imagecreatefromstring(file_get_contents($imagePath));
    imagealphablending($image, true);
    imagesavealpha($image, true);

    return $image;
  }

  public static function combineimages($stack)
  {
    $largestDims = self::largestDimsInStack($stack);
    $imgDst      = imagecreatetruecolor($largestDims[0], $largestDims[1]);
    imagealphablending($imgDst, true);
    imagesavealpha($imgDst, true);
    imagefill($imgDst, 0, 0, \IMG_COLOR_TRANSPARENT);
    foreach ($stack as $layer) {
      $currentDims = self::dims($layer);
      imagealphablending($layer, true);
      imagesavealpha($layer, true);
      imagecopy($imgDst, $layer, 0, 0, 0, 0, $currentDims[0], $currentDims[1]);
    }

    return $imgDst;
  }

  public static function largestDimsInStack($stack)
  {
    $dims = [0, 0];
    foreach ($stack as $layer) {
      $current = self::dims($layer);
      if ($current[0] > $dims[0] || $current[1] > $dims[1]) {
        $dims = $current;
      }
    }

    return $dims;
  }

  public static function dims($img)
  {
    $width  = imagesx($img);
    $height = imagesy($img);

    return [$width, $height];
  }

  public static function scaleMax($imgSrc, $dimTrg)
  {
    imagealphablending($imgSrc, true);
    imagesavealpha($imgSrc, true);

    $dimsImg = self::dims($imgSrc);
    $ratImg  = $dimsImg[0] / $dimsImg[1];
    $dimsTrg = [];

    if ($ratImg <= 1) {
      $dimsTrg[1] = (int) $dimTrg;
      $dimsTrg[0] = (int) ($ratImg * $dimTrg);
    }

    if ($ratImg > 1) {
      $dimsTrg[0] = (int) $dimTrg;
      $dimsTrg[1] = (int) ($dimTrg / $ratImg);
    }

    $imgTrg = imagecreatetruecolor($dimsTrg[0], $dimsTrg[1]);
    $color  = imagecolorallocatealpha($imgTrg, 0, 0, 0, 127);
    imagefill($imgTrg, 0, 0, $color);
    imagealphablending($imgTrg, false);
    imagesavealpha($imgTrg, true);
    imagecopyresampled($imgTrg, $imgSrc, 0, 0, 0, 0, $dimsTrg[0], $dimsTrg[1], $dimsImg[0], $dimsImg[1]);
    imagesavealpha($imgTrg, true);

    return $imgTrg;
  }

  public static function scaleMin($imgSrc, $dimTrg)
  {
    imagealphablending($imgSrc, false);
    imagesavealpha($imgSrc, true);

    $dimsImg = self::dims($imgSrc);
    $ratImg  = $dimsImg[0] / $dimsImg[1];
    $dimsTrg = [];

    if ($ratImg <= 1) { // portrait
      $dimsTrg[0] = (int) $dimTrg;
      $dimsTrg[1] = (int) ($dimTrg / $ratImg);
    }

    if ($ratImg > 1) { // landscape
      $dimsTrg[1] = (int) $dimTrg;
      $dimsTrg[0] = (int) $dimTrg * $ratImg;
    }

    $imgTrg = imagecreatetruecolor($dimsTrg[0], $dimsTrg[1]);
    $color  = imagecolorallocatealpha($imgTrg, 0, 0, 0, 127);
    imagefill($imgTrg, 0, 0, $color);
    imagealphablending($imgTrg, false);
    imagesavealpha($imgTrg, true);
    imagecopyresampled($imgTrg, $imgSrc, 0, 0, 0, 0, $dimsTrg[0], $dimsTrg[1], $dimsImg[0], $dimsImg[1]);
    imagesavealpha($imgTrg, true);

    return $imgTrg;
  }

  public static function tileStack($stack, $columns = null, $spacing = 0, $bgcolor = '#FFFFFF', $opacity = 0)
  {
    $dims     = self::smallestDimsInStack($stack);
    $tileSize = min($dims);
    if ($columns) {
      if ($columns > \count($stack)) {
        $columns = \count($stack);
      }
      $stackWidth  = $columns;
      $stackHeight = ceil(\count($stack) / $stackWidth);
    } else {
      $stackWidth = $stackHeight = round(sqrt(\count($stack)));
      if ($stackWidth ** 2 < \count($stack)) {
        ++$stackHeight;
      }
      if ($stackHeight > $stackWidth) {
        $tmp         = $stackHeight;
        $stackHeight = $stackWidth;
        $stackWidth  = $tmp;
      }
    }
    $trgWidth  = (int) ($tileSize * $stackWidth + ($spacing * ($stackWidth - 1)));
    $trgHeight = (int) ($tileSize * $stackHeight + ($spacing * ($stackHeight - 1)));
    $imgDst    = imagecreatetruecolor($trgWidth, $trgHeight);
    imagealphablending($imgDst, true);
    imagesavealpha($imgDst, true);
    if (null === $bgcolor) {
      imagefill($imgDst, 0, 0, \IMG_COLOR_TRANSPARENT);
    } else {
      $bgcolor = self::hex2rgb($bgcolor);
      $color   = imagecolorallocatealpha($imgDst, $bgcolor['red'], $bgcolor['green'], $bgcolor['blue'], (int) 127 * ($opacity / 1));
      imagefill($imgDst, 0, 0, $color);
      imagecolortransparent($imgDst, $color);
    }
    $idx = 0;
    for ($y = 0; $y < $stackHeight; ++$y) {
      for ($x = 0; $x < $stackWidth; ++$x) {
        if (isset($stack[$idx])) {
          imagealphablending($stack[$idx], true);
          imagesavealpha($stack[$idx], true);
          imagecopy($imgDst, $stack[$idx], $x * $tileSize + $x * $spacing, $y * $tileSize + $y * $spacing, 0, 0, $tileSize, $tileSize);
        }
        ++$idx;
      }
    }

    return $imgDst;
  }

  public static function smallestDimsInStack($stack)
  {
    $dims = self::dims($stack[0]);
    foreach ($stack as $image) {
      $currentDims = self::dims($image);
      if ($currentDims[0] < $dims[0] || $currentDims[1] < $dims[1]) {
        $dims = $currentDims;
      }
    }

    return $dims;
  }

  public static function punchCenter($imgSrc)
  {
    imagealphablending($imgSrc, true);
    imagesavealpha($imgSrc, true);
    $dimsSrc    = self::dims($imgSrc);
    $edgeLength = (int) min($dimsSrc);
    $imgDst     = imagecreatetruecolor($edgeLength, $edgeLength);
    $color      = imagecolorallocatealpha($imgDst, 255, 255, 255, 127);
    imagefill($imgDst, 0, 0, $color);
    imagecolortransparent($imgDst, $color);
    imagealphablending($imgDst, true);
    imagesavealpha($imgDst, true);
    $offsetX = floor((max($dimsSrc) - min($dimsSrc)) / 2);
    $offsetY = 0;
    if ($dimsSrc[0] <= $dimsSrc[1]) {
      $offsetY = $offsetX;
      $offsetX = 0;
    }
    imagecopy($imgDst, $imgSrc, 0, 0, $offsetX, $offsetY, $edgeLength, $edgeLength);

    return $imgDst;
  }

  public static function cropToRatio($imgSrc, $ratio)
  {
    $dims = self::dims($imgSrc);
    imagealphablending($imgSrc, false);
    imagesavealpha($imgSrc, true);
    if (1 === $ratio) {
      $targetX = $targetY = (int) min($dims);
    }

    if ($ratio > 1) {
      if ($dims[0] / $ratio <= $dims[1]) {
        $targetX = (int) $dims[0];
        $targetY = (int) ($targetX / $ratio);
      }
      if ($dims[0] / $ratio > $dims[1]) {
        $targetY = (int) $dims[1];
        $targetX = (int) ($targetY * $ratio);
      }
    }

    if ($ratio < 1) {
      if ($dims[0] / $ratio <= $dims[1]) {
        $targetX = (int) $dims[0];
        $targetY = (int) ($targetX / $ratio);
      }
      if ($dims[0] / $ratio > $dims[1]) {
        $targetY = (int) $dims[1];
        $targetX = (int) ($targetY * $ratio);
      }
    }

    $imgTrg = imagecreatetruecolor($targetX, $targetY);
    $color  = imagecolorallocatealpha($imgTrg, 0, 0, 0, 127);
    imagefill($imgTrg, 0, 0, $color);
    imagealphablending($imgTrg, false);
    imagesavealpha($imgTrg, true);
    $offsetX = (int) (($dims[0] - $targetX) / 2);
    $offsetY = (int) (($dims[1] - $targetY) / 2);
    imagecopyresampled($imgTrg, $imgSrc, 0, 0, $offsetX, $offsetY, $targetX, $targetY, $targetX, $targetY);

    return $imgTrg;
  }

  public static function scaleTo($imgSrc, $width, $height)
  {
    $srcDims = self::dims($imgSrc);
    $imgTrg  = imagecreatetruecolor((int) $width, (int) $height);
    imagecopyresampled($imgTrg, $imgSrc, 0, 0, 0, 0, (int) $width, (int) $height, $srcDims[0], $srcDims[1]);

    return $imgTrg;
  }

  public static function fetchPNGBase64($imgSrc)
  {
    imagesavealpha($imgSrc, true);
    imagealphablending($imgSrc, true);
    $file = tempnam(sys_get_temp_dir(), 'FOO');
    imagepng($imgSrc, $file);
    imagedestroy($imgSrc);
    $encoded = 'data:image/png;base64,'.base64_encode(file_get_contents($file));
    unlink($file);

    return $encoded;
  }

  public static function fetchBase64PNG($srcBase64)
  {
    $data = base64_decode(explode(',', $srcBase64)[1], true);
    $img  = imagecreatefromstring($data);
    imagesavealpha($img, true);
    imagealphablending($img, true);

    return $img;
  }

  public static function fetchPNG($imgSrc)
  {
    imagesavealpha($imgSrc, true);
    imagealphablending($imgSrc, true);
    $file = tempnam(sys_get_temp_dir(), 'FOO');
    imagepng($imgSrc, $file);
    imagedestroy($imgSrc);
    $png = file_get_contents($file);
    unlink($file);

    return $png;
  }

  public static function paddToSquare($imgSrc)
  {
    $srcDims = self::dims($imgSrc);
    $size    = (int) max($srcDims);
    $imgTrg  = imagecreatetruecolor($size, $size);
    $alpha   = imagecolorallocatealpha($imgTrg, 0, 0, 0, 127);
    imagefill($imgTrg, 0, 0, $alpha);
    imagealphablending($imgTrg, false);
    imagesavealpha($imgTrg, true);
    $offsetX = (int) (($srcDims[1] - $srcDims[0]) / 2);
    $offsetY = 0;
    if ($srcDims[0] > $srcDims[1]) {
      $offsetX = 0;
      $offsetY = (int) (($srcDims[0] - $srcDims[1]) / 2);
    }
    imagecopy($imgTrg, $imgSrc, $offsetX, $offsetY, 0, 0, $srcDims[0], $srcDims[1]);

    return $imgTrg;
  }

  public static function cropalpha($imgSrc)
  {
    $cropped = imagecropauto($imgSrc);

    return $cropped ? $cropped : $imgSrc;
  }
}
