<?php

namespace elanpl\L3;

define('_L3_ROUTE_404_NAME', '_L3_404');
class Router{

    protected $routes; // The defined routes collection
    protected $routeNameIndex; // An array with elements that reference to the routes ordered by a route names
    public $parsedParameters; // Parameters parsed from Request Path
    public $depth; // Number of nested nodes in Request Path
    public $routeInfo; // The RouteInfo object if the route was matched

    public function __construct($routing)
    {
        $this->routes = array(); 
        $this->routeNameIndex = array();
        //Set the routing from configuration object
        $routing->set($this);
    }

    public function add($method, $path, $result, $name = ''){
        $this->routes[] = new RouteInfo($method, $path, $result, $name);
        if(isset($name)&&$name!='') $this->routeNameIndex[$name] = &$this->routes[count($this->routes)-1];
        return $this;
    }

    public function add_preg($method, $path, $result, $name = ''){
        $this->routes[] = new RouteInfo($method, $path, $result, $name, RouteInfo::REGEX);
        if(isset($name)&&$name!='') $this->routeNameIndex[$name] = &$this->routes[count($this->routes)-1];
        return $this;
    }

    public function add404($result){
        return $this->add('404', '', $result, _L3_ROUTE_404_NAME);
    }

    public function get($path, $result, $name = ''){
        return $this->add('GET', $path, $result, $name);
    }

    public function post($path, $result, $name = ''){
        return $this->add('POST', $path, $result, $name);
    }

    public function put($path, $result, $name = ''){
        return $this->add('PUT', $path, $result, $name);
    }

    public function patch($path, $result, $name = ''){
        return $this->add('PATCH', $path, $result, $name);
    }

    public function delete($path, $result, $name = ''){
        return $this->add('DELETE', $path, $result, $name);
    }

    public function any($path, $result, $name = ''){
        return $this->add('ANY', $path, $result, $name);
    }

    // Regular expression based routes

    public function get_preg($path, $result, $name = ''){
        return $this->add_preg('GET', $path, $result, $name);
    }

    public function post_preg($path, $result, $name = ''){
        return $this->add_preg('POST', $path, $result, $name);
    }

    public function put_preg($path, $result, $name = ''){
        return $this->add_preg('PUT', $path, $result, $name);
    }

    public function patch_preg($path, $result, $name = ''){
        return $this->add_preg('PATCH', $path, $result, $name);
    }

    public function delete_preg($path, $result, $name = ''){
        return $this->add_preg('DELETE', $path, $result, $name);
    }

    public function any_preg($path, $result, $name = ''){
        return $this->add_preg('ANY', $path, $result, $name);
    }

    public function addBeforeAction($event_handler){
        $this->routes[count($this->routes)-1]->addBeforeAction($event_handler);
        return $this;
    }

    public function addAfterAction($event_handler){
        $this->routes[count($this->routes)-1]->addAfterAction($event_handler);
        return $this;
    }

    public function addAfterResult($event_handler){
        $this->routes[count($this->routes)-1]->addAfterAction($event_handler);
        return $this;
    }

    public function cleanPregMatch($match){
        $cleanMatch = array();
        foreach($match as $key => $value){
            if($value[1]>-1){
                $cleanMatch[$key] = $value[0];
            }
        }
        return $cleanMatch;
    }

    public function match($request){

        if(is_null($request->path)){
            $auri = array();
        }
        else{
            $auri = explode('/', trim($request->path, "/ \t\n\r\0\x0B"));
        }
        $curi = count($auri);
			
        foreach ($this->routes as $routeInfo) {
            if($routeInfo->type == RouteInfo::L3){
                $route = $routeInfo->path;
                $method = $routeInfo->method;
                if($method=='ANY' || strpos($request->method,$method)!==false){
                    if(is_null($route)){
                        $aroute = array();
                    }
                    else{
                        $aroute = explode('/', trim($route, "/ \t\n\r\0\x0B"));
                    }
                    
                    //print_r($aroute);
                    if($curi==count($aroute)){ //compare path element count
                        //optimistic assumption :)
                        $matchResult = true;
                        for($i = 0; $i<$curi; $i++){
                            $pathPartName = trim($aroute[$i],'{}');
                            if($aroute[$i]==$pathPartName){
                                if($auri[$i]!=$pathPartName){
                                    //echo "diffrence found";
                                    $matchResult = false;
                                    break;
                                }
                            }
                            else{ // {...} found -> catch $uri variable
                                $value = $auri[$i];
                                $valueKey = explode(':', $pathPartName);
                                //validation
                                if(isset($valueKey[1]) && $valueKey[1]=='int'){
                                    $value = intval($value);
                                }
                                //value store...
                                $this->parsedParameters[$valueKey[0]] = $value;
                            }
                        }
                        if($matchResult){ // match found
                            $this->depth = $curi;
                            $this->routeInfo = $routeInfo;
                            return $routeInfo->result;
                        }
                    }
                }
            }
            if($routeInfo->type == RouteInfo::REGEX){
                $route_pattern = $routeInfo->path;
                $method = $routeInfo->method;
                if($method=='ANY' || strpos($request->method, $method)!==false){
                    if(\preg_match($route_pattern, $request->path, $match, \PREG_OFFSET_CAPTURE)){
                        $this->parsedParameters = $this->cleanPregMatch($match);
                        $this->depth = $curi;
                        $this->routeInfo = $routeInfo;
                        return $routeInfo->result;
                    }
                }
            }
        }
        return false;
    }

    public function getRouteInfo($name){
        if(isset($this->routeNameIndex[$name]))
            return $this->routeNameIndex[$name];
        else
            return false;
    }

    public function calculateRequestDepth($request){
        if(is_null($request->path)) return 0;
        return count(explode('/',trim($request->path, "/ \t\n\r\0\x0B")));
    }

    public function setMatch404($request){
        if($routeInfo404 = $this->getRouteInfo(_L3_ROUTE_404_NAME)){
            $this->depth = $this->calculateRequestDepth($request);
            $this->routeInfo = $routeInfo404;
            return $routeInfo404->result;
        }
        else{
            return false;
        }
    }

    public function is_Match404(){
        return isset($this->routeInfo) && ($this->routeInfo->name === _L3_ROUTE_404_NAME);
    }

    public function link($name, $parameters){
        $route = $this->routeNameIndex[$name];
        if($route->type == RouteInfo::REGEX){
            throw new \Exception("Method Router::link not applicable to REGEX routes.");
        }
        $fields = array_keys($parameters);
        $values = array_values($parameters);
        array_walk($fields, function (&$item, $key){
            $item = "/\{".$item."\}/";
        });
        return preg_replace($fields, $values, $route->path);
    }

    public function getParameter($name){
        return $this->parsedParameters[$name];
    }

}