# Основной модуль: оркестратор MazeSolver, Observer для визуализации,
# Player, Command для пошагового управления, интерактивная игра

import sys
import time
import os
from maze_core import TextFileMazeBuilder, Maze
from pathfinding import BFSStrategy, DFSStrategy, AStarStrategy


class SearchStats:
    def __init__(self, time_ms, visited_cells, path_length):
        self.time_ms = time_ms
        self.visited_cells = visited_cells
        self.path_length = path_length


class Observer:
    
    def update(self, event_type, data):
        raise NotImplementedError


class ConsoleView(Observer):
    def __init__(self, player=None):
        self._last_path = None
        self._player = player
    
    def update(self, event_type, data):
        if event_type == "maze_loaded":
            self.render_maze(data)
        elif event_type == "path_found":
            self._last_path = data
            self.render_path(data)
        elif event_type == "player_moved":
            self.render_maze_with_player(data)
    
    def render_maze(self, maze):


        os.system('cls' if os.name == 'nt' else 'clear')
        print("=" * (maze.width * 2 + 4))
        print("   ЛАБИРИНТ")
        print("=" * (maze.width * 2 + 4))
        
        for y in range(maze.height):
            print("  ", end='')
            for x in range(maze.width):
                cell = maze.get_cell(x, y)
                if cell == maze.start:
                    print('S', end=' ')
                elif cell == maze.exit:
                    print('E', end=' ')
                elif cell.is_wall:
                    print('#', end=' ')
                else:
                    print('.', end=' ')
            print()
        print("=" * (maze.width * 2 + 4))
        print("  S - вход  E - выход  # - стена  . - проход")
    
    def render_maze_with_player(self, maze):

        os.system('cls' if os.name == 'nt' else 'clear')
        print("=" * (maze.width * 2 + 4))
        print("   ЛАБИРИНТ (P - игрок)")
        print("=" * (maze.width * 2 + 4))
        
        for y in range(maze.height):
            print("  ", end='')
            for x in range(maze.width):
                cell = maze.get_cell(x, y)
                if self._player and cell == self._player.current:
                    print('P', end=' ')
                elif cell == maze.start:
                    print('S', end=' ')
                elif cell == maze.exit:
                    print('E', end=' ')
                elif cell.is_wall:
                    print('#', end=' ')
                else:
                    print('.', end=' ')
            print()
        print("=" * (maze.width * 2 + 4))
        print(f"  Позиция игрока: ({self._player.current.x}, {self._player.current.y})")
        print("  S - вход  E - выход  # - стена  . - проход  P - игрок")
    
    def render_path(self, path):

        if not path:
            print("\n  Путь не найден!")
            return
        print(f"\n  Путь найден! Длина: {len(path)}")


class Player:


    def __init__(self, start_cell, maze):
        self._current = start_cell
        self._previous = None
        self._maze = maze
    
    @property
    def current(self):
        return self._current
    
    def move_to(self, cell):

        if cell and cell.is_passable():
            self._previous = self._current
            self._current = cell
            return True
        return False
    
    def undo_move(self):

        if self._previous:
            self._current, self._previous = self._previous, None
            return True
        return False


class Command:

    def execute(self):
        raise NotImplementedError
    
    def undo(self):
        raise NotImplementedError


class MoveCommand(Command):
    def __init__(self, player, direction, maze):
        self._player = player
        self._direction = direction
        self._maze = maze
        self._executed = False
    
    def execute(self):
        dx, dy = self._direction
        new_x = self._player.current.x + dx
        new_y = self._player.current.y + dy
        target_cell = self._maze.get_cell(new_x, new_y)
        
        if target_cell and target_cell.is_passable():
            self._player.move_to(target_cell)
            self._executed = True
            return True
        return False
    
    def undo(self):
        if self._executed:
            self._player.undo_move()
            self._executed = False
            return True
        return False


class MazeSolver:
    def __init__(self, maze):
        self._maze = maze
        self._strategy = None
        self._observers = []
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def notify(self, event_type, data):
        for observer in self._observers:
            observer.update(event_type, data)
    
    def set_strategy(self, strategy):
        self._strategy = strategy
    
    def solve(self):
        if self._strategy is None:
            return None
        
        start_time = time.perf_counter()
        path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit)
        end_time = time.perf_counter()
        time_ms = (end_time - start_time) * 1000
        
        self.notify("path_found", path)
        
        return SearchStats(time_ms, self._strategy.get_visited_count(), len(path))


def main():
    
    builder = TextFileMazeBuilder()
    
    # Загрузка лабиринта из файла
    if len(sys.argv) > 1:
        maze = builder.build_from_file(sys.argv[1])
    else:
        maze = builder.build_from_file("maze1.txt")
    
    # Создание игрока и визуализации
    player = Player(maze.start, maze)
    view = ConsoleView(player)
    view.render_maze(maze)
    
    # Создание решателя
    solver = MazeSolver(maze)
    solver.attach(view)
    
    print("\n  УПРАВЛЕНИЕ:")
    print("  H (влево)  J (вниз)  K (вверх)  L (вправо)")
    print("  U - отменить ход    Q - выход")
    print("\n  АВТО-ПОИСК:")
    print("  B - BFS (поиск в ширину)")
    print("  D - DFS (поиск в глубину)")
    print("  A - A* (звездочка)")
    print("\n" + "=" * 50)
    
    command_stack = []
    
    while True:
        key = input("\n  Команда > ").lower()
        
        if key == 'q':
            print("\n  До свидания!")
            break
        
        elif key == 'b':
            solver.set_strategy(BFSStrategy())
            stats = solver.solve()
            print(f"\n  BFS: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
        
        elif key == 'd':
            solver.set_strategy(DFSStrategy())
            stats = solver.solve()
            print(f"\n  DFS: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
        
        elif key == 'a':
            solver.set_strategy(AStarStrategy())
            stats = solver.solve()
            print(f"\n  A*: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
        
        elif key in ['h', 'j', 'k', 'l']:
            dirs = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
            cmd = MoveCommand(player, dirs[key], maze)
            if cmd.execute():
                command_stack.append(cmd)
                view.render_maze_with_player(maze)
                if player.current == maze.exit:
                    print("\n  ПОЗДРАВЛЯЮ! ВЫ НАШЛИ ВЫХОД!")
                    print(f"  Всего ходов: {len(command_stack)}")
                    break
            else:
                print("\n  Туда нельзя – там стена!")
        
        elif key == 'u':
            if command_stack:
                cmd = command_stack.pop()
                cmd.undo()
                view.render_maze_with_player(maze)
                print("\n  Ход отменён")
            else:
                print("\n  Нечего отменять")
        
        else:
            print("\n  Неизвестная команда. Используйте H,J,K,L для движения, U для отмены, Q для выхода")


if __name__ == "__main__":
    main()