#pragma once

#include <vector>
#include <iostream>

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

#include "Shader.h"
#include "MeshRenderer.h"

#define SHADOW_WIDTH 1024
#define SHADOW_HEIGHT 1024

class PointLight
{
public:
	unsigned int depth_map, depth_map_FBO;
	float near_plane = 1.0f, 
		far_plane = 100.0f, 
		ambient_intensity = 0.5f,
		diffuse_intensity = 1.0f,
		attenuation_constant = 1.0f,
		attenuation_linear = 0.09f,
		attenuation_exponential = 0.032f;
	glm::vec3 Position;
	glm::vec3 color;
	Shader shader;

	PointLight(glm::vec3 position, glm::vec3 color, Shader shader)
	{
		this->Position = position;
		this->color = color;
		this->shader = shader;
		SetupLighting();
	}

	void RenderSceneShadows(std::vector<MeshRenderer*> meshes)
	{
		glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), (float)SHADOW_WIDTH / (float)SHADOW_HEIGHT, near_plane, far_plane);
		std::vector<glm::mat4> shadowTransforms;
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f))); // Right
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f))); // Left
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f))); // Up
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f))); // Down
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f))); // Forward
		shadowTransforms.push_back(shadowProj * glm::lookAt(Position, Position + glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f))); // Backward

		glEnable(GL_CULL_FACE);

		glCullFace(GL_FRONT);

		glEnable(GL_DEPTH_TEST);
		glDepthMask(GL_TRUE);
		glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
		glBindFramebuffer(GL_FRAMEBUFFER, depth_map_FBO);
		glClear(GL_DEPTH_BUFFER_BIT);
		shader.Use();
		for (unsigned int i = 0; i < 6; ++i)
		{
			std::string matrix_name = "shadowMatrices[" + std::to_string(i) + "]";
			shader.SetMatrix4(matrix_name.c_str(), shadowTransforms[i]);		
		}
		shader.SetFloat("farPlane", far_plane);
		shader.SetVector3f("lightPosition", Position);

		for (MeshRenderer* mesh : meshes)
		{
			mesh->Render(shader);
		}
		
		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			std::cout << "Framebuffer not complete!" << std::endl;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		glCullFace(GL_BACK);

	}
private:

	void SetupLighting()
	{
		//std::cout << "SetupLighting" << std::endl;
		glGenFramebuffers(1, &depth_map_FBO);
		glGenTextures(1, &depth_map);
		glBindTexture(GL_TEXTURE_CUBE_MAP, depth_map);

		for (unsigned int i = 0; i < 6; i++)
		{
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
		}
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

		glBindFramebuffer(GL_FRAMEBUFFER, depth_map_FBO);
		glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_map, 0);
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);

		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			std::cout << "Framebuffer not complete!" << std::endl;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
};

