
#include <GLFW/glfw3.h>
#include <cstdint>
#include <vulkan/vulkan_core.h>
#include "VulkanDeviceManager.h"
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_vulkan.h"

#include "utilities/Logger.h"
#include "GlfwWindowManager.h"
#include "VulkanContext.hpp"
#include "Primitives.h"

struct AppConfig
{
	std::string Title = "Learning Vulkan";
	uint32_t	Width = 800;
	uint32_t	Height = 600;
	bool		bResizable = true;
	bool		bFullscreen = false;
	bool		bValidationEnabled = false;
	bool		bVerboseLogging = false;
};

// const std::vector<Vertex> TriangleVertices = {
// 	{ { 0.0f, -0.5f }, { 1.0f, 1.0f, 1.0f } },
// 	{ { 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f } },
// 	{ { -0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f } }
// };

const std::vector<Vertex> SquareVertices = {
	{ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f } },
	{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f } },
	{ { 0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f } },
	{ { -0.5f, 0.5f, 0.0f }, { 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f } },

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

const std::vector<uint16_t> SquareIndices = { 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 };

class HelloTriangleApplication
{
public:
	void Run()
	{
		Initialization();
		Log::Info("Initialization finished...");
		MainLoop();
		Cleanup();
	}

private:
	AppConfig Settings = {};

	GlfwWindowManager WindowManager;
	VulkanContext	  VkContext;

	bool bShowImGui = false;
	bool bShowDemo = false;

	static void KeyCallback(GLFWwindow* Window, int Key, int Scancode, int Action, int Mods)
	{
		auto App = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(Window));
		if (Action == GLFW_PRESS || Action == GLFW_REPEAT)
		{
			App->OnKey(Key);
		}
	}

	void OnKey(int Key)
	{
		switch (Key)
		{
			case GLFW_KEY_O:
				bShowImGui = !bShowImGui;
				Log::Info("ImGui visibility toggled: " + std::string(bShowImGui ? "ON" : "OFF"));
				break;
			case GLFW_KEY_L:
				bShowDemo = !bShowDemo;
				Log::Info("Demo window toggled: " + std::string(bShowDemo ? "ON" : "OFF"));
				break;
		}
	}

	void Initialization()
	{
		InitGlfw();
		InitVulkan();

		IMGUI_CHECKVERSION();
		ImGui::CreateContext();

		VkContext.InitImGui();
	}

	void InitVulkan()
	{
		FVulkanConfig Config = {
			Settings.bValidationEnabled,
			Settings.bVerboseLogging,
			WindowManager.GetWindow(),
		};
		VkContext.Initialize(Config, SquareVertices, SquareIndices);
	}

	void InitGlfw()
	{
		FWindowConfig Config = { Settings.Title,
								 Settings.Width,
								 Settings.Height,
								 Settings.bResizable,
								 Settings.bFullscreen };
		WindowManager.Initialize(Config);

		glfwSetWindowUserPointer(WindowManager.GetWindow(), this);
		glfwSetFramebufferSizeCallback(WindowManager.GetWindow(), FramebufferResizeCallback);

		WindowManager.SetKeyCallback(KeyCallback);
	}

	static void FramebufferResizeCallback(GLFWwindow* Window, int Width, int Height)
	{
		auto App = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(Window));
		App->VkContext.SetFramebufferResized(true);
	}

	void MainLoop()
	{
		VkPhysicalDeviceProperties Properties{};
		vkGetPhysicalDeviceProperties(VkContext.DeviceManager->GetPhysicalDevice(), &Properties);

		while (!WindowManager.ShouldClose())
		{
			WindowManager.PollEvents();

			if (bShowImGui)
			{
				ImGui_ImplVulkan_NewFrame();
				ImGui_ImplGlfw_NewFrame();
				ImGui::NewFrame();
				if (bShowDemo)
				{
					ImGui::ShowDemoWindow();
				}
				else
				{
					ImGui::Begin(Properties.deviceName);
					ImGui::Text("Application average %.1f FPS", ImGui::GetIO().Framerate);
					ImGui::End();
				}
			}

			VkContext.DrawFrame(bShowImGui, SquareVertices.size(), SquareIndices.size());
		}
	}

	void Cleanup()
	{
		Log::Info("Cleaning up...");

		VkContext.Cleanup();
		WindowManager.Cleanup();
		ImGui::DestroyContext();
	}
};

int main()
{
	HelloTriangleApplication app;

	try
	{
		app.Run();
	}
	catch (const std::exception& e)
	{
		Log::Error(e.what());
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
