<?php

use Intellex\Manicure\Engine;
use Intellex\Manicure\Source\LocalSource;

App::uses('Theme', 'Model');
App::uses('HtmlHelper', 'View/Helper');

/**
 * Deals with resizing and stuff.
 */
class ManicureHelper extends HtmlHelper {

	/** @const string The default icon for the non-image files. */
	const DEFAULT_ICON = '_default';

	/**
	 * Force the image to match the specified dimensions.
	 *
	 * @param mixed            $image   The image to resize.
	 * @param int              $width   The resulting width of the image.
	 * @param int              $height  The resulting height of the image.
	 * @param bool|string|null $group   The group for this image; Set to false to skip popup or
	 *                                  null to return only URL.
	 * @param array            $options Additional options for the manicure.
	 *
	 * @return string The URL to the resized image.
	 */
	public function match($image, $width, $height, $group = null, $options = []) {
		return $this->plugin('Resize\Match', $image, $width, $height, $group, $options);
	}

	/**
	 * Scale the image down so that it fits inside the desired dimension.
	 *
	 * @param mixed            $image   The image to resize.
	 * @param int              $width   The resulting maximum width of the image.
	 * @param int              $height  The resulting minimum height of the image.
	 * @param bool|string|null $group   The group for this image; Set to false to skip popup or null
	 *                                  to return only URL.
	 * @param array            $options Additional options for the manicure.
	 *
	 * @return string The URL to the resized image.
	 */
	public function scale($image, $width, $height, $group = null, $options = []) {
		return $this->plugin('Resize\Scale', $image, $width, $height, $group, $options);
	}

	/**
	 * Scale the image down so that it fits inside the desired dimension, an then center it.
	 *
	 * @param mixed            $image   The image to resize.
	 * @param int              $width   The resulting width of the image.
	 * @param int              $height  The resulting height of the image.
	 * @param bool|string|null $group   The group for this image; Set to false to skip popup or null
	 *                                  to return only URL.
	 * @param array            $options Additional options for the manicure.
	 *
	 * @return string The URL to the resized image.
	 */
	public function pad($image, $width, $height, $group = null, $options = [ 'bg' => [ 255, 255, 255, 0 ] ]) {
		return $this->plugin('Resize\Pad', $image, $width, $height, $group, $options);
	}

	/**
	 * Force the image to match the specified dimensions.
	 *
	 * @param string           $plugin  The plugin to apply on the image.
	 * @param mixed            $image   The image to resize.
	 * @param int              $width   The resulting width of the image.
	 * @param int              $height  The resulting height of the image.
	 * @param bool|string|null $group   The group for this image; Set to false to skip popup or null
	 *                                  to return only URL.
	 * @param array            $options Additional options for the manicure.
	 *
	 * @return string The URL to the resized image.
	 */
	public function plugin($plugin, $image, $width, $height, $group = null, $options = []) {
		try {

			// Get the image file
			$image = static::getFile($image);

			// Apply Manicure plugins
			if ($width && $height) {
				$thumb = $this->apply($image, [ [ $plugin, $width, $height ] ], $options);
				$url = static::relativeURL($thumb);
			} else {
				$thumb = static::relativeURL($image);
				$url = $thumb;
			}

			// Only image
			if ($group === null) {
				return $url;
			}

			// Create <img> HTML tag
			$img = $this->image($url, [ 'width' => $width, 'height' => $height, 'class' => 'cms-manicure' ]);

			// Build
			$rel = Upload::isImage($image->getExtension(true)) ? $group : null;
			$classes = array_filter([ 'cms-manicure', Upload::isImage($image->getMimetype()) ? 'showup' : null ]);
			return $group
				? $this->link($img, static::relativeURL($image), [ 'escape' => false, 'class' => implode(' ', $classes), 'rel' => $rel ])
				: $img;

		} catch (Exception $ex) {
			return null;
		}
	}

	/**
	 *  Apply multiple plugins on an image, using simple rules.
	 *
	 * @param mixed $image   The image to resize.
	 * @param array $rules   The rules to apply.
	 * @param array $options Additional options for the manicure.
	 *
	 * @return \Intellex\Filesystem\File The File of the resized image.
	 */
	public function apply($image, $rules, $options) {
		try {

			// Handle various image representations
			$image = static::getFile($image);

			// Handle non-images
			if (!Upload::isImage($image->getMimetype())) {
				$image = static::getFileForExtension($image->getExtension(true));

				// Replace Resize\Match with Resize\Pad
				foreach ($rules as $i => $rule) {
					if ($rule[0] == 'Resize\Match') {

						// Resize
						$rule[0] = 'Resize\Scale';
						$rule[2] *= 0.8;
						$rules[$i] = $rule;

						// Center
						$rules[] = [ 'Resize\Center', $rule[1], $rule[2] * 1.25 ];
						break;
					}
				}
			}

			// Parse options
			foreach ($options as $option => $value) {
				switch ($option) {
					case 'bg':
						foreach ($rules as $i => $rule) {
							$rules[$i][] = $value;
						}
						break;
				}
			}

			// Check cache
			$path = static::fromCache($image, $rules, $options);
			if (!$path) {

				// Do image exist
				if (!$image->isReadable()) {
					$image = new \Intellex\Filesystem\File(Theme::asset('no-image.png'));
				}

				// Apply Manicure
				$engine = new Engine(new LocalSource($image->getPath()));
				$result = $engine->quickApply($rules)->getResult()->getData();

				// Write to cache
				$path = $this->toCache($result, $image, $rules, $options);
			}

		} catch (Exception $ex) {

			// Write to log if supplied image was not empty
			if (!empty($image)) {
				CakeLog::warning($ex->getMessage());
			}

			return $this->apply(Theme::asset('no-image.png'), $rules, $options);
		}

		return $path;
	}

