from collections import deque
import heapq
from math import inf

class PathFindingStrategy:
    def findPath(self, maze, start, exit):
        raise NotImplementedError


def reconstruct_path(parent, start, goal):
    if goal not in parent and goal != start:
        return []
    path = []
    cur = goal
    while cur != start:
        path.append(cur)
        cur = parent[cur]
    path.append(start)
    path.reverse()
    return path


class BFSStrategy(PathFindingStrategy):
    def findPath(self, maze, start, exit):
        queue = deque([start])
        visited = {start}
        parent = {}
        visited_count = 0

        while queue:
            current = queue.popleft()
            visited_count += 1
            if current == exit:
                path = reconstruct_path(parent, start, exit)
                return path, visited_count

            for nxt in maze.getNeighbors(current):
                if nxt not in visited:
                    visited.add(nxt)
                    parent[nxt] = current
                    queue.append(nxt)

        return [], visited_count


class DFSStrategy(PathFindingStrategy):
    def findPath(self, maze, start, exit):
        stack = [start]
        visited = {start}
        parent = {}
        visited_count = 0

        while stack:
            current = stack.pop()
            visited_count += 1
            if current == exit:
                path = reconstruct_path(parent, start, exit)
                return path, visited_count

            for nxt in maze.getNeighbors(current):
                if nxt not in visited:
                    visited.add(nxt)
                    parent[nxt] = current
                    stack.append(nxt)

        return [], visited_count


class AStarStrategy(PathFindingStrategy):
    def h(self, a, b):
        return abs(a.x - b.x) + abs(a.y - b.y)

    def findPath(self, maze, start, exit):
        open_heap = []
        heapq.heappush(open_heap, (0, 0, start))
        parent = {}
        g = {start: 0}
        visited = set()
        visited_count = 0
        counter = 1

        while open_heap:
            _, _, current = heapq.heappop(open_heap)
            if current in visited:
                continue

            visited.add(current)
            visited_count += 1

            if current == exit:
                path = reconstruct_path(parent, start, exit)
                return path, visited_count

            for nxt in maze.getNeighbors(current):
                tentative_g = g[current] + nxt.weight
                if tentative_g < g.get(nxt, inf):
                    g[nxt] = tentative_g
                    parent[nxt] = current
                    f = tentative_g + self.h(nxt, exit)
                    heapq.heappush(open_heap, (f, counter, nxt))
                    counter += 1

        return [], visited_count