import math
from concurrent.futures import ThreadPoolExecutor

from pathfinder.core.BreadthFirstSearch import BreadthFirstSearch
from pathfinder.core.model.Node import Node
from pathfinder.core.model.Path import Path
from pathfinder.core.model.PathContainer import PathContainer


class BidirectionalPathFinder:

    def __init__(self, repository_name, plover_url, ngd_url, degree_url, logger):
        self.repo_name = repository_name
        self.plover_url = plover_url
        self.ngd_url = ngd_url
        self.degree_url = degree_url
        self.logger = logger

    def find_all_paths(self, node_id_1, node_id_2, hops_numbers=1):
        self.logger.info("Finding paths process has started")
        result = set()
        if hops_numbers == 0:
            return result
        if node_id_1 == node_id_2:
            return result

        hops_numbers_1 = math.floor((hops_numbers + 1) / 2)
        hops_numbers_2 = math.floor(hops_numbers / 2)

        path_container_1 = PathContainer()
        bfs_1 = BreadthFirstSearch(self.repo_name, self.plover_url, self.ngd_url, self.degree_url, path_container_1,
                                   self.logger)

        path_container_2 = PathContainer()
        bfs_2 = BreadthFirstSearch(self.repo_name, self.plover_url, self.ngd_url, self.degree_url, path_container_2,
                                   self.logger)

        with ThreadPoolExecutor(max_workers=2) as ex:
            f1 = ex.submit(bfs_1.traverse, node_id_1, hops_numbers_1)
            f2 = ex.submit(bfs_2.traverse, node_id_2, hops_numbers_2)

            f1.result()
            f2.result()

        intersection_list = path_container_1.path_dict.keys() & path_container_2.path_dict.keys()

        for node in intersection_list:
            for path_1 in path_container_1.path_dict[node]:
                for path_2 in path_container_2.path_dict[node]:
                    temp_path_1 = [Node(link.id, link.weight, link.name, link.degree, link.category) for link in
                                   path_1.links]
                    temp_path_2 = []
                    temp_path_1[-1].weight = (temp_path_1[-1].weight + path_2.links[-1].weight) / 2
                    for i in range(len(path_2.links) - 2, -1, -1):
                        n2 = Node(path_2.links[i].id, path_2.links[i].weight, path_2.links[i].name,
                                  path_2.links[i].degree, path_2.links[i].category)
                        temp_path_2.append(n2)
                    temp_path_1.extend(temp_path_2)
                    if len(temp_path_1) == len(set(temp_path_1)):
                        result.add(Path(0, temp_path_1))

        result = sorted(list(result), key=lambda path: path.compute_weight(), reverse=True)

        return result