	/**
	 * Get file from the supplied representation of the file.
	 *
	 * @param string|array|\Intellex\Filesystem\File|null $file Subject to convert to file.
	 *
	 * @return \Intellex\Filesystem\File The absolute path to the file.
	 * @throws \Intellex\Filesystem\Exception\NotAFileException If image not found.
	 */
	private static function getFile($file) {

		// No image supplied
		if ($file) {

			// From: File
			if ($file instanceof \Intellex\Filesystem\File) {
				return $file;
			}

			// From: String
			if (is_string($file)) {
				return new \Intellex\Filesystem\File(strpos($file, ROOT) !== 0 ? WWW_ROOT . $file : $file);
			}

			// From: Array
			if (is_array($file)) {

				// Temporal upload
				if (key_exists('token', $file) && !empty($file['token'])) {
					return new \Intellex\Filesystem\File(Upload::loadFileFromToken($file['token'])->getPath());

					// Upload
				} else if (key_exists('file', $file) && !empty($file['file'])) {
					return new \Intellex\Filesystem\File(WWW_ROOT . trim($file['file'], '/ '));
				}
			}
		}

		throw new \Intellex\Filesystem\Exception\NotAFileException($file);
	}

	/**
	 * Get the image path from the cache.
	 *
	 * @param mixed $image   The image to resize.
	 * @param array $rules   The rules to apply.
	 * @param array $options Additional options for the manicure.
	 *
	 * @return \Intellex\Filesystem\File The File of the previously cached image, or null if there
	 *                                   is no cache.
	 */
	private static function fromCache($image, $rules, $options = []) {

		// Check if cache should be skipped
		if (!key_exists('cache', $options) || $options['cache']) {
			$file = static::getLocation($image, $rules);
			return $file->isReadable() ? $file : null;
		}

		return null;
	}

	/**
	 * Write an image to cache.
	 *
	 * @param mixed $data    The data to save.
	 * @param mixed $image   The image to resize.
	 * @param array $rules   The rules to apply.
	 * @param array $options Additional options for the manicure.
	 *
	 * @return \Intellex\Filesystem\File The file of the stored image, inside cache.
	 * @throws \Exception
	 */
	private static function toCache($data, $image, $rules, $options = []) {
		$file = static::getLocation($image, $rules, $options);

		// Output based on the extension
		ob_start();
		switch (strtolower($image->getExtension(true))) {
			case 'jpg':
			case'jpeg':
				imagejpeg($data);
				break;

			case'png':
				imagepng($data);
				break;

			case'gif':
				imagegif($data);
				break;
		}

		// Write
		$binary = ob_get_clean();
		if ($binary) {
			$file->write($binary);
			return $file;
		}

		throw new \Exception('Not a valid image: ' . $image->getPath());
	}

	/**
	 * Get the location for the image thumb.
	 *
	 * @param mixed $image   The source image to resize.
	 * @param array $rules   The rules to apply.
	 * @param array $options Additional options for the manicure.
	 *
	 * @return Intellex\Filesystem\File The file to the.
	 */
	private static function getLocation($image, $rules, $options = []) {
		$image = static::getFile($image);
		$base = WWW_ROOT . 'thumbs' . DS . '_manicure' . DS;
		$options['hash'] = hash('sha256', $image->getPath() . '?' . serialize($rules));

		return new Intellex\Filesystem\File($base . $options['hash'] . '.' . $image->getExtension(true));
	}

	/**
	 * Convert the full filesystem path to a relative URL for the browser.
	 *
	 * @param \Intellex\Filesystem\File|string $file The file to get the relative URL for.
	 *
	 * @return string The URL relative to the webroot.
	 */
	private static function relativeURL($file) {
		return preg_replace('~^.+(/|\\\\)(app|VectorCMS)(/|\\\\)webroot(/|\\\\)~', '/', preg_replace('~//+~', '/', $file->getPath()));
	}

	/**
	 * Get the icon for the supplied extension.
	 *
	 * @param string $extension The extension to look the icon for.
	 *
	 * @return \Intellex\Filesystem\File The file of the icon to use.
	 * @throws Exception If no icon is found.
	 */
	private static function getFileForExtension($extension) {
		$extension = strtolower($extension);

		// Handle synonyms
		$synonyms = [ 'jpeg' => [ 'jpg' ] ];
		foreach ($synonyms as $standard => $list) {
			if (in_array($extension, $list)) {
				$extension = $standard;
			}
		}

		// Find
		$path = VECTORCMS_ROOT . WEBROOT_DIR . DS . 'img' . DS . 'mimetypes' . DS . $extension . '.png';
		$file = new \Intellex\Filesystem\File($path);

		// Found
		if ($file->isReadable()) {
			return $file;
		}

		// Show default
		if ($extension !== static::DEFAULT_ICON) {
			return static::getFileForExtension(static::DEFAULT_ICON);
		}

		// No default icon
		throw new Exception("Default icon cannot be found on `{$path}`.");
	}

}
