<?php
namespace Qii\Base;

use Qii\Config\Consts;
use Qii\Config\Register;
use Qii\Exceptions\InvalidFormat;
use Qii\Exceptions\Variable;
use Qii\Request\Http;

/**
 * Route
 * 使用方法，在路由文件夹中创建路由文件，内容如下：
 * Route::prefix("/test")->setMiddleware([test\middleware\verify::class])->group(function($route) {
 *      // /test/testa -> test\index::indexAction
 *      $route->any('/test/testa', test\index::class);
 *      // /test2/login.html 会调用 test\index::loginAction
 *      $route->any('/test2/login', [test\index::class, 'login']);
 * });

 * Route::prefix('/test')->setMiddleware(['admin\system\middleware\user'])->group(function ($route){
 *      // /test/manage/atest.html 会调用 remote\manage::atest，以下两种写法都可以
 *      $route->any('/manage/*', [remote\manage::class, '$2']);
 *      $route->any('/manage/*', remote\manage::class.':$2');
 * });
 */
class Route
{
    /**
     * group 路由前缀
     * @var string
     */
    public $prefix = '';
    /**
     * 路由 middleware
     * @var array
     */
    public $middleware = [];
    /**
     * 路由
     * @var array
     */
    private $route = [];
    /**
     * 存储转换后的路由和middleware
     *
     * @var array $stored
     */
    private $stored = [];
    /**
     * 存储默认 prefix
     * @var array $default
     */
    private $default = [];
    /**
     * 是否启用正则匹配
     *
     * @var bool $enableExpress 默认为启用
     */
    private $enableExpress = true;

    public function __construct() {
        $this->clean();
    }

    /**
     * 清理middleware和路由规则
     *
     * @return void
     */
    public function clean() {
        $this->prefix = '';
        $this->middleware = [];
        $this->route = [
            'GET' => [],
            'POST' => [],
            'DELETE' => [],
            'PUT' => [],
            'HEAD' => [],
            'ANY' => []
        ];
    }

    /**
     * 实例化Route
     * @return mixed|object|string|null
     */
    public static function getInstance() {
        return \Qii::getInstance("Qii\Base\Route");
    }

    /**
     * 设置路由规则
     *
     * @param array $route
     * @return void
     */
    public function setRoute($route){
        if (!is_array($route) && !in_array($route, $this->route)) {
            $this->route[] = $route;
        }
        $this->route = array_merge($this->route, $route);
    }

    /**
     * 设置URL前缀
     *
     * @param string $name
     * @return mixed|object|string|null
     */
    public static function prefix($name) {
        $route = self::getInstance();
        $route->clean();
        $route->prefix = $name;
        return $route;
    }

    /**
     * 设置中间件
     * @param array|string $middleware
     * @return $this
     */
    public function setMiddleware($middleware) {
        if(!is_array($middleware)) {
            $this->middleware[] = $middleware;
        }else{
            $this->middleware = array_merge($this->middleware, $middleware);
        }
        return $this;
    }

    public function gatherMiddleware(){
        return array_unique($this->middleware);
    }

    public function group($callable) {
        $route = \Qii::getInstance("Qii\Base\Route");
        call_user_func($callable, $route);
        $this->stored[] = [$route->middleware, $route->route];
        return $this;
    }

    public function get($uri, $callable){
        $uri = $this->appendPrefix($this->prefix, $uri);
        $this->route['GET'][] = [$uri, $callable];
        return $this;
    }

    public function post($uri, $callable) {
        $uri = $this->appendPrefix($this->prefix, $uri);
        $this->route['POST'][] = [$uri, $callable];
        return $this;
    }

    /**
     * 补充前缀
     * @param string $prefix 前缀
     * @param string $uri uri
     * @return mixed|string
     */
    protected function appendPrefix($prefix, $uri){
        if(!$prefix) {
            return $uri;
        }
        return $prefix . '/'. ltrim($uri, '/');;
    }
    public function put($uri, $callable) {
        $uri = $this->appendPrefix($this->prefix, $uri);
        $this->route['PUT'][] = [$uri, $callable];
        return $this;
    }

    public function delete($uri, $callable) {
        $uri = $this->appendPrefix($this->prefix, $uri);
        $this->route['DELETE'][] = [$uri, $callable];
        return $this;
    }

    public function any($uri, $callable) {
        $uri = $this->appendPrefix($this->prefix, $uri);
        $this->route['ANY'][] = [$uri, $callable];
        return $this;
    }

