<?php namespace Laravel\Routing;

use Closure;
use Laravel\Bundle;
use Laravel\Response;

class Route {

	/**
	 * The route key, including request method and URI.
	 *
	 * @var string
	 */
	public $key;

	/**
	 * The URI the route responds to.
	 *
	 * @var string
	 */
	public $uris;

	/**
	 * The bundle in which the route was registered.
	 *
	 * @var string
	 */
	public $bundle;

	/**
	 * The action that is assigned to the route.
	 *
	 * @var mixed
	 */
	public $action;

	/**
	 * The parameters that will passed to the route callback.
	 *
	 * @var array
	 */
	public $parameters;

	/**
	 * Create a new Route instance.
	 *
	 * @param  string   $key
	 * @param  array    $action
	 * @param  array    $parameters
	 * @return void
	 */
	public function __construct($key, $action, $parameters = array())
	{
		$this->key = $key;
		$this->action = $action;
		$this->parameters = $parameters;

		// Extract each URI from the route key. Since the route key has the request
		// method, we will extract that from the string. If the URI points to the
		// root of the application, a single forward slash will be returned.
		$uris = array_get($action, 'handles', array());

		$this->uris = array_map(array($this, 'extract'), $uris);

		// Determine the bundle in which the route was registered. We will know
		// the bundle by the first segment of the route's URI. We need to know
		// the bundle so we know if we need to run a bundle's global filters
		// when executing the route.
		$this->bundle = Bundle::resolve(head(explode('/', $this->uris[0])));
	}

	/**
	 * Retrieve the URI from a given route destination.
	 *
	 * If the request is to the application root, a slash is returned.
	 *
	 * @param  string  $segment
	 * @return string
	 */
	protected static function extract($segment)
	{
		$uri = substr($segment, strpos($segment, ' ') + 1);

		return ($uri !== '/') ? trim($uri, '/') : $uri;
	}

	/**
	 * Call a given route and return the route's response.
	 *
	 * @return Response
	 */
	public function call()
	{
		// The route is responsible for running the global filters, and any
		// filters defined on the route itself. Since all incoming requests
		// come through a route (either defined or ad-hoc), it makes sense
		// to let the route handle the global filters.
		$response = Filter::run($this->filters('before'), array(), true);

		if (is_null($response))
		{
			$response = $this->response();
		}

		$response = Response::prepare($response);

		Filter::run($this->filters('after'), array($response));

		return $response;
	}

	/**
	 * Execute the route action and return the response.
	 *
	 * Unlike the "call" method, none of the attached filters will be run.
	 *
	 * @return mixed
	 */
	public function response()
	{
		// If the action is a string, it is simply pointing the route to a 
		// controller action, and we can just call the action and return
		// its response. This is the most basic form of route, and is
		// the simplest to handle.
		if ( ! is_null($delegate = $this->delegate()))
		{
			return Controller::call($delegate, $this->parameters);
		}

		// If the route does not have a delegate, it should either be a
		// Closure instance or have a Closure in its action array, so
		// we will attempt to get the Closure and call it.
		elseif ( ! is_null($handler = $this->handler()))
		{
			return call_user_func_array($handler, $this->parameters);
		}
	}

	/**
	 * Get the filters that are attached to the route for a given event.
	 *
	 * If the route belongs to a bundle, the bundle's global filters are returned too.
	 *
	 * @param  string  $filter
	 * @return array
	 */
	protected function filters($event)
	{
		// Add the global filters to the array. We will also attempt to add
		// the bundle's global filter as well. However, we'll need to keep
		// the array unique since the default bundle's global filter will
		// be the same as the application's global filter.
		$filters = array_unique(array($event, Bundle::prefix($this->bundle).$event));

		// Next wee will check to see if there are any filters attached
		// for the given event. If there are, we'll merge them in with
		// the global filters for the application event.
		if (isset($this->action[$event]))
		{
			$filters = array_merge($filters, Filter::parse($this->action[$event]));
		}

		return array(new Filter_Collection($filters));
	}

	/**
	 * Get the controller action delegate assigned to the route.
	 *
	 * If no delegate is assigned, null will be returned by the method.
	 *
	 * @return string
	 */
	protected function delegate()
	{
		return array_get($this->action, 'uses');
	}

	/**
	 * Get the anonymous function assigned to handle the route.
	 *
	 * If no anonymous function is assigned, null will be returned by the method.
	 *
	 * @return Closure
	 */
	protected function handler()
	{
		return array_first($this->action, function($key, $value)
		{
			return $value instanceof Closure;
		});
	}

	/**
	 * Determine if the route has a given name.
	 *
	 * <code>
	 *		// Determine if the route is the "login" route
	 *		$login = Request::route()->is('login');
	 * </code>
	 *
	 * @param  string  $name
	 * @return bool
	 */
	public function is($name)
	{
		return is_array($this->action) and array_get($this->action, 'name') === $name;
	}

	/**
	 * Determine if the route handles a given URI.
	 *
	 * @param  string  $uri
	 * @return bool
	 */
	public function handles($uri)
	{
		$pattern = '#'.str_replace('*', '(.*)', $uri).'#';

		return ! is_null(array_first($this->uris, function($key, $uri) use ($pattern)
		{
			return preg_match($pattern, $uri);
		}));
	}

}