<?php

namespace Snaju\Inception\Route;

use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationReader;
use Snaju\Inception\Annotation\InceptionRoute;
use Snaju\Inception\Exception\RouteNotFoundException;
use Snaju\Inception\InceptionCore;
use Snaju\Inception\Util\AnnotationHelper;
use Snaju\Inception\Util\Util;

class RouteManager
{

    /**
     * @var RouteTree
     */
    private static RouteTree $tree;

    private static array $domainSpecificTrees = [];

    private static array $routeTable = [];

    private static $routeSelection = null;

    /**
     * @var array
     */
    private static array $loaders = [];

    private static ?RouteLoader $forceLoader = null;

    public static ?string $lastMatchedRoutePart;

    public static bool $useWeightedSearch = false;

    public static function register(Route $route, string $domainSpecific = null)
    {
        if ($domainSpecific == null) {
            self::$tree->pushPath($route->getFullPath(), $route);
        } else {
            // Has a domain specific tree.
            if (!isset(self::$domainSpecificTrees[$domainSpecific])) {
                self::$domainSpecificTrees[$domainSpecific] = new RouteTree(new RouteNode(null, null, null));
            }
            self::$domainSpecificTrees[$domainSpecific]->pushPath($route->getFullPath(), $route);
        }
    }

    public static function &findRoute($path)
    {

        if (self::$routeSelection != null) {
            if (isset(self::$domainSpecificTrees[self::$routeSelection])) {
                self::$tree->merge(self::$domainSpecificTrees[self::$routeSelection]);
            }
        }

        if (self::$useWeightedSearch) {
            $node = self::$tree->searchPathWeighted($path, true);
        } else {
            $node = self::$tree->searchPath($path);
        }

        if ($node instanceof RouteNode) {
            if ($node->getRouteObject() != null) {
                self::$lastMatchedRoutePart = $node->getRoutePart();
                return $node->getRouteObject();
            }
//            throw new RouteNotFoundException("No Route Found with path {$path}", 404);
            return self::$lastMatchedRoutePart;
        }

        throw new RouteNotFoundException("Route Not Found with path {$path}. Invalid RouteNode received.", 404);
    }

    public static function attachRouteFile(string $path, callable $func)
    {
        $route = self::findRoute($path);
        if ($route != null) {
            $route->attach(new RouteFile($func));
        }
    }

    public static function initTree()
    {
        self::$tree = new RouteTree(new RouteNode(null, null, null));
    }

    public static function init()
    {
        $classes = AnnotationHelper::findClassesWithAnnotation(InceptionRoute::class);

        /*
         * Load Route Table
         * */
        if (file_exists(InceptionCore::getBase() . "/route.php")) {
            self::$routeTable = require_once InceptionCore::getBase() . "/route.php";
        }

        if (count(self::$routeTable) > 0) {
            // Route Table is set.

            foreach (self::$routeTable as $routeName => $selections) {
                if (self::$routeSelection != null) {
                    break;
                }

                foreach ($selections as $selection) {
                    if (Util::isFQDN($selection) || $selection == "localhost") {
                        // Is a domain :)

                        if (InceptionCore::getRequest()->getUri()->getHost() == $selection) {
                            self::$routeSelection = $routeName;
                            break;
                        }

                    } else if (substr($selection, 0, 1) == "/") {
                        // Is a path
                        if (str_contains(InceptionCore::getRequest()->getUri()->getPath(), $selection)) {
                            self::$routeSelection = $routeName;
                            break;
                        }
                    }
                }
            }
        }

        foreach ($classes as $class) {
            if ($class instanceof \ReflectionClass) {
                $annon = AnnotationHelper::getClassAnnotation($class, InceptionRoute::class);
                if ($annon instanceof InceptionRoute) {
                    $obj = $class->newInstance($annon->attachToPath);
                    self::$tree->pushPath($annon->attachToPath, $obj);
                }
            }
        }

        if (self::$forceLoader instanceof RouteLoader) {
            self::$forceLoader->loadRoutes();
        } else {
            // Call the loaders
            foreach (self::$loaders as $loader) {
                if ($loader instanceof RouteLoader) {
                    $loader->loadRoutes();
                }
            }
        }
    }

    /**
     * @param RouteLoader|null $forceLoader
     */
    public static function setForceLoader(?RouteLoader $forceLoader): void
    {
        self::$forceLoader = $forceLoader;
    }

    public static function addLoader(RouteLoader $loader)
    {
        self::$loaders[] = $loader;
    }

    /**
     * @return RouteTree
     */
    public static function getTree(): RouteTree
    {
        return self::$tree;
    }

}