    /**
     * 匹配路由并执行
     *
     * @param $request
     * @return bool
     */
    public function match(Http $request, $next) {
        $route = $this->getStoredRoute();
        $method = strtoupper($request->getMethod());
        $uri = $request->getRequestUri();
        $middleware = [];
        $callable = '';
        $args = null;
        foreach ($route as $item) {
            $middleware = $item[0];
            $subRoute = $item[1];
            if(!isset($subRoute[$method]) && !isset($subRoute['ANY'])) {
                continue;
            }
            $subRoute['ANY'] = isset($subRoute['ANY']) ? (array) $subRoute['ANY'] : array();
            $subRoute[$method] = isset($subRoute[$method]) ? (array) $subRoute[$method] : array();
            $routes = array_merge($subRoute[$method], $subRoute['ANY']);
            $callable = '';
            foreach ($routes as $routeUri) {
                //如果有设置默认，把默认规则拿出来
                if(preg_match("/(\:default)/", $routeUri[0])) {
                    $arr = explode(":", $routeUri[0]);
                    $this->default[$arr[0]][$method] = [$middleware, $routeUri];
                }
                //启用正则匹配
                if($this->enableExpress) {
                    list($route, $args) = $this->parse($uri, $routeUri);
                    if($route) {
                        if(is_array($args)) {
                            //将$args的值设置到get参数里
                            foreach ($args as $key => $value) {
                                $request->set($key, urldecode($value));
                            }
                        }
                        $callable = $route[1];
                        break;
                    }
                }
                if ($uri == $routeUri[0]) {
                    $callable = $routeUri[1];
                    break;
                }
            }
            if ($callable) {
                break;
            }
        }
        if (!$callable || count($middleware) == 0) {
            //如果未命中规则，看看是否有默认规则
            if(count($this->default) > 0) {
                foreach ($this->default as $path => $value) {
                    if(substr($uri, 0, strlen($path)) == $path && isset($value[$method])) {
                        $middleware = $value[$method][0];
                        $callable = $value[$method][1][1];
                    }
                }
            }
            if(!$callable) {
                return $next($request);
            }
        }
        if (!$this->make($callable, $middleware, $request, $args)){
            return $next($request);
        }
        return false;
    }

    /**
     * 匹配规则
     * /user/get/{uid:num}/{name:string}/{sex:string}
     *
     * @param string $uri 请求uri
     * @param array $route 路由规则
     * @return null[]
     */
    public function parse($uri, $route) {
        //去掉后缀名，不匹配后缀名
        $ext = pathinfo($uri, PATHINFO_EXTENSION);;
        if($ext) {
            $ext = ".". $ext;
            if(substr($uri, -1 * strlen($ext)) == $ext) {
                $uri = substr($uri, 0,-1 * strlen($ext));
            }
        }
        $routeExt = pathinfo($route[0], PATHINFO_EXTENSION);
        if($routeExt) {
            $routeExt = ".". pathinfo($route[0], PATHINFO_EXTENSION);
            if(substr($route[0], -1 * strlen($routeExt)) == $routeExt) {
                $route[0] = substr($route[0], 0,-1 * strlen($routeExt));
            }
        }
        if(!preg_match("/\{.*?\}/i", $route[0]) && $uri == $route[0]) {
            //没有参数的匹配完整就直接返回route和参数值null
            return [$route, null];
        }
        //优先精确匹配
        if(preg_match("/\{.*?\}/i", $route[0])) {
            $uriArray = explode("/", $uri);
            $routeArray = explode("/", $route[0]);
            //如果uri的长度比route不相等，且前缀不匹配就直接退出规则匹配
            if(count($uriArray) != count($routeArray) && !preg_match("/(". str_replace("/", "\/", $uri).").*?/", $route[0])) {
                return [null, null];
            }

            $args = array();
            $firstMatch = -1;
            foreach ($routeArray as $index => $val) {
                if(preg_match("/\{.*?\}/i", $val)) {
                    if($firstMatch == -1) {
                        $firstMatch = $index;
                    }
                    $key = str_replace(array("{", "}"), "", $val);
                    $value = "";
                    if (isset($uriArray[$index])) {
                        $value = $uriArray[$index];
                    }
                    //如果key未xx:yy则yy表示xx的值类型，会做强制转换，支持的类型为number、string、bool、float
                    if(strpos($key, ":") > 0) {
                        $keyArr = explode(":", $key);
                        $key = $keyArr[0];
                        $value =  $this->convertValueTo($value, $keyArr[1]);
                    }
                    $args[$key] = $value;
                }
            }
            //首次变量的位置之前的内容不匹配，页直接返回不匹配
            $uriMatch = join("/", array_slice($uriArray, 0, $firstMatch));
            $routeMatch = join("/", array_slice($routeArray, 0, $firstMatch));
            if($uriMatch != $routeMatch) {
                return [null, null];
            }
            return [$route, $args];
        }

        if($route[0] == $uri . "*") {
            return [$route, null];
        }
        //匹配number
        if(preg_match("/(:number)$/i", $route[0])) {
            $exp = str_replace("/", "\\/",  preg_replace("/(\:number)$/i", "", $route[0]));
            $exp = "/(".$exp.")\d+?$/";
            preg_match($exp, $uri, $match);
            if($match) {
                return [$route, null];
            }
        }

        //匹配任何字符
        if(preg_match("/(\:any|\*)$/i", $route[0])) {
            $exp = "(". preg_replace("/(\:any|\*)$/i", "", $route[0]) .")";
            if(preg_match($exp, $uri)) {
                return [$route, null];
            }
        }
        return [null, null];
    }

