#include "engine.hpp"

#include "../audio/audio.hpp"

#include "game/world/chunk_renderer.hpp"
#include "common/network/network_manager.hpp"
#include "common/logger.hpp"

#include <GLFW/glfw3.h>
#include "vendor/imgui.h"
#include "vendor/imgui_impl_glfw.h"
#include "vendor/imgui_impl_opengl3.h"

using namespace polygun::renderer;
using namespace polygun::world;

int sizex = SCR_WIDTH, sizey = SCR_HEIGHT;

namespace polygun::engine {
	void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); glfwGetWindowSize(window, &sizex, &sizey);}

	void Engine::connection_thread_func() {
		network::NetworkManager network_manager("127.0.0.1", 1117);
		network::NetworkPacket handshake = network_manager.create_packet(network::NetworkPacketFlag::FLAG_RELIABLE, network::NetworkPacketID::PACKET_HANDSHAKE);
		network::NetworkPacket packet;
		handshake.write("mrkubax10");
		network_manager.send_packet(handshake);
		network_manager.update();
		while(m_running) {
			if(network_manager.get_socket().recv(packet)) {
				if((packet.get_flags() & network::NetworkPacketFlag::FLAG_RELIABLE) || packet.get_id()==network::NetworkPacketID::PACKET_ACK)
					network_manager.push_packet(packet);
				network_manager.update();
				if(network_manager.recv_packet(packet)) {
					// handle
				}
			}
			packet = network_manager.create_packet(network::NetworkPacketFlag::FLAG_NONE, network::NetworkPacketID::PACKET_ACTIVITY);
			network_manager.send_packet(packet);
		}
		packet = network_manager.create_packet(network::NetworkPacketFlag::FLAG_RELIABLE, network::NetworkPacketID::PACKET_EXIT);
		network_manager.send_packet(packet);
		while(network_manager.has_outgoing_packets()) {
			if(network_manager.get_socket().recv(packet) && packet.get_id()==network::NetworkPacketID::PACKET_ACK)
				network_manager.push_packet(packet);
			network_manager.update();
		}
	}

	void Engine::init() {
		audio::init();

		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, "Polygun", NULL, NULL);
		if (window == NULL) {
			std::cout << "Failed to create GLFW window" << std::endl;
			glfwTerminate();
			return;
		}
		glfwMakeContextCurrent(window);
		glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
		glfwSetCursorPosCallback(window, m_camera.mouse_callback);
		glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

		if (glewInit() != GLEW_OK) {
			std::cout << "GLEW init failed" << std::endl;
			return;
		}

		// Setup Dear ImGui context

		IMGUI_CHECKVERSION();
		ImGui::CreateContext();
		ImGuiIO& io = ImGui::GetIO(); (void)io;
		ImGui::StyleColorsDark();

		// Setup Platform/Renderer bindings
		ImGui_ImplGlfw_InitForOpenGL(window, true);
		ImGui_ImplOpenGL3_Init("#version 330");

		glEnable(GL_DEPTH_TEST);

		Shader chunk_shader("shaders/chunk_vertex.glsl", "shaders/chunk_fragment.glsl");

		float vertices[] = {
			-0.1f, -0.1f, -0.1f,  0.0f, 0.0f,
			0.1f, -0.1f, -0.1f,  1.0f, 0.0f,
			0.1f,  0.1f, -0.1f,  1.0f, 1.0f,
			0.1f,  0.1f, -0.1f,  1.0f, 1.0f,
			-0.1f,  0.1f, -0.1f,  0.0f, 1.0f,
			-0.1f, -0.1f, -0.1f,  0.0f, 0.0f,

			-0.1f, -0.1f,  0.1f,  0.0f, 0.0f,
			0.1f, -0.1f,  0.1f,  1.0f, 0.0f,
			0.1f,  0.1f,  0.1f,  1.0f, 1.0f,
			0.1f,  0.1f,  0.1f,  1.0f, 1.0f,
			-0.1f,  0.1f,  0.1f,  0.0f, 1.0f,
			-0.1f, -0.1f,  0.1f,  0.0f, 0.0f,

			-0.1f,  0.1f,  0.1f,  1.0f, 0.0f,
			-0.1f,  0.1f, -0.1f,  1.0f, 1.0f,
			-0.1f, -0.1f, -0.1f,  0.0f, 1.0f,
			-0.1f, -0.1f, -0.1f,  0.0f, 1.0f,
			-0.1f, -0.1f,  0.1f,  0.0f, 0.0f,
			-0.1f,  0.1f,  0.1f,  1.0f, 0.0f,

			0.1f,  0.1f,  0.1f,  1.0f, 0.0f,
			0.1f,  0.1f, -0.1f,  1.0f, 1.0f,
			0.1f, -0.1f, -0.1f,  0.0f, 1.0f,
			0.1f, -0.1f, -0.1f,  0.0f, 1.0f,
			0.1f, -0.1f,  0.1f,  0.0f, 0.0f,
			0.1f,  0.1f,  0.1f,  1.0f, 0.0f,

			-0.1f, -0.1f, -0.1f,  0.0f, 1.0f,
			0.1f, -0.1f, -0.1f,  1.0f, 1.0f,
			0.1f, -0.1f,  0.1f,  1.0f, 0.0f,
			0.1f, -0.1f,  0.1f,  1.0f, 0.0f,
			-0.1f, -0.1f,  0.1f,  0.0f, 0.0f,
			-0.1f, -0.1f, -0.1f,  0.0f, 1.0f,

			-0.1f,  0.1f, -0.1f,  0.0f, 1.0f,
			0.1f,  0.1f, -0.1f,  1.0f, 1.0f,
			0.1f,  0.1f,  0.1f,  1.0f, 0.0f,
			0.1f,  0.1f,  0.1f,  1.0f, 0.0f,
			-0.1f,  0.1f,  0.1f,  0.0f, 0.0f,
			-0.1f,  0.1f, -0.1f,  0.0f, 1.0f
		};

		Chunk chnk;

		unsigned int VBO, VAO;
		glGenVertexArrays(1, &VAO);
		glGenBuffers(1, &VBO);

		glBindVertexArray(VAO);

		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_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);


		unsigned int texture_atlas;

		glGenTextures(1, &texture_atlas);
		glBindTexture(GL_TEXTURE_2D, texture_atlas);
		
		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("res/textures/gold.png", &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);
	

		chunk_shader.bind();
		chunk_shader.set_uniform("texture_atlas", 0);

		m_delta_time = 0;
		m_last_frame = 0;


		bool is_esc_pressed = false;
		bool was_camera_locked = false;

		m_running = true;
		// Note: uncomment to test connection with server
		// m_connection_thread.reset(new std::thread(&Engine::connection_thread_func, this));

		while (!glfwWindowShouldClose(window))
		{
			float current_frame = static_cast<float>(glfwGetTime());
			m_delta_time = current_frame - m_last_frame;
			m_last_frame = current_frame;

			// input
			// -----

			if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
				if (!is_esc_pressed) {
					if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
						glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
					else
						glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
						was_camera_locked = true;
				}
				is_esc_pressed = true;
			}
			else
				is_esc_pressed = false;


			if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
				m_camera.process_movement(polygun::engine::FORWARD, m_delta_time);
			if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
				m_camera.process_movement(polygun::engine::BACKWARD, m_delta_time);
			if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
				m_camera.process_movement(LEFT, m_delta_time);
			if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
				m_camera.process_movement(polygun::engine::RIGHT, m_delta_time);
			if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
				m_camera.process_movement(polygun::engine::UP, m_delta_time);
			if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS)
				m_camera.process_movement(polygun::engine::DOWN, m_delta_time);
			
			if(glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) {
				if(was_camera_locked) {
					m_camera.update(true);
					was_camera_locked = false;
				}
				else
					m_camera.update(false);
			}


			// rendering
			glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

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

			// activate shader
			chunk_shader.bind();

			glm::mat4 projection = glm::perspective(glm::radians(m_camera.m_fov), (float)sizex / (float)sizey, 0.1f, 100.0f);
			chunk_shader.set_uniform("projection", projection);

			glm::mat4 view = m_camera.get_view_matrix();
			chunk_shader.set_uniform("view", view);

			glBindVertexArray(VAO);

			for (unsigned int x = 0; x < 32; x++) {
				for (unsigned int y = 0; y < 32; y++) {
					for (unsigned int z = 0; z < 32; z++) {
						if (chnk.get_node(glm::vec3(x,y,z))!= 0) {
							glm::mat4 model = glm::mat4(1.0f);
							model = glm::translate(model, glm::vec3(x/5.0f, y/5.0f, z/5.0f));
							chunk_shader.set_uniform("model", model);
							glDrawArrays(GL_TRIANGLES, 0, 36);
						}
					}
				}
			}


			// imgui

			ImGui_ImplOpenGL3_NewFrame();
			ImGui_ImplGlfw_NewFrame();
			ImGui::NewFrame();
			
			ImGui::SetNextWindowSize(ImVec2(340, 200)); 
		
			ImGui::Begin("Debug");

			ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
			// Camera pos
			ImGui::Text("Player Pos: %.3f, %.3f, %.3f", m_camera.m_position.x, m_camera.m_position.y, m_camera.m_position.z);
			// Camera rot
			ImGui::Text("Player Rot: %.3f, %.3f", m_camera.m_yaw, m_camera.m_pitch);

			int x_d, y_d, z_d, id_d;

			ImGui::InputInt("ID", &id_d);
			
			ImGui::InputInt("X", &x_d);
			ImGui::InputInt("Y", &y_d);
			ImGui::InputInt("Z", &z_d);

			if (ImGui::Button("Place Block"))
			{
				chnk.add_node(id_d, glm::vec3(x_d, y_d, z_d));
			}

			ImGui::End();

			ImGui::Render();

			ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());


			ImGui::EndFrame();

			glfwSwapBuffers(window);
			glfwPollEvents();
		}

		glDeleteVertexArrays(1, &VAO);
		glDeleteBuffers(1, &VBO);

		m_running = false;
		if(m_connection_thread->joinable()) {
			LOG_VERBOSE("Waiting for connection thread to finish");
			m_connection_thread->join();
		}

		glfwTerminate();
	}
}
