<?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_Property extends WV_Service_Property {
	protected $currentType = -1;

	public function loadFromArray(array $data, array $oldData = null) {
		$cache  = sly_Core::cache();
		$struct = $cache->get('metainfo', 'metainfos', null);

		if ($struct === null || $data !== $oldData) {
			$struct = array();
			$types  = array(
				WV2_Metainfo::TYPE_ARTICLE  => 'articles',
				WV2_Metainfo::TYPE_CATEGORY => 'categories',
				WV2_Metainfo::TYPE_MEDIUM   => 'media',
				WV2_Metainfo::TYPE_USER     => 'users'
			);

			foreach ($types as $typeID => $typeName) {
				$this->currentType = $typeID;

				$dataThisType    = isset($data[$typeName])    ? $data[$typeName]    : array();
				$oldDataThisType = isset($oldData[$typeName]) ? $oldData[$typeName] : array();
				$metainfos       = parent::loadFromArray($dataThisType, $oldDataThisType);

				foreach ($metainfos as $metainfo) {
					$name = $metainfo->getName();

					$struct[$typeID][$name] = $metainfo;
				}
			}

			sly_Core::cache();
			$cache->set('metainfo', 'metainfos', $struct);
		}

		return $struct;
	}

	protected function onNew(WV_Property $property) {
		$type     = $property->getType();
		$name     = $property->getName();
		$arttypes = $property->getArticleTypes();

		// Be *very* careful about fetching the datatype here. In can happen that
		// the user is just installing a project (with already existing metainfo
		// definitions) and has not yet installed the addOn which provides a
		// certain datatype. If we don't catch this, it becomes impossible to
		// install a project.

		try {
			$datatype = WV_Datatype::factory($property->getDatatype());
			$default  = $datatype->getDefaultValue($property);
		}
		catch (Exception $e) {
			// It's okay to assume NULL here, as the developer is probably going
			// to install a database dump in near future.
			$default = null;

			// scream a bit, maybe it was simply a broken configuration
			trigger_error($e->getMessage(), E_USER_WARNING);
		}

		$sql = WV_SQL::getInstance();

		// Standardwert übernehmen

		switch ($type) {
			case WV2_Metainfo::TYPE_ARTICLE:

				if (!empty($arttypes)) {
					$sql->query(
						'INSERT IGNORE INTO ~wv2_meta (object_id,clang,metainfo,meta_type,value) '.
						'SELECT id,clang,?,?,? FROM ~article WHERE type IN (\''.implode('\',\'', $arttypes).'\')',
						array($name, $type, $default), '~'
					);
				}

				break;

			case WV2_Metainfo::TYPE_CATEGORY:

				$sql->query(
					'INSERT IGNORE INTO ~wv2_meta (object_id,clang,metainfo,meta_type,value) '.
					'SELECT id,clang,?,?,? FROM ~article WHERE catpos <> 0',
					array($name, $type, $default), '~'
				);

				break;

			case WV2_Metainfo::TYPE_MEDIUM:

				$sql->query(
					'INSERT IGNORE INTO ~wv2_meta (object_id,clang,metainfo,meta_type,value) '.
					'SELECT ~file.id,~clang.id,?,?,? FROM ~file, ~clang',
					array($name, $type, $default), '~'
				);

				break;

			case WV2_Metainfo::TYPE_USER:

				$sql->query(
					'INSERT IGNORE INTO ~wv2_meta (object_id,clang,metainfo,meta_type,value) '.
					'SELECT ~user.id,~clang.id,?,?,? FROM ~user, ~clang',
					array($name, $type, $default), '~'
				);
		}

		WV2_Metainfo::clearDataCache();
	}

	protected function onDeleted(WV_Property $property) {
		$service = new _WV2_Service_Metadata();
		$service->deleteProperty($property);

		WV2_Metainfo::clearDataCache();
	}

	protected function onChangedDatatype(WV_Property $oldVersion, WV_Property $newVersion) {
		// alle Metadaten löschen
		$this->onDeleted($oldVersion);

		// Standardwerte neu eintragen
		$this->onNew($newVersion);

		WV2_Metainfo::clearDataCache();
	}

	protected function onChangedParameters(WV_Property $oldVersion, WV_Property $newVersion) {
		// Wenn sich die Artikeltypen geändert haben, müssen wir noch flugs
		// die Metadaten löschen/hinzufügen, bevor wir ein Update der Daten
		// ausführen.

		$this->onChanged($oldVersion, $newVersion);

		// Jetzt könnnen die bestehenden Daten aktualisiert werden.

		$oldParams = $oldVersion->getParams();
		$newParams = $newVersion->getParams();

		try {
			$actionsToTake = $oldVersion->datatypeCall('getIncompatibilityUpdateStatement', array($oldParams, $newParams));
		}
		catch (Exception $e) {
			return;
		}

		$prefix   = WV_SQL::getPrefix();
		$sql      = WV_SQL::getInstance();
		$metainfo = $oldVersion->getName();

		foreach ($actionsToTake as $action) {
			list ($type, $what, $where) = $action;
			$what  = str_replace('$$$value_column$$$', 'value', $what);
			$where = str_replace('$$$value_column$$$', 'value', $where);
			$where = 'metainfo = '.$sql->quote($metainfo).' AND meta_type = '.$oldVersion->getType().' AND ('.$where.')';

			switch ($type) {
				case 'DELETE':
					$sql->query('DELETE FROM '.$prefix.'wv2_meta WHERE '.$where);
					break;

				case 'UPDATE':
					$sql->query('UPDATE '.$prefix.'wv2_meta SET '.$what.' WHERE '.$where);
					break;

				default:
					trigger_error(t('metainfo_bad_update_statement', $type), E_USER_WARNING);
			}
		}

		WV2_Metainfo::clearDataCache();
	}

	protected function onChanged(WV_Property $oldVersion, WV_Property $newVersion) {
		$type = $oldVersion->getType();
		$name = $newVersion->getName();

		if ($type !== WV2_Metainfo::TYPE_ARTICLE) {
			return;
		}

		$oldTypes = $oldVersion->getArticleTypes();
		$newTypes = $newVersion->getArticleTypes();

		if ($oldTypes !== $newTypes) {
			$addedTypes   = array_diff($newTypes, $oldTypes);
			$deletedTypes = array_diff($oldTypes, $newTypes);
			$sql          = WV_SQL::getInstance();

			// Daten von den nicht mehr verknüpften Artikeln löschen

			if (!empty($deletedTypes)) {
				$sql->query('DELETE m FROM ~wv2_meta m, ~article a '.
					'WHERE m.object_id = a.id AND m.metainfo = ? AND m.meta_type = ? '.
					'AND a.type IN (\''.implode('\',\'', $deletedTypes).'\')',
					array($name, $type), '~'
				);
			}

			// Daten zu den neu verknüpften Artikeln hinzufügen

			if (!empty($addedTypes)) {
				$sql->query(
					'INSERT IGNORE INTO ~wv2_meta '.
					'SELECT id,clang,?,?,? FROM ~article '.
					'WHERE type IN (\''.implode('\',\'', $addedTypes).'\')',
					array($name, $type, $newVersion->getDefault()), '~'
				);
			}
		}

		WV2_Metainfo::clearDataCache();
	}

	protected function createProperty($name, array $property) {
		return new _WV2_Info($this->currentType, $name, $property);
	}

	public function rebuild(array $metainfos) {
		$types = array(WV2_Metainfo::TYPE_ARTICLE, WV2_Metainfo::TYPE_CATEGORY, WV2_Metainfo::TYPE_MEDIUM, WV2_Metainfo::TYPE_USER);
		$sql   = WV_SQL::getInstance();

		$sql->beginTransaction();

		// remove data for non-existing languages

		$clangs = implode(',', sly_Util_Language::findAll(true));
		$sql->query('DELETE FROM ~wv2_meta WHERE '.(empty($clangs) ? '1' : 'clang NOT IN ('.$clangs.')'), null, '~');

		foreach ($types as $type) {
			$list  = isset($metainfos[$type]) ? $metainfos[$type] : array();
			$names = array_keys($list);
			$names = array_map(array($sql, 'quote'), $names);

			// delete all metavalues which don't have a valid metainfo

			$sql->query(
				'DELETE FROM ~wv2_meta WHERE meta_type = ?'.
				(empty($names) ? '' : ' AND metainfo NOT IN ('.implode(',', $names).')'),
				$type, '~'
			);

			// check if the linked objects (articles, categories, files) still exist

			if (!empty($names)) {
				switch ($type) {
					case WV2_Metainfo::TYPE_ARTICLE:
						foreach ($list as $name => $metainfo) {
							$aTypes = $metainfo->getArticleTypes();

							// If this metainfo is not assigned to *any* type, delete all occurences
							// of it. Otherwise just delete those metavalues which belong to articles
							// that don't have the required article types.

							if (empty($aTypes)) {
								$articles = '';
							}
							else {
								$aTypes   = array_map(array($sql, 'quote'), $aTypes);
								$articles = ' AND (object_id,clang) NOT IN (SELECT id,clang FROM ~article WHERE `type` IN ('.implode(',', $aTypes).'))';
							}

							$sql->query(
								'DELETE FROM ~wv2_meta WHERE meta_type = ? AND metainfo = ?'.$articles,
								array($type, $name), '~'
							);
						}
						break;

					case WV2_Metainfo::TYPE_CATEGORY:
						$sql->query(
							'DELETE FROM ~wv2_meta WHERE meta_type = ? '.
							'AND (object_id,clang) NOT IN (SELECT id,clang FROM ~article WHERE startpage = 1)',
							$type, '~'
						);
						break;

					case WV2_Metainfo::TYPE_MEDIUM:
						$sql->query('DELETE FROM ~wv2_meta WHERE meta_type = ? AND object_id NOT IN (SELECT id FROM ~file)', $type, '~');
						break;

					case WV2_Metainfo::TYPE_USER:
						$sql->query('DELETE FROM ~wv2_meta WHERE meta_type = ? AND object_id NOT IN (SELECT id FROM ~user)', $type, '~');
				}
			}

			// add missing database rows
			// (doing this after the deletion makes the DELETEs a bit faster in worst
			// case scenarios where just everything is missing and has to be inserted)

			foreach ($list as $metainfo) {
				$this->onNew($metainfo); // run INSERT IGNORE
			}
		}

		$sql->commit();
	}
}
