<?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
 */

abstract class WV8_Settings {
	/**
	 * Zugriff auf eine Einstellung
	 *
	 * Diese Methode liefert den aktuellen Wert eier bestimmten Einstellung
	 * zurück. Wird die Einstellung nicht gefunden, so wird null zurückgegeben.
	 *
	 * @param  string $namespace  der Namespace der Einstellung
	 * @param  string $name       interner Name der Einstellung
	 * @param  mixed  $default    Standardwert, falls der Wert der Setting leer ist
	 * @param  int    $clang      die gewünschte Sprache der Einstellung
	 * @param  bool   $raw        return serialized data
	 * @return mixed              kann beliebigen Typs sein (je nach Datentyp und dessen Parameter)
	 */
	public static function getValue($namespace, $name, $default = null, $clang = null, $raw = false) {
		$cache   = sly_Core::cache();
		$ns      = 'globalsettings.values';
		$clangID = WV_Sally::clang($clang);
		$key     = sly_Cache::generateKey($namespace, $name);
		$setting = $cache->get($ns, $key, null);

		// if no setting has yet been cached, find it and pull the value
		if ($setting === null) {
			$obj = self::getSetting($namespace, $name);

			// read values form config or database
			if ($obj->isLocal()) {
				$configKey = $namespace.'.'.$name;
				$values    = sly_Core::config()->get('global_settings_values/'.$configKey, false);
			}
			else {
				$sql    = WV_SQL::getInstance();
				$values = $sql->fetch('`values`', 'wv8_values', '`namespace` = ? AND `name` = ?', array($namespace, $name));

				if ($values !== false) {
					$values = empty($values) ? array() : self::decodeValues($values);
				}
			}

			// broken database or missing config element?
			if ($values === false) {
				if ($obj->isLocal()) {
					throw new WV8_Exception('The local config is missing the item '.$namespace.'/'.$name.'. Something went wrong.');
				}
				else {
					throw new WV8_Exception('The database is missing the item '.$namespace.'/'.$name.'. Something went wrong.');
				}
			}

			// store data in cache
			$setting = array('params' => $obj->getParams(), 'datatype' => $obj->getDatatype(), 'raw' => $values);
			$cache->set($ns, $key, $setting);
		}

		// fix missing clangs
		$rawValues = self::fixValues($setting['raw']);

		// re-cache data if changed
		if ($rawValues !== $setting['raw']) {
			$setting['raw'] = $rawValues;
			$cache->set($ns, $key, $setting);
		}

		// raw data is required for forms
		if ($raw) {
			return $setting['raw'];
		}

		// prepare deserialization
		$datatype = WV_Datatype::factory($setting['datatype']);
		$params   = $setting['params'];

		// return values for all languages
		if ($clang === WV_Sally::CLANG_ALL) {
			foreach ($setting['raw'] as $clangID => $raw) {
				$value = $datatype->deserializeValue($raw, $params);

				if ($default !== null && $datatype->isValueEmpty($value, $params)) {
					$value = $default;
				}

				$setting['raw'][$clangID] = $value;
			}

			return $setting['raw'];
		}

		if (!array_key_exists($clangID, $setting['raw'])) {
			throw new Exception('Invalid language '.$clangID.' given!');
		}

		$value = $datatype->deserializeValue($setting['raw'][$clangID], $params);

		if ($default !== null && $datatype->isValueEmpty($value, $params)) {
			$value = $default;
		}

		return $value;
	}

	/**
	 * Zugriff auf einen Teilbaum aller Einstellungen
	 *
	 * Diese Methode liefert die aktuellen Werte der Einstellungen
	 * zurück, die im Teilbaum des Namespaces liegen.
	 *
	 * @param  string $namespace  der Namespace der Einstellung
	 * @param  int    $clang      die gewünschte Sprache der Einstellung
	 * @return array              Liste aller gefundenen Einstellungen und ihrer Werte
	 */
	public static function getValues($namespace, $clang = null) {
		$all    = self::loadAll();
		$result = array();

		foreach ($all as $ns => $settings) {
			if ($ns !== $namespace && !sly_Util_String::startsWith($ns, "$namespace.")) continue;

			foreach ($settings as $name => $setting) {
				$key   = trim(substr($ns, strlen($namespace)), '.');
				$key   = empty($key) ? $name : $key.'.'.$name;
				$value = self::getValue($ns, $name, null, $clang);

				$result[$key] = $value;
			}
		}

		return $result;
	}

