from __future__ import annotations
import logging
from typing import Optional, Type
from snecs.typedefs import EntityID
import scripts.engine.core.matter
from scripts.engine.core import hourglass, state, utility, world
from scripts.engine.core.component import Knowledge, Position
from scripts.engine.core.ui import ui
from scripts.engine.internal import debug, library
from scripts.engine.internal.action import Skill
from scripts.engine.internal.constant import (
    Direction,
    DirectionType,
    GameState,
    InputIntent,
    InputIntentType,
    TargetingMethod,
    UIElement,
)
from scripts.engine.internal.event import EndTurnEvent, event_hub
from scripts.engine.world_objects.tile import Tile
from scripts.nqp import command
from scripts.nqp.processors.targeting import targeting
from scripts.nqp.ui_elements.camera import camera
from scripts.nqp.ui_elements.dungen_viewer import DungenViewer
__all__ = ["process_intent"]
[docs]def process_intent(intent: InputIntentType, game_state: GameState = None):
    """
    Process the intent in the context of the game state. Intents are game state sensitive. If no game state  is
    passed the current one will be obtained.
    """
    _process_stateless_intents(intent)
    if not game_state:
        game_state = state.get_current()
    if game_state == GameState.GAME_MAP:
        _process_game_map_intents(intent)
    elif game_state == GameState.TARGETING:
        _process_targeting_mode_intents(intent)
    elif game_state == GameState.MENU:
        _process_menu_intents(intent) 
[docs]def _process_stateless_intents(intent: InputIntentType):
    """
    Process intents that don't rely on game state.
    """
    ## Activate Debug
    if intent == InputIntent.DEBUG_TOGGLE:
        # F1
        if debug.is_fps_visible():
            debug.set_fps_visibility(False)
        else:
            debug.set_fps_visibility(True)
    ## Refresh Library Data
    elif intent == InputIntent.REFRESH_DATA:
        # TODO - move to a command in the console.
        library.refresh_library()
    ## Activate data editor
    # TODO - have this trigger dev console and move skill editor to a command in the console.
    elif intent == InputIntent.DUNGEON_DEV_VIEW:
        # F5
        if ui.element_is_visible(UIElement.DUNGEN_VIEWER):
            ui.set_element_visibility(UIElement.DUNGEN_VIEWER, False)
            state.set_new(state.get_previous())
        else:
            if ui.has_element(UIElement.DUNGEN_VIEWER):
                dungen_viewer = ui.get_element(UIElement.DUNGEN_VIEWER)
            else:
                dungen_viewer = DungenViewer(command.get_element_rect(UIElement.DUNGEN_VIEWER), ui.get_gui_manager())
                ui.register_element(UIElement.DUNGEN_VIEWER, dungen_viewer)
            ui.set_element_visibility(UIElement.DUNGEN_VIEWER, True)
            dungen_viewer.refresh_viewer()
            state.set_new(GameState.MENU)
    elif intent == InputIntent.DEV_TOGGLE:
        # F2
        pass
    elif intent == InputIntent.BURST_PROFILE:
        # F3
        debug.enable_profiling(120)
    elif intent == InputIntent.TOGGLE_UI:
        # F6
        # toggle message log
        if ui.has_element(UIElement.MESSAGE_LOG):
            if ui.element_is_visible(UIElement.MESSAGE_LOG):
                ui.set_element_visibility(UIElement.MESSAGE_LOG, False)
            else:
                ui.set_element_visibility(UIElement.MESSAGE_LOG, True)
        # toggle skill bar
        if ui.has_element(UIElement.SKILL_BAR):
            if ui.element_is_visible(UIElement.SKILL_BAR):
                ui.set_element_visibility(UIElement.SKILL_BAR, False)
            else:
                ui.set_element_visibility(UIElement.SKILL_BAR, True)
    elif intent == InputIntent.TEST:
        # F12
        breakpoint() 
