<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2011 - 2013 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flRouterRoute Class
 *
 * Class to match a route against a passed path extracting variables.
 *
 * @package fusionLib
 */
class flRouterRoute {

	/**
	 * The route to match.
	 *
	 * @var array
	 */
	protected $route;

	/**
	 * Array of variable defaults.
	 *
	 * @var array
	 */
	protected $defaults;

	/**
	 * Variable requirements.
	 *
	 * @var array
	 */
	protected $varReq;

	/**
	 * The file extension.
	 *
	 * @var string
	 */
	protected $ext;

	/**
	 * The default extension when route ends .*
	 *
	 * @var string
	 */
	protected $defaultExt =  '/';

	/**
	 * The request object.
	 *
	 * @var flRequest
	 */
	protected $request;

	/**
	 * Initialise the route object.
	 *
	 * @param string $route The route string to match, exact/:var/*
	 * @param array $defaults Array of defaults.
	 * @param array $varReq Array of variables and the regular expressions they must match.
	 * @param flRequest $request Optional request object to provide base path during assemble.
	 */
	function __construct($route, $defaults = array(), $varReq = array(), $request = null) {
		$this->route = explode('/', $route);
		if(($idx = count($this->route) - 1) >= 0 && strpos($this->route[$idx], '.') !== false)
			list($this->route[$idx], $this->ext) = explode('.', $this->route[$idx]);
		else
			$this->ext = '';
		$this->defaults = $defaults;
		$this->varReq = $varReq;
		$this->request = $request;
	}

	/**
	 * Set the request object to use.
	 *
	 * @param flRequest $request The request object.
	 */
	function setRequest($request) {
		$this->request = $request;
	}

	/**
	 * Set the default extension to use.
	 *
	 * @param string $ext The default extension.
	 */
	function setDefaultExt($ext) {
		$this->defaultExt = $ext;
	}

	/**
	 * Match a path to the route.
	 *
	 * @param array $path Array of path segments to match against.
	 * @return array Parameters extracted from the root or false if route does not match.
	 */
	function match($path) {
		// Extract the file extension
		$r = array();
		if(($lastSegIdx = count($path) - 1) >= 0 && preg_match('/^(.*)\.([a-z0-9_]{2,4})$/', $path[$lastSegIdx], $r)) {
			$path[$lastSegIdx] = $r[1];
			$ext = $r[2];
		}
		else
			$ext = '';

		// Compare extensions
		if($this->ext != '*' && $ext != $this->ext)
			return false;

		// Match route to path & extract variables
		$variables = array('extension' => $ext);
		$part = reset($path);
		foreach($this->route as $idx => $match) {

			// If end of path reached and not wildcard or variable match then failed
			if($part === false && $match != '*' && $match[0] != ':')
				return false;

			// Match variable
			if($match[0] == ':') {
				$v = substr($match, 1);

				// If path used up check for default value
				if($part === false) {
					if(isset($this->defaults[$v]))
						$part = $this->defaults[$v];
					else
						return false;
				}
				// verify requirements if any
				else if(isset($this->varReq[$v]) && !preg_match('#^' . $this->varReq[$v] . '$#', $part))
					return false;
				$variables[$v] = $part;
			}
			// Wildcard match segments
			else if($match == '*') {

				// Calc segments to consume
				if(($d = ($lastSegIdx + 1) - count($this->route)) >= 0) {
					// Consume segments
					do {
						// verify requirements if any
						if(isset($this->varReq['*']) && !preg_match('#^' . $this->varReq['*'] . '$#', $part))
							return false;

						$variables['*'][] = $part;
						$part = next($path);
					} while(--$d >= 0);
				}
				$part = prev($path); // Go back 1
			}
			// Match exact string
			else if($part != $match)
				return false;

			$part = next($path);
		}

		if($part !== false)
			return false;

		$result = $variables + $this->defaults;

		return $result;
	}

	/**
	 * Assemble passed parameters into URI of the route pattern.
	 *
	 * @param array $params Associative array of parameters.
	 * @param bool $absolute true if an absolute path is required; else false.
	 * @param bool $secure true for https, false for http, null for as per request.
	 * @return string The assembled URI.
	 * @throws flRouterException
	 */
	function assemble($params, $absolute = false, $secure = null) {
		$segment = array();
		foreach($this->route as $part) {

			// Variable
			if($part[0] == ':') {
				// If have parameter
				if(isset($params[$v = substr($part, 1)]))
					$segment[] = urlencode($params[$v]);
				// If no default for parameter either
				else if(!isset($this->defaults[$v]))
					throw new flRouterException("Parameter '$v' missing during assembly of URI.");
			}
			// Wildcard
			else if($part == '*') {
				if(isset($params['*'])) {
					foreach($params['*'] as $v)
						$segment[] = urlencode($v);
				}
			}
			// const
			else
				$segment[] = $part;
		}

		if(count($segment))
			$uri = join('/', $segment) . (empty($this->ext) ? '/' : ($this->ext == '*' ? $this->defaultExt : '.' . $this->ext));
		else
			$uri = '';

		if($this->request === null)
			return $uri;
		else
			return (empty($uri) ? $this->request->getBasePath($absolute, $secure) : $this->request->getBaseUri($absolute, $secure)) . $uri;
	}

	/**
	 * Get the route as a string.
	 *
	 * @return string The route string.
	 */
	function toString() {
		return join('/', $this->route);
	}

	/**
	 * Get the route as a string.
	 *
	 * @return string The route string.
	 */
	function __toString() {
		return join('/', $this->route);
	}
}
