<?php namespace Laravel;

use Closure;
use Laravel\Session\Drivers\Driver;
use Laravel\Session\Drivers\Sweeper;

class Session {

	/**
	 * The session array that is stored by the driver.
	 *
	 * @var array
	 */
	protected static $session;

	/**
	 * Indicates if the session already exists in storage.
	 *
	 * @var bool
	 */
	protected static $exists = true;

	/**
	 * Start the session handling for the current request.
	 *
	 * @param  Driver  $driver
	 * @return void
	 */
	public static function start(Driver $driver)
	{
		if ( ! is_null($id = Cookie::get(Config::$items['session']['cookie'])))
		{
			static::$session = $driver->load($id);
		}

		if (static::invalid())
		{
			static::$exists = false;

			static::$session = array('id' => Str::random(40), 'data' => array());
		}

		if ( ! static::has('csrf_token'))
		{
			// A CSRF token is stored in every session. The token is used by the
			// Form class and the "csrf" filter to protect the application from
			// cross-site request forgery attacks. The token is simply a long,
			// random string which should be posted with each request.
			static::put('csrf_token', Str::random(40));
		}
	}

	/**
	 * Deteremine if the session payload instance is valid.
	 *
	 * The session is considered valid if it exists and has not expired.
	 *
	 * @return bool
	 */
	protected static function invalid()
	{
		$lifetime = Config::$items['session']['lifetime'];

		return is_null(static::$session) or (time() - static::$session['last_activity'] > ($lifetime * 60));
	}

	/**
	 * Determine if the session or flash data contains an item.
	 *
	 * @param  string  $key
	 * @return bool
	 */
	public static function has($key)
	{
		return ( ! is_null(static::get($key)));
	}

	/**
	 * Get an item from the session.
	 *
	 * The session flash data will also be checked for the requested item.
	 *
	 * <code>
	 *		// Get an item from the session
	 *		$name = Session::get('name');
	 *
	 *		// Return a default value if the item doesn't exist
	 *		$name = Session::get('name', 'Taylor');
	 * </code>
	 *
	 * @param  string  $key
	 * @param  mixed   $default
	 * @return mixed
	 */
	public static function get($key, $default = null)
	{
		foreach (array($key, ':old:'.$key, ':new:'.$key) as $possibility)
		{
			if (array_key_exists($possibility, static::$session['data']))
			{
				return static::$session['data'][$possibility];
			}
		}

		return ($default instanceof Closure) ? call_user_func($default) : $default;
	}

	/**
	 * Write an item to the session.
	 *
	 * @param  string  $key
	 * @param  mixed   $value
	 * @return void
	 */
	public static function put($key, $value)
	{
		static::$session['data'][$key] = $value;
	}

	/**
	 * Write an item to the session flash data.
	 *
	 * Flash data only exists for the next request to the application.
	 *
	 * @param  string  $key
	 * @param  mixed   $value
	 * @return void
	 */
	public static function flash($key, $value)
	{
		static::put(':new:'.$key, $value);
	}

	/**
	 * Keep all of the session flash data from expiring at the end of the request.
	 *
	 * @return void
	 */
	public static function reflash()
	{
		static::keep(array_keys(static::$session['data']));
	}

	/**
	 * Keep a session flash item from expiring at the end of the request.
	 *
	 * @param  string|array  $key
	 * @return void
	 */
	public static function keep($keys)
	{
		foreach ((array) $keys as $key) static::flash($key, static::get($key));
	}

	/**
	 * Remove an item from the session data.
	 *
	 * @param  string  $key
	 * @return Driver
	 */
	public static function forget($key)
	{
		unset(static::$session['data'][$key]);
	}

	/**
	 * Remove all of the items from the session.
	 *
	 * @return void
	 */
	public static function flush()
	{
		static::$session['data'] = array();
	}

	/**
	 * Assign a new, random ID to the session.
	 *
	 * @return void
	 */
	public static function regenerate()
	{
		static::$session['id'] = Str::random(40);

		static::$exists = false;
	}

	/**
	 * Get the CSRF token that is stored in the session data.
	 *
	 * @return string
	 */
	public static function token()
	{
		return static::get('csrf_token');
	}

	/**
	 * Store the session payload in storage.
	 *
	 * @param  Driver  $driver
	 * @return void
	 */
	public static function save(Driver $driver)
	{
		static::$session['last_activity'] = time();

		static::age();

		$config = Config::$items['session'];

		$driver->save(static::$session, $config, static::$exists);

		static::cookie();

		// Some session drivers implement the Sweeper interface, meaning that they
		// must clean up expired sessions manually. If the driver is a sweeper, we
		// need to determine if garbage collection should be run for the request.
		// Since garbage collection can be expensive, the probability of it
		// occuring is controlled by the "sweepage" configuration option.
		if ($driver instanceof Sweeper and (mt_rand(1, $config['sweepage'][1]) <= $config['sweepage'][0]))
		{
			$driver->sweep(time() - ($config['lifetime'] * 60));
		}
	}

	/**
	 * Age the session flash data.
	 *
	 * Session flash data is only available during the request in which it
	 * was flashed, and the request after that. To "age" the data, we will
	 * remove all of the :old: items and re-address the new items.
	 *
	 * @return void
	 */
	protected static function age()
	{
		foreach (static::$session['data'] as $key => $value)
		{
			if (strpos($key, ':old:') === 0) static::forget($key);
		}

		// Now that all of the "old" keys have been removed from the session data,
		// we can re-address all of the newly flashed keys to have old addresses.
		// The array_combine method uses the first array for keys, and the second
		// array for values to construct a single array from both.
		$keys = str_replace(':new:', ':old:', array_keys(static::$session['data']));

		static::$session['data'] = array_combine($keys, array_values(static::$session['data']));
	}

	/**
	 * Send the session ID cookie to the browser.
	 *
	 * @return void
	 */
	protected static function cookie()
	{
		$config = Config::$items['session'];

		extract($config, EXTR_SKIP);

		$minutes = ( ! $expire_on_close) ? $lifetime : 0;

		Cookie::put($cookie, static::$session['id'], $minutes, $path, $domain, $secure);	
	}

}