<?php

namespace XF\Image;

use XF\Util\File;

use function in_array;

class Imagick extends AbstractDriver
{
	/**
	 * @var \Imagick
	 */
	protected $imagick;

	public static function isDriverUsable()
	{
		return class_exists('Imagick');
	}

	protected function _imageFromFile($file, $type)
	{
		switch ($type)
		{
			case IMAGETYPE_GIF:
			case IMAGETYPE_JPEG:
			case IMAGETYPE_PNG:
			case IMAGETYPE_WEBP:
				$image = new \Imagick($file);
				break;

			default:
				throw new \InvalidArgumentException("Unknown image type '$type'");
		}

		$this->setImage($image);

		return true;
	}

	protected function _createImage($width, $height)
	{
		$image = new \Imagick();

		$background = new \ImagickPixel('white');
		$image->newImage($width, $height, $background);

		$this->setImage($image);

		return true;
	}

	public function getImage()
	{
		return $this->imagick;
	}

	public function setImage(\Imagick $image)
	{
		if (method_exists($image, 'setRegistry'))
		{
			$image::setRegistry('temporary-path', File::getTempDir());
		}

		$image->setImageBackgroundColor(new \ImagickPixel('transparent'));
		$image = $image->coalesceImages();

		$this->imagick = $image;

		$this->updateDimensions();
	}

	protected function updateDimensions()
	{
		$this->width = $this->imagick->getImageWidth();
		$this->height = $this->imagick->getImageHeight();
	}

	protected function supportsWebP(): bool
	{
		return method_exists($this->imagick, 'queryFormats') && in_array('WEBP', $this->imagick->queryFormats());
	}

	protected function supportsAnimatedWebP(): bool
	{
		$version = $this->imagick->getVersion();
		if (!preg_match(
			'#ImageMagick (\d+\.\d+\.\d+)-(\d+)#i',
			$version['versionString'],
			$match
		))
		{
			return false;
		}

		if (version_compare($match[1], '7.0.8', '<'))
		{
			return false;
		}

		if (version_compare($match[1], '7.0.8', '==') && (int) $match[2] < 68)
		{
			return false;
		}

		return true;
	}

	protected function isOldImagick()
	{
		// imagick module < 3 or ImageMagick < 6.3.2 don't support the 4th thumbnailImage param
		$oldImagick = version_compare(phpversion('imagick'), '3', '<');

		$version = $this->imagick->getVersion();
		if (preg_match('#ImageMagick (\d+\.\d+\.\d+)#i', $version['versionString'], $match))
		{
			if (version_compare($match[1], '6.3.2', '<'))
			{
				$oldImagick = true;
			}
		}

		return $oldImagick;
	}

	public function resizeTo($width, $height)
	{
		$scaleUp = ($width > $this->width || $height > $this->height);

		try
		{
			foreach ($this->imagick AS $frame)
			{
				if ($scaleUp)
				{
					$frame->resizeImage($width, $height, \Imagick::FILTER_QUADRATIC, .5, true);
				}
				else if ($this->isOldImagick())
				{
					$frame->thumbnailImage($width, $height, false);
				}
				else
				{
					$frame->thumbnailImage($width, $height, false, true);
				}
				$frame->setImagePage($width, $height, 0, 0);
			}

			$this->updateDimensions();
		}
		catch (\Exception $e)
		{
		}

		return $this;
	}

	public function crop($width, $height, $x = 0, $y = 0, $srcWidth = null, $srcHeight = null)
	{
		foreach ($this->imagick AS $frame)
		{
			$frame->cropImage($srcWidth ?: $width, $srcHeight ?: $height, $x, $y);
			if ($this->isOldImagick())
			{
				$frame->thumbnailImage($width, $height, false);
			}
			else
			{
				$frame->thumbnailImage($width, $height, false, true);
			}
			$frame->setImagePage($frame->getImageWidth(), $frame->getImageHeight(), 0, 0);
		}
		$this->updateDimensions();

		return $this;
	}

	public function rotate($angle)
	{
		foreach ($this->imagick AS $frame)
		{
			$frame->rotateImage(new \ImagickPixel('none'), $angle);
		}
		$this->updateDimensions();

		return $this;
	}

	public function flip($mode)
	{
		foreach ($this->imagick AS $frame)
		{
			switch ($mode)
			{
				case self::FLIP_HORIZONTAL:
					$frame->flopImage();
					break;

				case self::FLIP_VERTICAL:
					$frame->flipImage();
					break;

				case self::FLIP_BOTH:
					$frame->flopImage();
					$frame->flipImage();
					break;

				default:
					throw new \InvalidArgumentException("Unknown flip mode");
			}
		}

		$this->updateDimensions();

		return $this;
	}

	public function setOpacity($opacity)
	{
		foreach ($this->imagick AS $frame)
		{
			$frame->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity, \Imagick::CHANNEL_ALPHA);
		}

