#####################################################################################################################################################
######################################################################## INFO #######################################################################
#####################################################################################################################################################

"""
This program is a solution to the practical session 2 of the PyRat project:
https://hub.imt-atlantique.fr/ueinfo-fise1a/s5/project/session2/practical/
"""

#####################################################################################################################################################
###################################################################### IMPORTS ######################################################################
#####################################################################################################################################################

# External imports
import enum

# PyRat imports
from pyrat import Player, Maze, GameState, Action
from utils.BFSTraversal import BFSTraversal
from utils.DFSTraversal import DFSTraversal

#####################################################################################################################################################
###################################################################### CLASSES ######################################################################
#####################################################################################################################################################

class TraversalAlgorithm (enum.Enum):

    """
    This enumeration defines all the possible algorithms handled by the ``TraversalPlayer``.

    Possible algorithms are:
        * ``BFS``: Breadth-first search.
        * ``DFS``: Depth-first search.
    """

    BFS = "bfs"
    DFS = "dfs"

#####################################################################################################################################################

class TraversalPlayer (Player):

    """
    *(This class inherits from* ``Player`` *).*

    This player controls a PyRat character that explores the maze using a graph traversal algorithm.
    The algorithm can be chosen among those defined in the ``TraversalAlgorithm`` enumeration.
    """

    #############################################################################################################################################
    #                                                                CONSTRUCTOR                                                                #
    #############################################################################################################################################

    def __init__ ( self,
                   algorithm: TraversalAlgorithm,
                   *args:     object,
                   **kwargs:  object
                 ) ->         None:

        """
        Initializes a new instance of the class.
        Here, the constructor is only used to initialize the player.
        It transmits the arguments to the parent constructor, which is responsible for initializing the name and the skin of the player.

        Args:
            algorithm: The traversal algorithm to use, among those defined in the ``TraversalAlgorithm`` enumeration.
            args:      Arguments to pass to the parent constructor.
            kwargs:    Keyword arguments to pass to the parent constructor.
        """

        # Inherit from parent class
        super().__init__(*args, **kwargs)

        # Debug
        assert isinstance(algorithm, TraversalAlgorithm), "The algorithm must be an instance of the TraversalAlgorithm enumeration"
       
        # We create an attribute to store the actions to perform
        self.actions = None

        # We initialize the chosen traversal algorithm
        self.traversal_algorithm = None
        if algorithm == TraversalAlgorithm.BFS:
            self.traversal_algorithm = BFSTraversal()
        elif algorithm == TraversalAlgorithm.DFS:
            self.traversal_algorithm = DFSTraversal()

    #############################################################################################################################################
    #                                                               PYRAT METHODS                                                               #
    #############################################################################################################################################

    def preprocessing ( self,
                        maze:       Maze,
                        game_state: GameState,
                      ) ->          None:
        
        """
        *(This method redefines the method of the parent class with the same name).*

        This method is called once at the beginning of the game.
        Here, we use the traversal algorithm to compute a shortest path that reaches the piece of cheese from the initial position of the player.

        Args:
            maze:       An object representing the maze in which the player plays.
            game_state: An object representing the state of the game.
        """

        # We perform a traversal of the maze graph from the initial position of the player
        my_location = game_state.player_locations[self.get_name()]
        self.traversal_algorithm.traversal(maze, my_location)

        # Find the series of locations/actions to go from the player location to the first piece of cheese
        path = self.traversal_algorithm.find_route(game_state.cheese[0])
        self.actions = maze.locations_to_actions(path)

    #############################################################################################################################################

    def turn ( self,
               maze:       Maze,
               game_state: GameState,
             ) ->          Action:

        """
        *(This method redefines the method of the parent class with the same name).*

        It is called at each turn of the game.
        It returns an action to perform among the possible actions, defined in the ``Action`` enumeration.

        Args:
            maze:       An object representing the maze in which the player plays.
            game_state: An object representing the state of the game.
        
        Returns:
            One of the possible actions.
        """

        # At each turn, we perform the next action in the list of actions
        return self.actions.pop(0)

#####################################################################################################################################################
#####################################################################################################################################################
