<?php
/*****************************************************************************
 * PhotoMosaic.php
 * Contains the photo mosaic model, an iterator to create tiled photo galleries.
 *
 * Kabuki CMS (C) 2013-2015, Aaron van Geffen
 *****************************************************************************/

class PhotoMosaic
{
	private AssetIterator $iterator;
	private $layouts;
	private $processedImages = 0;
	private $queue = [];

	const NUM_DAYS_CUTOFF = 7;

	public function __construct(AssetIterator $iterator)
	{
		$this->iterator = $iterator;
		$this->layouts = $this->availableLayouts();
	}

	public function __destruct()
	{
		$this->iterator->clean();
	}

	private function availableLayouts()
	{
		static $layouts = [
			// Single panorama
			'panorama' => [Image::TYPE_PANORAMA],

			// A whopping six landscapes?
			'sixLandscapes' => [Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE,
								Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE],

			// Big-small juxtapositions
			'sidePortrait' => [Image::TYPE_PORTRAIT, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE,
													 Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE],
			'sideLandscape' => [Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE],

			// Single row of three
			'threeLandscapes' => [Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE],
			'threePortraits' => [Image::TYPE_PORTRAIT, Image::TYPE_PORTRAIT, Image::TYPE_PORTRAIT],

			// Dual layouts
			'dualLandscapes' => [Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE],
			'dualPortraits' => [Image::TYPE_PORTRAIT, Image::TYPE_PORTRAIT],

			// Fallback layouts
			'singleLandscape' => [Image::TYPE_LANDSCAPE],
			'singlePortrait' => [Image::TYPE_PORTRAIT],
		];

		return $layouts;
	}

	private static function daysApart(Image $a, Image $b)
	{
		return $a->getDateCaptured()->diff($b->getDateCaptured())->days;
	}

	private function fetchImage($desired_type = Image::TYPE_PORTRAIT | Image::TYPE_LANDSCAPE | Image::TYPE_PANORAMA, Image $refDateImage = null)
	{
		// First, check if we have what we're looking for in the queue.
		foreach ($this->queue as $i => $image)
		{
			// Image has to match the desired type and be taken within a week of the reference image.
			if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF))
			{
				unset($this->queue[$i]);
				return $image;
			}
		}

		// Check whatever's next up!
		while (($asset = $this->iterator->next()) && ($image = $asset->getImage()))
		{
			// Image has to match the desired type and be taken within a week of the reference image.
			if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF))
				return $image;
			else
				$this->pushToQueue($image);
		}

		return false;
	}

	public function getRow()
	{
		$currentImages = [];
		$selectedLayout = null;
		foreach ($this->layouts as $layout => $requiredImageTypes)
		{
			// Fetch images per the layout requirements
			// TODO: pass last image back into fetchImage for date cutoff purposes
			$currentImages = array_filter(array_map([$this, 'fetchImage'], $requiredImageTypes));

			// Matching requirements?
			if (count($currentImages) === count($requiredImageTypes))
			{
				$selectedLayout = $layout;
				break;
			}

			// Push mismatches back into the queue
			array_map([$this, 'pushToQueue'], $currentImages);
			$currentImages = [];
		}

		if ($selectedLayout)
		{
			// Hurray, we've got something that works
			usort($currentImages, [$this, 'orderPhotos']);
			$this->processedImages += count($currentImages);
			return [$currentImages, $selectedLayout];
		}
		else
		{
			// Ensure we have no images left in the iterator before giving up
			assert($this->processedImages === $this->iterator->num());
			return false;
		}
	}

	private static function matchTypeMask(Image $image, $type_mask)
	{
		return ($type_mask & Image::TYPE_PANORAMA) && $image->isPanorama() ||
			  ($type_mask & Image::TYPE_LANDSCAPE) && $image->isLandscape() ||
			  ($type_mask & Image::TYPE_PORTRAIT)  && $image->isPortrait();
	}

	private static function orderPhotos(Image $a, Image $b)
	{
		// Leave images of different types as-is
		if ($a->isLandscape() !== $b->isLandscape())
			return 0;

		// Otherwise, show images of highest priority first
		$priority_diff = $a->getPriority() - $b->getPriority();
		return -$priority_diff;
	}

	private function pushToQueue(Image $image)
	{
		$this->queue[] = $image;
	}
}