		return $this;
	}

	public function appendImageAt($x, $y, $toAppend)
	{
		if (!($toAppend instanceof \Imagick))
		{
			throw new \InvalidArgumentException('Image to append must be a valid Imagick object.');
		}

		foreach ($this->imagick AS $frame)
		{
			$frame->compositeImage($toAppend, $toAppend->getImageCompose(), $x, $y);
		}

		return $this;
	}

	protected function _optimizeImage(string $file, ?int $quality = null): bool
	{
		$success = $this->imagick->setImageFormat('webp');
		if (!$success)
		{
			return false;
		}

		$this->updateDimensions();
		$this->setType(IMAGETYPE_WEBP);

		try
		{
			return $this->imagick->writeImages($file, true);
		}
		catch (\ImagickException $e)
		{
		}

		return false;
	}

	protected function _unsharpMask($radius, $sigma, $amount, $threshold)
	{
		foreach ($this->imagick AS $frame)
		{
			$frame->unsharpMaskImage($radius, $sigma, $amount, $threshold);
		}

		return $this;
	}

	protected function getPixelColorArray($x, $y)
	{
		$pixel = $this->imagick->getImagePixelColor($x, $y);
		$array = $pixel->getColor();

		$lut = [
			'r' => 'red',
			'g' => 'blue',
			'b' => 'green',
			'a' => 'alpha',
		];

		$rgba = [];

		foreach ($lut AS $key => $value)
		{
			$rgba[$value] = $array[$key] ?? 0;
		}

		return $rgba;
	}

	public function save($file, $format = null, $quality = null)
	{
		if ($format === null)
		{
			$format = $this->type;
		}

		if ($quality === null)
		{
			$quality = \XF::config('maxImageResizeQuality');
		}

		if (method_exists($this->imagick, 'getImageProfiles'))
		{
			$profiles = $this->imagick->getImageProfiles('icc');
			$this->imagick->stripImage();

			if ($profiles && !empty($profiles['icc']))
			{
				$this->imagick->setImageProfile('icc', $profiles['icc']);
			}
		}
		else
		{
			$this->imagick->stripImage();
		}

		switch ($format)
		{
			case IMAGETYPE_GIF:
				if (is_callable([$this->imagick, 'optimizeimagelayers']))
				{
					$optimized = @$this->imagick->optimizeimagelayers();
					if ($optimized instanceof \Imagick)
					{
						$this->imagick = $optimized;
					}

					$deconstructed = @$this->imagick->deconstructImages();
					if ($deconstructed instanceof \Imagick)
					{
						$this->imagick = $deconstructed;
					}
				}
				$success = $this->imagick->setImageFormat('gif');
				break;

			case IMAGETYPE_JPEG:
				$success = $this->imagick->setImageFormat('jpeg')
					&& $this->imagick->setImageCompression(\Imagick::COMPRESSION_JPEG)
					&& $this->imagick->setImageCompressionQuality($quality);
				break;

			case IMAGETYPE_WEBP:
				$success = $this->imagick->setImageFormat('webp');
				break;

			case IMAGETYPE_PNG:
				$success = $this->imagick->setImageFormat('png');
				break;

			default:
				throw new \InvalidArgumentException('Invalid format given. Expects IMAGETYPE_XXX constant.');
		}

		if ($success)
		{
			try
			{
				return $this->imagick->writeImages($file, true);
			}
			catch (\ImagickException $e)
			{
			}
		}

		return false;
	}

	public function output($format = null, $quality = null)
	{
		if ($format === null)
		{
			$format = $this->type;
		}

		if ($quality === null)
		{
			$quality = \XF::config('maxImageResizeQuality');
		}

		$this->imagick->stripImage();

		switch ($format)
		{
			case IMAGETYPE_GIF:
				$success = $this->imagick->optimizeImageLayers();
				break;

			case IMAGETYPE_JPEG:
				$success = $this->imagick->setImageFormat('jpeg')
					&& $this->imagick->setImageCompression(\Imagick::COMPRESSION_JPEG)
					&& $this->imagick->setImageCompressionQuality($quality);
				break;

			case IMAGETYPE_WEBP:
				$success = $this->imagick->setImageFormat('webp');
				break;

			case IMAGETYPE_PNG:
				$success = $this->imagick->setImageFormat('png');
				break;

			default:
				throw new \InvalidArgumentException('Invalid format given. Expects IMAGETYPE_XXX constant.');
		}

		if ($success)
		{
			try
			{
				echo $this->imagick->getImagesBlob();
			}
			catch (\ImagickException $e)
			{
			}
		}
	}

	public function isValid()
	{
		$this->imagick->setFirstIterator();

		return $this->imagick->valid();
	}

	public function __destruct()
	{
		if ($this->imagick)
		{
			$this->imagick->destroy();
			$this->imagick = null;
		}
	}
}
