from typing import List, Dict, Optional

from ._FoundRoute import FoundRoute
from ._HttpRouter import HttpRouter
from ._Route import Route


class SimpleHttpRouter(HttpRouter):
    def __init__(self, routes):
        # type: (List[Route]) -> None
        self.method_dict = MethodDict()
        for route in routes:
            self.method_dict.add_route(route)

    def get_route(self, route):
        return self.method_dict.get_route(route)


class InternalRoute(object):
    def __init__(self, route, url_regex):
        self.original_route = route
        self.url_regex = url_regex


class MethodDict(object):
    def __init__(self):
        self.method_routes = {}  # type: Dict[str, PathDict]

    def add_route(self, route):
        # type: (Route) -> None
        if route.method not in self.method_routes:
            self.method_routes[route.method] = PathDict()

        self.method_routes[route.method].add_route(route)

    def get_route(self, route):
        try:
            p = self.method_routes[route.method]
        except KeyError:
            return None
        return p.get_longest(route.path)


class PathDict(object):
    def __init__(self):
        self.fixed_routes = {}  # type: Dict[str, List[PathDict]]
        self.variable_routes = []  # type: List[PathDict]
        self.final_paths = {}  # type: Dict[str, RouteNode]
        self.variable_name = None  # type: Optional[str]
        self.variable_route = None  # type: Optional[RouteNode]

    def add_route(self, route):
        # type: (Route) -> None
        parts = route.path[1:].split("/")
        self.add_route_to_dicts(parts, route, 0)

    def get_longest(self, path):
        # type: (str) -> Optional[FoundRoute]
        path_parameters = {}
        available_routes = self.get_valid_nodes(path[1:].split("/"), path_parameters)
        if len(available_routes) == 0:
            return None
        longest_route = None
        for route in available_routes:
            if longest_route is None or longest_route < route:
                longest_route = route

        return FoundRoute(longest_route.route, longest_route.path_parameters)

    def add_route_to_dicts(self, path, route, height):
        # type: (List[str], Route, int) -> None
        if len(path) == 1:
            if path[0].startswith("{") and path[0].endswith("}"):
                self.variable_name = path[0][1:-1]
                self.variable_route = RouteNode(route, height)
            else:
                self.final_paths[path[0]] = RouteNode(route, height)
        else:
            if path[0].startswith("{") and path[0].endswith("}"):
                p = PathDict()
                p.add_route_to_dicts(path[1:], route, height + 1)
                self.variable_name = path[0][1:-1]
                self.variable_routes.append(p)
            else:
                if path[0] not in self.fixed_routes:
                    self.fixed_routes[path[0]] = []
                p = PathDict()
                p.add_route_to_dicts(path[1:], route, height + 1)
                self.fixed_routes[path[0]].append(p)

    def get_valid_nodes(self, path, variables):
        # type: (List[str], Dict[str, str]) -> List[FoundNode]
        available_nodes = []
        if len(self.final_paths) > 0 and len(path) == 1 and path[0] in self.final_paths:
            available_nodes.append(FoundNode(self.final_paths[path[0]].route,
                                             self.final_paths[path[0]].height,
                                             variables))
        elif self.variable_route is not None and self.variable_name is not None:
            variables[self.variable_name] = "/".join(path)
            available_nodes.append(FoundNode(self.variable_route.route,
                                             self.variable_route.height,
                                             variables))
            return available_nodes

        if len(path) == 1:
            return available_nodes

        path_dicts = []  # type: List[PathDict]
        if path[0] in self.fixed_routes:
            path_dicts.extend(self.fixed_routes[path[0]])
        if self.variable_name is not None:
            variables[self.variable_name] = path[0]
            path_dicts.extend(self.variable_routes)

        if path_dicts is not None:
            for path_dict in path_dicts:
                node_variables = variables.copy()
                available_nodes.extend(path_dict.get_valid_nodes(path[1:], node_variables))
        return available_nodes


class RouteNode(object):
    def __init__(self, route, height):
        # type: (Route, int) -> None
        self.route = route
        self.height = height


class FoundNode(object):
    def __init__(self, route, height, path_parameters):
        # type: (Route, int , Dict[str, str]) -> None
        self.route = route
        self.height = height
        self.path_parameters = path_parameters

    def __lt__(self, o):
        # type: (object) -> bool
        if self.height < o.height:
            return True
        elif self.height == o.height and len(self.path_parameters) > len(o.path_parameters):
            return True

        return False

    def __gt__(self, o):
        # type: (object) -> bool
        return not self.__lt__(o)

