#include "chunk_renderer.hpp"

namespace polygun::engine {
	ChunkRenderer::ChunkRenderer(Chunk *chunk_to_render) {
		m_chunk = chunk_to_render;

		m_chunk_shader = renderer::Shader("shaders/chunk_vertex.glsl", "shaders/chunk_fragment.glsl");

		generate_mesh();

		glGenVertexArrays(1, &m_vao);
		glGenBuffers(1, &m_vbo);

		glBindVertexArray(m_vao);

		glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(m_vertices), m_vertices, GL_DYNAMIC_DRAW);

		// position attribute
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);
		// texture coord attribute
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
		glEnableVertexAttribArray(1);

		load_textures();
	}

	ChunkRenderer::~ChunkRenderer() {
		glDeleteVertexArrays(1, &m_vao);
		glDeleteBuffers(1, &m_vbo);
	}

	void ChunkRenderer::load_textures() {/*
		for (int i = 0; i < sizeof(nodes); i++) {
			glGenTextures(1, &m_textures[i]);
			glBindTexture(GL_TEXTURE_2D, m_textures[i]);
			
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			int width, height, nrChannels;
			stbi_set_flip_vertically_on_load(true); 
			unsigned char *data = stbi_load(nodes[i++].texture.c_str(), &width, &height, &nrChannels, 0);
			if (data)
			{
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
				glGenerateMipmap(GL_TEXTURE_2D);
			}
			else
			{
				std::cout << "Failed to load texture" << std::endl;
			}
			stbi_image_free(data);
		} 

		m_chunk_shader.bind();
		m_chunk_shader.set_uniform("texture1", 0);*/

		glGenTextures(1, &m_texture1);
		glBindTexture(GL_TEXTURE_2D, m_texture1);
		
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		int width, height, nrChannels;
		stbi_set_flip_vertically_on_load(true); 
		unsigned char *data = stbi_load(nodes[2].texture.c_str(), &width, &height, &nrChannels, 0);
		if (data)
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
			glGenerateMipmap(GL_TEXTURE_2D);
		}
		else
		{
			std::cout << "Failed to load texture" << std::endl;
		}
		stbi_image_free(data);
	
		m_chunk_shader.bind();
		m_chunk_shader.set_uniform("texture1", 0);
	}

	void ChunkRenderer::generate_mesh() {
		int index = 0;
		for (int x = 0; x < Chunk::CHUNK_SIZE; x++) {
			for (int y = 0; y < Chunk::CHUNK_SIZE; y++) {
				for (int z = 0; z < Chunk::CHUNK_SIZE; z++) {
					if (!m_chunk->is_air(glm::vec3(x, y, z))) {
						// check if the neighbour is air and if so add the face vertices to the array
						if (m_chunk->is_air(glm::vec3(x + 1, y, z))) {
							// right face
							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;
						}
						if (m_chunk->is_air(glm::vec3(x - 1, y, z))) {
							// left face
							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;
						}
						if (m_chunk->is_air(glm::vec3(x, y + 1, z))) {
							// top face
							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;
						}
						if (m_chunk->is_air(glm::vec3(x, y - 1, z))) {
							// bottom face
							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;
						}

						if (m_chunk->is_air(glm::vec3(x, y, z + 1))) {
							// front face
							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z + 1;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;
						}

						if (m_chunk->is_air(glm::vec3(x, y, z - 1))) {
							// back face
							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 0.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x + 1;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 1.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y + 1;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 1.0f;

							m_vertices[index++] = x;
							m_vertices[index++] = y;
							m_vertices[index++] = z;
							m_vertices[index++] = 0.0f;
							m_vertices[index++] = 0.0f;
						}
					}
				}
			}
		}	
	}

	void ChunkRenderer::render(Camera camera) {
			glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

			// bind textures on corresponding texture units
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_2D, m_texture1);

			// activate shader
			m_chunk_shader.bind();

			glm::mat4 projection = glm::perspective(glm::radians(camera.m_zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
			m_chunk_shader.set_uniform("projection", projection);

			glm::mat4 view = camera.get_view_matrix();
			m_chunk_shader.set_uniform("view", view);

			glBindVertexArray(m_vao);

			glm::mat4 model = glm::mat4(1.0f);
			model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
			m_chunk_shader.set_uniform("model", model);

			glDrawArrays(GL_TRIANGLES, 0, 36);
	}
}