"""
This module provides a simple console application demonstration of the pyGameAIFoundation package.

__main__
First, demonstrates the Gob (goal-oriented behavior) class selecting a top goal and best action to achieve that goal:
(1) Creates a Gob (goal-oriented behavior) object, and adds two goals with different insistence values.
(2) Then adds two actions to the Gob that can reduce the insistence of the goals by different amounts.
(3) Then asks the Gob to choose the best action to fulfill the most insistent goal, and schedules that
    action in an ActionManager.
(4) Finally, it executes the ActionManager and outputs the state before and after execution.
Second, demonstrates the GameActionSequence:
(1) Creates a GameActionSequence consisting of the two actions created earlier, and adds it to the Gob.
(2) Then asks the Gob to choose the best action to fulfill the most insistent goal, and schedules that
    action in an ActionManager. This will be the GameActionSequence.
(3) Finally, it executes the ActionManager multiple times to execute each action in the sequence, and outputs
    the state before and after each execution.
Third, demonstrates the GameActionCombination:
(1) Creates a GameActionCombination consisting of the two actions created earlier, and adds it to the Gob.
(2) Then asks the Gob to choose the best action to fulfill the most insistent goal, and schedules that
    action in an ActionManager. This will be the GameActionCombination.
(3) Finally, it executes the ActionManager and outputs the state before and after execution.

Exported Classes:
    GoalOne - A goal class with an insistence value of 2.
    GoalTwo - A goal class with an insistence value of 4.
    ActionOne - An action class that reduces the insistence of GoalOne by 4 and GoalTwo by 2.
    ActionTwo - An action class that reduces the insistence of GoalTwo by 4 and GoalOne by 2.

Exported Exceptions:
    None
 
Exported Functions:
    None
"""


# Standard imports


# Local imports
from pyGameAIFoundation.gob import Gob
from pyGameAIFoundation.game_goal import GameGoal, GoalInsistence
from pyGameAIFoundation.action_manager import ActionManager
from pyGameAIFoundation.game_action import GameAction, GameActionCombination, GameActionSequence


# Create a couple of concrete GameGoal subclasses with different insistence values.

class GoalOne(GameGoal):
    def getInsistence(self):
        """
        Return the insistence value of the goal.
        :return: The insistence value of the goal, int
        """
        return GoalInsistence.LOW


class GoalTwo(GameGoal):
    def getInsistence(self):
        """
        Return the insistence value of the goal.
        :return: The insistence value of the goal, int
        """
        return GoalInsistence.MEDIUM


# Create a pair of GameAction subclasses with different implementations of getGoalChange(...)

class ActionOne(GameAction):
    def __init__(self, expiry_time=0, priority=0, read_blackboard=None, write_blackboard=None):
        """
        :parameter expiry_time: The time in an arbitrary count-up from zero until the action expires, as int
        :parameter priority: The priority of the action. Higher numbers indicate higher priority. As int
        :parameter read_blackboard: A function to read from the blackboard, as callable
            Signature: read_blackboard(key: str) -> any
        :parameter write_blackboard: A function to write to the blackboard, as callable
            Signature: write_blackboard(key: str, value: any) -> None
        """
        super().__init__(expiry_time, priority, read_blackboard, write_blackboard)
        self._canInterrupt = False
        self._canDoBoth = False
        self._completed=False

    def canInterrupt(self):
        """
        Return whether this action can interrupt any other action.
        :return: True if the action can interrupt any other action, False otherwise, as boolean.
        """
        return self._canInterrupt

    def canDoBoth(self, other_action):
        """
        Return whether this action can be done at the same time as another action.
        :param other_action: The other action to check against, as GameAction object.
        :return: True if both actions can be done at the same time, False otherwise, as boolean.
        """
        assert(isinstance(other_action, GameAction))
        return self._canDoBoth

    def isComplete(self):
        """
        Return whether this action is complete.
        :return: True if the action is complete, False otherwise, as boolean.
        """
        return self._completed

    def execute(self):
        """
        Execute the action. In this case, by writing to the blackboard (if we have one) and
        setting the completed flag to True.
        :return: None
        """
        print("Executing ActionOne")
        if self._write_blackboard:
            self._write_blackboard('ActionOne Output', 15)
            print("Wrote to blackboard: ActionOne Output = 15")
        self._completed = True
        return None

    def getGoalChange(self, goal=None):
        """
        Return the goal insistence change associated with this action.
        :param goal: The goal to check against, as GameGoal object.
        :return: The goal insistence change associated with this action, as int.
        """
        assert(isinstance(goal, GameGoal))
        if isinstance(goal, GoalOne):
            return -GoalInsistence.MEDIUM
        else:
            return -GoalInsistence.LOW


