#include "graphics/sprite.h"
#include "graphics/texture.h"
#include "util.h"
#include "utility/events.h"

SpriteStatic::SpriteStatic(const char* texturePath)
{
	EBO = 0, VBO = 0, VAO = 0;
	texture = new Texture();
	texture->loadTexture(texturePath);
	if (texture)
		setupSprite();
}

void SpriteStatic::setupSprite()
{
	// Assigning vertex data
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);

	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// Position
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	// Texture Coordinates
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

void Sprite::bind()
{
	if (texture)
		texture->bind();
}

void SpriteStatic::draw()
{
	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
}

SpriteStatic::~SpriteStatic()
{
	delete texture;
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glDeleteVertexArrays(1, &VAO);
}

SpriteAnimated::SpriteAnimated(const char* textureAtlasPath, float frameSize)
{
	EBO = 0;
	texture = new Texture();
	texture->loadTexture(textureAtlasPath);
	if (texture)
		SetupAnimated(frameSize);
}

void SpriteAnimated::SetupAnimated(float frameSize)
{
	int width  = texture->getWidth();
	int height = texture->getHeight();
	float frameRows = height / frameSize;
	float frameCols = width  / frameSize;

	glGenBuffers(1, &EBO);

	for (int row = 0; row < frameRows; row++)
	{
		for (int column = 0; column < frameCols; column++)
		{
			float left    = (column) * (frameSize / width);
			float right   = (column + 1) * (frameSize / width);
			float bottom  = (row) * (frameSize / height);
			float top	  = (row + 1) * (frameSize / height);
			float vertices[] = {
				0.0f, 0.0f, 0.0f, left, bottom,		  // bottom left
				1.0f, 0.0f, 0.0f, right, bottom, // bottom right
				1.0f, 1.0f, 0.0f, right, top, // top right
				0.0f, 1.0f, 0.0f, left, top		  // top left
			};

			unsigned VAO, VBO;
			glGenVertexArrays(1, &VAO);
			glGenBuffers(1, &VBO);

			glBindVertexArray(VAO);
			glBindBuffer(GL_ARRAY_BUFFER, VBO);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
			
			glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
			glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
			glEnableVertexAttribArray(0);
			glEnableVertexAttribArray(1);

			glBindBuffer(GL_ARRAY_BUFFER, 0);
			glBindVertexArray(0);

			ids.push_back({ VAO, VBO });
		}
	}
}

void SpriteAnimated::draw()
{
	if (isPlaying)
	{
		Uint32 currentTime = SDL_GetTicks();
		if (currentTime - lastFrameTick >= 1000.f / FPS)
		{
			if (++currentFrame > ids.size() - 1)
				currentFrame = 0;
			lastFrameTick = currentTime;
		}
		glBindVertexArray(ids[currentFrame].VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
	}
	else
	{
		glBindVertexArray(ids[0].VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
	}
}

void SpriteAnimated::play() { isPlaying = true; }
void SpriteAnimated::idle() { isPlaying = false; }

SpriteAnimated::~SpriteAnimated()
{
	delete texture;
	glDeleteBuffers(1, &EBO);
	for (VertexIDs idSet : ids)
	{
		glDeleteBuffers(1, &idSet.VBO);
		glDeleteVertexArrays(1, &idSet.VAO);
	}
}

SpriteDirectionalAnimated::SpriteDirectionalAnimated(const char* textureAtlas, float frameSize)
{
	texture = new Texture();
	texture->loadTexture(textureAtlas);
	if (texture)
		Setup(frameSize);
}

void SpriteDirectionalAnimated::Setup(float frameSize)
{
	int width = texture->getWidth();
	int height = texture->getHeight();
	int frameRows = height / frameSize;
	int frameCols = width / frameSize;

	glGenBuffers(1, &EBO);

	for (int row = 0; row < frameRows; row++)
	{
		for (int col = 0; col < frameCols; col++)
		{
			Direction dir;
			// top of the spritesheet is the entities idle directions
			// then each row is a 4 frame walk cycle of each direction
			if (row == 0) dir = (Direction)(col + 1);
			else dir = (Direction)(row);

			float left		= (col) * (frameSize / width);
			float right		= (col + 1) * (frameSize / width);
			float bottom	= (row) * (frameSize / height);
			float top		= (row + 1) * (frameSize / height);
			float vertices[] = {
				0.0f, 0.0f, 0.0f, left, bottom,		  // bottom left
				1.0f, 0.0f, 0.0f, right, bottom, // bottom right
				1.0f, 1.0f, 0.0f, right, top, // top right
				0.0f, 1.0f, 0.0f, left, top		  // top left
			};

			unsigned VAO, VBO;
			glGenVertexArrays(1, &VAO);
			glGenBuffers(1, &VBO);

			glBindVertexArray(VAO);
			glBindBuffer(GL_ARRAY_BUFFER, VBO);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

			glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
			glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
			glEnableVertexAttribArray(0);
			glEnableVertexAttribArray(1);

			glBindBuffer(GL_ARRAY_BUFFER, 0);
			glBindVertexArray(0);

			if (row == 0)
				idleIDs.emplace(std::pair<Direction, VertexIDs>(dir, { VAO, VBO }));
			else
				walkingIDs[dir].push_back({ VAO, VBO });
		}
	}
}

void SpriteDirectionalAnimated::draw()
{
	if (isPlaying)
	{
		Uint32 currentTime = SDL_GetTicks();
		if (currentTime - lastFrameTick >= 1000.f / FPS)
		{
			if (++currentFrame > walkingIDs[direction].size() - 1)
				currentFrame = 0;
			lastFrameTick = currentTime;
		}
		glBindVertexArray(walkingIDs[direction][currentFrame].VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
	}
	else
	{
		glBindVertexArray(idleIDs[direction].VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
	}
}

void SpriteDirectionalAnimated::play() { isPlaying = true; }
void SpriteDirectionalAnimated::idle() { isPlaying = false; }

SpriteDirectionalAnimated::~SpriteDirectionalAnimated()
{
	delete texture;
	glDeleteBuffers(1, &EBO);
	auto deleteBuffers = [](std::pair<Direction, VertexIDs> item) {
		glDeleteBuffers(1, &item.second.VBO);
		glDeleteVertexArrays(1, &item.second.VAO);
	};
	auto deleteBufferList = [](std::pair <Direction, std::vector<VertexIDs>> items) {
		for (VertexIDs v : items.second) {
			glDeleteBuffers(1, &v.VBO);
			glDeleteVertexArrays(1, &v.VAO);
		}
	};
	std::for_each(idleIDs.begin(), idleIDs.end(), deleteBuffers);
	std::for_each(walkingIDs.begin(), walkingIDs.end(), deleteBufferList);
	idleIDs.clear();
	walkingIDs.clear();
}
