from abc import ABC, abstractmethod
from collections import deque
import heapq

class PathFindingStrategy(ABC):
    @abstractmethod
    def findPath(self, maze, start, exit_cell):
        pass
    def restorePath(self, parent, start, exit_cell):
        path = []
        current = exit_cell
        while current != start:
            path.append(current)
            current = parent[current]
        path.append(start)
        path.reverse()
        return path
class BFSStrategy(PathFindingStrategy):
    def findPath( self, maze, start, exit_cell):
        queue = deque([start])
        visited = {start}
        parent = {}
        while queue:
            current = queue.popleft()
            if current == exit_cell:
                return (self.restorePath(parent, start, exit_cell), len(visited))
            for neighbor in maze.getNeighbors(current):
                if neighbor not in visited:
                    visited.add(neighbor)
                    parent[neighbor] = current
                    queue.append(neighbor)
        return [], len(visited)

class DFSStrategy(PathFindingStrategy):
    def findPath(self, maze, start, exit_cell):
        stack = [start]
        visited = {start}
        parent = {}
        while stack:
            current = stack.pop()
            if current == exit_cell:
                return (self.restorePath(parent,start,exit_cell),len(visited))
            for neighbor in maze.getNeighbors(current):
                if neighbor not in visited:
                    visited.add(neighbor)
                    parent[neighbor] = current
                    stack.append(neighbor)
        return [], len(visited)
    
class AStarStrategy(PathFindingStrategy):
    def heuristic(self,cell,exit_cell):
        return (abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y))
    def findPath(self, maze, start, exit_cell):
        pq = []
        heapq.heappush(pq,(0, id(start), start))
        parent = {}
        g_score = {start: 0}
        visited = set()
        while pq:
            _, _, current = (heapq.heappop(pq))
            if current in visited:
                continue
            visited.add(current)
            if current == exit_cell:
                return (self.restorePath(parent, start, exit_cell),  len(visited))
            for neighbor in maze.getNeighbors(current):
                new_cost = (g_score[current]+ 1)
                if (neighbor not in g_score or new_cost < g_score[neighbor] ):
                    g_score[neighbor] = new_cost
                    parent[neighbor] = current
                    priority = (new_cost + self.heuristic(neighbor, exit_cell))
                    heapq.heappush(pq,(priority,id(neighbor),neighbor))
        return [], len(visited)