#include "graphics/animation.h"
#include "utility/xmlloader.h"
#include "utility/resourcemanager.h"
#include "utility/events.h"
#include "utility/logger.h"
#include "util.h"

AnimationSystem::AnimationSystem(std::weak_ptr<ResourceManager> resMan, std::weak_ptr<EventManager> eventMan)
{
	eventManager = eventMan;
	resourceManager = resMan;
}

/* TODO: UPDATES AND DRAWS, maybe remove the draw entirely and forward specific frame data to the renderer?? */

void AnimationSystem::update()
{
	
}

bool AnimationSystem::registerComponent(const int entityID, const std::string& animID)
{
	if (auto res = resourceManager.lock()) {
		auto animSet = res->loadAnimationSet(animID);
		if (animSet.empty()) {
			LOG(ERROR, "No animations with prefix '{}' found!", animID);
			return false;
		}
		auto animComp = std::make_unique<AnimationComponent>();
		animComp->entityID = entityID;
		for (const auto& data : animSet) {
			auto atlas = res->loadSpriteAtlas(data->id, data->frameSize);
			auto type = UTIL::get_type(data->id);
			if (atlas == nullptr) continue;

			std::unique_ptr<Animation> anim = std::make_unique<Animation>();
			anim->spriteAtlas = atlas;
			anim->data = data;
			anim->playing = false;
			anim->FPS = data->FPS;

			animComp->anims[type] = std::move(anim);
			if (type == "idle") {
				// Given this entity has an idle animation, we will have the entity animate off the hop.
				animComp->curAnim->playing = true;
				animComp->curAnim = animComp->anims[type].get();
			}
		}
		// If the set does not contain an idle animation we will just set the current animation to the top of the set
		if (animComp->curAnim == nullptr) {
			animComp->curAnim = animComp->anims[UTIL::get_type(animSet[0]->id)].get();
			if (animComp->curAnim == nullptr) {
				LOG(ERROR, "Could not load any sprites for animation set '{}'", animID);
				return false;
			}
		}
		animComponents.push_back(std::move(animComp));
		registerEvents(animComponents.back().get());
	} else {
		LOG(ERROR, "Could not obtain resource manager!", NULL);
		return false;
	}

	return true;
}

void AnimationSystem::registerEvents(AnimationComponent *comp)
{
	if (auto event = eventManager.lock()) {

		event->subscribe<DirectionChangeEvent>([comp](const DirectionChangeEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID) {
					Direction d = e.direction;
					comp->curAnim->facingDir = d;
				}
			}
		});

		event->subscribe<EntityMoveEvent>([comp](const EntityMoveEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID)
				{
					if (comp->curAnim->data->directional)
					{
						if (comp->anims["move"] != NULL)
							comp->curAnim = comp->anims["move"].get();
					}
					else
						comp->curAnim->playing = true;
				}
			}
		});

		event->subscribe<EntityStopEvent>([comp](const EntityStopEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID)
				{
					if (comp->curAnim->data->directional)
					{
						if (comp->anims["idle"] != NULL)
							comp->curAnim = comp->anims["idle"].get();
					}
					else
						comp->curAnim->playing = false;
				}
			}
		});

		event->subscribe<EntityReloadEvent>([comp](const EntityReloadEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID)
				{
					if (comp->anims["reload"] != NULL)
					{
						comp->curAnim->reset();
						comp->curAnim = comp->anims["reload"].get();
						comp->curAnim->reset();
					}
				}
			}
		});

		event->subscribe<EntityFinishReloadEvent>([comp](const EntityFinishReloadEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID)
				{
					if (comp->anims["idle"] != NULL) {
						comp->curAnim->reset();
						comp->curAnim = comp->anims["idle"].get();
						comp->curAnim->reset();
					}
				}
			}
		});

		event->subscribe<EntityFireEvent>([comp](const EntityFireEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID)
				{
					if (comp->anims["fire"] != NULL)
					{
						comp->curAnim = comp->anims["fire"].get();
						comp->curAnim->reset();
						float newFPS = (1000.f / e.fireDelay) * 15.f;
						comp->curAnim->FPS = newFPS;
					}
				}
			}
		});

		event->subscribe<AnimationFinishedEvent>([comp](const AnimationFinishedEvent& e) {
			if (comp) {
				if (e.entityid == comp->entityID && e.animType == "fire")
				{
					if (comp->anims["idle"] != NULL)
						comp->curAnim = comp->anims["idle"].get();
				}
			}
		});
}

Animation::Animation(const AnimationData* animData, ResourceManager* resourceManager) :
	spriteAtlas(resourceManager->loadSpriteAtlas(animData->spriteAtlas, animData->frameSize, animData->directional)),
	FPS(animData->FPS),
	currentFrame(0),
	cycles(0),
	isDirectional(animData->directional),
	isPlaying(isDirectional)
{
	prefix = UTIL::parsePrefix(animData->id);
	type = UTIL::get_type(animData->id);
}

void Animation::bind()
{
	spriteAtlas->bind();
}

void Animation::draw()
{
	singleDraw();
}

