#include "gameplay/weapons/weapon.h"
#include "utility/resourcemanager.h"
#include "gameplay/weapons/bulletmanager.h"
#include "gameplay/weapons/bullet.h"
#include "gameplay/gameactor.h"
#include "utility/component.h"
#include "utility/events.h"
#include "gameplay/physics.h"
#include <SDL_timer.h>

#include "utility/debugdraw.h"

// TODO: Regular clean up, make this mess readable!
// TODO: Attach script to weapons, create custom shoot scripts, allowing the addition of shotguns and other whacky things

Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager)
	: 
	Entity			(weaponShader),
	bulletShader	(bulletShader),
	weaponSize		(glm::vec2(data->sizeX, data->sizeY)),
	weaponOffset	(glm::vec2(data->offsetX, data->offsetY)),
	bulletDrop		(data->bulletDrop),
	bulletSpeed		(data->bulletSpeed),
	bulletSize		(glm::vec2(data->bulletSizeX, data->bulletSizeY)),
	bulletSpread	(std::make_shared<UTIL::RandomGenerator>(-data->bulletSpread, data->bulletSpread)),
	bulletModifer	(std::make_shared<UTIL::RandomGenerator>(data->modMin, data->modMax)),
	fireSpeed		(data->fireSpeed),
	bulletManager	(std::make_shared<BulletManager>()),
	bulletSprite	(data->bulletAnimated ? 
					 std::make_shared<SpriteComponent>(resourceManager->loadSpriteAnimated(data->bulletSprite, data->bulletFrameSize)) 
				   : std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->bulletSprite)))
{
	if (data->animated)
	{
		addComponent(std::make_shared<SpriteComponent>(resourceManager->loadSpriteAnimated(data->sprite, data->frameSize)));
		for (auto& c : components)
			c->play();
	}
	else
		addComponent(std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->sprite)));
	this->setScale(glm::vec3(weaponSize.x, weaponSize.y, 1.0f));
};

void Weapon::addComponent(const std::shared_ptr<Component>& comp) {
	components.push_back(comp);
}

void Weapon::shoot()
{
	if (wielder)
	{
		Uint32 currentTime = SDL_GetTicks();
		if (currentTime - lastFireTime >= fireSpeed)
		{
			// create bullet using this generated data
			BulletData b = genBulletData();
			bulletManager->addBullet(createBullet(b));
			lastFireTime = currentTime;
		}
	}
}

void Weapon::hookEventManager(const std::shared_ptr<EventManager>& eventManager)
{
	this->eventManager = eventManager;
	bulletManager->hookEventManager(eventManager);
}

void Weapon::update(float deltaTime)
{
	Entity::update(deltaTime);
	// move the weapon into place as the wielder rotates and moves
	if (wielder)
		adjustWeapon();

	for (auto& component : components)
		component->update();
	bulletManager->update(deltaTime);
}

void Weapon::render(const std::shared_ptr<Camera>& camera)
{
	Entity::render(camera);
	for (auto& component : components)
	{
		component->bind();
		component->render();
	}
	bulletManager->render(camera);
}

void Weapon::adjustWeapon()
{
	float rotation = glm::radians(wielder->getRotation());

	glm::vec3 offset = glm::vec3(
		cos(rotation) * ((wielder->getScale().x) - weaponSize.x * 0.5f),
		sin(rotation) * ((wielder->getScale().y) - weaponSize.y * 0.5f),
		0.0f
	);
	glm::vec3 origin = wielder->getCenter() + offset;
	origin.x -= (weaponSize.x) * 0.5f;
	origin.y -= (weaponSize.y) * 0.5f;

	// Flip the texture if the weapon is facing upwards or downwards
	Direction d = getDirectionFromRotation(glm::degrees(rotation));
	if ((lastDir == Direction::Up || lastDir == Direction::Left) &&
		(d == Direction::Down || d == Direction::Right) && !isFlipped())
		flip();
	if ((lastDir == Direction::Down || lastDir == Direction::Right) &&
		(d == Direction::Up || d == Direction::Left) && isFlipped())
		flip();
				
	setRotation(wielder->getRotation() - 180);
	setPosition(origin);
	lastDir = getDirectionFromRotation(glm::degrees(rotation));
}

Weapon::BulletData Weapon::genBulletData()
{
	BulletData b;
	float rotation = glm::radians(wielder->getRotation());
	b.mass = 0.1f;
	glm::vec2 facing = glm::vec2(
		cos(rotation + glm::radians(static_cast<float>(bulletSpread->genFloat()))),
		sin(rotation + glm::radians(static_cast<float>(bulletSpread->genFloat())))
	);
	b.direction = glm::normalize(facing);
	b.sizeMod = bulletModifer->genFloat();
	b.speedMod = bulletModifer->genFloat();
	b.dropMod = bulletModifer->genFloat();

	float radius = wielder->getScale().x + (weaponSize.x * 0.5) + weaponOffset.x;

	b.origin = glm::vec3(
		// x offset from the wielder
		wielder->getCenter().x + cos(rotation) * radius - sin(rotation) * weaponOffset.y,
		// y offset from the wielder
		wielder->getCenter().y + sin(rotation) * radius + cos(rotation) * weaponOffset.y,
		0.0f
	);
	b.origin.x -= ((bulletSize.x) * b.sizeMod) * 0.5f;
	b.origin.y -= ((bulletSize.y) * b.sizeMod) * 0.5f;
	return b;
}

std::shared_ptr<Bullet> Weapon::createBullet(const Weapon::BulletData& data)
{
	auto bullet = std::make_shared<Bullet>(wielder->getActorID(), bulletShader, data.origin, data.direction, bulletSpeed, bulletDrop, bulletSize);
	bullet->addComponent(bulletSprite);
	bullet->addPhysicsComponent(std::make_shared<PhysicsComponent>(PhysicsComponentFactory::makeBullet(wielder->getActorID(), data.origin, data.mass, bulletSize.x / 2)));
	bullet->getPhysicsComponent()->rigidBody.velocity += bulletSpeed * glm::vec3(data.direction.x, data.direction.y, 0.f) / data.mass;

	if (eventManager)
		eventManager->notify(std::make_shared<BulletFiredEvent>(bullet));
	return bullet;
}

