#include "LevelMap.h"

#include <iostream>

LevelMap::LevelMap()
{

}

LevelMap::~LevelMap()
{

}

void LevelMap::Compile(std::string level_map_code)
{
	std::stringstream level_map_stream(level_map_code);
	std::string line;

	bool in_sector = false, in_wall = false;

	unsigned int sector_index = 0;
	unsigned int wall_index = 0;

	while (std::getline(level_map_stream, line))
	{
		if (line.empty() || line[0] == '#') continue;

		if (line.find("[SECTOR]") != std::string::npos)
		{
			in_sector = true;
			in_wall = false;
			continue;
		}

		if (line.find("[WALL]") != std::string::npos)
		{
			in_sector = false;
			in_wall = true;
			continue;
		}

		if (in_sector)
		{
			Sector sector = ParseSector(line);
			sectors.push_back(sector);
		}

		if (in_wall && !sectors.empty())
		{
			Wall wall = ParseWall(line);

			if (wall_index >= sectors[sector_index].number_of_walls)
			{
				sector_index++;
				wall_index = 0;
			}

			sectors[sector_index].walls.push_back(wall);

			wall_index++;
		}
	}

	wall_index = 0;
	unsigned int floor_index = 0;
	unsigned int ceiling_index = 0;
	for (Sector& sector : sectors)
	{
		std::vector<glm::vec3> floor_vertices;
		std::vector<glm::vec3> ceiling_vertices;

		for (Wall& wall : sector.walls)
		{
			glm::vec3 floor_vertex(wall.a.x, sector.floor_height, wall.a.y);
			glm::vec3 ceiling_vertex(wall.a.x, sector.ceiling_height, wall.a.y);

			floor_vertices.push_back(floor_vertex);
			ceiling_vertices.push_back(ceiling_vertex);

			AddWall(sector, wall, wall_index);
		}

		AddFloor(sector, floor_vertices);
		AddCeiling(sector, ceiling_vertices);
		//AddFloorOrCeiling(sector, floor_vertices, true);
		//AddFloorOrCeiling(sector, ceiling_vertices, false);
	}
}

void LevelMap::RenderSetup
(
	Shader *wall_shader, 
	Shader *floor_shader, 
	Shader *ceiling_shader
)
{
	wall_renderer.Setup(wall_vertices, wall_normals, wall_colors, wall_indices, wall_shader);
	floor_renderer.Setup(floor_vertices, floor_normals, floor_colors, floor_indices, floor_shader);
	ceiling_renderer.Setup(ceiling_vertices, ceiling_normals, ceiling_colors, ceiling_indices, ceiling_shader);
}

void LevelMap::Update(glm::mat4 projection, glm::mat4 view, glm::vec3 light_position, glm::vec3 light_color, glm::vec3 view_position)
{
	wall_renderer.Render(projection, view, light_position, light_color, view_position);
	floor_renderer.Render(projection, view, light_position, light_color, view_position);
	ceiling_renderer.Render(projection, view, light_position, light_color, view_position);
}

std::vector<MeshRenderer*> LevelMap::GetMeshes()
{
	return { &wall_renderer, &floor_renderer, &ceiling_renderer };
}

Sector LevelMap::ParseSector(std::string& line)
{
	Sector sector;
	std::stringstream ss(line);
	ss >> sector.index >> sector.number_of_walls >> sector.floor_height >> sector.ceiling_height >> sector.outward_facing >> sector.color.x >> sector.color.y >> sector.color.z;

	/*static std::random_device rd;  // Obtain a random number from hardware
	static std::mt19937 eng(rd()); // Seed the generator
	std::uniform_real_distribution<float> distr(0.0f, 1.0f); // Define the range

	glm::vec3 randomColor(distr(eng), distr(eng), distr(eng));*/

	//sector.color = glm::vec3(sector.index+1 / 5.0f, 0.0f, sector.index+1 / 7.0f);

	return sector;
}

Wall LevelMap::ParseWall(std::string& line)
{
	Wall wall;
	std::stringstream ss(line);
	ss >> wall.a.x >> wall.a.y >> wall.b.x >> wall.b.y >> wall.portal;
	return wall;
}

