<?php
/*
 * Copyright (c) 2012, 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
 */

/**
 * Database wrapper
 *
 * @since  v4.0
 * @author Christoph
 */
class _WV24_Storage {
	private $db;
	private $cache;

	public function __construct(sly_DB_PDO_Persistence $db, BabelCache $cache = null) {
		$this->db    = $db;
		$this->cache = $cache;
	}

	private function replace(array $data) {
		$cols    = array();
		$values  = array();
		$updates = array();

		foreach ($data as $key => $val) {
			$cols[]    = '`'.$key.'`';
			$values[]  = $this->db->quote($val);
			$updates[] = '`'.$key.'` = VALUES(`'.$key.'`)';
		}

		$cols    = implode(', ', $cols);
		$values  = implode(', ', $values);
		$updates = implode(', ', $updates);
		$prefix  = sly_Core::getTablePrefix();

		$query = sprintf(
			'INSERT INTO %swv24_urls (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s',
			$prefix, $cols, $values, $updates
		);

		$this->db->exec($query);
	}

	public function setUrl($namespace, $ident, $url, $articleID, $clang, $path = '', array $params = null) {
		$now  = date('Y-m-d H:i:s');
		$json = $params === null ? '' : json_encode($params);
		$data = array('namespace' => $namespace, 'ident' => $ident, 'url' => $url, 'article_id' => (int) $articleID, 'clang' => (int) $clang, 'path' => $path, 'params' => $json, 'created' => $now);

		$this->replace($data);

		$url = new _WV24_URL($data);

		if ($this->cache !== null) {
			$this->cache->set('realurl2.ns.'.$namespace, $ident, $url);
			$this->cache->flush('realurl2.volatile', true);
		}

		return $url;
	}

	public function setMany($namespace, array $urls) {
		$this->db->transactional(array($this, 'setManyTransaction'), array($namespace, $urls));
	}

	public function setManyTransaction($namespace, array $urls) {
		$now = date('Y-m-d H:i:s');

		foreach ($urls as $url) {
			$urlObj = null;

			if ($url instanceof _WV24_URL) {
				$urlObj = $url;
				$url    = array(
					'namespace' => $url->getNamespace(), 'ident' => $url->getIdent(),
					'article_id' => $url->getArticleID(), 'clang' => $url->getClang(),
					'path' => $url->getPath(), 'url' => $url->getRawURL(),
					'created' => date('Y-m-d H:i:s', $url->getCreated()),
					'params' => json_encode($url->getParams())
				);
			}
			else {
				$url['url']       = $url['url'] instanceof _WV24_URL ? $url['url']->getRawURL() : $url['url'];
				$url['path']      = isset($url['path'])   ? $url['path']                : '';
				$url['params']    = isset($url['params']) ? json_encode($url['params']) : '';
				$url['created']   = $now;
				$url['namespace'] = $namespace;
			}

			$this->replace($url);
		}

		if ($this->cache !== null) {
			$this->cache->flush('realurl2.ns.'.$namespace, true);
			$this->cache->flush('realurl2.volatile', true);
		}
	}

	/* Methoden zum Leeren des Caches */

	public function clearAll() {
		$this->clear($this->where());
	}

	public function clearByNamespace($namespace) {
		$this->clear($this->where($namespace));
	}

	public function clearByIdent($namespace, $ident, $isLike = false) {
		$this->clear($this->where($namespace, $ident, $isLike));
	}

	public function clearByPath($namespace, $path, $isLike = true, $clang = null) {
		$this->clear($this->where($namespace, null, null, $path, $isLike, null, $clang));
	}

	public function clearByArticle($article, $clang = null, $namespace = null) {
		$this->clear($this->where($namespace, null, null, null, null, $article, $clang));
	}

	public function clearByClang($clang, $namespace = null) {
		$this->clear($this->where($namespace, null, null, null, null, null, $clang));
	}

	public function complexClear($namespace = null, $ident = null, $identIsLike = null, $path = null, $pathIsLike = null, $article = null, $clang = null) {
		$this->clear($this->where($namespace, $ident, $identIsLike, $path, $pathIsLike, $article, $clang));
	}

	/* Methoden zum Auffinden von Routen */

	public function findAll($asObjects = false) {
		return $this->find($this->where(), null, null, null, $asObjects);
	}

