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

GameActor::~GameActor() { }

void GameActor::addComponent(std::shared_ptr<Component> component)
{
	component->ownerid = entityid;
	components.push_back(component);
}

const std::optional<std::shared_ptr<Weapon>> GameActor::getHeldWeapon() const
{
	return (weapons.empty() || currentWeaponIndex >= weapons.size()) ? std::nullopt : std::make_optional(weapons[currentWeaponIndex]);
}

void GameActor::pickupWeapon(std::shared_ptr<Weapon> weapon)
{
	weapon->setWielder(std::shared_ptr<GameActor>(this));
	weapons.push_back(weapon);
	// wield the newly picked up weapon.
	getHeldWeapon().value()->putaway();
	currentWeaponIndex = weapons.size() - 1;
	getHeldWeapon().value()->wield();
}

void GameActor::hookEventManager(std::shared_ptr<EventManager> eventManager)
{
	this->eventManager = eventManager;
	for (auto& weapon : weapons)
		weapon->hookEventManager(eventManager);
}

void GameActor::setRotation(const float& rotation) 
{
	if (!isRotatable && eventManager) {
		Direction newDir = getDirectionFromRotation(rotation);
		if (getDirectionFromRotation(this->rotation) != newDir)
			eventManager->notify(std::make_shared<DirectionChangeEvent>(entityid, newDir));
	}
	this->rotation = rotation;
	updateModelMatrix();
}

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

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

	if (eventManager)
	{
		if (isMoving && !wasMoving)
		{
			eventManager->notify(std::make_shared<EntityMoveEvent>(entityid));
			wasMoving = true;
		}
		else if (!isMoving && wasMoving)
		{
			eventManager->notify(std::make_shared<EntityStopEvent>(entityid));
			wasMoving = false;
		}
	}
	isMoving = false;
}

void GameActor::render(const std::shared_ptr<Camera>& camera)
{
	Entity::render(camera);

	// regular loop through components, but if the component returns true to kill, we erase it.
	// Components are always assumed to be smart pointers!
	for (auto& component : components)
	{
		component->bind();
		component->render();
	}
	for (auto& weapon : weapons)
		weapon->render(camera);
}

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()) weapon.value()->shoot(); }
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 + 0.5f * scale.x, localPosition.y + 0.5f * scale.y);
	float newRotation = glm::degrees(glm::atan(direction.y, direction.x));
	if (getDirectionFromRotation(rotation) != getDirectionFromRotation(newRotation))
		if (eventManager)
			eventManager->notify(std::make_shared<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() { 
	if (physics) {
		physics->rigidBody.velocity.x += cos(glm::radians(rotation)) * speed;
		physics->rigidBody.velocity.y += sin(glm::radians(rotation)) * speed;
	}
	isMoving = true; 
}
