<?php
/*****************************************************************************
 * Asset.php
 * Contains key class Asset.
 *
 * Kabuki CMS (C) 2013-2015, Aaron van Geffen
 *****************************************************************************/

class Asset
{
	protected $id_asset;
	protected $id_user_uploaded;
	protected $subdir;
	protected $filename;
	protected $title;
	protected $mimetype;
	protected $image_width;
	protected $image_height;
	protected $date_captured;
	protected $priority;
	protected $meta;
	protected $tags;

	protected function __construct(array $data)
	{
		foreach ($data as $attribute => $value)
			$this->$attribute = $value;

		if (!empty($data['date_captured']) && $data['date_captured'] !== 'NULL')
			$this->date_captured = new DateTime($data['date_captured']);
	}

	public static function fromId($id_asset, $return_format = 'object')
	{
		$db = Registry::get('db');

		$row = $db->queryAssoc('
			SELECT *
			FROM assets
			WHERE id_asset = {int:id_asset}',
			[
				'id_asset' => $id_asset,
			]);

		// Asset not found?
		if (empty($row))
			return false;

		$row['meta'] = $db->queryPair('
			SELECT variable, value
			FROM assets_meta
			WHERE id_asset = {int:id_asset}',
			[
				'id_asset' => $id_asset,
			]);

		return $return_format == 'object' ? new Asset($row) : $row;
	}

	public static function fromIds(array $id_assets, $return_format = 'array')
	{
		if (empty($id_assets))
			return [];

		$db = Registry::get('db');

		$res = $db->query('
			SELECT *
			FROM assets
			WHERE id_asset IN ({array_int:id_assets})
			ORDER BY id_asset',
			[
				'id_assets' => $id_assets,
			]);

		$assets = [];
		while ($asset = $db->fetch_assoc($res))
		{
			$assets[$asset['id_asset']] = $asset;
			$assets[$asset['id_asset']]['meta'] = [];
		}

		$metas = $db->queryRows('
			SELECT id_asset, variable, value
			FROM assets_meta
			WHERE id_asset IN ({array_int:id_assets})
			ORDER BY id_asset',
			[
				'id_assets' => $id_assets,
			]);

		foreach ($metas as $meta)
			$assets[$meta[0]]['meta'][$meta[1]] = $meta[2];

		if ($return_format == 'array')
			return $assets;
		else
		{
			$objects = [];
			foreach ($assets as $id => $asset)
				$objects[$id] = new Asset($asset);
			return $objects;
		}
	}

	public static function byPostId($id_post, $return_format = 'object')
	{
		$db = Registry::get('db');

		// !!! TODO
	}

