<?php

namespace Core\Objects\Router;

use Core\Driver\Logger\Logger;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Route;

class Router {

  private Context $context;
  private Logger $logger;
  private ?Route $activeRoute;
  private ?string $requestedUri;
  protected array $routes;
  protected array $statusCodeRoutes;

  public function __construct(Context $context) {
    $this->context = $context;
    $this->routes = [];
    $this->statusCodeRoutes = [];
    $this->activeRoute = null;
    $this->requestedUri = null;
    $this->context->router = $this;

    $sql = $context->getSQL();
    if ($sql) {
      $this->addRoute(new ApiRoute());
      $this->logger = new Logger("Router", $sql);
    } else {
      $this->logger = new Logger("Router");
    }
  }

  public function getActiveRoute(): ?Route {
    return $this->activeRoute;
  }

  public function getRequestedUri(): ?string {
    return $this->requestedUri;
  }

  public function run(string $url, array &$pathParams): ?Route {
    // replace multiple leading slashes with one, otherwise parse_url() might interpret path as domain
    // e.g. //index.php --> /index.php
    $this->requestedUri = preg_replace("/^\/{2,}/", "/", $url);

    $url = strtok($url, "?");
    foreach ($this->routes as $route) {
      $match = $route->match($url);
      if ($match !== false) {
        $this->activeRoute = $route;
        $pathParams = $match;
        return $this->activeRoute;
      }
    }

    return null;
  }

  public function returnStatusCode(int $code, array $params = []): string {
    http_response_code($code);
    $params["status_code"] = $code;
    $params["status_description"] = HTTP_STATUS_DESCRIPTIONS[$code] ?? "Unknown Error";
    $route = $this->statusCodeRoutes[strval($code)] ?? null;
    if ($route) {
      return $route->call($this, $params);
    } else {
      $req = new \Core\API\Template\Render($this->context);
      $res = $req->execute(["file" => "error_document.twig", "parameters" => $params]);
      if ($res) {
        return $req->getResult()["html"];
      } else {
        $description = htmlspecialchars($params["status_description"]);
        return "<b>$code - $description</b>";
      }
    }
  }

  public function addRoute(Route $route): void {
    if (preg_match("/^\/(\d+)$/", $route->getPattern(), $re)) {
      $this->statusCodeRoutes[$re[1]] = $route;
    }

    $this->routes[] = $route;
  }

  public function writeCache(string $file): bool {

    $routes = "";
    $uses = [
      "Core\Objects\Context",
      "Core\Objects\Router\Router",
    ];

    foreach ($this->routes as $route) {
      // do not generate cache for static api route
      if ($route instanceof ApiRoute) {
        continue;
      }

      $class = $route->getClass();
      $constructor = $route->generateCache();
      $uses[] = $class->getName();
      $routes .= "\n    \$this->addRoute($constructor);";
    }

    $uses = implode("\n", array_map(function ($use) { return "use $use;"; }, array_unique($uses)));
    $date = (new \DateTime())->format("Y/m/d H:i:s");
    $code = "<?php

/**
 * DO NOT EDIT! 
 * This file is automatically generated by the RoutesAPI on $date.
 */

namespace Site\Cache;
$uses

class RouterCache extends Router {

  public function __construct(Context \$context) {
    parent::__construct(\$context);$routes
  }
}
";

    $directory = dirname($file);
    if (!is_dir($directory)) {
      if (!mkdir($directory, 775, true)) {
        $this->logger->severe("Could not create directory: $directory");
        return false;
      }
    }

    if (@file_put_contents($file, $code) === false) {
      $this->logger->severe("Could not write Router cache file: $file");
      return false;
    }

    return true;
  }

  public function getContext(): Context {
    return $this->context;
  }

  public function getLogger(): Logger {
    return $this->logger;
  }

  public static function cleanURL(string $url, bool $cleanGET = true): string {
    // strip GET parameters
    if ($cleanGET) {
      if (($index = strpos($url, "?")) !== false) {
        $url = substr($url, 0, $index);
      }
    }

    // strip document reference part
    if (($index = strpos($url, "#")) !== false) {
      $url = substr($url, 0, $index);
    }

    // strip leading slash
    return preg_replace("/^\/+/", "", $url);
  }

  public function getRoutes(bool $includeStatusRoutes = false): array {

    if (!$includeStatusRoutes && !empty($this->statusCodeRoutes)) {
      return array_filter($this->routes, function ($route) {
        return !in_array($route, $this->statusCodeRoutes);
      });
    }

    return $this->routes;
  }
}