<?php
/*
 * Copyright (c) 2013, webvariants GbR, http://www.webvariants.de
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

class _WV2_Service_Metadata {
	protected $table = 'wv2_meta';

	/***************************************************************************\
	  INSERT
	\***************************************************************************/

	public function insert($objectID, $clangID, $objectType, $metainfo, $value) {
		$db = sly_DB_Persistence::getInstance();
		$db->insert($this->table, array(
			'object_id' => (int) $objectID,
			'clang'     => (int) $clangID,
			'metainfo'  => trim($metainfo),
			'meta_type' => (int) $objectType,
			'value'     => trim($value)
		));
	}

	public function insertArticle(sly_Model_Article $article, $metainfo, $value) {
		$this->insert($article->getId(), $article->getClang(), WV2_Metainfo::TYPE_ARTICLE, $metainfo, $value);
	}

	public function insertCategory(sly_Model_Category $category, $metainfo, $value) {
		$this->insert($category->getId(), $category->getClang(), WV2_Metainfo::TYPE_CATEGORY, $metainfo, $value);
	}

	public function insertMedium(sly_Model_Medium $medium, $clangID, $metainfo, $value) {
		$this->insert($medium->getId(), $clangID, WV2_Metainfo::TYPE_MEDIUM, $metainfo, $value);
	}

	public function insertUser(sly_Model_User $user, $clangID, $metainfo, $value) {
		$this->insert($user->getId(), $clangID, WV2_Metainfo::TYPE_USER, $metainfo, $value);
	}

	public function insertMany(array $metadata) {
		$db = sly_DB_Persistence::getInstance();
		$db->transactional(array($this, '_many'), array('insert', $metadata));
	}

	public function insertObject($objectID, $clangID, $objectType, array $metadata) {
		$db = sly_DB_Persistence::getInstance();
		$db->transactional(array($this, '_objectdata'), array('insert', $objectID, $clangID, $objectType, $metadata));
	}

	public function insertArticleData(sly_Model_Article $article, $metadata) {
		$this->insertObject($article->getId(), $article->getClang(), WV2_Metainfo::TYPE_ARTICLE, $metadata);
	}

	public function insertCategoryData(sly_Model_Category $category, $metadata) {
		$this->insertObject($category->getId(), $category->getClang(), WV2_Metainfo::TYPE_CATEGORY, $metadata);
	}

	public function insertMediumData(sly_Model_Medium $medium, $clangID, $metadata) {
		$this->insertObject($medium->getId(), $clangID, WV2_Metainfo::TYPE_MEDIUM, $metadata);
	}

	public function insertUserData(sly_Model_User $user, $clangID, $metadata) {
		$this->insertObject($user->getId(), $clangID, WV2_Metainfo::TYPE_USER, $metadata);
	}

	/***************************************************************************\
	  SELECT
	\***************************************************************************/

	public function findObjectData($objectID, $objectType, $clangID = null) {
		$db     = sly_DB_Persistence::getInstance();
		$where  = array('object_id' => (int) $objectID, 'meta_type' => (int) $objectType);
		$result = array();

		if ($clangID !== null) {
			$where['clang'] = (int) $clangID;
		}

		$db->select($this->table, 'clang, metainfo, value', $where);

		foreach ($db->all() as $row) {
			$clang    = (int) $row['clang'];
			$metainfo = $row['metainfo'];
			$value    = $row['value'];

			if ($clangID === null) {
				$result[$clang][$metainfo] = $value;
			}
			else {
				$result[$metainfo] = $value;
			}
		}

		return $result;
	}

	public function findArticleData(sly_Model_Article $article) {
		$this->findObjectData($article->getId(), $article->getClang());
	}

	public function findCategoryData(sly_Model_Category $category) {
		$this->findObjectData($category->getId(), $category->getClang());
	}

	public function findMediumData(sly_Model_Medium $medium, $clangID) {
		$this->findObjectData($medium->getId(), $clangID);
	}

	public function findUserData(sly_Model_Medium $user, $clangID) {
		$this->findObjectData($user->getId(), $clangID);
	}

	public function findMetainfoData($metainfo, $objectType, $clangID = null, $distinct = false) {
		$db     = sly_DB_Persistence::getInstance();
		$where  = array('metainfo' => $metainfo, 'meta_type' => (int) $objectType);
		$result = array();

		if ($clangID !== null) {
			$where['clang'] = (int) $clangID;
		}

		$db->select($this->table, 'object_id, clang, value', $where, $distinct ? array('value') : null);

		foreach ($db->all() as $row) {
			$clang    = (int) $row['clang'];
			$objectID = (int) $row['object_id'];
			$value    = $row['value'];

			if ($distinct) {
				$result[] = $value;
			}
			else {
				if ($clangID === null) {
					$result[$clang][$objectID] = $value;
				}
				else {
					$result[$objectID] = $value;
				}
			}
		}

		return $result;
	}

	protected function find($select, $where) {
		$db = sly_DB_Persistence::getInstance();
		$db->select($this->table, $select, $where);
	}

	/***************************************************************************\
	  UPDATE
	\***************************************************************************/

	public function update($objectID, $clangID, $objectType, $metainfo, $value) {
		$db = sly_DB_Persistence::getInstance();
		$db->update($this->table, array('value' => trim($value)), array(
			'object_id' => (int) $objectID,
			'clang'     => (int) $clangID,
			'metainfo'  => trim($metainfo),
			'meta_type' => (int) $objectType
		));

		WV2_Metainfo::clearDataCache();
	}

	public function updateArticle(sly_Model_Article $article, $metainfo, $value) {
		$id = $article->getId();
		$this->update($id, $article->getClang(), WV2_Metainfo::TYPE_ARTICLE, $metainfo, $value);

		$params = array(
		    'article_id' => $id,
		    'clang'      => WV_Sally::clang(),
		    'values'     => array($metainfo => $value)
		);

		sly_Core::dispatcher()->notify('WV2_ARTICLE_META_UPDATED', true, $params);
	}

	public function updateCategory(sly_Model_Category $category, $metainfo, $value) {
		$id = $category->getId();
		$this->update($id, $category->getClang(), WV2_Metainfo::TYPE_CATEGORY, $metainfo, $value);

		$params = array(
		    'category_id' => $id,
		    'clang'       => WV_Sally::clang(),
		    'values'      => array($metainfo => $value)
		);

		sly_Core::dispatcher()->notify('WV2_CATEGORY_META_UPDATED', true, $params);
	}

	public function updateMedium(sly_Model_Medium $medium, $clangID, $metainfo, $value) {
		$id = $medium->getId();
		$this->update($id, $clangID, WV2_Metainfo::TYPE_MEDIUM, $metainfo, $value);

		$params = array(
		    'file_id' => $id,
		    'clang'   => WV_Sally::clang(),
		    'values'  => array($metainfo => $value)
		);

		sly_Core::dispatcher()->notify('WV2_MEDIUM_META_UPDATED', true, $params);
	}

	public function updateUser(sly_Model_User $user, $clangID, $metainfo, $value) {
		$id = $user->getId();
		$this->update($id, $clangID, WV2_Metainfo::TYPE_USER, $metainfo, $value);

		$params = array(
		    'user_id' => $id,
		    'clang'   => WV_Sally::clang(),
		    'values'  => array($metainfo => $value)
		);

		sly_Core::dispatcher()->notify('WV2_USER_META_UPDATED', true, $params);
	}

	public function updateUserMany(sly_Model_User $user, $values, $clangID = WV_Sally::CLANG_ALL ) {
		$params = array(
			'user_id'    => $user->getId(),
			'clang'      => $clangID,
			'new_values' => $values
		);
		sly_Core::dispatcher()->notify('WV2_PRE_USER_META_MANY_UPDATED', $user, $params);

		$metadata = array();
		$langs    = $clangID === WV_Sally::CLANG_ALL ? sly_Util_Language::findAll(true) : WV_Sally::clang($clangID);

		foreach ($langs as $clang) {
			foreach ($values as $key => $value) {
				$metadata[] = array(
					'object_id'   => $user->getId(),
					'clang_id'    => $clang,
					'object_type' => WV2_Metainfo::TYPE_USER,
					'metainfo'    => $key,
					'value'       => $value
				);
			}
		}

		$this->updateMany($metadata);
		WV2_Metainfo::clearDataCache();
		sly_Core::dispatcher()->notify('WV2_USER_META_MANY_UPDATED', $user, $params);
	}

	public function updateMany(array $metadata) {
		$db = sly_DB_Persistence::getInstance();
		$db->transactional(array($this, '_many'), array('update', $metadata));
	}

	public function updateObject($objectID, $clangID, $objectType, array $newMetadata) {
		$db = sly_DB_Persistence::getInstance();
		$db->transactional(array($this, '_objectdata'), array('update', $objectID, $clangID, $objectType, $newMetadata));

		WV2_Metainfo::clearDataCache();
	}

	public function updateArticleData(sly_Model_Article $article, $newMetadata) {
		$id = $article->getId();
		$this->updateObject($id, $article->getClang(), WV2_Metainfo::TYPE_ARTICLE, $newMetadata);

		$params = array(
		    'article_id' => $id,
		    'clang'      => WV_Sally::clang(),
		    'values'     => $newMetadata
		);

		sly_Core::dispatcher()->notify('WV2_ARTICLE_META_UPDATED', true, $params);
	}

	public function updateCategoryData(sly_Model_Category $category, $newMetadata) {
		$id = $category->getId();
		$this->updateObject($id, $category->getClang(), WV2_Metainfo::TYPE_CATEGORY, $newMetadata);

		$params = array(
		    'category_id' => $id,
		    'clang'       => WV_Sally::clang(),
		    'values'      => $newMetadata
		);

		sly_Core::dispatcher()->notify('WV2_CATEGORY_META_UPDATED', true, $params);
	}

	public function updateMediumData(sly_Model_Medium $medium, $clangID, $newMetadata) {
		$id = $medium->getId();
		$this->updateObject($id, $clangID, WV2_Metainfo::TYPE_MEDIUM, $newMetadata);

		$params = array(
		    'file_id' => $id,
		    'clang'   => WV_Sally::clang(),
		    'values'  => $newMetadata
		);

		sly_Core::dispatcher()->notify('WV2_MEDIUM_META_UPDATED', true, $params);
	}

	public function updateUserData(sly_Model_User $user, $clangID, $newMetadata) {
		$id = $user->getId();
		$this->updateObject($id, $clangID, WV2_Metainfo::TYPE_USER, $newMetadata);

		$params = array(
		    'user_id' => $id,
		    'clang'   => WV_Sally::clang(),
		    'values'  => $newMetadata
		);

		sly_Core::dispatcher()->notify('WV2_USER_META_UPDATED', true, $params);
	}

	/***************************************************************************\
	  DELETE
	\***************************************************************************/

	public function delete($objectID, $clangID, $objectType, $metainfo) {
		$where = array(
			'object_id' => (int) $objectID,
			'meta_type' => (int) $objectType,
			'metainfo'  => $metainfo
		);

		if ($clangID !== null) {
			$where['clang'] = (int) $clangID;
		}

		$this->_delete($where);
	}

	public function deleteObject($objectID, $objectType, $clangID = null) {
		$where = array(
			'object_id' => (int) $objectID,
			'meta_type' => (int) $objectType
		);

		if ($clangID !== null) {
			$where['clang'] = (int) $clangID;
		}

		$this->_delete($where);
	}

	public function deleteArticle(sly_Model_Article $article) {
		$this->deleteObject($article->getId(), WV2_Metainfo::TYPE_ARTICLE);
	}

	public function deleteCategory(sly_Model_Category $category) {
		$this->deleteObject($category->getId(), WV2_Metainfo::TYPE_CATEGORY);
	}

	public function deleteMedium(sly_Model_Medium $medium) {
		$this->deleteObject($medium->getId(), WV2_Metainfo::TYPE_MEDIUM);
	}

	public function deleteUser(sly_Model_User $user) {
		$this->deleteObject($user->getId(), WV2_Metainfo::TYPE_USER);
	}

	public function deleteMetainfo($metainfo, $objectType, $clangID = null) {
		$where = array('metainfo' => $metainfo, 'meta_type' => (int) $objectType);

		if ($clangID !== null) {
			$where['clang'] = (int) $clangID;
		}

		$this->_delete($where);
	}

	public function deleteClang($clangID) {
		$this->_delete(array('clang' => (int) $clangID));
	}

	public function deleteProperty(WV_IProperty $property, $clangID = null) {
		$this->deleteMetainfo($property->getName(), $property->getType(), $clangID);
	}

	/***************************************************************************\
	  COPY
	\***************************************************************************/

	public function copy(array $source, array $destination, $objectType) {
		$db = sly_DB_Persistence::getInstance();
		$db->transactional(array($this, '_copy'), array($source, $destination, $objectType));
	}

	/***************************************************************************\
	  helper
	\***************************************************************************/

	/**
	 * @internal
	 */
	public function _many($operation, array $metadata) {
		foreach ($metadata as $idx => $row) {
			if (!isset($row['object_id']))   throw new UnexpectedValueException('Meta date #'.$idx.' has no "object_id" field.');
			if (!isset($row['clang_id']))    throw new UnexpectedValueException('Meta date #'.$idx.' has no "clang_id" field.');
			if (!isset($row['object_type'])) throw new UnexpectedValueException('Meta date #'.$idx.' has no "object_type" field.');
			if (!isset($row['metainfo']))    throw new UnexpectedValueException('Meta date #'.$idx.' has no "metainfo" field.');
			if (!isset($row['value']))       throw new UnexpectedValueException('Meta date #'.$idx.' has no "value" field.');

			$this->$operation($row['object_id'], $row['clang_id'], $row['object_type'], $row['metainfo'], $row['value']);
		}
	}

	/**
	 * @internal
	 */
	public function _objectdata($operation, $objectID, $clangID, $objectType, array $metadata) {
		foreach ($metadata as $metainfo => $value) {
			$this->$operation($objectID, $clangID, $objectType, $metainfo, $value);
		}
	}

	/**
	 * @internal
	 */
	public function _copy(array $source, array $destination, $objectType = null) {
		if ($source === $destination) return;

		if ($objectType !== null) {
			$source['meta_type']      = (int) $objectType;
			$destination['meta_type'] = (int) $objectType;
		}

		// remove target data
		$this->_delete($destination);

		// select source data
		$db = sly_DB_Persistence::getInstance();
		$db->select($this->table, '*', $source);
		$sourceData = $db->all();

		// insert the data as new rows for the destination
		foreach ($sourceData as $row) {
			foreach ($destination as $prop => $value) {
				$row[$prop] = $value;
			}

			$this->insert($row['object_id'], $row['clang'], $row['meta_type'], $row['metainfo'], $row['value']);
		}
	}

	protected function _delete(array $where) {
		$db = sly_DB_Persistence::getInstance();
		$db->delete($this->table, $where);
	}
}
