#pragma once

#ifndef MAP_READER_H
#define MAP_READER_H

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <random>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "Shader.h"

struct Wall
{
	glm::vec2 a, b;
	int portal;
};

struct Sector
{
	int id;
	int index;
	int numberOfWalls;
	float floorHeight;
	float ceilingHeight;

	bool outwardFacing;

	glm::vec3 color;

	std::vector<Wall> walls;
};

class Map
{
public:
	Map(std::string filename) : wallShader(Shader("wall_test.vert", "wall_test.frag")), floorShader(Shader("wall_test.vert", "wall_test.frag"))
	{
		parseMapFile(filename);
		setupBuffers();
	}

	void render(glm::mat4 projection, glm::mat4 view, glm::vec3 lightPos)
	{
		wallShader.use();
		wallShader.setMat4("projection", projection);
		wallShader.setMat4("view", view);

		glm::mat4 model = glm::mat4(1.0f);
		wallShader.setMat4("model", model);
		//shader.setVec3("objectColor", 1.0f, 0.0f, 0.0f);
		wallShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		wallShader.setVec3("lightPos", lightPos);

		glBindVertexArray(wallVAO);

		// Use glDrawElements since we are using indices
		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, wallIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);

		floorShader.use();
		floorShader.setMat4("projection", projection);
		floorShader.setMat4("view", view);

		model = glm::mat4(1.0f);
		floorShader.setMat4("model", model);
		floorShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		floorShader.setVec3("lightPos", lightPos);

		glBindVertexArray(floorVAO);

		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, floorIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);
	}

	void render(glm::mat4 projection, glm::mat4 view, glm::vec3 lightPos, float deltaTime, glm::vec3 resolution, float far_plane)
	{
		time += deltaTime;

		wallShader.use();
		wallShader.setMat4("projection", projection);
		wallShader.setMat4("view", view);
		wallShader.setVec3("iResolution", resolution);
		wallShader.setFloat("iTime", time);
		wallShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		wallShader.setVec3("lightPos", lightPos);
		wallShader.setInt("shadows", 1);
		wallShader.setFloat("far_plane", far_plane);
		wallShader.setInt("diffuseTexture", 0);
		wallShader.setInt("shadowMap", 1);

		glm::mat4 model = glm::mat4(1.0f);
		wallShader.setMat4("model", model);
		//shader.setVec3("objectColor", 1.0f, 0.0f, 0.0f);

		glBindVertexArray(wallVAO);

		// Use glDrawElements since we are using indices
		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, wallIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);

		floorShader.use();
		floorShader.setMat4("projection", projection);
		floorShader.setMat4("view", view);
		floorShader.setVec3("iResolution", resolution);
		floorShader.setFloat("iTime", time);
		floorShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		floorShader.setVec3("lightPos", lightPos);
		floorShader.setInt("shadows", 1);
		floorShader.setFloat("far_plane", far_plane);
		floorShader.setInt("diffuseTexture", 0);
		floorShader.setInt("shadowMap", 1);

		model = glm::mat4(1.0f);
		floorShader.setMat4("model", model);

		glBindVertexArray(floorVAO);

		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, floorIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);
	}

	void render(glm::mat4 projection, glm::mat4 view, glm::vec3 lightPos, Shader _shader)
	{
		glm::mat4 model = glm::mat4(1.0f);
		_shader.setMat4("model", model);

		glBindVertexArray(wallVAO);

		// Use glDrawElements since we are using indices
		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, wallIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);

		glBindVertexArray(floorVAO);

		//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, floorIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);

		/*floorShader.use();
		floorShader.setMat4("projection", projection);
		floorShader.setMat4("view", view);

		model = glm::mat4(1.0f);
		floorShader.setMat4("model", model);
		floorShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		floorShader.setVec3("lightPos", lightPos);

		glBindVertexArray(floorVAO);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDrawElements(GL_TRIANGLES, floorIndices.size(), GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);*/
	}
