#pragma once

#ifndef CUBE_FACTORY_H
#define CUBE_FACTORY_H

#include <list>

#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>
#include <cube.h>

class CubeFactory
{
private:
	Shader shader;

	unsigned int VAO, VBO, EBO;

	std::list<Cube> cubes;

	const unsigned int CUBE_INDICES[36]{
		0, 1, 2, 0, 2, 3, // Front face
		4, 5, 6, 4, 6, 7, // Back face
		0, 1, 5, 0, 5, 4, // Bottom face
		3, 2, 6, 3, 6, 7, // Top face
		0, 3, 7, 0, 7, 4, // Left face
		1, 2, 6, 1, 6, 5  // Right face
	};

	void setupBuffers()
	{
		float CUBE_VERTICES[] = {
	   -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
		0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
		0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
		0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	   -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	   -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

	   -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
		0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
		0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
		0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
	   -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
	   -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

	   -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
	   -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	   -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	   -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	   -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
	   -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

		0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
		0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
		0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
		0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
		0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
		0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

	   -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
		0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
		0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
		0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
	   -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
	   -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

	   -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
		0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
		0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
		0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
	   -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
	   -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
		};

		glGenVertexArrays(1, &VAO);
		glGenBuffers(1, &VBO);
		glGenBuffers(1, &EBO);

		glBindVertexArray(VAO);

		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(CUBE_VERTICES), CUBE_VERTICES, GL_STATIC_DRAW);

		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(CUBE_INDICES), CUBE_INDICES, GL_STATIC_DRAW);

		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
		glEnableVertexAttribArray(1);

		//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
		//glEnableVertexAttribArray(1);

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
	}
public:
	CubeFactory() : shader (Shader("cube.vert", "cube.frag"))
	{
		std::cout << "Setting up cube factory" << std::endl;
		setupBuffers();
		//std::cout << VAO << " " << VBO << " " << EBO << std::endl;
	}

	void clear()
	{
		glDeleteVertexArrays(1, &VAO);
		glDeleteBuffers(1, &VBO);
		glDeleteBuffers(1, &EBO);
	}

	void addCube(glm::vec3 position, glm::vec3 scale, glm::vec3 rotation, glm::vec3 color)
	{
		cubes.push_back(Cube(position, scale, rotation, color));
	}

	void render(glm::mat4 view, glm::mat4 projection, glm::vec3 lightPos, float deltaTime)
	{
		//std::cout << "Rendering cubes" << std::endl;
		shader.use();
		shader.setMat4("projection", projection);
		shader.setMat4("view", view);
		//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		for (Cube& cube : cubes)
		{
			//std::cout << "Cube position: (" << cube.getColor().x << "," << cube.getColor().y << "," << cube.getColor().z << ")" << std::endl;
			cube.setRotation(cube.getRotation() + (glm::vec3(5.0f, 10.0f, 20.0f))*deltaTime);
			glm::mat4 model = cube.getModelMatrix();
			glm::vec3 color = cube.getColor();
			shader.setMat4("model", model);
			shader.setVec3("objectColor", color);
			shader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
			shader.setVec3("lightPos", lightPos);

			glBindVertexArray(VAO);
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			glDrawArrays(GL_TRIANGLES, 0, 36);
			//glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
			//shader.setVec3("objectColor", 0.0f, 0.0f, 0.0f);
			//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			//glDrawArrays(GL_TRIANGLES, 0, 36);
			//glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

			glBindVertexArray(0);
		}
	}

	void render(glm::mat4 view, glm::mat4 projection, glm::vec3 lightPos, float deltaTime, int amountOfCubes)
	{
		shader.use();
		shader.setMat4("projection", projection);
		shader.setMat4("view", view);

		int i = 0;
		for (Cube& cube : cubes)
		{
			if (i >= amountOfCubes)
			{
				break;
			}
			//std::cout << "Cube position: (" << cube.getColor().x << "," << cube.getColor().y << "," << cube.getColor().z << ")" << std::endl;
			cube.setRotation(cube.getRotation() + (glm::vec3(5.0f, 10.0f, 20.0f)) * deltaTime);
			glm::mat4 model = cube.getModelMatrix();
			glm::vec3 color = cube.getColor();
			shader.setMat4("model", model);
			shader.setVec3("objectColor", color);
			shader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
			shader.setVec3("lightPos", lightPos);

			glBindVertexArray(VAO);
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			glDrawArrays(GL_TRIANGLES, 0, 36);
			//glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
			//shader.setVec3("objectColor", 0.0f, 0.0f, 0.0f);
			//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			//glDrawArrays(GL_TRIANGLES, 0, 36);
			//glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

			glBindVertexArray(0);
			i++;
		}
	}


	std::list<Cube> getCubes()
	{
		return cubes;
	}
};

#endif