[docs]def _process_game_map_intents(intent: InputIntentType):
    """
    Process intents for the player turn game state.
    """
    player = scripts.engine.core.matter.get_player()
    position = scripts.engine.core.matter.get_entitys_component(player, Position)
    possible_move_intents = [InputIntent.DOWN, InputIntent.UP, InputIntent.LEFT, InputIntent.RIGHT]
    possible_skill_intents = [
        InputIntent.SKILL0,
        InputIntent.SKILL1,
        InputIntent.SKILL2,
        InputIntent.SKILL3,
        InputIntent.SKILL4,
        InputIntent.SKILL5,
    ]
    ## Player movement
    if intent in possible_move_intents and position:
        direction = _get_pressed_direction(intent)
        target_tile = world.get_tile((position.x, position.y))
        move = scripts.engine.core.matter.get_known_skill(player, "Move")
        if direction in move.target_directions:
            # ensure it's actually the player's turn by checking the event queue for an unprocessed end turn event
            if not event_hub.peek(EndTurnEvent):
                _process_skill_use(player, move, target_tile, direction)
    ## Use a skill
    elif intent in possible_skill_intents and position:
        skill_name = _get_pressed_skills_name(intent)
        current_tile = world.get_tile((position.x, position.y))
        if skill_name:
            # is skill ready to use
            if scripts.engine.core.matter.can_use_skill(player, skill_name):
                skill = scripts.engine.core.matter.get_known_skill(player, skill_name)
                if skill:
                    # if auto targeting use the skill
                    if skill.targeting_method == TargetingMethod.AUTO:
                        # pass centre as it doesnt matter, the skill will pick the right direction
                        _process_skill_use(player, skill, current_tile, Direction.CENTRE)
                    elif skill.targeting_method in [TargetingMethod.TILE, TargetingMethod.DIRECTION]:
                        # trigger targeting overlay
                        state.set_new(GameState.TARGETING)
                        state.set_active_skill(skill_name)
    ## Show actor info - we're in GAMEMAP so it cant be visible
    elif intent == InputIntent.ACTOR_INFO_TOGGLE:
        # show
        state.set_new(GameState.MENU)
        ui.set_element_visibility(UIElement.ACTOR_INFO, True)
    elif intent == InputIntent.EXIT:
        command.exit_game()
    elif intent == InputIntent.LEFT_CLICKED:
        targeting.process_click() 
[docs]def _process_targeting_mode_intents(intent):
    """
    Process intents for the player turn game state.
    """
    player = scripts.engine.core.matter.get_player()
    position = scripts.engine.core.matter.get_entitys_component(player, Position)
    active_skill_name = state.get_active_skill()
    skill = scripts.engine.core.matter.get_known_skill(player, active_skill_name)
    possible_skill_intents = [
        InputIntent.SKILL0,
        InputIntent.SKILL1,
        InputIntent.SKILL2,
        InputIntent.SKILL3,
        InputIntent.SKILL4,
        InputIntent.SKILL5,
    ]
    ## Cancel use
    if intent == InputIntent.CANCEL:
        # go back to previous state
        state.set_new(state.get_previous())
    elif intent == InputIntent.LEFT_CLICKED:
        targeting.process_click()
    ## Select new skill
    elif intent in possible_skill_intents:
        pressed_skill_name = _get_pressed_skills_name(intent)
        if pressed_skill_name:
            # if skill pressed doesn't match skill already being targeted
            if pressed_skill_name != active_skill_name:
                # reactivate targeting mode with the new skill
                if scripts.engine.core.matter.can_use_skill(player, pressed_skill_name):
                    state.set_active_skill(pressed_skill_name)
    ## Use skill
    elif intent.upper() in utility.get_class_members(Direction):
        direction = _get_pressed_direction(intent)
        if position and skill and direction:
            outermost = position.get_outermost(direction)
            tile = world.get_tile((outermost[0] + direction[0], outermost[1] + direction[1]))
            if skill.targeting_method == TargetingMethod.DIRECTION:
                if not state.get_skill_target_valid():
                    tile = None
                else:
                    tile = world.get_tile(state.get_active_skill_target())
            if tile:
                # we already checked if we could use the skill before activating the targeting mode
                _process_skill_use(player, skill, tile, direction)
                # resume previous state
                state.set_new(state.get_previous()) 
                # ui.update_targeting_overlay(False)
