#include "character/states/jobs/swordman/SwordmanAttackState.h"
#include "character/CharacterObject.h"
#include "character/states/jobs/swordman/SwordmanActionHandler.h"
#include "character/states/jobs/swordman/SwordmanBasicAttackHandler.h"
#include "character/states/jobs/swordman/SwordmanSkill1Handler.h"
#include <cmath>

namespace frostbite2D {
namespace {

bool HasMoveIntent(const CharacterIntent& intent) {
  return intent.moveMode != CharacterMoveMode::None
      && (std::abs(intent.moveX) > 0.01f || std::abs(intent.moveY) > 0.01f);
}

CharacterStateId ResolveGroundState(const CharacterIntent& intent) {
  return HasMoveIntent(intent) ? CharacterStateId::Move : CharacterStateId::Idle;
}

} // namespace

SwordmanAttackState::SwordmanAttackState()
    : CharacterStateBase(CharacterStateId::Action) {
  handlers_.emplace("attack", std::make_unique<SwordmanBasicAttackHandler>());
  handlers_.emplace("skill_1", std::make_unique<SwordmanSkill1Handler>());
}

bool SwordmanAttackState::TryEnter(CharacterStateMachine& machine,
                                   CharacterStateContext& context) {
  for (const auto& requestedActionId : context.intent.requestedActions) {
    const CharacterActionDefinition* action = context.owner.FindAction(requestedActionId);
    if (!action) {
      context.owner.ReportFatalCharacterError(
          "SwordmanAttackState::TryEnter",
          "requested logical action is not registered",
          requestedActionId);
      return false;
    }

    if (!FindHandler(action->actionId)) {
      context.owner.ReportFatalCharacterError(
          "SwordmanAttackState::TryEnter",
          "requested logical action has no swordman action handler",
          action->actionId);
      return false;
    }

    if (TryStartAction(machine, context, requestedActionId, action->actionId)) {
      return true;
    }
  }

  return false;
}

void SwordmanAttackState::OnEnter(CharacterStateMachine& machine,
                                  CharacterStateContext& context,
                                  CharacterStateId previousState) {
  (void)previousState;

  const CharacterActionDefinition* action = GetCurrentAction(machine);
  if (!action) {
    context.owner.ReportFatalCharacterError("SwordmanAttackState::OnEnter",
                                            "action state entered without current action");
    return;
  }

  ISwordmanActionHandler* handler = FindHandler(action->actionId);
  if (!handler) {
    context.owner.ReportFatalCharacterError("SwordmanAttackState::OnEnter",
                                            "current logical action has no swordman handler",
                                            action->actionId);
    return;
  }

  handler->OnEnter(context, *action);
  InvokeHook(machine, context.owner, action, "on_enter");
}

void SwordmanAttackState::OnUpdate(CharacterStateMachine& machine,
                                   CharacterStateContext& context) {
  auto& owner = context.owner;
  auto& motor = owner.GetMotorMutable();
  const CharacterActionDefinition* action = GetCurrentAction(machine);
  if (!action) {
    ChangeState(machine, context, ResolveGroundState(context.intent));
    return;
  }

  ISwordmanActionHandler* handler = FindHandler(action->actionId);
  if (!handler) {
    owner.ReportFatalCharacterError("SwordmanAttackState::OnUpdate",
                                    "current logical action has no swordman handler",
                                    action->actionId);
    return;
  }

  InvokeHook(machine, owner, action, "on_update");
  SwordmanActionUpdateResult updateResult = handler->OnUpdate(context, *action);
  if (updateResult.type == SwordmanActionUpdateResult::Type::Transition) {
    if (updateResult.requestedActionId.empty()) {
      owner.ReportFatalCharacterError("SwordmanAttackState::OnUpdate",
                                      "handler requested an empty transition action id",
                                      action->actionId);
      return;
    }

    bool started = updateResult.requireBufferedInput
        ? TryStartAction(machine, context, updateResult.requestedActionId)
        : StartAction(machine, context, updateResult.requestedActionId);
    (void)started;
    return;
  }

  if (updateResult.type == SwordmanActionUpdateResult::Type::Finished) {
    if (!motor.grounded) {
      ChangeState(machine, context, CharacterStateId::Fall);
    } else {
      ChangeState(machine, context, ResolveGroundState(context.intent));
    }
  }
}

void SwordmanAttackState::OnExit(CharacterStateMachine& machine,
                                 CharacterStateContext& context,
                                 CharacterStateId nextState) {
  (void)nextState;

  const CharacterActionDefinition* action = GetCurrentAction(machine);
  if (action) {
    if (ISwordmanActionHandler* handler = FindHandler(action->actionId)) {
      handler->OnExit(context, *action);
    }
  }
  InvokeHook(machine, context.owner, action, "on_exit");
}

ISwordmanActionHandler* SwordmanAttackState::FindHandler(const std::string& actionId) const {
  auto it = handlers_.find(actionId);
  if (it == handlers_.end()) {
    return nullptr;
  }
  return it->second.get();
}

} // namespace frostbite2D
