#include "graphics/instancedraw.h"
#include "graphics/texture.h"

TileTextureInstance::TileTextureInstance(const char* texturePath)
{
	texture = new Texture();
	texture->loadTexture(texturePath);
	if (texture)
		setup();
}

TileTextureInstance::TileTextureInstance(const std::vector<const char*> texturePaths)
{
	textures = new TextureArray();
	if (textures->loadTextures(texturePaths))
		setup();
}

void TileTextureInstance::setup()
{
	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);

	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);

	glGenBuffers(1, &instanceVBO);
	glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
	
	glBufferData(GL_ARRAY_BUFFER, sizeof(InstanceData) * MAX_INSTANCES, nullptr, GL_DYNAMIC_DRAW);

	// tileIndex instanceData layout position 2
	glVertexAttribIPointer(2, 1, GL_INT, sizeof(InstanceData), (void*)(offsetof(InstanceData, tileIndex)));
	glEnableVertexAttribArray(2);
	glVertexAttribDivisor(2, 1);

	// textureIndex instanceData layout position 3 -> We are using multiple textures to hold each tileset
	glVertexAttribIPointer(3, 1, GL_INT, sizeof(InstanceData), (void*)(offsetof(InstanceData, textureIndex)));
	glEnableVertexAttribArray(3);
	glVertexAttribDivisor(3, 1);

	// tilesPerRow instanceData layout position 4, This is needed since we are supporting multiple tilesets
	// of different sizes
	glVertexAttribIPointer(4, 1, GL_INT, sizeof(InstanceData), (void*)(offsetof(InstanceData, tilesPerRow)));
	glEnableVertexAttribArray(4);
	glVertexAttribDivisor(4, 1);

	// startID instanceData layout position 5
	glVertexAttribIPointer(5, 1, GL_INT, sizeof(InstanceData), (void*)(offsetof(InstanceData, startID)));
	glEnableVertexAttribArray(5);
	glVertexAttribDivisor(5, 1);

	// originalSize instanceData layout position 6
	glVertexAttribPointer(6, 2, GL_FLOAT, GL_FALSE, sizeof(InstanceData), (void*)(offsetof(InstanceData, originalSize)));
	glEnableVertexAttribArray(6);
	glVertexAttribDivisor(6, 1);

	// modelMatrix instanceData as 4 vec 4s (a 4x4 matrix) layout head position 7
	for (int i = 0; i < 4; i++)
	{
		glVertexAttribPointer(7 + i, 4, GL_FLOAT, GL_FALSE, sizeof(InstanceData), (void*)(offsetof(InstanceData, modelMatrix) + (sizeof(glm::vec4) * i)));
		glEnableVertexAttribArray(7 + i);
		glVertexAttribDivisor(7 + i, 1);
	}
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

void TileTextureInstance::updateInstanceData(const std::vector<InstanceData>& instanceData)
{
	if (instanceData.empty())
		return;
	numOfInstances = instanceData.size();
	glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);

	InstanceData* instances = (InstanceData*)glMapBufferRange(GL_ARRAY_BUFFER, 0,
		sizeof(InstanceData) * instanceData.size(),
		GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
	if (!instances)
		return;

	if (instances)
	{
		std::memcpy(instances, instanceData.data(), sizeof(InstanceData) * instanceData.size());
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	/*
	for (int i = 0; i < instanceData.size(); i++)
	{
		instances[i].modelMatrix = instanceData[i].modelMatrix;
		instances[i].originalSize = instanceData[i].originalSize;
		instances[i].tileIndex = instanceData[i].tileIndex;
		instances[i].textureIndex = instanceData[i].textureIndex;
		instances[i].tilesPerRow = instanceData[i].tilesPerRow;
		instances[i].startID = instanceData[i].startID;
	}
	*/

	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void TileTextureInstance::draw()
{
	// bind textures
	if (texture)
		texture->bind();
	else if (textures)
		textures->bind();

	glBindVertexArray(VAO);
	glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr, numOfInstances);
	glBindVertexArray(0);
}

TileTextureInstance::~TileTextureInstance()
{
	if (texture)
		delete texture;
	else if (textures)
		delete textures;
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glDeleteVertexArrays(1, &VAO);
}