	public static function clearCache(array $params = array()) {
		sly_Core::cache()->flush('globalsettings', true);
		return isset($params['subject']) ? $params['subject'] : true;
	}

	public static function onClearCache(array $params = array()) {
		$isSystem   = sly_Core::getCurrentControllerName() === 'system';
		$controller = sly_Core::getCurrentController();
		$selective  = $isSystem && method_exists($controller, 'isCacheSelected');
		$clearData  = !$selective || $controller->isCacheSelected('wv8-data');
		$rebuild    = !$selective || $controller->isCacheSelected('wv8-rebuild');

		if ($clearData) {
			self::clearCache();
		}

		if ($rebuild) {
			self::rebuild(array('subject' => true));
		}

		return isset($params['subject']) ? $params['subject'] : true;
	}

	/**
	 * SLY_SYSTEM_CACHES
	 *
	 * @param array $params  event parameters
	 */
	public static function systemCacheList(array $params) {
		$select     = $params['subject'];
		$selected   = $select->getValue();
		$selected[] = 'wv8-data';

		if (sly_Core::isDeveloperMode()) {
			$selected[] = 'wv8-rebuild';
		}

		$select->addValue('wv8-data',    'Global Settings-Cache leeren');
		$select->addValue('wv8-rebuild', 'Global Settings validieren & neu aufbauen');
		$select->setSelected($selected);

		return $select;
	}

	public static function getPages() {
		$all    = self::loadAll();
		$result = array();

		foreach ($all as $ns => $settings) {
			foreach ($settings as $setting) {
				$result[] = $setting->getPagename();
			}
		}

		return array_unique($result);
	}

	public static function getGroups($pagename) {
		return self::_getGroups($pagename, 'pagename');
	}

	public static function getGroupsByNamespace($namespace) {
		return self::_getGroups($namespace, 'namespace');
	}

	protected static function _getGroups($value, $name) {
		$all    = self::loadAll();
		$result = array();

		if ($name === 'namespace') {
			if (empty($all[$value])) return array();

			foreach ($all[$value] as $setting) {
				$result[] = $setting->getGroup();
			}
		}
		else { // by pagename
			foreach ($all as $ns => $settings) {
				foreach ($settings as $setting) {
					if ($setting->getPagename() === $value) {
						$result[] = $setting->getGroup();
					}
				}
			}
		}

		return array_unique($result);
	}

	public static function getSettings($namespace = null, $pagename = null, $group = null) {
		$all    = self::loadAll();
		$result = array();

		foreach ($all as $ns => $settings) {
			// if filtering by namespace, it's easy to skip this sublist
			if ($namespace !== null && $ns !== $namespace) continue;

			foreach ($settings as $name => $setting) {
				// filter by pagename
				if ($pagename !== null && $pagename !== $setting->getPagename()) continue;

				// filter by group
				if ($group !== null && $group !== $setting->getGroup()) continue;

				$key          = $ns.'/'.$name;
				$result[$key] = $setting;
			}
		}

		return $result;
	}

	public static function exists($namespace, $name) {
		$properties = self::loadAll();
		return isset($properties[$namespace][$name]);
	}

	public static function getSetting($namespace, $name) {
		$properties = self::loadAll();

		if (!isset($properties[$namespace][$name])) {
			throw new WV8_Exception('Setting "'.$namespace.'/'.$name.'" does not exist.');
		}

		return $properties[$namespace][$name];
	}

	public static function translate($string) {
		preg_match_all('#translate:([a-z0-9_-]+)#i', $string, $matches, PREG_SET_ORDER);

		foreach ($matches as $match) {
			$string = str_replace($match[0], t($match[1]), $string);
		}

		return $string;
	}

	public static function translateList($list) {
		foreach ($list as $key => $value) {
			$list[$key] = self::translate($value);
		}

		uasort($list, 'strnatcasecmp');
		return $list;
	}

	public static function renderPartialForm($namespace) {
		self::prepareHead();
		$groups = self::getGroupsByNamespace($namespace);
		include _WV8_PATH.'templates/partial.phtml';
	}

