
#include <iostream>
#include <fstream>
#include <memory>

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

#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>

#include "ResourceManager.h"
#include "PointLight.h"

void FramebufferSizeCallback(GLFWwindow* window, int width, int height);

int SCR_WIDTH = 1600;
int SCR_HEIGHT = 900;

//Camera camera(glm::vec3(20.0f, 1.0f, 20.0f));
Player* PlayerCharacter = nullptr;

float LastX = SCR_WIDTH / 2.0f;
float LastY = SCR_HEIGHT / 2.0f;
bool FirstMouse = true;

float DeltaTime = 0.0f;
float LastFrame = 0.0f;
float ElapsedTime = 0.0f;

//glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

int main() 
{
	const char* GLSLVersion = "#version 330";
	std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* Window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearningOpenGl", NULL, NULL);
	if (Window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}

	glfwMakeContextCurrent(Window);
	glfwSetFramebufferSizeCallback(Window, FramebufferSizeCallback);
	//glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	std::cout << "Loaded GLFW context, OpenGL 3.3" << std::endl;

	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO();
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
	io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;

	//ImGui_ImplGlfw_InitForOpenGL(window, true);
	//ImGui_ImplOpenGL3_Init(glsl_version);

	if (!ImGui_ImplGlfw_InitForOpenGL(Window, true)) {
		std::cout << "Failed to initialize ImGui GLFW backend" << std::endl;
	}
	if (!ImGui_ImplOpenGL3_Init(GLSLVersion)) {
		std::cout << "Failed to initialize ImGui OpenGL backend" << std::endl;
	}

	Shader SimpleDepthShader = ResourceManager::LoadShader("point_shadow_depth.vert", "point_shadow_depth.frag", "point_shadow_depth.geom", "simple_depth_shader");

	Shader WallShader = ResourceManager::LoadShader("wall_test.vert", "wall_test.frag", nullptr, "wall");

	LevelMap CurrentLevelMap = ResourceManager::LoadLevelMap("map_test.txt", "test_level");

	CurrentLevelMap.RenderSetup(&WallShader, &WallShader, &WallShader);

	glm::vec3 StartingPosition = glm::vec3(20.0f, 0.0f, 20.0f);
	glm::vec3 StartingRotation = glm::vec3(0.0f, 0.0f, 0.0f);
	PlayerCharacter = new Player(StartingPosition, StartingRotation, CurrentLevelMap.GetSectors(), Window);

	int NumberOfPointLights = 0;
	std::vector<PointLight> PointLightsVector;

	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(10.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(0.0f, 2.0f, 10.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(20.0f, 3.0f, 10.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(25.0f, 2.0f, 20.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	PointLightsVector.push_back(PointLight(PlayerCharacter->Position + glm::vec3(50.0f, 1.0f, 30.0f), glm::vec3(1.0f, 1.0f, 1.0f), WallShader));
	NumberOfPointLights = PointLightsVector.size();
	
	float r = 0.05f, g = 0.05f, b = 0.05f;

	while (!glfwWindowShouldClose(Window)) {
		//timing
		float currentFrame = (float)glfwGetTime();
		DeltaTime = currentFrame - LastFrame;
		LastFrame = currentFrame;
		ElapsedTime += DeltaTime;

		ImGui_ImplOpenGL3_NewFrame();
		ImGui_ImplGlfw_NewFrame();
		ImGui::NewFrame();

		ImGui::Begin("Settings");
		ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
		if (ImGui::Button("Create Light") && NumberOfPointLights < 10) {
			PointLightsVector.push_back(PointLight(PlayerCharacter->Position, glm::vec3(1.0f, 1.0f, 1.0f), SimpleDepthShader));
			NumberOfPointLights = PointLightsVector.size();
		}
		ImGui::End();

		ImGui::Render();

		// view/projection transformations

		glClearColor(r, g, b, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		for (PointLight& point_light : PointLightsVector)
		{
			point_light.RenderSceneShadows(CurrentLevelMap.GetMeshes());
		}

		glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		WallShader.Use();
		// CHANGED PROJECTION TO USE FLAT 45 TO GET PLAYER CHARACTER TO WORK
		glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
		glm::mat4 view = PlayerCharacter->GetViewMatrix();
		WallShader.SetMatrix4("projection", projection);
		WallShader.SetMatrix4("view", view);
		WallShader.SetVector3f("viewPosition", PlayerCharacter->PlayerCamera->Position);

		WallShader.SetVector3f("material.ambientColor", glm::vec3(1.0f, 0.0f, 0.0f));
		WallShader.SetVector3f("material.diffuseColor", glm::vec3(0.5f, 0.5f, 0.5f));
		WallShader.SetVector3f("material.specularColor", glm::vec3(0.0f, 0.0f, 1.0f));
		WallShader.SetFloat("material.shininess", 1.0f);
		// set lighting uniforms
		WallShader.SetInteger("numOfPointLights", NumberOfPointLights);
		int light_index = 0;
		for (PointLight& point_light : PointLightsVector)
		{
			std::string name = "pointLights[" + std::to_string(light_index) + "].";
			WallShader.SetVector3f((name + "base.color").c_str(), point_light.color);
			WallShader.SetFloat((name + "base.ambientIntensity").c_str(), point_light.ambient_intensity);
			WallShader.SetFloat((name + "base.diffuseIntensity").c_str(), point_light.diffuse_intensity);
			WallShader.SetFloat((name + "atten.constant").c_str(), point_light.attenuation_constant);
			WallShader.SetFloat((name + "atten.linear").c_str(), point_light.attenuation_linear);
			WallShader.SetFloat((name + "atten.exponential").c_str(), point_light.attenuation_exponential);
			WallShader.SetVector3f((name + "position").c_str(), point_light.Position);
			WallShader.SetFloat((name + "farPlane").c_str(), point_light.far_plane);
			WallShader.SetInteger((name + "depthMap").c_str(), light_index);
			glActiveTexture(GL_TEXTURE0 + light_index);
			glBindTexture(GL_TEXTURE_CUBE_MAP, point_light.depth_map);

			light_index++;
		}

		GLenum err;
		while(err = glGetError())
		{
			std::cout << "Error: " << err << std::endl;
		}

		PlayerCharacter->Update(DeltaTime);
		//PlayerCharacter->PlayerCamera->RenderScene(CurrentLevelMap, PointLightsVector, SCR_WIDTH, SCR_HEIGHT);

		for (auto* mesh : CurrentLevelMap.GetMeshes()) 
		{
		//	//std::cout << "Rendering mesh" << std::endl;
			mesh->Render(WallShader);
		}

		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
		//check and call
		glfwSwapBuffers(Window);
		glfwPollEvents();
	}

	// Cleanup
	ImGui_ImplOpenGL3_Shutdown();
	ImGui_ImplGlfw_Shutdown();
	ImGui::DestroyContext();
	glfwDestroyWindow(Window);
	glfwTerminate();
}

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) 
{
	glViewport(0, 0, width, height);
}

