<?php namespace Laravel\Routing;

use Laravel\Request;

class Filter {

	/**
	 * The route filters for the application.
	 *
	 * @var array
	 */
	protected static $filters = array();

	/**
	 * Register an array of route filters.
	 *
	 * @param  array  $filters
	 * @return void
	 */
	public static function register($filters)
	{
		static::$filters = array_merge(static::$filters, $filters);
	}

	/**
	 * Call a filter or set of filters.
	 *
	 * @param  array|string  $filters
	 * @param  array         $pass
	 * @param  bool          $override
	 * @return mixed
	 */
	public static function run($filters, $pass = array(), $override = false)
	{
		foreach (static::parse($filters) as $filter)
		{
			$parameters = array();

			// Parameters may be passed into routes by specifying the list of
			// parameters after a colon. If parameters are present, we will
			// merge them into the parameter array that was passed to the
			// method and slice the parameters off of the filter string.
			if (($colon = strpos($filter, ':')) !== false)
			{
				$parameters = explode(',', substr($filter, $colon + 1));

				$filter = substr($filter, 0, $colon);
			}

			if ( ! isset(static::$filters[$filter])) continue;

			$parameters = array_merge($pass, $parameters);

			$response = call_user_func_array(static::$filters[$filter], $parameters);

			// "Before" filters may override the request cycle. For example,
			// an authentication filter may redirect a user to a login view
			// if they are not logged in. Because of this, we will return
			// the first filter response if overriding is enabled.
			if ( ! is_null($response) and $override) return $response;
		}
	}

	/**
	 * Parse a string of filters into an array.
	 *
	 * @param  string|array  $filters
	 * @return array
	 */
	public static function parse($filters)
	{
		return (is_string($filters)) ? explode('|', $filters) : (array) $filters;
	}

}

class Filter_Collection {

	/**
	 * The event being filtered.
	 *
	 * @var string
	 */
	public $name;

	/**
	 * The included controller methods.
	 *
	 * @var array
	 */
	public $only = array();

	/**
	 * The excluded controller methods.
	 *
	 * @var array
	 */
	public $except = array();

	/**
	 * The filters contained by the collection.
	 *
	 * @var string|array
	 */
	public $filters = array();

	/**
	 * The HTTP methods for which the filter applies.
	 *
	 * @var array
	 */
	public $methods = array();

	/**
	 * Create a new filter collection instance.
	 *
	 * @param  string        $name
	 * @param  string|array  $filters
	 */
	public function __construct($name, $filters)
	{
		$this->name = $name;
		$this->filters = Filter::parse($filters);
	}

	/**
	 * Determine if this collection's filters apply to a given method.
	 *
	 * Methods may be included / excluded using the "only" and "except" methods on the
	 * filter collection. Also, the "on" method may be used to set certain filters to
	 * only run when the request uses a given HTTP verb.
	 *
	 * @param  string  $method
	 * @return bool
	 */
	public function applies($method)
	{
		if (count($this->only) > 0 and ! in_array($method, $this->only))
		{
			return false;
		}

		if (count($this->except) > 0 and in_array($method, $this->except))
		{
			return false;
		}

		if (count($this->methods) > 0 and ! in_array(strtolower(Request::method()), $this->methods))
		{
			return false;
		}

		return true;
	}

	/**
	 * Set the excluded controller methods.
	 *
	 * When methods are excluded, the collection's filters will be run for each
	 * controller method except those explicitly specified via this method.
	 *
	 * <code>
	 *		// Specify a filter for all methods except "index"
	 *		$this->filter('before', 'auth')->except('index');
	 *
	 *		// Specify a filter for all methods except "index" and "home"
	 *		$this->filter('before', 'auth')->except('index', 'home');
	 * </code>
	 *
	 * @param  array              $methods
	 * @return Filter_Collection
	 */
	public function except($methods)
	{
		$this->except = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;
		return $this;
	}

	/**
	 * Set the included controller methods.
	 *
	 * This method is the inverse of the "except" methods. The methods specified
	 * via this method are the only controller methods on which the collection's
	 * filters will be run.
	 *
	 * <code>
	 *		// Specify a filter for only the "index" method
	 *		$this->filter('before', 'auth')->only('index');
	 *
	 *		// Specify a filter for only the "index" and "home" methods
	 *		$this->filter('before', 'auth')->only('index', 'home');
	 * </code>
	 *
	 * @param  array              $methods
	 * @return Filter_Collection
	 */
	public function only($methods)
	{
		$this->only = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;
		return $this;
	}

	/**
	 * Set the HTTP methods for which the filter applies.
	 *
	 * Since some filters, such as the CSRF filter, only make sense in a POST
	 * request context, this method allows you to limit which HTTP methods
	 * the filter will apply to.
	 *
	 * <code>
	 *		// Specify that a filter only applies on POST requests
	 *		$this->filter('before', 'csrf')->on('post');
	 *
	 *		// Specify that a filter applies for multiple HTTP request methods
	 *		$this->filter('before', 'csrf')->on('post', 'put');
	 * </code>
	 *
	 * @param  array              $methods
	 * @return Filter_Collection
	 */
	public function on($methods)
	{
		$methods = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;

		foreach ($methods as $method)
		{
			$this->methods[] = strtolower($method);
		}
	
		return $this;
	}

}