	public static function createNew(array $data, $return_format = 'object')
	{
		// Extract the data array.
		extract($data);

		// No filename? Abort!
		if (!isset($filename_to_copy) || !is_file($filename_to_copy))
			return false;

		// No subdir? Use YYYY/MM
		if (!isset($preferred_subdir))
			$preferred_subdir = date('Y') . '/' . date('m');

		// Does this dir exist yet? If not, create it.
		if (!is_dir(ASSETSDIR . '/' . $preferred_subdir))
			mkdir(ASSETSDIR . '/' . $preferred_subdir, 0755, true);

		// Construct the destination filename. Make sure we don't accidentally overwrite anything.
		if (!isset($preferred_filename))
			$preferred_filename = basename($filename_to_copy);

		$new_filename = $preferred_filename;
		$destination = ASSETSDIR . '/' . $preferred_subdir . '/' . $preferred_filename;
		while (file_exists($destination))
		{
			$filename = pathinfo($preferred_filename, PATHINFO_FILENAME) . '_' . mt_rand(10, 99);
			$extension = pathinfo($preferred_filename, PATHINFO_EXTENSION);
			$new_filename = $filename . '.' . $extension;
			$destination = dirname($destination) . '/' . $new_filename;
		}

		// Can we write to the target directory? Then copy the file.
		if (is_writable(ASSETSDIR . '/' . $preferred_subdir))
			copy($filename_to_copy, $destination);
		else
			return false;

		// Figure out the mime type for the file.
		$finfo = finfo_open(FILEINFO_MIME_TYPE);
		$mimetype = finfo_file($finfo, $destination);
		finfo_close($finfo);

		// Do we have a title yet? Otherwise, use the filename.
		$title = isset($data['title']) ? $data['title'] : pathinfo($preferred_filename, PATHINFO_FILENAME);

		// Same with the slug.
		$slug = isset($data['slug']) ? $data['slug'] : $preferred_subdir . '/' . pathinfo($preferred_filename, PATHINFO_FILENAME);

		// Detected an image?
		if (substr($mimetype, 0, 5) == 'image')
		{
			$image = new Imagick($destination);
			$d = $image->getImageGeometry();

			// Get image dimensions, bearing orientation in mind.
			switch ($image->getImageOrientation())
			{
				case Imagick::ORIENTATION_LEFTBOTTOM:
				case Imagick::ORIENTATION_RIGHTTOP:
					$image_width = $d['height'];
					$image_height = $d['width'];
					break;

				default:
					$image_width = $d['width'];
					$image_height = $d['height'];
			}

			unset($image);

			$exif = EXIF::fromFile($destination);
			$date_captured = intval($exif->created_timestamp);
		}

		$db = Registry::get('db');
		$res = $db->query('
			INSERT INTO assets
			(id_user_uploaded, subdir, filename, title, slug, mimetype, image_width, image_height, date_captured, priority)
			VALUES
			({int:id_user_uploaded}, {string:subdir}, {string:filename}, {string:title}, {string:slug}, {string:mimetype},
			 {int:image_width}, {int:image_height},
			 IF({int:date_captured} > 0, FROM_UNIXTIME({int:date_captured}), NULL),
			 {int:priority})',
			[
				'id_user_uploaded' => isset($id_user) ? $id_user : Registry::get('user')->getUserId(),
				'subdir' => $preferred_subdir,
				'filename' => $new_filename,
				'title' => $title,
				'slug' => $slug,
				'mimetype' => $mimetype,
				'image_width' => isset($image_width) ? $image_width : 'NULL',
				'image_height' => isset($image_height) ? $image_height : 'NULL',
				'date_captured' => isset($date_captured) ? $date_captured : 'NULL',
				'priority' => isset($priority) ? (int) $priority : 0,
			]);

		if (!$res)
		{
			unlink($destination);
			return false;
		}

		$data['id_asset'] = $db->insert_id();
		return $return_format == 'object' ? new self($data) : $data;
	}

	public function getId()
	{
		return $this->id_asset;
	}

	public function getDateCaptured()
	{
		return $this->date_captured;
	}

	public function getFilename()
	{
		return $this->filename;
	}

	public function getLinkedPosts()
	{
		$posts = Registry::get('db')->queryValues('
			SELECT id_post
			FROM posts_assets
			WHERE id_asset = {int:id_asset}',
			['id_asset' => $this->id_asset]);

		// TODO: fix empty post iterator.
		if (empty($posts))
			return [];

		return PostIterator::getByOptions([
			'ids' => $posts,
			'type' => '',
		]);
	}

	public function getMeta()
	{
		return $this->meta;
	}

	public function getPath()
	{
		return $this->subdir;
	}

	public function getPriority()
	{
		return $this->priority;
	}

	public function getTags()
	{
		if (!isset($this->tags))
			$this->tags = Tag::byAssetId($this->id_asset);

		return $this->tags;
	}

	public function getTitle()
	{
		return $this->title;
	}

	public function getUrl()
	{
		return BASEURL . '/assets/' . $this->subdir . '/' . $this->filename;
	}

	public function getType()
	{
		return substr($this->mimetype, 0, strpos($this->mimetype, '/'));
	}

	public function getDimensions($as_type = 'string')
	{
		return $as_type === 'string' ? $this->image_width . 'x' . $this->image_height : [$this->image_width, $this->image_height];
	}

	public function isImage()
	{
		return substr($this->mimetype, 0, 5) === 'image';
	}

	public function getImage()
	{
		if (!$this->isImage())
			throw new Exception('Trying to upgrade an Asset to an Image while the Asset is not an image!');

		return new Image(get_object_vars($this));
	}

