<?php
/**
 * url解析器，代码实现参考至yii框架
 * @author yzxh24
 */
class iUrlManager
{
    const CACHE_KEY = 'iUrlManager.Rules';
    
    const GET_FORMAT='get';
	
    const PATH_FORMAT='path';
	
    public $urlSuffix = '.html';
    
    /**
     * url路由规则
     * @var array()
     */
    public $rules = array();
    
    /**
     * 经过解析以后的路由规则
     * @var array()
     */
    private $_rules = array();
    
    private $_urlFormat = self::PATH_FORMAT;
    
    public function __construct($rules)
    {
        $this->rules = $rules;
        $this->processRules();
    }
    
    public function processRules()
    {
        if (empty($this->rules))
        {
            return;
        }
        /**/
        $hash = md5(serialize($this->rules));
        if(($data = $GLOBALS['cache']->get(self::CACHE_KEY)) !== false && isset($data[1]) && $data[1] === $hash)
		{
			$this->_rules = $data[0];
			return;
		}
		/**/
		foreach($this->rules as $pattern => $route)
		{
			$this->_rules[] = $this->createUrlRule($route, $pattern);
		}
		$GLOBALS['cache']->set(self::CACHE_KEY, array($this->_rules, $hash));
    }
    
    public function parseUrl($pathInfo, $rawPathInfo)
	{
		if($this->getUrlFormat() === self::PATH_FORMAT)
		{
			foreach($this->_rules as $rule)
			{
				$rule->parseUrl($this, $pathInfo, $rawPathInfo);
			}
		}
	}
    
    public function createUrlRule($route, $pattern)
    {
        return new iUrlRule($route, $pattern);
    }
    
    public function createUrl($route, $params = array(), $ampersand = '&')
    {
        foreach($params as &$param)
        {
			if($param === null)
			{
				$param = '';
			}
        }
        
		if(isset($params['#']))
		{
			$anchor = '#' . $params['#'];
			unset($params['#']);
		}
		else
		{
			$anchor = '';
		}
		$route = trim($route, '/');

		foreach($this->_rules as $rule)
		{
			if(($url = $rule->createUrl($this, $route, $params, $ampersand)) !== false)
			{
				return $rule->hasHostInfo ? $url . $anchor : $this->getBaseUrl() . '/' . $url . $anchor;
			}
		}
		
		//return $this->createDefaultUrl($route, $params, $ampersand) . $anchor;
    }
    
    public function createDefaultUrl($route, $params, $ampersand)
    {
        if($this->getUrlFormat() === self::PATH_FORMAT)
		{
			$url = rtrim($this->getBaseUrl() . '/' . $route, '/');
			if($this->appendParams)
			{
				$url = rtrim($url . '/' . $this->createPathInfo($params, '/', '/'), '/');
				return $route==='' ? $url : $url . $this->urlSuffix;
			}
			else
			{
				if($route!=='')
				{
					$url .= $this->urlSuffix;
				}
				$query = $this->createPathInfo($params, '=', $ampersand);
				return $query==='' ? $url : $url . '?' . $query;
			}
		}
		else
		{
			$url = $this->getBaseUrl();
			if(!$this->showScriptName)
			{
				$url.='/';
			}
			if($route !== '')
			{
				$url .= '?' . $this->routeVar. '=' . $route;
				if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
				{
					$url .= $ampersand . $query;
				}
			}
			else if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
			{
				$url.='?'.$query;
			}
			return $url;
		}
    }
    
    public function getUrlFormat()
	{
		return $this->_urlFormat;
	}
    
    public function getBaseUrl()
	{
		if($this->_baseUrl!==null)
			return $this->_baseUrl;
		else
		{
			if($this->showScriptName)
				$this->_baseUrl = iRequest::getScriptUrl();
			else
				$this->_baseUrl = iRequest::getBaseUrl();
			return $this->_baseUrl;
		}
	}
    
    public function createPathInfo($params, $equal, $ampersand, $key=null)
	{
		$pairs = array();
		foreach($params as $k => $v)
		{
			if ($key !== null)
			{
				$k = $key . '[' . $k . ']';
			}

			if (is_array($v))
			{
				$pairs[] = $this->createPathInfo($v,$equal,$ampersand, $k);
			}
			else
			{
				$pairs[] = urlencode($k) . $equal . urlencode($v);
			}
		}
		
		return implode($ampersand, $pairs);
	}
	
