<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Traits\ForwardsCalls;
use Imagick;

use App\Casts\PostgresBytea;
use App\Jobs\CatalogMove;

class Photo extends Abstracted\Catalog
{
	use ForwardsCalls;

	public const config = 'photo';

	protected $casts = [
		'created'=>'datetime:Y-m-d H:i:s',
		'thumbnail'=>PostgresBytea::class,
	];

	protected static $includeSubSecTime = TRUE;

	// Imagick Object
	private ?Imagick $_o;
	protected array $init = [
		'creation_date',
		'gps',
		'heightwidth',
		'signature',
		'software',
		'subsectime',
	];

	// How should the image be rotated, based on the value of orientation
	private array $_rotate = [
		3 => 180,
		6 => 90,
		8 => -90,
	];

	public static function boot()
	{
		parent::boot();

		// Any photo saved, queue it to be moved.
		self::saved(function($item) {
			if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
				Log::info(sprintf('%s: Need to Move [%s]','Photo',$item->id.'|'.serialize($x)));

				CatalogMove::dispatch($item)
					->onQueue('move');
			}
		});
	}

	/**
	 * Calculate the GPS coordinates
	 */
	public static function latlon(array $coordinate,string $hemisphere): ?float
	{
		if ((! $coordinate) || (! $hemisphere))
			return NULL;

		for ($i=0; $i<3; $i++) {
			$part = explode('/', $coordinate[$i]);

			if (count($part) == 1)
				$coordinate[$i] = $part[0];

			elseif (count($part) == 2)
				$coordinate[$i] = floatval($part[0])/floatval($part[1]);

			else
				$coordinate[$i] = 0;
		}

		list($degrees,$minutes,$seconds) = $coordinate;

		$sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;

		return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4));
	}

	/**
	 * Forward calls to Imagick
	 *
	 * @param $method
	 * @param $parameters
	 * @return mixed|null
	 */
	public function __call($method,$parameters) {
		if (str_starts_with($method,'Imagick_')) {
			$method = preg_replace('/^Imagick_/','',$method);

			return $this->o ? $this->forwardCallTo($this->_o,$method,$parameters) : NULL;

		} else
			return parent::__call($method,$parameters);
	}

	public function __get($key): mixed
	{
		if ($key === 'o') {
			if (isset($this->_o))
				return $this->_o;

			if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
				return $this->_o = NULL;

			if (! isset($this->_o))
				return $this->_o = new Imagick($this->file_name(FALSE));
		}

		return parent::__get($key);
	}

	/* ATTRIBUTES */

	public function getFileSignatureAttribute(string $val=NULL): string
	{
		return $val ?: $this->getObjectOriginal('file_signature');
	}

	public function getHeightAttribute(string $val=NULL): ?int
	{
		return $val ?: $this->getObjectOriginal('height');
	}

	public function getOrientationAttribute(int $val=NULL): ?int
	{
		return $val ?: $this->getObjectOriginal('orientation');
	}

	public function getSignatureAttribute(string $val=NULL): ?string
	{
		return $val ?: $this->getObjectOriginal('signature');
	}

	public function getWidthAttribute(string $val=NULL): ?int
	{
		return $val ?: $this->getObjectOriginal('width');
	}

	/* METHODS */

	public function custom_init(): void
	{
		$this->orientation = $this->getObjectOriginal('orientation');

		try {
			if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
				$this->o->setImageFormat('jpg');

				$this->thumbnail = $this->o->getImageBlob();
			}

		} catch (\Exception $e) {
			Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
		}

		if ($this->thumbnail === FALSE)
			$this->thumbnail = NULL;
	}

	public function getObjectOriginal(string $property): mixed
	{
		switch ($property) {
			case 'creation_date':
				if ($this->Imagick_getImageProperty('exif:DateTimeOriginal') === '0000:00:00 00:00:00'
					&& $this->Imagick_getImageProperty('exif:DateTime') === '0000:00:00 00:00:00')
					return NULL;

				$result = Carbon::create($x=
					($this->Imagick_getImageProperty('exif:DateTimeOriginal') && ($this->Imagick_getImageProperty('exif:DateTimeOriginal') !== '0000:00:00 00:00:00'))
						? $this->Imagick_getImageProperty('exif:DateTimeOriginal').$this->Imagick_getImageProperty('exif:OffsetTimeOriginal')
						: $this->Imagick_getImageProperty('exif:DateTime').$this->Imagick_getImageProperty('exif:OffsetTime'));

				return $result ?: NULL;

			case 'file_signature':
				return md5_file($this->file_name(FALSE));

			case 'gps_lat':
				return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLatitude')),$this->Imagick_getImageProperty('exif:GPSLatitudeRef'));

			case 'gps_lon':
				return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLongitude')),$this->Imagick_getImageProperty('exif:GPSLongitudeRef'));

			case 'height':
				return $this->Imagick_getImageHeight();

			case 'identifier':
				return NULL;

			case 'make':
				return $this->Imagick_getImageProperty('exif:Make');

			case 'model':
				return $this->Imagick_getImageProperty('exif:Model');

			case 'orientation':
				return $this->Imagick_getImageOrientation();

			case 'signature':
				return $this->Imagick_getImageSignature();

			case 'software':
				return $this->Imagick_getImageProperty('exif:Software');

			case 'subsectime':
				$this->subsectime = (int)$this->Imagick_getImageProperty('exif:SubSecTimeOriginal');

				// In case of an error.
				if ($this->subsectime > 32767)
					$this->subsectime = 32767;

				if ($this->subsectime === FALSE)
					$this->subsectime = 0;

				return $this->subsectime;

			case 'width':
				return $this->Imagick_getImageWidth();

			default:
				throw new \Exception('To implement: '.$property);
		}
	}

	/**
	 * Return the image, rotated
	 */
	public function image(): ?string
	{
		if (! $this->o)
			return NULL;

		$imo = clone($this->o);

		return $imo ? $this->rotate($imo) : NULL;
	}

	/**
	 * Display the orientation of a photo
	 */
	public function orientation(): string
	{
		switch ($this->orientation) {
			case 1: return 'None!';
			case 3: return 'Upside Down';
			case 6: return 'Rotate Right';
			case 8: return 'Rotate Left';
			default: return 'unknown?';
		}
	}

	/**
	 * Rotate the image
	 */
	private function rotate(\Imagick $imo,string $format='jpg'): string
	{
		if (array_key_exists($this->orientation,$this->_rotate))
			$imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]);

		$imo->setImageFormat($format);

		if (array_key_exists('exif',$imo->getImageProfiles()))
			$imo->removeImageProfile('exif');

		return $imo->getImageBlob();
	}

	/**
	 * Return the image's thumbnail
	 */
	public function thumbnail($rotate=TRUE): ?string
	{
		if (! $this->thumbnail) {
			if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
				$this->o->setImageFormat('jpg');

			    return $this->o->getImageBlob();

			} else {
				return NULL;
			}
		}

		if ((! $rotate) || (! array_key_exists($this->orientation,$this->_rotate)) || (! extension_loaded('imagick')))
			return $this->thumbnail;

		$imo = new \Imagick();
		$imo->readImageBlob($this->thumbnail);
		$imo->setImageFormat('jpg');

		return $this->rotate($imo);
	}

	/**
	 * Return the extension of the image
	 */
	public function type(bool $mime=FALSE): string
	{
		return strtolower($mime ? mime_content_type($this->file_name(FALSE)) : pathinfo($this->filename,PATHINFO_EXTENSION));
	}
}