<?php

    include_once "Request.php";
    include_once "Helpers.php";

    function magicRoute($path)
    {
        $regex = [];
        $names = [];
        $first = true;
        $magic = false;
        foreach (tstring::from($path)->split("/") as $value) {
            $matched = $value->match("/^(.*?)\{(.+?)\}(.*?)$/");
            if($first){
                $first = false;
                continue;
            };
            if($matched === false)
            {
                $regex[] = "\/" . preg_quote($value);
            }else{
                $magic = true;
                $symbol = tstring::from($matched[2]);
                if($symbol->endsWith("?"))
                {
                    $symbol = $symbol->slice(0,-1);
                    $pattern = "([^\/^?]+?)?";
                }else{
                    $pattern = "([^\/^?]+?)";
                }
                $regex[] = "\/". $matched[1] . $pattern . $matched[3];
                $names[] = $symbol;
            }
        }
        return [
            "/^" . implode('', $regex) . "$/",
            $names,
            $magic
        ];
    };
    function validateMagicRoute($mroute, $path)
    {
        if(is_string($mroute))
        {
            list($regex, $names) = magicRoute($mroute);
        }else{
            list($regex, $names) = $mroute;
        }
        $matches = tstring::from($path)->match($regex);
        if($matches !== false)
        {
            array_shift($matches);
            $matcher = new stdClass;
            $index = 0;
            foreach ($matches as $value) {
                $matcher->{
                    $names[$index]->raw()
                } = $matches[
                    $index
                ];
                $index++;
            };
            return $matcher;
        }else{
            return false;
        }
    };

    class Route
    {
        public static $routes = [];
        public static $errors = [];
        public static $publics = [];
        public static $current = null;
        public static function post($path, $callback)
        {
            $magic = magicRoute($path);
            $textPath = false;
            $type = "text";
            if($magic[2] == true)
            {
                $type = "magic";
                $textPath = $magic;
            }else{
                $textPath = $path;
            }
            $route = Route::from([
                "type" => $type,
                "path" => $path,
                "callback" => $callback,
                "method" => "post"
            ]);
            Route::$routes[] = $route;
            return $route;
        }
        public static function error($method, $code, $callback)
        {
            if(!isset(Route::$errors[$code]))
            {
                Route::$errors[$code] = [];
            }
            Route::$errors[$code][$method] = $callback;
        }
        public static function get($path, $callback)
        {
            $magic = magicRoute($path);
            $textPath = false;
            $type = "text";
            if($magic[2] == true)
            {
                $type = "magic";
                $textPath = $magic;
            }else{
                $textPath = $path;
            }
            $route = Route::from([
                "type" => $type,
                "path" => $path,
                "callback" => $callback,
                "method" => "get"
            ]);
            Route::$routes[] = $route;
            return $route;
        }
        public static function public($route, $storagepath)
        {
            Route::$publics[] = Route::from([
                "type" => "public",
                "path" => preg_quote($route),
                "src" => $storagepath,
                "method" => "get"
            ]);
        }
        public static function any($path, $callback)
        {
            $magic = magicRoute($path);
            $textPath = false;
            $type = "text";
            if($magic[2] == true)
            {
                $type = "magic";
                $textPath = $magic;
            }else{
                $textPath = $path;
            }
            $route = Route::from([
                "type" => $type,
                "path" => $path,
                "callback" => $callback,
                "method" => "*"
            ]);
            Route::$routes[] = $route;
            return $route;
        }
        public static function ThrowErrorCode($code)
        {
            $method = Request::$method;
            if(isset(Route::$errors[$code][$method]))
            {
                $callback = Route::$errors[$code][$method];
                Route::$errors[$code][$method]();
                return;
            };
            Response::Code($code);
        }
        public static function CheckCurrent()
        {
            try{
                if(Route::$current == null)
                {
                    foreach (Route::$routes as $route) {
                        $route->clear();
                        if($route->Run())
                        {
                            Route::$current = $route;
                            return $route;
                        }
                    };
                    foreach (Route::$publics as $route) {
                        $route->clear();
                        if($route->Run())
                        {
                            Route::$current = $route;
                            return $route;
                        }
                    };
                    Route::ThrowErrorCode(404);
                    return false;
                };
            }
            catch(Exception $e)
            {
                Route::ThrowErrorCode(500);
            }
            return Route::$current;
        }

        public static function Execute()
        {
            $route = Route::CheckCurrent();
            if($route !== false)
            {
                $route->call();
            };
        }

        public static function from(...$args)
        {
            return new Route(...$args);
        }

        public string $type;
        public string $path;
        public string $src;
        public $callback;
        public string $method;

        public $match = null;

        public function __construct($obj)
        {
            foreach ($obj as $name => $value) {
                $this->{$name} = $value;
            }
        }
        public function clear()
        {
            $this->match = null;
        }
        public function call(...$args)
        {
            switch ($this->type) {
                case 'magic':
                case 'text':
                    $callback = $this->callback;
                    $callback(...$args);
                    break;
                case 'public':
                    $callback = $this->callback;
                    Response::File($callback);
                    break;
            }
        }
        public function Run()
        {
            $requestUrl = Request::$url;
            $method = Request::$method;
            if($this->method != $method)
            {
                return false;
            }
            $url = parse_url($requestUrl, PHP_URL_PATH);
            switch($this->type)
            {
                case "text":{
                    if($url == $this->path)
                    {
                        return true;
                    }else{
                        return false;
                    }
                    break;
                }
                case "magic":{
                    if($t === false)
                    {
                        return false;
                    }else{
                        $this->match = $t;
                        return true;
                    }
                    break;
                }
                case "public":{
                    $path = tstring::from($this->path);
                    $url = tstring::from($url);
                    if($url->startsWith($path))
                    {
                        $filename = $url->slice(strlen($path)); // remove first / char
                        $rpath = realpath($this->src . $filename);
                        if(file_exists($rpath) && is_file($rpath))
                        {
                            $this->callback = $rpath;
                            return true;
                        }else{
                            return false;
                        }
                    }else{
                        return false;
                    }
                    break;
                }
            }
        }
    }