<?php

namespace Heanup\Frame;


use Heanup\Config\Route;
use Heanup\Frame\Logger;

class Dispatcher
{
    const CONFIG_PREFIX = '\\Heanup\\Config';

    const CONTROLLER_ERROR = 'Error';
    const CONTROLLER_DEFAULT = 'Index';
    const METHOD_DEFAULT = 'main';

    /**
     * 使用方法
     * @param $customPrefix
     * @param string $pathInfo
     */
    public static function run($customPrefix, $pathInfo = '')
    {
        // 获取前缀
        $prefix = '\Heanup\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
        if (!$pathInfo) {
            $pathInfo = self::getPathInfo();
        }
        $pathInfo = self::convertToClassName($pathInfo);
        $pathInfo = explode('\\', $pathInfo);
        if (count($pathInfo) > 1) {
            $method = array_pop($pathInfo);
        } else {
            $method = self::METHOD_DEFAULT;
        }
        $controller = $prefix . '\\' . implode('\\', $pathInfo);
        // 加入一次出错重试的机会
        for ($i = 0; $i < 2; $i++) {
            try {
                // 判断是否是公开方法
                $reflection = new \ReflectionMethod($controller, $method);
                if ($reflection->isPublic() == false) {
                    throw new \Exception($controller . "::$method is not public");
                }
                $controller = new $controller();
                $controller->$method();
                break;
            } catch (\Exception $ex) {
                Logger::emerg($ex, 'dispatcher.run');
                $controller = $prefix . '\\' . self::CONTROLLER_ERROR;
                $method = self::METHOD_DEFAULT;
            }
        }
    }

    /**
     * 分发请求
     * @param string $customPrefix
     * @param string $pathInfo
     */
    public static function dispatch($customPrefix, $pathInfo = '')
    {
        // 获取前缀
        $prefix = '\Heanup\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
        if (!$pathInfo) {
            $pathInfo = self::getPathInfo();
        }
        $pathInfo = self::convertToClassName($pathInfo);
        try {
            $path = explode('\\', $pathInfo);
            $id = array_pop($path);
            if (is_numeric($id)) {
                $_GET['id'] = $id;
                $pathInfo = implode('\\', $path);
            }
            $controller = $prefix . '\\' . $pathInfo;
            // 如果类不存在则使用默认的错误类
            if (!class_exists($controller)) {
                //类不存在则尝试通过自定义路由解析
                $url = $_SERVER["REQUEST_URI"];
                // 去除 ？后面的部分
                $request = parse_url($url);
                $url = $request['path'];
                $route = Route::getData($customPrefix);
                foreach ($route as $val) {
                    $pattern = $val["pattern"];
                    $params = self::checkUrl(strtolower($url), strtolower($pattern));
                    if ($params != null) {
                        $controller = $val["action"];
                        $count = count($params);
                        for ($i = 0; $i < $count; $i++) {
                            $_GET['args_' . $i] = $params[$i];
                        }
                        break;// 寻找到第一个匹配的路由即执行，然后返回
                    }
                }
                if (!class_exists($controller)) {
                    Logger::error($controller, 'dispatcher.controller_not_exist');
                    throw new \Exception($controller . ' not exist');
                }

            }
            // 判断主方法
            if (!method_exists($controller, 'run')) {
                throw new \Exception($controller . ' has no method called "run"');
            }
            $controller = new $controller();
            $controller->run();
        } catch (\Exception $ex) {
            $method = strtolower($_SERVER['REQUEST_METHOD']);
            if ($method == 'options') {
                header("HTTP/1.1 200");
                header("Access-Control-Allow-Origin: *");
                header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE");
                header("Access-Control-Allow-Headers: lang,token,content-type");
                die();
            }
            Response::sendResponse(404, [], $ex->getMessage());
            Logger::emerg($ex, 'dispatcher.dispatch');
        }
    }

    /**
     * 正则匹配检查，并提取出参数
     * @param $str
     * @param $pattern
     * @return array|null
     */
    private static function checkUrl($str, $pattern)
    {
        $ma = array();
        $pattern = ltrim(rtrim($pattern, "/"));
        $pattern = "/" . str_replace("/", "\/", $pattern) . "\/?$/";
        $pattern = str_replace(":s", "([^\/]+)", $pattern);
        if (preg_match($pattern, $str, $ma) > 0) {
            array_shift($ma);
            return $ma;
        }
        return null;
    }

    /**
     * 获取pathInfo, 兼容Apache, Nginx
     * 如果使用Apache的话可以直接使用 $_SERVER['PATH_INFO']的
     * @return string
     */
    protected static function getPathInfo()
    {
        //去掉uri中的PHP_SELF 信息
        $length = strlen($_SERVER['PHP_SELF']);
        if (substr($_SERVER['REQUEST_URI'], 0, $length) == $_SERVER['PHP_SELF']) {
            $request = substr($_SERVER['REQUEST_URI'], $length);
        } else {
            $request = $_SERVER['REQUEST_URI'];
        }
        // 去除 ？后面的部分
        $request = parse_url($request);
        $request = $request['path'];
        $request = urldecode($request);
        $request = trim($request, '/_ ');
        // 去掉 '.' 后缀
        if (strpos($request, '.') !== false) {
            $request = explode('.', $request);
            $request = array_shift($request);
        }
        if (empty($request)) {
            $request = self::CONTROLLER_DEFAULT;
        }
        return $request;
    }

    /**
     * 将字符串转成带命名空间的类名
     * @param $string
     * @return string
     */
    public static function convertToClassName($string)
    {
        $string = trim($string, '\\/_ ');
        $string = strtolower($string);
        $string = str_replace('/', '\\', $string);
        $string = str_replace(' ', '\\', ucwords(str_replace('\\', ' ', $string)));
        $string = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
        return $string;
    }

}
