<?php

namespace Tools;

/**
 * Will manager new connections to the server
 * and try to match the requests and the controllers
**/
class Router
{
    /**
     * @var string $rootPath
     * Contains the application's root path (ex: http://myshop.com/) with trailing slash
     * Can be accessed read-only via $instance->rootPath
    **/
    private $rootPath;

    /**
     * @var string $rootPath
     * Contains the application's root path (ex: /srv/http/myshop/) with trailing slash
     * Can be accessed read-only via $instance->rootUrl
    **/
    private $rootUrl;

    /**
     * @var string $requestUrl
     * Contains request
    **/
    private $requestUrl;

    /**
     * @var array ( array ( uri => controller ) ) $routes
     **/
    private $routes;

    /**
     * @var \Tools\Context $context
     * /core/tools/Context.php
     * Contains website's informations
    **/
    private $context;

	/**
	 * @var array RouteParams
	 * Contains route parameter
	 * ex: /product/:id will contains {
	 *    0 => product
	 *    1 => [ID]
	 *    ':id' => [ID]
	**/
	private $routeParams;

	/**
	 * @var array $overridden
	 * contains url and paths to rewrite
	 **/
	private $overridden;

    /**
     * @var string $modulePath
     * Contains the module directory
    **/

    /**
     * @var string $moduleUrl
     * Contains the module Uri
    **/

    /**
     * @var string $themePath
     * Contains the theme directory
    **/

    /**
     * @var string $themeUrl
     * Contains the theme Uri
    **/

    /**
     * Create the router, initialize url and path
    **/
    public function __construct($server, $context)
    {
        $pos = strrpos($server["SCRIPT_NAME"], "/");
        $relativePath = (($pos === FALSE) ? "" : substr($server["SCRIPT_NAME"], 0, $pos));
        $this->rootPath = $server["DOCUMENT_ROOT"] . $relativePath . "/";
        $this->rootUrl = $server["REQUEST_SCHEME"] . "://" . $server["HTTP_HOST"] . $relativePath ."/";
        $this->requestUrl = substr($server["REQUEST_URI"], count($this->rootUrl) -1);
        $this->context = $context;
        $this->routes = array();
		$this->overridden = null;
    }

    /**
     * Called after database initialization
     * Check the site url and redirect user if the HOST does not match
     * If the site url is not defined in database, do not redirect
    **/
    public function init($server)
    {
        $siteUrl = \Entity\Config::getConfig(null, "siteUrl");
		// @codeCoverageIgnoreStart
		// This code is tested under another process
        if ($siteUrl != $server["HTTP_HOST"] && $siteUrl !== null)
        {
            header("location: http://{$siteUrl}{$server['REQUEST_URI']}");
            die;
        }
		// @codeCoverageIgnoreEnd
    }

    /**
     * @return \Controller\AController controller
     * /core/controller/AController.php
     * Match request to a controller
     * return FALSE on failure (eg. 404)
    **/
    public function serveUrl()
    {
		//TODO trigger hook GET, POST
        $this->prepareUrl();
        $requestParams = explode("/", $this->requestUrl);
        foreach ($this->routes as $i)
        {
            $routeParams = explode("/", $i[0]);
			$p = $this->routeMatch($requestParams, $routeParams);
			if ($p === false)
				continue;
			$controller = $this->createController($i[1], $p);
			if ($controller)
				return $controller;
        }
        return false;
    }

	/**
	 * Create and return Controller for the given route
	 * @param $className
	 * @param array $routeParameters
	 * @return \Tools\AController on succes, false otherwise
	**/
	private function createController($className, $params)
	{
		if (!class_exists($className))
			return false;
		$this->routeParams = $params;
		$result = null;
		try
		{
			$result = new $className($this->context, $params);
			if (!($result instanceof \Tools\AController))
				return false;
		}
		catch (\Exception\Error404 $e)
		{
			return false;
		}
		return $result;
	}

	/**
	 * Check if the request match route
	 * @param array $request User request
	 * @param array $route Route to check
	 * @return array on success, false on failure
	**/
	private function routeMatch($request, $route)
	{
		$i = count($request);
		$params = array();
		if ($i != count($route))
			return false;
		while ($i)
		{
			$i--;
			if ($route[$i] == '' && $request[$i] == '')
				continue;
			if ($route[$i] == '' || $request[$i] == '')
				return false;
			if ($route[$i][0] != ':' && ($route[$i] != $request[$i]))
				return false;
			if ($route[$i][0] == ':')
				$params[$route[$i]] = $request[$i];
			$params[$i -1] = $request[$i];
		}
		return array_reverse($params);
	}

    /**
     * Append local routes to router
     * Will load CMS pages, categories page, products page, cart pages, etc.
    **/
    private function prepareUrl()
    {
		$fetcher = new \Entity\Cms();
		$pages = $fetcher->selects(null, array("order"));
		foreach ($pages as $i)
			$this->doRouteAdd($i->shurl, $i->controller);
    }

	/**
	 * Add a route to the internal route list
	 * Internal procedure
	**/
    private function doRouteAdd($route, $controller)
    {
        $this->routes[] = array($route, $controller);
    }

	/**
	 * @param string $route Uri to match the controller
	 * Uri can be formatted as '/:param/static'.
	 * expl. '/product/:id/'
	 * @param string $controller Controller class name.
	 * new $controller() MUST return a \Tool\AController instance
	 *
	 * Add a route and a Controller to the list
	 * Can only be called from `routerSetup' hook
	**/
    public function routeAdd($route, $controller)
    {
        if (!$this->context->hookManager->isInHook("routerSetup"))
            throw new \Exception("You can only add routes from `routerSetup' hook");
        $this->doRouteAdd($route, $controller);
    }

	/**
	 * Override url
	 * This SHOULD not be called for security purpose
	 * @param string $type the url type to override
	 * @param string $value the new value
	 * Will fail if called from a controller
	 * @return boolean false on failure
	**/
	public function overrideUrl($type, $value)
	{
		if (!$this->context->isTestingEnvironment())
			return false;
		$this->overridden[$type] = $value;
		return true;
	}

	/**
	 * Getter
	**/
    public function __get($key)
    {
		if (isset($this->overridden) && in_array($key, array("modulesPath")))
			return $this->overridden[$key];
        switch ($key)
        {
        case "rootPath": return $this->rootPath; break;
        case "rootUrl": return $this->rootUrl; break;
        case "modulesPath": return $this->rootPath."content/modules/"; break;
        case "modulesUrl": return $this->rootUrl."content/modules/"; break;
        case "themesPath": return $this->rootPath."content/theme/"; break;
        case "themesUrl": return $this->rootUrl."content/theme/"; break;
        }
        throw new \Exception("Cannot access attribute {$key}");
    }
}