def _process_menu_intents(intent):
    ## Exit current menu
    if intent == InputIntent.ACTOR_INFO_TOGGLE:
        state.set_new(state.get_previous())
        if ui.has_element(UIElement.ACTOR_INFO):
            ui.set_element_visibility(UIElement.ACTOR_INFO, False)
################## HELPER FUNCTIONS ############################
[docs]def _process_skill_use(player: EntityID, skill: Type[Skill], target_tile: Tile, direction: DirectionType):
    """
    Process the use of specified skill. Wrapper for actions needed to handle a full skill use. Assumed
    'can_use_skill' already completed.
    """
    # get players starting position for camera updates
    pos = scripts.engine.core.matter.get_entitys_component(player, Position)
    start_pos = pos.x, pos.y
    if scripts.engine.core.matter.use_skill(player, skill, target_tile, direction):
        scripts.engine.core.matter.pay_resource_cost(player, skill.resource_type, skill.resource_cost)
        scripts.engine.core.matter.set_skill_on_cooldown(player, skill.__class__.__name__, skill.base_cooldown)
        hourglass.end_turn(player, skill.time_cost)
        # update camera if position changes
        try:
            if (pos.x, pos.y) != start_pos:
                # ui.get_element(UIElement.CAMERA).set_target((pos.x, pos.y))
                camera.set_target((pos.x, pos.y))
        except KeyError:
            logging.warning("Process skill use: tried to call camera but not init`d.") 
[docs]def _get_pressed_direction(intent: InputIntentType) -> DirectionType:
    """
    Get the value of the directions pressed. Returns as (x, y). Values are ints between -1 and 1.
    """
    if intent == InputIntent.UP:
        direction = Direction.UP
    elif intent == InputIntent.UP_RIGHT:
        direction = Direction.UP_RIGHT
    elif intent == InputIntent.UP_LEFT:
        direction = Direction.UP_LEFT
    elif intent == InputIntent.RIGHT:
        direction = Direction.RIGHT
    elif intent == InputIntent.LEFT:
        direction = Direction.LEFT
    elif intent == InputIntent.DOWN:
        direction = Direction.DOWN
    elif intent == InputIntent.DOWN_RIGHT:
        direction = Direction.DOWN_RIGHT
    elif intent == InputIntent.DOWN_LEFT:
        direction = Direction.DOWN_LEFT
    else:
        direction = Direction.CENTRE
    return direction 
[docs]def _get_pressed_skills_name(intent: InputIntentType) -> Optional[str]:
    """
    Get the pressed skill number. Returns value of skill number pressed. If not found returns None.
    """
    player = scripts.engine.core.matter.get_player()
    skill_name = None
    if player:
        skills = scripts.engine.core.matter.get_entitys_component(player, Knowledge)
        if skills:
            skill_order = skills.skill_order
            try:
                if intent == InputIntent.SKILL0:
                    skill_name = skill_order[0]
                elif intent == InputIntent.SKILL1:
                    skill_name = skill_order[1]
                elif intent == InputIntent.SKILL2:
                    skill_name = skill_order[2]
                elif intent == InputIntent.SKILL3:
                    skill_name = skill_order[3]
                elif intent == InputIntent.SKILL4:
                    skill_name = skill_order[4]
            except IndexError:
                logging.warning(f"_get_pressed_skills_name: Tried to use skill {intent} but no skill in that slot.")
    return skill_name