private:
	std::vector<Sector> sectors;

	unsigned int wallVAO;
	unsigned int wallVBO, wallEBO, wallNBO, wallCBO;
	unsigned int floorVAO;
	unsigned int floorVBO, floorEBO, floorNBO, floorCBO;

	std::vector<float> wallVertices, wallNormals, wallColors;
	std::vector<unsigned int> wallIndices;

	std::vector<float> floorVertices, floorNormals, floorColors;
	std::vector<unsigned int> floorIndices;

	Shader wallShader;

	Shader floorShader;

	float time;

	Sector parseSector(std::string& line)
	{
		Sector sector;
		std::stringstream ss(line);
		ss >> sector.id >> sector.index >> sector.numberOfWalls >> sector.floorHeight >> sector.ceilingHeight >> sector.outwardFacing;

		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 = randomColor;

		return sector;
	}

	Wall 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 parseMapFile(std::string filename)
	{
		//std::cout << "Parsing map file: " << filename << std::endl;
		std::ifstream file(filename);
		std::string line;

		bool inSector = false, inWall = false;
		while (std::getline(file, line))
		{
			//std::cout << "Line: " << line << std::endl;
			if (line.empty() || line[0] == '#') continue;

			if (line.find("[SECTOR]") != std::string::npos)
			{
				inSector = true;
				inWall = false;
				continue;
			}
			else if (line.find("[WALL]") != std::string::npos)
			{
				inSector = false;
				inWall = true;
				continue;
			}

			if (inSector)
			{
				sectors.push_back(parseSector(line));
			}

			if (inWall && !sectors.empty())
			{
				Wall wall = parseWall(line);
				sectors.back().walls.push_back(wall);
			}
		}
	}

	void setupBuffers()
	{
		std::cout << "Setting up buffers" << std::endl;

		int wallBaseIndex = 0;
		int floorBaseIndex = 0;
		for (Sector sector : sectors)
		{
			std::vector<glm::vec3> _floorVertices;
			std::vector<glm::vec3> _ceilingVertices;
			for (Wall wall : sector.walls)
			{
				glm::vec3 v0(wall.a.x, sector.floorHeight, wall.a.y);
				glm::vec3 v1(wall.b.x, sector.floorHeight, wall.b.y);
				glm::vec3 v2(wall.b.x, sector.ceilingHeight, wall.b.y);
				glm::vec3 v3(wall.a.x, sector.ceilingHeight, wall.a.y);

				_floorVertices.push_back(v0);

				_ceilingVertices.push_back(v3);

				if (wall.portal != 0)
				{
					Sector& portalSector = sectors[wall.portal - 1];
					if (portalSector.floorHeight > sector.floorHeight)
					{
						v2.y = portalSector.floorHeight;
						v3.y = portalSector.floorHeight;

						addWallVectors(v0, v1, v2, v3, sector.color, wallBaseIndex, sector.outwardFacing);
					}

					if (portalSector.ceilingHeight < sector.ceilingHeight)
					{
						v0.y = portalSector.ceilingHeight;
						v1.y = portalSector.ceilingHeight;
						v2.y = sector.ceilingHeight;
						v3.y = sector.ceilingHeight;

						addWallVectors(v0, v1, v2, v3, sector.color, wallBaseIndex, sector.outwardFacing);
					}
					continue;
				}

				addWallVectors(v0, v1, v2, v3, sector.color, wallBaseIndex, sector.outwardFacing);
				continue;
			}

			addFloorVectors(_floorVertices, floorBaseIndex, true);
			addFloorVectors(_ceilingVertices, floorBaseIndex, false);
		}

		glGenVertexArrays(1, &wallVAO);
		glGenBuffers(1, &wallVBO);
		glGenBuffers(1, &wallEBO);
		glGenBuffers(1, &wallNBO);
		glGenBuffers(1, &wallCBO);

		glBindVertexArray(wallVAO);

		glBindBuffer(GL_ARRAY_BUFFER, wallVBO);
		glBufferData(GL_ARRAY_BUFFER, wallVertices.size() * sizeof(float), wallVertices.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glBindBuffer(GL_ARRAY_BUFFER, wallNBO);
		glBufferData(GL_ARRAY_BUFFER, wallNormals.size() * sizeof(float), wallNormals.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(1);

		glBindBuffer(GL_ARRAY_BUFFER, wallCBO);
		glBufferData(GL_ARRAY_BUFFER, wallColors.size() * sizeof(float), wallColors.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(2);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wallEBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, wallIndices.size() * sizeof(unsigned int), wallIndices.data(), GL_STATIC_DRAW);

		glBindVertexArray(0);

		glGenVertexArrays(1, &floorVAO);
		glGenBuffers(1, &floorVBO);
		glGenBuffers(1, &floorEBO);
		glGenBuffers(1, &floorNBO);
		glGenBuffers(1, &floorCBO);

		glBindVertexArray(floorVAO);

		glBindBuffer(GL_ARRAY_BUFFER, floorVBO);
		glBufferData(GL_ARRAY_BUFFER, floorVertices.size() * sizeof(float), floorVertices.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glBindBuffer(GL_ARRAY_BUFFER, floorNBO);
		glBufferData(GL_ARRAY_BUFFER, floorNormals.size() * sizeof(float), floorNormals.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(1);

		glBindBuffer(GL_ARRAY_BUFFER, floorCBO);
		glBufferData(GL_ARRAY_BUFFER, floorColors.size() * sizeof(float), floorColors.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(2);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, floorEBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, floorIndices.size() * sizeof(unsigned int), floorIndices.data(), GL_STATIC_DRAW);

		glBindVertexArray(0);
	}

	void addWallVectors(glm::vec3 v0, glm::vec3 v1, glm::vec3 v2, glm::vec3 v3, glm::vec3 color, int& baseIndex, bool outward)
	{
		wallVertices.push_back(v0.x);
		wallVertices.push_back(v0.y);
		wallVertices.push_back(v0.z);

		wallVertices.push_back(v1.x);
		wallVertices.push_back(v1.y);
		wallVertices.push_back(v1.z);

		wallVertices.push_back(v2.x);
		wallVertices.push_back(v2.y);
		wallVertices.push_back(v2.z);

		wallVertices.push_back(v3.x);
		wallVertices.push_back(v3.y);
		wallVertices.push_back(v3.z);

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

		wallNormals.push_back(normal.x);
		wallNormals.push_back(normal.y);
		wallNormals.push_back(normal.z);

		wallNormals.push_back(normal.x);
		wallNormals.push_back(normal.y);
		wallNormals.push_back(normal.z);

		wallNormals.push_back(normal.x);
		wallNormals.push_back(normal.y);
		wallNormals.push_back(normal.z);

		wallNormals.push_back(normal.x);
		wallNormals.push_back(normal.y);
		wallNormals.push_back(normal.z);

		wallColors.push_back(color.x);
		wallColors.push_back(color.y);
		wallColors.push_back(color.z);

		wallColors.push_back(color.x);
		wallColors.push_back(color.y);
		wallColors.push_back(color.z);

		wallColors.push_back(color.x);
		wallColors.push_back(color.y);
		wallColors.push_back(color.z);

		wallColors.push_back(color.x);
		wallColors.push_back(color.y);
		wallColors.push_back(color.z);

		// First triangle
		wallIndices.push_back(baseIndex);
		wallIndices.push_back(baseIndex + 1);
		wallIndices.push_back(baseIndex + 2);

		// Second triangle
		wallIndices.push_back(baseIndex);
		wallIndices.push_back(baseIndex + 2);
		wallIndices.push_back(baseIndex + 3);

		baseIndex += 4;

	}

	void addFloorVectors(std::vector<glm::vec3> _vertices, int& baseIndex, bool floor)
	{
		for (int i = 0; i + 2 < _vertices.size() ; i+=1)
		{
			this->floorVertices.push_back(_vertices[0].x);
			this->floorVertices.push_back(_vertices[0].y);
			this->floorVertices.push_back(_vertices[0].z);

			this->floorVertices.push_back(_vertices[i + 1].x);
			this->floorVertices.push_back(_vertices[i + 1].y);
			this->floorVertices.push_back(_vertices[i + 1].z);

			this->floorVertices.push_back(_vertices[i + 2].x);
			this->floorVertices.push_back(_vertices[i + 2].y);
			this->floorVertices.push_back(_vertices[i + 2].z);

			glm::vec3 normal = glm::normalize(glm::cross(_vertices[i + 1] - _vertices[0], _vertices[i + 2] - _vertices[0]));
			normal = floor ? -normal : normal;

			floorNormals.push_back(normal.x);
			floorNormals.push_back(normal.y);
			floorNormals.push_back(normal.z);

			floorNormals.push_back(normal.x);
			floorNormals.push_back(normal.y);
			floorNormals.push_back(normal.z);

			floorNormals.push_back(normal.x);
			floorNormals.push_back(normal.y);
			floorNormals.push_back(normal.z);

			glm::vec3 color;
			color = floor ? glm::vec3(0.75f, 0.5f, 0.25f) : glm::vec3(0.25f, 0.5f, 0.75f);

			this->floorColors.push_back(color.x);
			this->floorColors.push_back(color.y);
			this->floorColors.push_back(color.z);

			this->floorColors.push_back(color.x);
			this->floorColors.push_back(color.y);
			this->floorColors.push_back(color.z);

			this->floorColors.push_back(color.x);
			this->floorColors.push_back(color.y);
			this->floorColors.push_back(color.z);

			floorIndices.push_back(baseIndex);
			floorIndices.push_back(baseIndex + 1);
			floorIndices.push_back(baseIndex + 2);

			baseIndex += 3;
			}
	}
};

#endif