void LevelMap::AddWall(Sector& sector, Wall& wall, unsigned int& wall_index)
{
	glm::vec3 v0(wall.a.x, sector.floor_height, wall.a.y);
	glm::vec3 v1(wall.a.x, sector.ceiling_height, wall.a.y);
	glm::vec3 v2(wall.b.x, sector.ceiling_height, wall.b.y);
	glm::vec3 v3(wall.b.x, sector.floor_height, wall.b.y);

	glm::vec3 normal = glm::normalize(glm::cross(v1 - v0, v3 - v1));
	normal = sector.outward_facing ? -normal : normal;

	if (sector.outward_facing)
	{
		std::swap(v0, v3);
		std::swap(v1, v2);
	}

	if (wall.portal == 0)
	{
		AddVertices({ v0, v1, v2, v3 }, wall_vertices);
		AddNormals({ normal, normal, normal, normal }, wall_normals);
		AddColors({ sector.color, sector.color, sector.color, sector.color }, wall_colors);
		AddIndices({ wall_index, wall_index + 1, wall_index + 2, wall_index, wall_index + 2, wall_index + 3 }, wall_indices);
		wall_index += 4;
	}
	else
	{
		Sector& portal_sector = sectors[wall.portal - 1];

		if (portal_sector.floor_height > sector.floor_height)
		{
			v1.y = portal_sector.floor_height;
			v2.y = portal_sector.floor_height;

			AddVertices({ v0, v1, v2, v3 }, wall_vertices);
			AddNormals({ normal, normal, normal, normal }, wall_normals);
			AddColors({ sector.color, sector.color, sector.color, sector.color }, wall_colors);
			AddIndices({ wall_index, wall_index + 1, wall_index + 2, wall_index, wall_index + 2, wall_index + 3 }, wall_indices);
			wall_index += 4;
		}

		if (portal_sector.ceiling_height < sector.ceiling_height)
		{
			v0.y = portal_sector.ceiling_height;
			v1.y = sector.ceiling_height;
			v2.y = sector.ceiling_height;
			v3.y = portal_sector.ceiling_height;;

			AddVertices({ v0, v1, v2, v3 }, wall_vertices);
			AddNormals({ normal, normal, normal, normal }, wall_normals);
			AddColors({ sector.color, sector.color, sector.color, sector.color }, wall_colors);
			AddIndices({ wall_index, wall_index + 1, wall_index + 2, wall_index, wall_index + 2, wall_index + 3 }, wall_indices);
			wall_index += 4;
		}
	}
}

void LevelMap::AddFloor(Sector& sector, std::vector<glm::vec3> vertices)
{
	unsigned int starting_index = sector.index;
	unsigned int _index = starting_index + 1;
	glm::vec3 normal = glm::vec3(0.0f, 1.0f, 0.0f);

	for (int i = vertices.size() - 1; i >= 0; i--)
	{
		AddVertices({ vertices[i] }, floor_vertices);
		AddNormals({ normal }, floor_normals);
		AddColors({ glm::vec3(1.0f) - sector.color }, floor_colors);
		if (_index + 1 < sector.index + sector.number_of_walls)
		{
			unsigned int next_index = _index + 1;
			AddIndices({ starting_index, _index, next_index }, floor_indices);
			_index++;
		}
	}
}

void LevelMap::AddCeiling(Sector& sector, std::vector<glm::vec3> vertices)
{
	unsigned int starting_index = sector.index;
	unsigned int _index = starting_index + 1;
	glm::vec3 normal = glm::vec3(0.0f, 1.0f, 0.0f);
	normal = -normal;

	for (int i = 0; i < vertices.size(); i++)
	{
		AddVertices({ vertices[i] }, ceiling_vertices);
		AddNormals({ normal }, ceiling_normals);
		AddColors({ glm::vec3(1.0f) - sector.color }, ceiling_colors);
		if ( _index + 1 < sector.index + sector.number_of_walls)
		{
			unsigned int next_index = _index + 1;
			AddIndices({ starting_index, _index, next_index }, ceiling_indices);
			_index++;
		}
	}
}

void LevelMap::AddVertices(std::vector<glm::vec3> vertices, std::vector<float>& target_vertices)
{
	for (unsigned int i = 0; i < vertices.size(); i += 1) 
	{
		target_vertices.push_back(vertices[i].x);
		target_vertices.push_back(vertices[i].y);
		target_vertices.push_back(vertices[i].z);
	}
}

void LevelMap::AddNormals(std::vector<glm::vec3> normals, std::vector<float>& target_normals)
{
	for (unsigned int i = 0; i < normals.size(); i += 1)
	{
		target_normals.push_back(normals[i].x);
		target_normals.push_back(normals[i].y);
		target_normals.push_back(normals[i].z);
	}
}

void LevelMap::AddColors(std::vector<glm::vec3> colors, std::vector<float>& target_colors)
{
	for (unsigned int i = 0; i < colors.size(); i += 1)
	{
		target_colors.push_back(colors[i].x);
		target_colors.push_back(colors[i].y);
		target_colors.push_back(colors[i].z);
	}
}

void LevelMap::AddIndices(std::vector<unsigned int> indices, std::vector<unsigned int>& target_indices)
{
	for (unsigned int i = 0; i < indices.size(); i += 1)
	{
		target_indices.push_back(indices[i]);
	}
}