	public function findByNamespace($namespace, $asObjects = false) {
		return $this->find($this->where($namespace), null, null, null, $asObjects);
	}

	public function findByIdent($namespace, $ident, $isLike = false, $asObjects = false) {
		return $this->find($this->where($namespace, $ident, $isLike), null, null, null, $asObjects);
	}

	public function findByUrl($url, $asObjects = false) {
		return $this->find(array('url' => $url), null, null, null, $asObjects);
	}

	public function findByPath($namespace, $path, $isLike = true, $asObjects = false) {
		return $this->find($this->where($namespace, null, null, $path, $isLike), null, null, null, $asObjects);
	}

	public function findByArticle($article, $clang = null, $namespace = null, $asObjects = false) {
		return $this->find($this->where($namespace, null, null, null, null, $article, $clang), null, null, null, $asObjects);
	}

	public function findByClang($clang, $namespace = null, $asObjects = false) {
		return $this->find($this->where($namespace, null, null, null, null, null, $clang), null, null, null, $asObjects);
	}

	public function complexFind($namespace = null, $ident = null, $identIsLike = null, $path = null, $pathIsLike = null, $asObjects = false, $article = null, $clang = null) {
		return $this->find($this->where($namespace, $ident, $identIsLike, $path, $pathIsLike, $article, $clang), null, null, null, $asObjects);
	}

	/* Helper */

	public function find($where, $order = null, $offset = null, $limit = null, $asObjects = false) {
		if (!$asObjects) {
			$this->db->select('wv24_urls', 'namespace, ident', $where, null, $order, $offset, $limit);
			return $this->db->all();
		}

		$this->db->select('wv24_urls', '*, UNIX_TIMESTAMP(created) AS unix', $where, null, $order, $offset, $limit);

		$rows   = $this->db->all();
		$result = array();

		foreach ($rows as $row) {
			$result[] = new _WV24_URL($row);
		}

		return $result;
	}

	public function clear($where) {
		// cleanup cache
		if ($this->cache !== null) {
			if ($where === '1') {
				$this->cache->flush('realurl2', true);
			}
			else {
				foreach ($this->find($where) as $url) {
					$this->cache->delete('realurl2.ns.'.$url['namespace'], $url['ident']);
				}

				// we don't know what URLs will resolve to the just removed URLs
				// (think of '/index.html' -> '/', domain switches, ...), so we just
				// clear the whole resolver cache.
				$this->cache->flush('realurl2.volatile', true);
			}
		}

		// cleanup database
		$this->db->delete('wv24_urls', $where);
	}

	public function where($namespace = null, $ident = null, $identIsLike = null, $path = null, $pathIsLike = null, $article = null, $clang = null) {
		$wheres   = array();
		$params   = array();
		$asString = false;

		if ($namespace !== null) {
			$wheres[] = 'namespace = ?';
			$params[] = $namespace;
		}

		if ($ident !== null) {
			$wheres[]  = 'ident '.($identIsLike ? 'LIKE ?' : '= ?');
			$params[]  = $ident;
			$asString |= $identIsLike;
		}

		if ($path !== null) {
			$wheres[]  = 'path '.($pathIsLike ? 'LIKE ?' : '= ?');
			$params[]  = $path;
			$asString |= $pathIsLike;
		}

		if ($article !== null) {
			$wheres[] = 'article_id = ?';
			$params[] = WV_Sally::getIDForArticle($article);
		}

		if ($clang !== null) {
			$wheres[] = 'clang = ?';
			$params[] = (int) $clang;
		}

		// If we got some LIKE statements, we need to build the WHERE as a full string
		// (sly_DB cannot handle non-equal statements).
		if ($asString) {
			$result = array();

			foreach ($wheres as $idx => $where) {
				$value = $params[$idx];
				$type  = is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR;
				$value = $this->db->quote($value, $type);

				$result[] = substr($where, 0, -1).$value;
			}

			return empty($result) ? '1' : implode(' AND ', $result);
		}

		// build the regular {col: value, col: value} list
		$result = array();

		foreach ($wheres as $idx => $where) {
			$value = $params[$idx];
			$col   = substr($where, 0, strpos($where, ' '));

			$result[$col] = $value;
		}

		return empty($result) ? '1' : $result;
	}
}
