#include "character/CharacterActionLibrary.h"
#include <algorithm>
#include <cctype>
#include <sstream>
#include <utility>
#include <vector>

namespace frostbite2D {
namespace {

std::string toLower(const std::string& value) {
  std::string result = value;
  std::transform(result.begin(), result.end(), result.begin(),
                 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
  return result;
}

std::string trim(const std::string& value) {
  size_t begin = 0;
  size_t end = value.size();
  while (begin < end && std::isspace(static_cast<unsigned char>(value[begin]))) {
    ++begin;
  }
  while (end > begin && std::isspace(static_cast<unsigned char>(value[end - 1]))) {
    --end;
  }
  return value.substr(begin, end - begin);
}

std::string normalizeKey(const std::string& value) {
  return toLower(trim(value));
}

void addRegisteredAction(std::map<std::string, CharacterActionDefinition>& actions,
                         const std::string& actionId) {
  std::string key = normalizeKey(actionId);
  if (key.empty() || actions.count(key) > 0) {
    return;
  }

  CharacterActionDefinition definition;
  definition.actionId = key;
  actions[key] = std::move(definition);
}

void registerCommonActions(std::map<std::string, CharacterActionDefinition>& actions) {
  addRegisteredAction(actions, "idle");
  addRegisteredAction(actions, "move");
  addRegisteredAction(actions, "run");
  addRegisteredAction(actions, "jump");
  addRegisteredAction(actions, "fall");
  addRegisteredAction(actions, "landing");
  addRegisteredAction(actions, "hurt_light");
}

void registerSwordmanActions(std::map<std::string, CharacterActionDefinition>& actions) {
  addRegisteredAction(actions, "attack");
  addRegisteredAction(actions, "skill_1");
}

} // namespace

void CharacterCommandBuffer::Advance(float deltaTime) {
  for (auto& button : buttons_) {
    button.age += deltaTime;
  }
  buttons_.erase(
      std::remove_if(buttons_.begin(), buttons_.end(),
                     [this](const BufferedButton& button) {
                       return button.age > bufferWindowSeconds_;
                     }),
      buttons_.end());
}

void CharacterCommandBuffer::Submit(const CharacterCommand& command) {
  if (command.type == CharacterCommandType::Move) {
    moveAxis_ = command.moveAxis;
    moveMode_ = command.moveMode;
    if (moveAxis_.lengthSquared() > 1.0f) {
      moveAxis_ = moveAxis_.normalized();
    }
    if (moveAxis_.lengthSquared() < 0.0001f) {
      moveMode_ = CharacterMoveMode::None;
    }
    return;
  }

  buttons_.push_back({command.type, command.actionId, 0.0f});
}

CharacterIntent CharacterCommandBuffer::BuildIntent() const {
  CharacterIntent intent;
  intent.moveX = moveAxis_.x;
  intent.moveY = moveAxis_.y;
  intent.moveMode = moveMode_;
  intent.wantJump = HasBuffered(CharacterCommandType::JumpPressed);
  for (const auto& button : buttons_) {
    if (button.type != CharacterCommandType::ActionPressed || button.actionId.empty()) {
      continue;
    }
    if (std::find(intent.requestedActions.begin(), intent.requestedActions.end(),
                  button.actionId) == intent.requestedActions.end()) {
      intent.requestedActions.push_back(button.actionId);
    }
  }
  return intent;
}

bool CharacterCommandBuffer::HasBuffered(CharacterCommandType type) const {
  for (const auto& button : buttons_) {
    if (button.type == type) {
      return true;
    }
  }
  return false;
}

void CharacterCommandBuffer::Consume(CharacterCommandType type) {
  auto it = std::find_if(buttons_.begin(), buttons_.end(),
                         [type](const BufferedButton& button) {
                           return button.type == type;
                         });
  if (it != buttons_.end()) {
    buttons_.erase(it);
  }
}

bool CharacterCommandBuffer::HasBufferedAction(const std::string& actionId) const {
  std::string key = normalizeKey(actionId);
  if (key.empty()) {
    return false;
  }

  for (const auto& button : buttons_) {
    if (button.type == CharacterCommandType::ActionPressed
        && normalizeKey(button.actionId) == key) {
      return true;
    }
  }
  return false;
}

void CharacterCommandBuffer::ConsumeAction(const std::string& actionId) {
  std::string key = normalizeKey(actionId);
  if (key.empty()) {
    return;
  }

  auto it = std::find_if(buttons_.begin(), buttons_.end(),
                         [&key](const BufferedButton& button) {
                           return button.type == CharacterCommandType::ActionPressed
                               && normalizeKey(button.actionId) == key;
                         });
  if (it != buttons_.end()) {
    buttons_.erase(it);
  }
}

void CharacterCommandBuffer::ClearBufferedActions() {
  buttons_.erase(
      std::remove_if(buttons_.begin(), buttons_.end(),
                     [](const BufferedButton& button) {
                       return button.type == CharacterCommandType::ActionPressed;
                     }),
      buttons_.end());
}

void CharacterCommandBuffer::Clear() {
  moveAxis_ = Vec2::Zero();
  moveMode_ = CharacterMoveMode::None;
  buttons_.clear();
}

bool CharacterActionLibrary::LoadForConfig(const character::CharacterConfig& config) {
  actions_.clear();
  return BuildExplicitActionRegistry(config);
}

const CharacterActionDefinition* CharacterActionLibrary::FindAction(
    const std::string& actionId) const {
  std::string key = normalizeKey(actionId);
  auto it = actions_.find(key);
  if (it != actions_.end()) {
    return &it->second;
  }
  return nullptr;
}

const CharacterActionDefinition* CharacterActionLibrary::GetDefaultAction() const {
  return FindAction("idle");
}

std::string CharacterActionLibrary::DescribeActionIds() const {
  if (actions_.empty()) {
    return "<none>";
  }

  std::ostringstream stream;
  bool first = true;
  for (const auto& [actionId, definition] : actions_) {
    (void)definition;
    if (!first) {
      stream << ", ";
    }
    stream << actionId;
    first = false;
  }
  return stream.str();
}

bool CharacterActionLibrary::BuildExplicitActionRegistry(
    const character::CharacterConfig& config) {
  registerCommonActions(actions_);

  if (config.jobTag == "swordman") {
    registerSwordmanActions(actions_);
  }

  return !actions_.empty();
}

std::optional<CharacterActionLibrary> loadCharacterActionLibrary(
    const character::CharacterConfig& config) {
  CharacterActionLibrary library;
  if (!library.LoadForConfig(config)) {
    return std::nullopt;
  }
  return library;
}

} // namespace frostbite2D