	public static function prepareHead() {
		static $init = false;

		if ($init === false) {
			$layout  = sly_Core::getLayout();
			$prefix1 = sly_Util_AddOn::assetBaseUri('webvariants/developer-utils');
			$prefix2 = sly_Util_AddOn::assetBaseUri('webvariants/global-settings');

			$layout->addJavaScriptFile($prefix1.'js/jquery.wvtogglefieldset.min.js');
			$layout->addJavaScriptFile($prefix1.'js/jquery.cookie.min.js');
			$layout->addJavaScriptFile($prefix2.'js/globalsettings.js');
			$layout->addCSSFile($prefix2.'css/globalsettings.less');

			$init = true;
		}
	}

	/**
	 * Reload config
	 *
	 * This method is supposed to be called by addOns during their installation
	 * if they need to access their global settings directly (like realURL2,
	 * which will populate the settings with some sensible default values).
	 *
	 * loadAll() is supposed to be somewhat private and should only be called
	 * internally. Accessing the settings should be done via getSettings().
	 */
	public static function reloadConfig() {
		self::loadAll(true);
	}

	public static function loadAll($forceRefresh = false) {
		static $properties = null;

		if ($properties === null || $forceRefresh) {
			$config    = sly_Core::config();
			$settings  = $config->get('global_settings', array());
			$service   = sly_Service_Factory::getAddOnService();
			$cacheFile = $service->internalDirectory('webvariants/global-settings').'/settings.php';
			$oldState  = array();

			if (file_exists($cacheFile)) {
				include $cacheFile;
				$oldState = $known;
			}

			$service    = new WV8_Service();
			$properties = $service->loadFromArray($settings, $oldState);

			if ($oldState !== $settings) {
				file_put_contents($cacheFile, '<?php $known = '.var_export($settings, true).';');
			}
		}

		return $properties;
	}

	public static function fixValues($values) {
		$values    = sly_makeArray($values);
		$languages = sly_Util_Language::findAll();

		// missing values?
		foreach (array_keys($languages) as $clangID) {
			if (!isset($values[$clangID])) {
				$values[$clangID] = null;
			}
		}

		// too many values?
		foreach (array_keys($values) as $clangID) {
			if (!isset($languages[$clangID])) {
				unset($values[$clangID]);
			}
		}

		return $values;
	}

	public static function hash($str) {
		return sprintf('%u', crc32($str));
	}

	public static function encodeValues($values) {
		return json_encode($values);
	}

	public static function decodeValues($values) {
		return json_decode($values, true);
	}

	public static function rebuild(array $params) {
		// refresh the metainfos
		sly_Core::cache()->delete('globalsettings', 'settings');
		$settings = WV8_Settings::loadAll(true);

		// and here we go (re-using the implementation of some methods)
		$service = new WV8_Service();
		$service->rebuild($settings);

		// done
		return $params['subject'];
	}

	public static function bootcache(array $params) {
		sly_Util_BootCache::addClass(__CLASS__);

		if ($params['event'] === 'SLY_BOOTCACHE_CLASSES_BACKEND') {
			sly_Util_BootCache::addClass('WV8_Navigation');
		}
	}

	public static function mediaUsages(array $params) {
		$usages   = $params['subject'];
		$filename = $params['filename'];
		$sql      = WV_SQL::getInstance();

		$sql->query('SELECT `namespace`,`name` FROM ~wv8_values WHERE `values` LIKE ?', array('%'.$filename.'%'), '~');
		foreach ($sql as $row) {
			$setting = self::getSetting($row['namespace'], $row['name']);
			$usages[] = array(
				'title' => ht('wv8_title').': '.$setting->getTitle(),
				'type'  => 'global-setting',
				'link'  => 'index.php?page=globalsettings_settings&wv8page='.self::hash($setting->getPagename())
			);
		}

		return $usages;
	}

	public static function backendNavigation(array $params) {
		$user = sly_Util_User::getCurrentUser();

		if ($user !== null && ($user->isAdmin() || $user->hasRight('pages', 'globalsettings'))) {
			$nav   = sly_Core::getLayout()->getNavigation();
			$group = $nav->getGroup('addons');
			$nav->addPage($group, 'globalsettings', t('wv8_title'));
		}

		return $params['subject'];
	}
}