class ActionTwo(GameAction):
    def __init__(self, expiry_time=0, priority=0, read_blackboard=None, write_blackboard=None):
        """
        :parameter expiry_time: The time in an arbitrary count-up from zero until the action expires, as int
        :parameter priority: The priority of the action. Higher numbers indicate higher priority. As int
        :parameter read_blackboard: A function to read from the blackboard, as callable
            Signature: read_blackboard(key: str) -> any
        :parameter write_blackboard: A function to write to the blackboard, as callable
            Signature: write_blackboard(key: str, value: any) -> None
        """
        super().__init__(expiry_time, priority, read_blackboard, write_blackboard)
        self._canInterrupt = False
        self._canDoBoth = False
        self._completed=False

    def canInterrupt(self):
        """
        Return whether this action can interrupt any other action.
        :return: True if the action can interrupt any other action, False otherwise, as boolean.
        """
        return self._canInterrupt

    def canDoBoth(self, other_action):
        """
        Return whether this action can be done at the same time as another action.
        :param other_action: The other action to check against, as GameAction object.
        :return: True if both actions can be done at the same time, False otherwise, as boolean.
        """
        assert(isinstance(other_action, GameAction))
        return self._canDoBoth

    def isComplete(self):
        """
        Return whether this action is complete.
        :return: True if the action is complete, False otherwise, as boolean.
        """
        return self._completed

    def execute(self):
        """
        Execute the action. In this case, by reading from the blackboard (if we have one) and
        setting the completed flag to True.
        :return: None
        """
        print("Executing ActionTwo")
        if self._read_blackboard:
            value = self._read_blackboard('ActionOne Output')
            print(f"Read from blackboard: ActionOne Output = {value}")
        self._completed = True
        return None

    def getGoalChange(self, goal=None):
        """
        Return the goal insistence change associated with this action.
        :param goal: The goal to check against, as GameGoal object.
        :return: The goal insistence change associated with this action, as int.
        """
        assert(isinstance(goal, GameGoal))
        if isinstance(goal, GoalTwo):
            return -GoalInsistence.MEDIUM
        else:
            return -GoalInsistence.LOW


if __name__ == '__main__':
    
    """
    Query the user for how they wish to demonstrate pyGameAIFoundation package.
    """
    
    print('----------------------------------------')
    print('*** pyGameAIFoundation Demonstration ***')
    print('----------------------------------------')
        
    # Create the Gob (goal-oriented behavior) object. 
    gob = Gob()
    # Add goals to the Gob.
    gob.add_goal(GoalOne('goal_1'))
    gob.add_goal(GoalTwo('goal_2'))
    # Create actions and add them to the Gob.
    act1 = ActionOne(expiry_time=10)
    act2 = ActionTwo(expiry_time=10)
    gob.add_action(act1)
    gob.add_action(act2)
    
    # Create the ActionManager.
    mgr = ActionManager()

    # Choose the best action to execute.
    (bestAct, topGoal) = gob.chooseAction()
    bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
    print(f'Chose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')

    # Schedule the best action in the ActionManager.
    mgr.scheduleAction(bestAct)

    # Output the action manager state.
    print(f'Action Manager State before execution:\n{mgr}')

    # Execute the action manager, which will activate the scheduled action and execute it
    mgr.execute()
    # Output the action manager state.
    print(f'Action Manager State after first execution:\n{mgr}')
    # Execute the action manager again, which will remove the completed action from the active list.
    mgr.execute()
    # Output the action manager state.
    print(f'Action Manager State after second execution:\n{mgr}')

    # Create a GameActionCombination and demonstrate its use in the Gob and ActionManager.
    combo = GameActionCombination(expiry_time=10, combo_acts=[act1, act2])
    gob.add_action(combo)
    (bestAct, topGoal) = gob.chooseAction()
    bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
    print(f'\nChose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')
    mgr.scheduleAction(bestAct)
    print(f'Action Manager State before execution:\n{mgr}')
    # Activate the scheduled combination action, and execute both actions in the combo.
    mgr.execute()
    print(f'Action Manager State after first execution:\n{mgr}')
    # Remove the completed combination action from the active list.
    mgr.execute()
    print(f'Action Manager State after second execution:\n{mgr}')
    # Remove the combination action from the Gob's action list, so it doesn't interfere with the rest of the demo,
    # since it's insistence change is the same as the sequence action created next.
    gob.remove_action(combo)

    # Create a GameActionSequence, demonstrate its use in the Gob and ActionManager, and use of the
    # blackboard to pass information between actions in a sequence.
    seq = GameActionSequence(expiry_time=10)
    act1 = ActionOne(expiry_time=10, read_blackboard=seq.readFromBlackBoard, write_blackboard=seq.writeToBlackBoard)
    act2 = ActionTwo(expiry_time=10, read_blackboard=seq.readFromBlackBoard, write_blackboard=seq.writeToBlackBoard)
    seq.addAction(act1)
    seq.addAction(act2)
    gob.add_action(seq)
    (bestAct, topGoal) = gob.chooseAction()
    bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
    print(f'\nChose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')
    mgr.scheduleAction(bestAct)
    print(f'Action Manager State before execution:\n{mgr}')
    # Activate the scheduled sequence action, and execute the first action in the sequence.
    mgr.execute()
    print(f'Action Manager State after first execution:\n{mgr}')
    # Execute the second action in the sequence.
    mgr.execute()
    print(f'Action Manager State after second execution:\n{mgr}')
    # Remove the completed sequence action from the active list.
    mgr.execute()
    print(f'Action Manager State after third execution:\n{mgr}')
