#include "system_class.h"
#include <iostream>
#include <shellapi.h> // Include for DragAcceptFiles and DragQueryFile
#include <windows.h>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

system_class::system_class()
{
	input_ = 0;
	application_ = 0;
	imgui_manager_ = 0;
	application_name_ = 0;
	hinstance_ = 0;
	hwnd_ = 0;
	initial_window_width_ = 0;
	initial_window_height_ = 0;
	is_direct_3d_initialized_ = false;
	
}

system_class::~system_class()
{
}

bool system_class::initialize()
{
	int screenHeight, screenWidth = 0;
	bool result;

	Logger::Get().Log("Initializing system class", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	try
	{
		// initialize the windows api.
		initialize_windows(screenWidth, screenHeight);

		// Create and initialize the input object.  This object will be used to handle reading the keyboard input from the user.
		input_ = std::make_shared<input_class>();

		result = input_->Initialize(hinstance_, hwnd_, screenWidth, screenHeight);
		if (!result)
		{
			Logger::Get().Log("Failed to initialize input class", __FILE__, __LINE__, Logger::LogLevel::Error);
			return false;
		}

		// Create and initialize the application class object.  This object will handle rendering all the graphics for this application.
		application_ = std::make_shared<application_class>();

		result = application_->initialize(screenWidth, screenHeight, hwnd_, false);
		if (!result)
		{
			return false;
		}

		is_direct_3d_initialized_ = true;

		// If we received a WM_SIZE message before Direct3D was initialized, resize the swap chain now
		if (initial_window_width_ > 0 && initial_window_height_ > 0)
		{
			application_->get_direct_3d()->resize_swap_chain(initial_window_width_, initial_window_height_);
		}

		// initialize imgui
		if(DEBUG_MODE)
		{
			imgui_manager_ = std::make_shared<imguiManager>();
			imgui_manager_->SetApp(application_);
			result = imgui_manager_->Initialize(hwnd_, application_->get_direct_3d()->get_device(), application_->get_direct_3d()->get_device_context());
			if (!result)
			{
				return false;
			}
		}
		
	}
	catch (const std::exception& e)
	{
		Logger::Get().Log(std::string("Exception caught during initialization: ") + e.what(), __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	Logger::Get().Log("System class initialized", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	return true;
}

void system_class::shutdown()
{
	Logger::Get().Log("Shutting down system class", __FILE__, __LINE__, Logger::LogLevel::Shutdown);

	std::lock_guard<std::mutex> guard(render_mutex_);

	// shutdown the window.
	shutdown_windows();

	Logger::Get().Log("System class shut down", __FILE__, __LINE__, Logger::LogLevel::Shutdown);
	
}

void system_class::run()
{
	MSG msg;
	bool done, result;

	Logger::Get().Log("Running the system", __FILE__, __LINE__);

	// initialize the message structure.
	ZeroMemory(&msg, sizeof(MSG));

	// Loop until there is a quit message from the window or the user.
	done = false;
	
	while (!done)
	{
		// Handle the windows messages.
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message == WM_QUIT)
			{
				done = true;
			}
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		// If windows signals to end the application then exit out.
		if (application_ != nullptr && application_->get_should_quit())
		{
			Logger::Get().Log("Received quit signal from application", __FILE__, __LINE__);
			done = true;
		}
		else
		{
			// Otherwise do the frame processing.
			result = frame();
			if (!result)
			{
				Logger::Get().Log("Failed to process frame", __FILE__, __LINE__, Logger::LogLevel::Error);
				done = true;
			}
		}

	}

	return;
}

bool system_class::frame()
{
	// Clear the buffers to begin the scene.
	application_->get_direct_3d()->begin_scene(0.0f, 0.0f, 0.0f, 1.0f);

	std::lock_guard<std::mutex> guard(render_mutex_);
	bool result;
	
	result = input_->Frame();
	if (!result)
	{
		Logger::Get().Log("Failed to process input frame", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Do the frame processing for the application class object.
	result = application_->frame(input_.get());
	if (!result)
	{
		Logger::Get().Log("Failed to process application frame", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	if(!input_->IsKeyDown(222))
	{
		//log the key press
		is_debug_key_pressed_ = false;
	}
	else if (input_->IsKeyDown(222) && !is_debug_key_pressed_)
	{
		// Log the key release state
		is_debug_key_pressed_ = true;
		DEBUG_MODE = !DEBUG_MODE;
	}
	
	if (DEBUG_MODE)
	{
		// render ImGui
		result = imgui_manager_->ImGuiWidgetRenderer();
		if (!result)
		{
			Logger::Get().Log("Failed to render ImGui widgets", __FILE__, __LINE__, Logger::LogLevel::Error);
			return false;
		}
	}
	
	application_->get_direct_3d()->end_scene();

	return true;
}

LRESULT CALLBACK system_class::message_handler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{

	if (ImGui_ImplWin32_WndProcHandler(hwnd, umsg, wparam, lparam))
	{
		return true;
	}

	switch (umsg)
	{
		// Check if a key has been pressed on the keyboard.
		case WM_KEYDOWN:
		{
			// If a key is pressed send it to the input object so it can record that state.
			input_->KeyDown((unsigned int)wparam);
			return 0;
		}

		// Check if a key has been released on the keyboard.
		case WM_KEYUP:
		{
			// If a key is released then send it to the input object so it can unset the state for that key.
			input_->KeyUp((unsigned int)wparam);
			return 0;
		}
		case WM_SIZE:
		{
			int newWidth = LOWORD(lparam);
			int newHeight = HIWORD(lparam);

			//  If Direct3D is initialized, update the swap chain. Otherwise, store the window dimensions
			if (is_direct_3d_initialized_ && application_ && application_->get_direct_3d())
			{
				application_->set_screen_width(newWidth);
				application_->set_screen_height(newHeight);
				application_->get_direct_3d()->resize_swap_chain(newWidth, newHeight);
			}
			else
			{
				initial_window_width_ = newWidth;
				initial_window_height_ = newHeight;
			}
			return 0;
		}
		case WM_ENTERSIZEMOVE:
		{
			is_resizing_ = true;
			break;
		}
		case WM_EXITSIZEMOVE:
		{
			is_resizing_ = false;
			break;
		}
		case WM_DROPFILES:
		{
			HDROP hDrop = reinterpret_cast<HDROP>(wparam);
			UINT numFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);

			if (numFiles > 0) {
				for (UINT i = 0; i < numFiles; ++i) {
					WCHAR filePath[MAX_PATH];
					DragQueryFile(hDrop, i, filePath, MAX_PATH);

					// Get the file extension
					std::wstring fileName = filePath;
					std::wstring extension = fileName.substr(fileName.find_last_of(L".") + 1);

					// Check if the file has a valid extension
					if (extension == L"txt" || extension == L"kobj") {
						// Handle dropped files with valid extensions
						std::wcout << L"File dropped: " << filePath << std::endl;
						application_->add_kobject(fileName);
					}
					else {
						// Handle files with invalid extensions (optional)
						std::wcout << L"Ignored file: " << filePath << std::endl;
					}
				}
			}

			DragFinish(hDrop);
			return 0;
		}
		case WM_CLOSE:
		{
			Logger::Get().Log("WM_CLOSE message received", __FILE__, __LINE__);
			application_->set_should_quit(true);
			return 0;
		}
		// Any other messages send to the default message handler as our application won't make use of them.
		default:
		{
			return DefWindowProc(hwnd, umsg, wparam, lparam);
		}

	}

	return 0;
}

void system_class::initialize_windows(int& screenWidth, int& screenHeight)
{
	WNDCLASSEX wc;
	DEVMODE dmScreenSettings;
	int posX, posY;

	Logger::Get().Log("Initializing windows", __FILE__, __LINE__, Logger::LogLevel::Initialize);
	// Get an external pointer to this object.	
	application_handle = this;

	// Get the instance of this application.
	hinstance_ = GetModuleHandle(NULL);

	// Give the application a name.
	application_name_ = L"Khaotic Engine";

	// Setup the windows class with default settings.
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc = wnd_proc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hinstance_;
	wc.hIcon = LoadIcon(hinstance_,MAKEINTRESOURCE(IDI_ICON1));
	wc.hIconSm = LoadIcon(hinstance_, MAKEINTRESOURCE(IDI_ICON1));
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = application_name_;
	wc.cbSize = sizeof(WNDCLASSEX);

	// Register the window class.
	RegisterClassEx(&wc);

	// Determine the resolution of the clients desktop screen.
	screenWidth = GetSystemMetrics(SM_CXSCREEN);
	screenHeight = GetSystemMetrics(SM_CYSCREEN);

	// Setup the screen settings depending on whether it is running in full screen or in windowed mode.
	if (full_screen)
	{
		// If full screen set the screen to maximum size of the users desktop and 32bit.
		memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
		dmScreenSettings.dmSize = sizeof(dmScreenSettings);
		dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth;
		dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
		dmScreenSettings.dmBitsPerPel = 32;
		dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		// Change the display settings to full screen.
		ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);

		// Set the position of the window to the top left corner.
		posX = posY = 0;
	}
	else
	{
		// If windowed then set it to 1600x900 resolution.
		screenWidth = 1600;
		screenHeight = 900;

		// Place the window in the middle of the screen.
		posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2;
		posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
	}

	// Create the window with the screen settings and get the handle to it.
	hwnd_ = CreateWindowEx(WS_EX_APPWINDOW, application_name_, application_name_,
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
		posX, posY, screenWidth, screenHeight, NULL, NULL, hinstance_, NULL);

	// Bring the window up on the screen and set it as main focus.
	ShowWindow(hwnd_, SW_SHOW);
	SetForegroundWindow(hwnd_);
	SetFocus(hwnd_);

	// Hide the mouse cursor.
	ShowCursor(true);

	//drag and drop 
	DragAcceptFiles(hwnd_, TRUE);

	return;
}

void system_class::shutdown_windows()
{
	Logger::Get().Log("Shutting down the windows", __FILE__, __LINE__, Logger::LogLevel::Shutdown);
	// Show the mouse cursor.
	ShowCursor(true);

	// Fix the display settings if leaving full screen mode.
	if (full_screen)
	{
		ChangeDisplaySettings(NULL, 0);
	}

	// Remove the window.
	DestroyWindow(hwnd_);
	hwnd_ = NULL;

	// Remove the application instance.
	UnregisterClass(application_name_, hinstance_);
	hinstance_ = NULL;

	// Release the pointer to this class.
	application_handle = NULL;

	return;
}

LRESULT CALLBACK wnd_proc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
	switch (umessage)
	{
		// Check if the window is being destroyed.
	case WM_DESTROY:
	{
		PostQuitMessage(0);
		return 0;
	}

	// Check if the window is being closed.
	case WM_CLOSE:
	{
		PostQuitMessage(0);
		return 0;
	}

	case WM_DROPFILES:
	{
		application_handle->message_handler(hwnd, umessage, wparam, lparam);
		return(0);
	}

	// All other messages pass to the message handler in the system class.
	default:
	{
		return application_handle->message_handler(hwnd, umessage, wparam, lparam);
	}
	}
}

void system_class::send_path(wchar_t* path, std::filesystem::path w_folder)
{
	application_->set_path(path);
	application_->set_w_folder(w_folder);
}