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

GameActor::~GameActor() { }

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

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

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

void GameActor::setRotation(const float& rotation) 
{
	if (!isRotatable) {
		if (auto eventManager = sceneContext->getEventManager().lock()) {
			Direction newDir = getDirectionFromRotation(rotation);
			if (getDirectionFromRotation(this->rotation) != newDir)
				eventManager->notify(std::make_shared<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);

	if (isMoving && !wasMoving)
	{
		if (auto event = sceneContext->getEventManager().lock()) {
			event->notify(std::make_shared<EntityMoveEvent>(entityid));
		}
		wasMoving = true;
	}
	else if (!isMoving && wasMoving)
	{
		if (auto event = sceneContext->getEventManager().lock()) {
			event->notify(std::make_shared<EntityStopEvent>(entityid));
		}
		wasMoving = false;
	}
	isMoving = false;
}

void GameActor::draw()
{
	Entity::draw();

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

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 (sceneContext->getPlayerID() == entityid) {
				if (auto gEvent = sceneContext->getGlobalEventManager().lock()) {
					gEvent->notify(std::make_shared<ScreenShakeEvent>(0.01f, 0.8f));
					gEvent->notify(std::make_shared<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 + 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 (auto event = sceneContext->getEventManager().lock()) {
			event->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; 
}
