<?php namespace Router;

require_once("Response.php");
require_once("Route.php");

class Router
{
    private string $uri;
    private string $requestType;

    private array $routes;
    private array $middleware;

    private array $request;
    private Response $response;

    /**
     * Router
     * @author Johannes Kantz
     */
    public function __construct(string $pathname)
    {
        $this->uri = is_countable($_SERVER['REQUEST_URI']) && count($_SERVER['REQUEST_URI']) > 1 ? rtrim($_SERVER['REQUEST_URI'], "/") : $_SERVER['REQUEST_URI'];
        $this->uri = str_replace($pathname, "", $this->uri);
        $this->requestType = $_SERVER['REQUEST_METHOD'];
        $this->routes = [];
        $this->middleware = [];

        if (isset($_SERVER)) {
            $this->request = $_SERVER;
        }
        if (isset($_POST)) {
            $this->request["body"] = $_POST;
        }
        if (isset($_POST)) {
            $this->request["params"] = $_GET;
        }

        $this->response = new Response();
    }


    /**
     * Use Middleware
     * @param string $uri
     * @param $middleware
     * @return void
     * @author Johannes Kantz
     */
    public function use(string $uri, callable $middleware): void
    {
        $this->middleware[$uri] = $middleware;
    }

    /**
     * get Middleware for uri
     * @param string $uri
     * @return array
     * @author Johannes Kantz
     */
    private function getMiddleware(): array
    {
        $middleware = [];
        foreach ($this->middleware as $key => $value) {
            if (str_starts_with($this->uri, $key)) {
                $middleware[] = $value;
            }
        }
        return $middleware;
    }

    /**
     * Calls the middleware
     * @param array $middleware
     * @return void
     * @author Johannes Kantz
     */
    private function callMiddleware(array $middleware): void
    {
        foreach ($middleware as $m) {
            $m($this->request, $this->response);
        }
    }

    /**
     * GET Method
     * @param string $uri
     * @param $controller
     * @return void
     * @author Johannes Kantz
     */
    public function get(string $uri, callable $controller): void
    {
        $this->routes[$uri] = new Route("GET", $controller);
    }

    /**
     * POST Method
     * @param string $uri
     * @param $controller
     * @return void
     * @author Johannes Kantz
     */
    public function post(string $uri, callable $controller): void
    {
        $this->routes[$uri] = new Route("POST", $controller);
    }

    /**
     * PUT Method
     * @param string $uri
     * @param $controller
     * @return void
     * @author Johannes Kantz
     */
    public function put(string $uri, callable $controller): void
    {
        $this->routes[$uri] = new Route("PUT", $controller);
    }

    /**
     * DELETE Method
     * @param string $uri
     * @param $controller
     * @return void
     * @author Johannes Kantz
     */
    public function delete(string $uri, callable $controller): void
    {
        $this->routes[$uri] = new Route("DELETE", $controller);
    }

    /**
     * ALL Method
     * @param string $uri
     * @param $controller
     * @return void
     * @author Johannes Kantz
     */
    public function all(string $uri, callable $controller): void
    {
        $this->routes[$uri] = new Route("ALL", $controller);
    }

    /**
     * Route
     * @param string $uri
     * @return void
     * @author Johannes Kantz
     */
    public function route(string $uri): Route
    {
        $this->routes[$uri] = new Route($uri);
        return $this->routes[$uri];
    }

    /**
     * Checks if the given uri matches the given route
     * @param string $uri
     * @param array $params
     * @return string
     * @author Johannes Kantz
     */
    private function routeMatches(string $route, string $uri): bool
    {
        $routeParts = explode("/", $route);
        $uriParts = explode("/", $uri);

        if (count($routeParts) !== count($uriParts)) {
            return false;
        }
        foreach ($routeParts as $key => $part) {
            if (str_starts_with($part, ":")) {
                continue;
            }
            if ($part !== $uriParts[$key]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the params of the given uri
     * given that the uri matches the given route
     * @param string $uri
     * @param array $params
     * @return array
     * @author Johannes Kantz
     */
    private function getParams(string $route, string $uri): array
    {
        $routeParts = explode("/", $route);
        $uriParts = explode("/", $uri);

        $params = [];
        foreach ($routeParts as $key => $part) {
            if (str_starts_with($part, ":")) {
                $params[ltrim($part, ":")] = $uriParts[$key];
            }
        }
        return $params;
    }

    /**
     * gets the controller for the given uri
     * @param array $routes
     * @author Johannes Kantz
     */
    private function getController(): callable|bool
    {
        foreach ($this->routes as $uri => $route) {
            if ($this->uri === $uri) {
                return $route->getController($this->requestType) ?? false;
            }
            if ($this->routeMatches($uri, $this->uri)) {
                $this->request["params"] = $this->getParams($uri, $this->uri);
                return $route->getController($this->requestType) ?? false;
            }
        }
        return false;
    }

    /**
     * Starts the router
     * @return void
     * @author Johannes Kantz
     */
    public function start(): void
    {
        $middleware = $this->getMiddleware();
        $this->callMiddleware($middleware);

        $controller = $this->getController();
        if ($controller) {
            $controller($this->request, $this->response);
        } else {
            echo "404";
        }
    }
}