void Animation::draw(Direction dir)
{
	directionalDraw(dir);
}

void Animation::singleDraw()
{
	if (isPlaying)
	{
		frameTick();
		auto frame = spriteAtlas->frame(currentFrame);
		spriteAtlas->bindFrame(&frame);
		spriteAtlas->draw();
	}
	else
	{
		auto frame = spriteAtlas->frame(0);
		spriteAtlas->bindFrame(&frame);
		spriteAtlas->draw();
	}
}

void Animation::directionalDraw(Direction dir)
{
	if (isPlaying)
	{
		frameTick();
	}
	auto frame = spriteAtlas->frame(currentFrame, dir);
	spriteAtlas->bindFrame(&frame);
	spriteAtlas->draw();
}

void Animation::frameTick()
{
	Uint32 currentTime = SDL_GetTicks();
	elapsedTime = currentTime - lastFrameTick;
	if (elapsedTime >= 1000.0f / FPS)
	{
		if (++currentFrame > spriteAtlas->size() - 1)
		{
			currentFrame = 0;
			cycles += 1;
		}
		lastFrameTick = currentTime;
	}
}

AnimationSet::AnimationSet(const int& entityid) : entityid(entityid), isDirectional(false) {};
AnimationSet::AnimationSet(const int& entityid, ResourceManager* resourceManager, std::vector<AnimationData*> animSet) : entityid(entityid)
{
	curAnim = nullptr;
	for (const auto& anim : animSet)
	{
		std::string type = UTIL::get_type(anim->id);
		anims.try_emplace(type, std::make_unique<Animation>(anim, resourceManager));
		if (type == "idle")
			curAnim = anims[type].get();
	}
	// if we don't have an idle animation, we are just gonna set the current animation to the top of the set
	if (curAnim == nullptr)
		curAnim = anims[UTIL::get_type(animSet[0]->id)].get();
	isDirectional = curAnim->getDirectional();
}
AnimationSet::AnimationSet(const int& entityid, std::unordered_map<std::string, std::unique_ptr<Animation>> animations) 
: entityid(entityid), anims(std::move(animations))
{
	for (auto& [type, anim] : anims)
	{
		curAnim = anim.get();
		if (type == "idle")
		{
			break;
		}
	}
	isDirectional = curAnim->getDirectional();
}

void AnimationSet::attachEventManager(std::weak_ptr<EventManager> e)
{
	eventManager = e;

	if (auto event = eventManager.lock()) {
		std::weak_ptr<AnimationSet> weakSelf = shared_from_this();

		event->subscribe<DirectionChangeEvent>([weakSelf](const DirectionChangeEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid) {
					Direction d = e.direction;
					self->setFacingDir(d);
				}
			}
		});

		event->subscribe<EntityMoveEvent>([weakSelf](const EntityMoveEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid)
				{
					if (self->isDirectional)
					{
						if (self->anims["move"] != NULL)
							self->curAnim = self->anims["move"].get();
					}
					else
						self->play();
				}
			}
		});

		event->subscribe<EntityStopEvent>([weakSelf](const EntityStopEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid)
				{
					if (self->isDirectional)
					{
						if (self->anims["idle"] != NULL)
							self->curAnim = self->anims["idle"].get();
					}
					else
						self->stop();
				}
			}
		});

		event->subscribe<EntityReloadEvent>([weakSelf](const EntityReloadEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid)
				{
					if (self->anims["reload"] != NULL)
					{
						self->curAnim->reset();
						self->curAnim = self->anims["reload"].get();
					}
				}
			}
		});

		event->subscribe<EntityFinishReloadEvent>([weakSelf](const EntityFinishReloadEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid)
				{
					if (self->anims["idle"] != NULL) {
						self->curAnim->reset();
						self->curAnim = self->anims["idle"].get();
					}
				}
			}
		});

		event->subscribe<EntityFireEvent>([weakSelf](const EntityFireEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid)
				{
					if (self->anims["fire"] != NULL)
					{
						self->curAnim = self->anims["fire"].get();
						self->curAnim->reset();
						float newFPS = (1000.f / e.fireDelay) * 15.f;
						self->curAnim->setFPS(newFPS);
					}
				}
			}
		});

		event->subscribe<AnimationFinishedEvent>([weakSelf](const AnimationFinishedEvent& e) {
			if (auto self = weakSelf.lock()) {
				if (e.entityid == self->entityid && e.animType == "fire")
				{
					if (self->anims["idle"] != NULL)
						self->curAnim = self->anims["idle"].get();
				}
			}
		});
	}
}

void AnimationSet::bind()
{
	curAnim->bind();
}

void AnimationSet::draw()
{
	int lastCycle = curAnim->getCycles();
	if (curAnim->getDirectional())
		curAnim->draw(facing);
	else
		curAnim->draw();

	// Sending animation events
	// If the animation has cycled, we send this event after every cycle
	if ((curAnim->getCycles() - lastCycle) > 0) {
		if (auto event = eventManager.lock()) {
			event->notify<AnimationFinishedEvent>({entityid, curAnim->getType()});
		}
	}
}