	public function replaceFile($filename)
	{
		// No filename? Abort!
		if (!isset($filename) || !is_readable($filename))
			return false;

		// Can we write to the target file?
		$destination = ASSETSDIR . '/' . $this->subdir . '/' . $this->filename;
		if (!is_writable($destination))
			return false;

		copy($filename, $destination);

		// Figure out the mime type for the file.
		$finfo = finfo_open(FILEINFO_MIME_TYPE);
		$this->mimetype = finfo_file($finfo, $destination);
		finfo_close($finfo);

		// Detected an image?
		if (substr($this->mimetype, 0, 5) == 'image')
		{
			$image = new Imagick($destination);
			$d = $image->getImageGeometry();
			$this->image_width = $d['width'];
			$this->image_height = $d['height'];
			unset($image);

			$exif = EXIF::fromFile($destination);
			if (!empty($exif->created_timestamp))
				$this->date_captured = new DateTime(date('r', $exif->created_timestamp));
			else
				$this->date_captured = null;
		}
		else
		{
			$this->image_width = null;
			$this->image_height = null;
			$this->date_captured = null;
		}

		return Registry::get('db')->query('
			UPDATE assets
			SET
				mimetype = {string:mimetype},
				image_width = {int:image_width},
				image_height = {int:image_height},
				date_captured = {datetime:date_captured},
				priority = {int:priority}
			WHERE id_asset = {int:id_asset}',
			[
				'id_asset' => $this->id_asset,
				'mimetype' => $this->mimetype,
				'image_width' => isset($this->image_width) ? $this->image_width : 'NULL',
				'image_height' => isset($this->image_height) ? $this->image_height : 'NULL',
				'date_captured' => isset($this->date_captured) ? $this->date_captured : 'NULL',
				'priority' => $this->priority,
			]);
	}

	protected function saveMetaData()
	{
		$this->setMetaData($this->meta);
	}

	public function setMetaData(array $new_meta, $mode = 'replace')
	{
		$db = Registry::get('db');

		// If we're replacing, delete current data first.
		if ($mode === 'replace')
		{
			$to_remove = array_diff_key($this->meta, $new_meta);
			if (!empty($to_remove))
				$db->query('
					DELETE FROM assets_meta
					WHERE id_asset = {int:id_asset} AND
						variable IN({array_string:variables})',
					[
						'id_asset' => $this->id_asset,
						'variables' => array_keys($to_remove),
					]);
		}

		// Build rows
		$to_insert = [];
		foreach ($new_meta as $key => $value)
			$to_insert[] = [
				'id_asset' => $this->id_asset,
				'variable' => $key,
				'value' => $value,
			];

		// Do the insertion
		$res = Registry::get('db')->insert('replace', 'assets_meta', [
			'id_asset' => 'int',
			'variable' => 'string',
			'value' => 'string',
		], $to_insert, ['id_asset', 'variable']);

		if ($res)
			$this->meta = $new_meta;
	}

	public function delete()
	{
		return Registry::get('db')->query('
			DELETE FROM assets
			WHERE id_asset = {int:id_asset}',
			[
				'id_asset' => $this->id_asset,
			]);
	}

	public function linkTags(array $id_tags)
	{
		if (empty($id_tags))
			return true;

		$pairs = [];
		foreach ($id_tags as $id_tag)
			$pairs[] = ['id_asset' => $this->id_asset, 'id_tag' => $id_tag];

		Registry::get('db')->insert('ignore', 'assets_tags', [
			'id_asset' => 'int',
			'id_tag' => 'int',
		], $pairs, ['id_asset', 'id_tag']);

		Tag::recount($id_tags);
	}

	public function unlinkTags(array $id_tags)
	{
		if (empty($id_tags))
			return true;

		Registry::get('db')->query('
			DELETE FROM assets_tags
			WHERE id_asset = {int:id_asset} AND id_tag IN ({array_int:id_tags})',
			[
				'id_asset' => $this->id_asset,
				'id_tags' => $id_tags,
			]);

		Tag::recount($id_tags);
	}

	public static function getCount()
	{
		return $db->queryValue('
			SELECT COUNT(*)
			FROM assets');
	}

	public function setKeyData($title, DateTime $date_captured = null, $priority)
	{
		$params = [
			'id_asset' => $this->id_asset,
			'title' => $title,
			'priority' => $priority,
		];

		if (isset($date_captured))
			$params['date_captured'] = $date_captured->format('Y-m-d H:i:s');

		return Registry::get('db')->query('
			UPDATE assets
			SET title = {string:title},' . (isset($date_captured) ? '
				date_captured = {datetime:date_captured},' : '') . '
				priority = {int:priority}
			WHERE id_asset = {int:id_asset}',
			$params);
	}
}