    /**
     * 执行middleware
     *
     * @param array $middleware
     * @return mixed|true
     */
    protected function handleMiddleware($middleware) {
        //命中后，执行相应程序后就不往下执行
        $middleware = array_reverse(array_unique((array) $middleware));
        //获取全局middleware
        return array_reduce($middleware, function ($carry, $item){
            return function () use ($carry, $item){
                return _loadClass($item)->handle(\Qii::getInstance()->request, $carry);
            };
        }, function(){
            return true;
        })();
    }

    /**
     * 执行 $callable
     *
     * @param mix $callable 执行方法
     * @param array $middleware 中间件
     * @param HTTP $request request
     * @param array | null $args 正则匹配后拿到的参数及值
     * @return bool
     * @throws Variable
     */
    public function make($callable, $middleware, $request, $args = null) {
        if($args === null) {
            $args = array($request);
        }
        if(is_array($args)) {
            array_unshift($args, $request);
        }
        $res = null;
        switch ($callable) {
            case ($callable instanceof  \Closure):
                if(count($middleware) > 0 && !$this->handleMiddleware($middleware)) {
                    return true;
                }
                $res = call_user_func_array($callable, $args);
                break;
            case is_array($callable):
            case gettype($callable) == 'string':
                //支持路由格式
                // $route->any('/default/*', ['class', '$2']); 这里的$2值的是request->get(2)这个值
                if(is_array($callable)) {
                    $action = (isset($callable[1]) ? $callable[1] : Register::get("APP_DEFAULT_ACTION"));
                    $controllerCls = _loadClass($callable[0]);
                }else{
                    if(strpos($callable, ":") > 0) {
                        $split = explode(":", $callable);
                        $callable = $split[0];
                        $action = $split[1];
                    }else{
                        $action = Register::get("APP_DEFAULT_ACTION");
                    }
                    $controllerCls = _loadClass($callable);
                }

                preg_match('/\$\d$/', $action, $match);
                if($match) {
                    $key = ltrim($action, '$');
                    $action = $request->get($key, Register::get("APP_DEFAULT_ACTION"));
                }
                $action = $action . Register::get(Consts::APP_DEFAULT_ACTION_SUFFIX);
                if(preg_match("/^\d/", $action)) {
                    throw new InvalidFormat('方法名不能以数字开头');
                }

                if(method_exists($controllerCls, 'beforeRun') && !$controllerCls->beforeRun()) {
                    return true;
                }
                if(count($middleware) > 0 && !$this->handleMiddleware($middleware)) {
                    return true;
                }
                if(method_exists($controllerCls, 'initView')) {
                    $controllerCls->initView();
                }
                if(method_exists($controllerCls, 'initialization')) {
                    $controllerCls->initialization();
                }
                $res = call_user_func_array(array($controllerCls, $action), $args);
                break;
        }
        if($res instanceof Response) {
            echo $res->response();
        }else if($res !== null){
            echo $res;
        }
        if ($callable) {
            return true;
        }
        return false;
    }

    /**
     * 将值转换为指定类型
     *
     * @param $value
     * @param $to
     * @return bool|mixed|string
     */
    protected function convertValueTo($value, $to) {
        if(!in_array($to, ['number', 'string', 'bool'])) {
            return $value;
        }
        switch ($to) {
            case 'number':
                //取字符串中连续数字
                preg_match('/(\d+)/',$value,$r);
                if($r) {
                    return $r[0];
                }
                return 0;
            case 'string':
                return (string) $value;
            case 'bool':
                if(in_array($value, ['true', 'false'])) {
                    return $value == 'true';
                }
                return (bool) $value;
        }
        return "";
    }
    /**
     * 获取所有设置的路由
     *
     * @return mixed
     */
    public function getStoredRoute(){
        return self::getInstance()->stored;
    }
}