#include "gameplay/gameactor.h"
#include "gameplay/entity.h"
#include "gameplay/physics.h"
#include "gameplay/scene.h"
#include "gameplay/weapons/weapon.h"
#include "utility/component.h"
#include "utility/direction.h"
#include "utility/events.h"
#include <memory>

// The components are smart pointer references to resources owned by the
// resource manager. So the GameActor is not responsible for cleaning resources.
GameActor::~GameActor() {}

// Adding a component also means attaching the id of the entity to the
// component, so it's aware of who owns it. This is important for event handling
void GameActor::addComponent(std::unique_ptr<Component> component) {
  component->ownerid = entityid;
  components.push_back(std::move(component));
}

Weapon *GameActor::getHeldWeapon() const {
  return (weapons.empty() || currentWeaponIndex >= weapons.size())
             ? nullptr
             : weapons[currentWeaponIndex].get();
}

std::span<Weapon *> GameActor::getAllWeapons() { return weaponCache; }

// Keep a seperate vector that is used as the cache for external calls to
// getAllWeapons()
void GameActor::pickupWeapon(std::unique_ptr<Weapon> weapon) {
  weapon->setWielder(this);
  if (auto eventManager = sceneContext->getEventManager().lock()) {
    weapon->hookEventManager(eventManager);
  }
  weaponCache.push_back(weapon.get());
  weapons.push_back(std::move(weapon));
  // wield the newly picked up weapon.
  getHeldWeapon()->putaway();
  currentWeaponIndex = weapons.size() - 1;
  getHeldWeapon()->wield();
}

void GameActor::setRotation(const float &rotation) {
  // Any attached animation component would be interested if their owner needs
  // their sprite swapped
  if (!isRotatable) {
    if (auto eventManager = sceneContext->getEventManager().lock()) {
      Direction newDir = getDirectionFromRotation(rotation);
      if (getDirectionFromRotation(this->rotation) != newDir)
        eventManager->notify<DirectionChangeEvent>({entityid, newDir});
    }
  }
  this->rotation = rotation;
  updateModelMatrix();
}

void GameActor::update(double deltaTime) {
  Entity::update(deltaTime);

  for (const auto &component : components)
    component->update();
  for (const auto &weapon : weapons)
    weapon->update(deltaTime);

  // Not the cleanest solution, but this is to make sure the animation isn't
  // starting to move over and over.
  if (isMoving && !wasMoving) {
    if (auto event = sceneContext->getEventManager().lock()) {
      event->notify<EntityMoveEvent>({entityid});
    }
    wasMoving = true;
  } else if (!isMoving && wasMoving) {
    if (auto event = sceneContext->getEventManager().lock()) {
      event->notify<EntityStopEvent>({entityid});
    }
    wasMoving = false;
  }
  isMoving = false;
}

void GameActor::draw() {
  Entity::draw();
  for (const auto &component : components) {
    component->bind();
    component->render();
  }
}

void GameActor::moveUp() {
  if (physics)
    physics->rigidBody.applyForce(glm::vec3(0.f, -1.f, 0.f), 1500.25f);
  isMoving = true;
}
void GameActor::moveDown() {
  if (physics)
    physics->rigidBody.applyForce(glm::vec3(0.f, 1.f, 0.f), 1500.25f);
  isMoving = true;
}
void GameActor::moveLeft() {
  if (physics)
    physics->rigidBody.applyForce(glm::vec3(-1.f, 0.f, 0.f), 1500.25f);
  isMoving = true;
}
void GameActor::moveRight() {
  if (physics)
    physics->rigidBody.applyForce(glm::vec3(1.f, 0.f, 0.f), 1500.25f);
  isMoving = true;
}

// top-down shooter mode controls
void GameActor::fireWeapon() const {
  if (auto weapon = getHeldWeapon()) {
    if (weapon->shoot()) {
      // If it's the player that is shooting apply a shake and blur effect to
      // give the shots some weight
      if (sceneContext->getPlayerID() == entityid) {
        if (auto gEvent = sceneContext->getGlobalEventManager().lock()) {
          gEvent->notify<ScreenShakeEvent>({0.01f, 0.8f});
          gEvent->notify<ScreenBlurEvent>({1.f, 0.8f});
        }
      }
    }
  }
}

void GameActor::cycleUpWeapons() {
  if (!weapons.empty()) {
    weapons[currentWeaponIndex]->putaway();
    currentWeaponIndex = (currentWeaponIndex + 1) % weapons.size();
    weapons[currentWeaponIndex]->wield();
  }
}
void GameActor::cycleDownWeapons() {
  if (!weapons.empty()) {
    weapons[currentWeaponIndex]->putaway();
    currentWeaponIndex =
        (currentWeaponIndex + weapons.size() - 1) % weapons.size();
    weapons[currentWeaponIndex]->wield();
  }
}
void GameActor::cycleWeapons(const MouseState &mouse_state) {
  if (mouse_state.scroll < 0)
    cycleUpWeapons();
  else if (mouse_state.scroll > 0)
    cycleDownWeapons();
}
void GameActor::followMouse(const MouseState &mouse_state) {
  glm::vec2 direction = glm::vec2(mouse_state.x, mouse_state.y) -
                        glm::vec2(localPosition.x, localPosition.y);
  float newRotation = glm::degrees(glm::atan(direction.y, direction.x));
  if (getDirectionFromRotation(rotation) !=
      getDirectionFromRotation(newRotation)) {
    if (auto event = sceneContext->getEventManager().lock()) {
      event->notify<DirectionChangeEvent>(
          {entityid, getDirectionFromRotation(newRotation)});
    }
  }
  // setRotation(glm::degrees(glm::atan(direction.y, direction.x)));
  this->rotation = newRotation;
}
void GameActor::strafeLeft() {
  position.x += sin(glm::radians(rotation)) * speed;
  position.y -= cos(glm::radians(rotation)) * speed;
}
void GameActor::strafeRight() {
  position.x -= sin(glm::radians(rotation)) * speed;
  position.y += cos(glm::radians(rotation)) * speed;
}
void GameActor::moveBackward() {
  position.x -= cos(glm::radians(rotation)) * speed;
  position.y -= sin(glm::radians(rotation)) * speed;
}
void GameActor::moveForward() { // More than likely the most useful of the
                                // movement commands so far. But this could
                                // change in the future
  if (physics) {
    physics->rigidBody.velocity.x += cos(glm::radians(rotation)) * speed;
    physics->rigidBody.velocity.y += sin(glm::radians(rotation)) * speed;
  }
  isMoving = true;
}