    public function parsePathInfo($pathInfo)
	{
		if($pathInfo === '')
		{
			return;
		}
		$segs = explode('/', $pathInfo . '/');
		$n = count($segs);
		for($i = 0; $i < $n - 1; $i += 2)
		{
			$key = $segs[$i];
			if($key === '') continue;
			$value = $segs[$i + 1];
			if(($pos = strpos($key, '[')) !== false && ($pos2 = strpos($key, ']', $pos + 1)) !== false)
			{
				$name = substr($key, 0, $pos);
				if($pos2 === $pos + 1)
				{
					$_REQUEST[$name][] = $_GET[$name][] = $value;
				}
				else
				{
					$key = substr($key, $pos + 1, $pos2 - $pos - 1);
					$_REQUEST[$name][$key] = $_GET[$name][$key] = $value;
				}
			}
			else
			{
				$_REQUEST[$key] = $_GET[$key] = $value;
			}
		}
	}
}

class iUrlRule
{
	public $route;
	
	public $template;
	
	public $pattern;
	
	public $append;
	
	public $params = array();
	
	public $defaultParams = array();
	
	public $urlSuffix;
	
	public $hasHostInfo;
	
	public function __construct($route, $pattern)
	{
		if(is_array($route))
		{
			if(isset($route['urlSuffix']))
			{
				$this->urlSuffix = $route['urlSuffix'];
			}
			
			$route = $this->route = $route[0];
		}
		else
		{
			$this->route=$route;
		}
		
		$tr2['/']=$tr['/']='\\/';
		
		$this->hasHostInfo =! strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);
		
		if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches))
		{
			$tokens = array_combine($matches[1], $matches[2]);
			foreach($tokens as $name=>$value)
			{
				if($value === '')
				{
					$value = '[^\/]+';
				}
				$tr["<$name>"] = "(?P<$name>$value)";
				$this->params[$name] = $value;
			}
		}
	
		$p = rtrim($pattern, '*');
		$this->append = $p !== $pattern;
		$p = trim($p, '/');
		$this->template = preg_replace('/<(\w+):?.*?>/', '<$1>', $p);
		$this->pattern = '/^' . strtr($this->template, $tr) . '\/';
		$this->pattern .= $this->append ? '/u' : '$/u';
	}
	
	public function createUrl($manager, $route, $params, $ampersand = '&')
	{
		if ($route !== $this->route)
		{
			return false;
		}
		
		$tr = array();
		foreach($this->params as $key=>$value)
		{
			$tr["<$key>"] = urlencode($params[$key]);
			unset($params[$key]);
		}

		$suffix = $this->urlSuffix === null ? $manager->urlSuffix : $this->urlSuffix;

		$url = strtr($this->template, $tr);
		
		if($this->hasHostInfo)
		{
			$hostInfo = iRequest::getHostInfo();
			if(strpos($url, $hostInfo) === 0)
			{
				$url = substr($url, strlen($hostInfo));
			}
		}

		if(empty($params))
		{
			return $url !== '' ? $url . $suffix : $url;
		}

		if($this->append)
		{
			$url .= '/' . $manager->createPathInfo($params, '/' , '/') . $suffix;
		}
		else
		{
			if($url !== '')
			{
				$url .= $suffix;
			}
			$url .= '?' . $manager->createPathInfo($params, '=', $ampersand);
		}

		return $url;
	}
	
	public function parseUrl($manager, $pathInfo, $rawPathInfo)
	{
		if($this->hasHostInfo)
		{
			$pathInfo = iRequest::getHostInfo() . rtrim('/' . $pathInfo, '/');
		}
		
		if($this->urlSuffix !== null)
		{
			$pathInfo = removeUrlSuffix($rawPathInfo, $this->urlSuffix);
		}
		
		$pathInfo .= '/';
		if(preg_match($this->pattern, $pathInfo, $matches))
		{
			foreach($this->defaultParams as $name => $value)
			{
				if(!isset($_GET[$name]))
				{
					$_REQUEST[$name] = $_GET[$name] = $value;
				}
			}
			$tr = array();
			foreach($matches as $key => $value)
			{
				if(isset($this->params[$key]))
				{
					$_REQUEST[$key] = $_GET[$key] = $value;
				}
			}
			if($pathInfo !== $matches[0])
			{
				$manager->parsePathInfo(ltrim(substr($pathInfo, strlen($matches[0])), '/'));
			}
			if($this->routePattern !== null)
			{
				return strtr($this->route, $tr);
			}
			else
			{
				return $this->route;
			}
		}
		
		return false;
	}
}