<?php
if (!defined('VB_ENTRY')) die('Access denied.');
/*======================================================================*\
   || #################################################################### ||
   || # vBulletin 5.0.0 Beta 10
   || # ---------------------------------------------------------------- # ||
   || # Copyright ?2000-2012 vBulletin Solutions Inc. All Rights Reserved. ||
   || # This file may not be redistributed in whole or significant part. # ||
   || # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
   || # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
   || #################################################################### ||
   \*======================================================================*/

/**
 * vB_Cache_Memory
 *
 * @package vBForum
 * @access public
 */

class vB_Cache_Memory extends vB_Cache
{
	/**This is a page-level cache handler. The contents are used for one page load only
	*
	*
	***/
	protected $events = array();

	protected $cache = array();

	protected static $instance;

	/**
	 * Constructor public to allow for separate automated unit testing. Actual code should use
	 * vB_Cache::instance();
	 * @see vB_Cache::instance()
	 */
	public function __construct()
	{

	}

	/**
	 * Returns singleton instance of self.
	 *
	 * @return vB_Cache_Memory		- Reference to singleton instance of the cache handler
	 */
	public static function instance($type = self::CACHE_STD)
	{
		if (!isset(self::$instance))
		{
			$class = __CLASS__;
			self::$instance = new $class();
		}

		return self::$instance;
	}

	/** This reads a variable from the cache
	 *
	 *	@param	string	a cache key
	 *
	 *	@return	mixed	either false or a value
	 **/
	public function read($cacheKey, &$write_lock = false, $save_meta = false)
	{
		if (empty($cacheKey) OR !is_string($cacheKey))
		{
			throw new vB_Exception_Cache('invalid_data');
		}
		if (isset($this->cache[$cacheKey]))
		{
			if (($this->cache[$cacheKey] === false) OR
				(($this->cache[$cacheKey]['expires'] > 0) AND ($this->cache[$cacheKey]['expires']) < time()))
			{
				return false;
			}

			return $this->cache[$cacheKey]['data'];
		}
		return false;
	}

	/* Writes the cache data to storage.
	*
	* @param vB_CacheObject $cache
	*/
	protected function writeCache(vB_CacheObject $cache)
	{
		$data = $cache->getData();
		$cacheKey = $cache->getKey();

		//remove any existing event keys
		$this->purgeCache($cacheKey);

		$this->cache[$cacheKey] = array('data' => $data,
			'expires' => $cache->getExpiry());
		$events = $cache->getEvents();

		if (!empty($events))
		{
			$this->addEvents($cacheKey, $events);
		}
	}


	public function addEvents($key, $events)
	{
		if (!is_array($events))
		{
			$events = array($events);
		}

		foreach ($events as $event)
		{
			if (empty($this->events[$event]))
			{
				$this->events[$event] = array();
			}
			$this->events[$event][$key] = $key;
		}
	}


	public function removeEvents($event)
	{
		if (isset($this->events[$event]))
		{
			unset($this->events[$event]);
		}
	}



	/**
	 * Reads the cache object from storage.
	 *
	 * @param string $key						- Id of the cache entry to read
	 * @return vB_CacheObject
	 */
	protected function readCache($key)
	{
		if (!array_key_exists($key, $this->cache))
		{
			return false;
		}

		$cache = $this->cache[$key];
		if (($this->cache[$cacheKey] === false) OR
			( ($cache['expires'] > 0) AND (vB::getRequest()->getTimeNow() > $cache['expires'])))
		{
			return 0;
		}
		return $cache;
	}


	/**
	 * Reads an array of cache objects from storage.
	 *
	 * @param string $keys						- Ids of the cache entry to read
	 * @return array of vB_CacheObjects
	 */
	protected function readCacheArray($keys)
	{
		$found = array();
		foreach ($keys AS $key)
		{
			$found[$key] = $this-> readCache($key);
		}
		return $found;
	}


	/** This unsets a variable in the cache
	 *
	 *	@param	mixed	a cache event key or array of keys
	 *
	 **/
	public function event($events)
	{

		if (!is_array($events))
		{
			$events = array($events);
		}

		foreach ($events as $event)
		{
			if (empty($event) OR !is_string($event))
			{
				throw new vB_Exception_Cache('invalid_data');
			}

			if (array_key_exists($event, $this->events))
			{
				$cacheKeys = $this->events[$event];
				foreach($cacheKeys AS $cacheKey)
				{
					$this->purgeCache($cacheKey);
				}
			}
		}
	}



	/**
	 * Sets a cache entry as expired in storage.
	 *
	 * @param string $key						- Key of the cache entry to expire
	 */
	protected function expireCache($key)
	{
		$this->purgeCache($key);

		return true;
	}

	/**
	 * Locks a cache entry.
	 *
	 * @param string $key						- Key of the cache entry to lock
	 * @return bool - TRUE iff the lock was obtained
	 */
	public function lock($key)
	{
		return true; //not needed
	}


	/***
	 *	Disable the memory caching on the main cache class.
	 **/
	protected function setLoadedValue(vB_CacheObject $cache)
	{
	}

	protected function unsetLoadedValue($key)
	{
	}

	protected function expireLoadedValue($key)
	{
	}

	protected function getLoadedValue($key)
	{
		return false;
	}

	protected function setLoadedValueEmpty($key)
	{
	}

	protected function unsetLoadedValueEmpty($key)
	{
	}
	
	/**
	 * Removes a cache object from storage.
	 *
	 * @param int $key							- Key of the cache entry to purge
	 * @return bool								- Whether anything was purged
	 */
	protected function purgeCache($cacheKey)
	{
		if (empty($cacheKey) OR !is_string($cacheKey))
		{
			return false;
		}

		foreach ($this->events AS $key => $events)
		{
			if (array_key_exists($cacheKey, $events ))
			{
				unset($this->events[$key][$cacheKey]);
			}

			if (empty($this->events[$key]))
			{
				unset($this->events[$key]);
			}
		}

		if (array_key_exists($cacheKey, $this->cache))
		{
			unset($this->cache[$cacheKey]);
		}

	}


	/**
	 * Cleans cache.
	 *
	 * @param bool $only_expired				- Only clean expired entries
	 */
	public function clean($only_expired = true)
	{
		//when something is expired we delete immediately
		if ($only_expired)
		{
			$timenow = vB::getRequest()->getTimeNow();
			foreach($this->cache as $cacheKey => $cacheRecord)
			{
				if (!empty($cacheRecord['expires']) AND ($cacheRecord['expires'] > $timeNow))
				{
					$this->purgeCache($cacheKey);
				}
			}
		}
		else
		{
			$this->cache = array();
			$this->events = array();
		}
	}
}
