from behaviors.custom.keyframe.get_up.get_up import GetUp
from behaviors.custom.keyframe.keyframe import KeyframeSkill
from behaviors.custom.keyframe.poses.neutral.neutral import Neutral
from behaviors.behavior import Behavior
from behaviors.custom.reinforcement.walk.walk import Walk


class BehaviorManager:

    def __init__(self, agent) -> None:
        self.current_skill_name: str | None = None
        self.previous_skill_name: str | None = None
        self.current_sub_skill_name: str | None = None
        self.skills: dict[str, Behavior] = {}
        self.agent = agent

        self.create_skills()

    def create_skills(self):
        """
        Loads all Skill classes and instantiates them.
        Each skill is indexed by its class name.
        """

        classes: list[type[Behavior]] = [Walk, Neutral, GetUp]

        # instantiate each Skill and store in the skills dictionary
        self.skills = {cls.__name__: cls(agent=self.agent) for cls in classes}

    def get_skill_object(self, name: str) -> Behavior:
        """Returns the Skill instance corresponding to the given name."""
        if name not in self.skills:
            raise KeyError(f"No skill found with the name '{name}'")
        return self.skills[name]

    def execute(self, skill_name: str, *args, **kwargs) -> bool:
        """
        Executes one step of the specified skill.

        - On the first call for a skill, `reset=True` is sent.
        - Returns True when the skill has finished execution.
        """
        skill = self.get_skill_object(skill_name)

        # detect if the skill has changed to trigger automatic reset
        reset = self.current_skill_name != skill_name
        if reset:
            # previous skill was interrupted
            if self.current_skill_name is not None:
                self.previous_skill_name = self.current_skill_name
            self.current_skill_name = skill_name

        # call the Skill's own execute method
        finished = skill.execute(reset, *args, **kwargs)
        if not finished:
            return False

        # skill finished execution
        self.previous_skill_name = self.current_skill_name
        self.current_skill_name = None
        return True

    def execute_sub_skill(self, skill_name: str, reset: bool, *args, **kwargs) -> bool:
        """
        Executes a step of a sub-skill within another skill.

        - Does not change `current_skill_name`.
        - `current_sub_skill_name` reflects the sub-skill currently being executed.
        """
        skill = self.get_skill_object(skill_name)
        self.current_sub_skill_name = skill_name
        return skill.execute(reset, *args, **kwargs)

    def is_ready(self, skill_name: str, *args, **kwargs) -> bool:
        """
        Checks if the specified skill is ready to start based on current conditions.
        """
        skill = self.get_skill_object(skill_name)
        return skill.is_ready(*args, **kwargs)
