#pragma once
#ifndef RTRSHADOWS_H
#define RTRSHADOWS_H

//////////////
// Includes //
//////////////
// C++ general includes
#include <string>
#include <iostream>

// OpenGL Includes
#include <glad/glad.h>
#include <GLFW/glfw3.h> // GLFW must be included after GLAD!

#include "stb_image.h"
#include "rtrSetupGLFWandGLAD.h" //rtrGLFWGLAD::
#include "rtrShader.h" //rtrShader::
#include "rtrVAO.h"
#include "rtrCamera.h"
#include "rtrModelLoading.h"
#include "rtrMaterialsAndLighting.h"
#include "rtrCubeMap.h"
#include "include/jcrVAO.h"


namespace rtrShadows
{
	class PointShadow
	{

		public:
			const int ShadowWidth = 1000;
			const int ShadowHeight = 1000;
			unsigned int ShadowCubeMap = 0;
			unsigned int ShadowFBO = 0;
			float ShadowNearPlane = 1.0f;
			float ShadowFarPlane = 100.0f;
			glm::mat4 ShadowProjectionMatrix = glm::perspective(glm::radians(90.0f), (float)ShadowWidth / (float)ShadowHeight, ShadowNearPlane, ShadowFarPlane);
			rtrShader::Shader DepthShader = rtrShader::Shader("shaders\\depth.vert", "shaders\\depth.frag", "shaders\\depth.geom");
			const glm::vec3 *ptrLightPosition;
			//For Debug: Depth Screen Variables:
			bool debug = false;
			unsigned int VAO_DepthScreen, VBO_DepthScreen;
			rtrShader::Shader shaderDepthScreen = rtrShader::Shader("shaders\\depthScreen.vert", "shaders\\depthscreen.frag");
			bool activated = true;
			bool keyAlreadyPressed = false;

			PointShadow(glm::vec3 *lightPositionInputPtr): ptrLightPosition(lightPositionInputPtr)
			{
				//Shadow: Setup Cubemap to contain depth values of scene
				glGenTextures(1, &ShadowCubeMap);
				glBindTexture(GL_TEXTURE_CUBE_MAP, ShadowCubeMap);
				for (unsigned int i = 0; i < 6; i++)
				{
					glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, ShadowWidth, ShadowHeight, 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);

				// Shadow: Setup depth FBO with Cubemap as depth attachment
				glGenFramebuffers(1, &ShadowFBO);
				glBindFramebuffer(GL_FRAMEBUFFER, ShadowFBO);
				glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, ShadowCubeMap, 0);
				glDrawBuffer(GL_NONE);
				glReadBuffer(GL_NONE);
				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

				// For Debug: Setup Depth Map Screen
				float verticesDebugScreen[] = {
					// vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates. NOTE that this plane is now much smaller and at the top of the screen
					// positions   // texCoords
					0.6f,  1.0f,  1.0f, 0.0f, // left top
					0.6f,  0.6f,  1.0f, 1.0f, // left bottom
					1.0f,  0.6f,  0.0f, 1.0f, // right bottom 

					0.6f,  1.0f,  1.0f, 0.0f, // left top again
					1.0f,  0.6f,  0.0f, 1.0f, // right bottom 
					1.0f,  1.0f,  0.0f, 0.0f  // right top
				};
				
				unsigned int dimensionOfAttributes[] = { 2,2 };
				jcrVAO::createVAOwithoutEBO(VAO_DepthScreen, VBO_DepthScreen, verticesDebugScreen, 6, 2, dimensionOfAttributes);
			}

			void renderDepthMap( rtrModelLoading::PhysicalObject& object)
			{
				if (activated)
				{
					// Setup Transformations to Light Space
					glm::vec3 Position = *ptrLightPosition;
					std::vector<glm::mat4> shadowTrafos;
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)));
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)));
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
					shadowTrafos.push_back(ShadowProjectionMatrix * glm::lookAt(Position, Position + glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));

					//render depth values of object w.r.t. light position to Depth FBO (ShadowFBO);
					glViewport(0, 0, ShadowWidth, ShadowHeight);
					glBindFramebuffer(GL_FRAMEBUFFER, ShadowFBO);
					glClear(GL_DEPTH_BUFFER_BIT);
					DepthShader.use();
					for (unsigned int i = 0; i < 6; ++i) DepthShader.setMat4F("shadowMatrices[" + std::to_string(i) + "]", shadowTrafos[i]);
					DepthShader.setMat4F("modelMatrix", object.ModelMatrixBody);
					DepthShader.setFloat("farPlane", ShadowFarPlane);
					DepthShader.setVec3F("lightPosition", Position);

					glActiveTexture(GL_TEXTURE0);
					glBindTextureUnit(0, ShadowCubeMap);

					object.DrawDepth(DepthShader);
					glBindFramebuffer(GL_FRAMEBUFFER, 0);
					glBindTextureUnit(0, 0);
				}
			}

			void sendDataToShader(rtrShader::Shader& shader)
			{
				shader.use();
				shader.setFloat("shadowFarPlane", ShadowFarPlane);
				shader.setFloat("shadowsActive", activated);
			}

			void drawDepthScreen(glm::vec3 rocketPosition)
			{
				if (debug && activated)
				{
					glDisable(GL_DEPTH_TEST);
					shaderDepthScreen.use();
					glActiveTexture(GL_TEXTURE0);
					glBindTexture(GL_TEXTURE_CUBE_MAP, ShadowCubeMap);
					shaderDepthScreen.setInt("textureUnit", 0);
					shaderDepthScreen.setVec3F("lightPosition", rocketPosition);
					glBindVertexArray(VAO_DepthScreen);
					glDrawArrays(GL_TRIANGLES, 0, 6);
					glEnable(GL_DEPTH_TEST);
				}
			}

			void processKeyboardInput(GLFWwindow* Window)
			{
				// Toggle shadows if s was pressed 
				if (glfwGetKey(Window, GLFW_KEY_S) == GLFW_PRESS)
				{

					if (!keyAlreadyPressed)
					{
						activated = !activated;
						keyAlreadyPressed = true;
					}
				}
				else
				{
					keyAlreadyPressed = false;
				}
			}

	};
}
#endif