#include "gameplay/map.h"
#include "gameplay/camera.h"
#include "graphics/shader.h"
#include "graphics/texture.h"
#include "utility/xmlloader.h"
#include "utility/resourcemanager.h"
#include "utility/logger.h"

#include <glm/gtc/matrix_transform.hpp>

Map::Map(std::shared_ptr<MapData> mapData, const unsigned shaderID, std::shared_ptr<ResourceManager> resourceManager) :
	mapData(mapData),
	tileIds(mapData->tiles)
{
	this->shaderID = shaderID;

	for (auto& tileSet : mapData->tileSets)
		tileSetData.push_back(resourceManager->loadTileSet(tileSet.path));

	if (!tileSetData.empty())
	{
		std::vector<const char*> buffer;
		for (int layer = 0; layer < tileIds.size(); layer++)
		{
			buffer.clear();
			for (auto& set : tileSetData)
				buffer.push_back(set->file.c_str());
			if (!buffer.empty())
				instanceHandles.push_back(std::make_shared<TileTextureInstance>(buffer));
		}

		loadMap();
		createCollisionMap();
	}

}
#include <glm/ext.hpp>
void Map::loadMap()
{
	tileData.resize(tileIds.size());
	for (int layer = 0; layer < tileIds.size(); layer++)
	{
		for (int y = 0; y < tileIds[layer].size(); y++)
		{
			for (int x = 0; x < tileIds[layer][y].size(); x++)
			{
				glm::mat4 modelMatrix =
					glm::translate(glm::mat4(1.f), glm::vec3(x * mapData->tileSize, y * mapData->tileSize, 0.0f)) *
					glm::scale(glm::mat4(1.f), glm::vec3(mapData->tileSize, mapData->tileSize, 1.0f));

				int textureIndex = getTileSetIndex(tileIds[layer][y][x]);
				glm::vec2 originalSize = (textureIndex != -1) ?
					glm::vec2(tileSetData[textureIndex]->width, tileSetData[textureIndex]->height) :
					glm::vec2(0.0f);
				int tilesPerRow = (textureIndex != -1) ? tileSetData[textureIndex]->columns : 0;
				int startID = (textureIndex != -1) ? mapData->tileSets[textureIndex].startID : 0;
				int tileIndex = tileIds[layer][y][x];

				tileData[layer].push_back({modelMatrix, originalSize, tileIndex, textureIndex, tilesPerRow, startID});
			}
		}
		instanceHandles[layer]->updateInstanceData(tileData[layer]);
	}
	glm::vec2 canvasSize = glm::vec2(instanceHandles[0]->getTextureArray()->getWidth(), instanceHandles[0]->getTextureArray()->getHeight());
	editUniformOnce("uCanvasSize", canvasSize);
}

void Map::createCollisionMap()
{
	// Match collisionMap to map size
	collisionMap.resize(tileIds[0].size());
	for (int y = 0; y < tileIds[0].size(); ++y)
	{
		collisionMap[y].resize(tileIds[0][y].size(), 0);
	}

	for (int layer = 0; layer < tileIds.size(); layer++)
	{
		for (int y = 0; y < tileIds[layer].size(); y++)
		{
			for (int x = 0; x < tileIds[layer][y].size(); x++)
			{
				int id = tileIds[layer][y][x];
				int tileSetIndex = getTileSetIndex(id);
				if (tileSetIndex == -1)
					collisionMap[y][x] = 0;
				else
				{
					int startID = mapData->tileSets[tileSetIndex].startID;
					auto& tile = tileSetData[tileSetIndex]->tiles[id - startID];
					if (!tile->walkable)
						collisionMap[y][x] = 1;
				}
			}
		}
	}
}

void Map::draw()
{
	for (int layer = 0; layer < instanceHandles.size(); layer++)
	{
		instanceHandles[layer]->draw();
	}
}

/* 
Use this function to get the tileSetIndex from a tile ID. 
the index of the tileSet is the same index as the texture index used in the instanceHandle

returns tileSetIndex of the tile id passed. Unless the id is either 0 or 
the returned tileSetIndex is out of bounds, returns -1
*/
int Map::getTileSetIndex(int id) const
{
	// work from the bottom, break if ID > startID
	// If we get a textureIndex of -1 then we are on an empty tile
	size_t tileSetIndex = mapData->tileSets.size() - 1;
	for (; tileSetIndex != -1; --tileSetIndex)
	{
		if (id >= mapData->tileSets[tileSetIndex].startID)
			break;
	}
	return (tileSetIndex >= mapData->tileSets.size()) ? -1 : tileSetIndex;
}