from typing import List, Dict
from cura.Scene.CuraSceneNode import CuraSceneNode


class HitChecker:
    """Checks if nodes can be printed without causing any collisions and interference"""

    def __init__(self, nodes: List[CuraSceneNode]) -> None:
        self._hit_map = self._buildHitMap(nodes)

    def anyTwoNodesBlockEachOther(self, nodes: List[CuraSceneNode]) -> bool:
        """Returns True if any 2 nodes block each other"""
        for a in nodes:
            for b in nodes:
                if self._hit_map[a][b] and self._hit_map[b][a]:
                    return True
        return False

    def canPrintBefore(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
        """Returns True if node doesn't block other_nodes and can be printed before them"""
        no_hits = all(not self._hit_map[node][other_node] for other_node in other_nodes)
        return no_hits

    def canPrintAfter(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
        """Returns True if node doesn't hit other nodes and can be printed after them"""
        no_hits = all(not self._hit_map[other_node][node] for other_node in other_nodes)
        return no_hits

    def calculateScore(self, a: CuraSceneNode, b: CuraSceneNode) -> int:
        """Calculate score simply sums the number of other objects it 'blocks'

        :param a: node
        :param b: node
        :return: sum of the number of other objects
        """

        score_a = sum(self._hit_map[a].values())
        score_b = sum(self._hit_map[b].values())
        return score_a - score_b

    def canPrintNodesInProvidedOrder(self, ordered_nodes: List[CuraSceneNode]) -> bool:
        """Returns True If nodes don't have any hits in provided order"""
        for node_index, node in enumerate(ordered_nodes):
            nodes_before = ordered_nodes[:node_index - 1] if node_index - 1 >= 0 else []
            nodes_after = ordered_nodes[node_index + 1:] if node_index + 1 < len(ordered_nodes) else []
            if not self.canPrintBefore(node, nodes_after) or not self.canPrintAfter(node, nodes_before):
                return False
        return True

    @staticmethod
    def _buildHitMap(nodes: List[CuraSceneNode]) -> Dict[CuraSceneNode, CuraSceneNode]:
        """Pre-computes all hits between all objects

        :nodes: nodes that need to be checked for collisions
        :return: dictionary where hit_map[node1][node2] is False if there node1 can be printed before node2
        """
        hit_map = {j: {i: HitChecker._checkHit(j, i) for i in nodes} for j in nodes}
        return hit_map

    @staticmethod
    def _checkHit(a: CuraSceneNode, b: CuraSceneNode) -> bool:
        """Checks if a can be printed before b

        :param a: node
        :param b: node
        :return: False if a can be printed before b
        """

        if a == b:
            return False

        a_hit_hull = a.callDecoration("getConvexHullBoundary")
        b_hit_hull = b.callDecoration("getConvexHullHeadFull")
        overlap = a_hit_hull.intersectsPolygon(b_hit_hull)

        if overlap:
            return True

        # Adhesion areas must never overlap, regardless of printing order
        # This would cause over-extrusion
        a_hit_hull = a.callDecoration("getAdhesionArea")
        b_hit_hull = b.callDecoration("getAdhesionArea")
        overlap = a_hit_hull.intersectsPolygon(b_hit_hull)

        if overlap:
            return True
        else:
            return False