class SearchStats:
    def __init__(self, timeMs: float, visitedCells: int, pathLength: int):
        self.timeMs = timeMs
        self.visitedCells = visitedCells
        self.pathLength = pathLength


class MazeSolver:
    def __init__(self, maze: Maze, strategy: PathFindingStrategy):
        self.maze = maze
        self.strategy = strategy
        self.observers = []

    def setStrategy(self, strategy: PathFindingStrategy):
        self.strategy = strategy

    def addObserver(self, observer):
        self.observers.append(observer)

    def _notify(self, event: str):
        for obs in self.observers:
            obs.update(event)

    def solve(self) -> tuple:
        self._notify("Поиск начат")
        start_time = time.perf_counter()

        path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)

        end_time = time.perf_counter()
        time_ms = (end_time - start_time) * 1000

        stats = SearchStats(time_ms, self.strategy.visited_count, len(path))
        self._notify("Поиск завершен")
        return path, stats


class Observer(ABC):
    @abstractmethod
    def update(self, event: str):
        pass


class ConsoleView(Observer):
    def update(self, event: str):
        pass

    def render(self, maze: Maze, path: list):
        path_set = set(path)
        for y in range(maze.height):
            row = ""
            for x in range(maze.width):
                cell = maze.getCell(x, y)
                if cell == maze.start:
                    row += "S"
                elif cell == maze.exit:
                    row += "E"
                elif cell in path_set:
                    row += "*"
                elif cell.isWall:
                    row += "#"
                elif cell.weight == 3:
                    row += "W"  # Болото
                elif cell.weight == 2:
                    row += "D"  # Песок
                else:
                    row += "."
            print(row)


def get_test_mazes():
    builder = TextMazeBuilder()
    mazes = {}

    small = [
        "S.........",
        "#####.####",
        "..........",
        "####.#####",
        "..........",
        "#.#######.",
        "..........",
        "######.###",
        "..........",
        "........XE"
    ]
    small[-1] = small[-1].replace('X', '.')
    mazes["Small (10x10)"] = builder.buildFromStringList(small)

    empty = ["." * 50 for _ in range(50)]
    empty_list = list(empty)
    empty_list[0] = "S" + empty_list[0][1:]
    empty_list[-1] = empty_list[-1][:-1] + "E"
    mazes["Empty (50x50)"] = builder.buildFromStringList(empty_list)

    medium = []
    for y in range(50):
        if y == 0:
            row = "S" + "." * 49
        elif y == 49:
            row = "." * 49 + "E"
        elif y % 2 == 1:
            row = "#" * 45 + "." * 5 if y % 4 == 1 else "." * 5 + "#" * 45
        else:
            row = "." * 50
        medium.append(row)
    mazes["Middle with dead ends (50x50)"] = builder.buildFromStringList(medium)

    large = []
    for y in range(100):
        if y == 0: row = "S" + "." * 99
        elif y == 99: row = "." * 99 + "E"
        elif y % 2 == 1:
            row = ("#" * 9 + ".") * 10
        else:
            row = "." * 100
        large.append(row)
    mazes["Big (100x100)"] = builder.buildFromStringList(large)

    no_exit = [
        "S....#....",
        "##########",
        "##########",
        "##########",
        "##########",
        "##########",
        "##########",
        "##########",
        "##########",
        "######...E"
    ]
    mazes["Without exit (10x10)"] = builder.buildFromStringList(no_exit)


    return mazes


def main():
    mazes = get_test_mazes()

    strategies = {
        "BFS": BFSStrategy(),
        "DFS": DFSStrategy(),
        "AStar": AStarStrategy(),
        "Dijkstra": DijkstraStrategy()
    }

    output_rows = []

    print("Запуск всех тестов...")
    print("-" * 70)

    for maze_name, maze in mazes.items():
        print(f"Testing: {maze_name}")

        for strat_name, strategy in strategies.items():

            solver = MazeSolver(maze, strategy)

            runs = 5
            total_time = 0
            path = []
            stats = None

            for _ in range(runs):
                path, stats = solver.solve()
                total_time += stats.timeMs

            avg_time = total_time / runs

            output_rows.append([
                maze_name,
                strat_name,
                f"{avg_time:.4f}",
                stats.visitedCells,
                stats.pathLength
            ])

            print(
                f"  -> {strat_name}: Time: {avg_time:.3f}ms | Passed cells: {stats.visitedCells} | Path length: {stats.pathLength}")
        print("-" * 70)

    with open("results_all.csv", "w", newline="", encoding="utf-8") as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["labyrint", "strategy", "time_ms", "passed_cells", "path_length"])
        writer.writerows(output_rows)

    print("Все замеры успешно выполнены! Результаты сохранены в 'results_all.csv'")


if __name__ == "__main__":
    main()