#include "pch.h"

// #define CHOSEN_SCENE VarsTreeParticles
// #define CHOSEN_SCENE VarsCat
// #define CHOSEN_SCENE VarsBruh
// #define CHOSEN_SCENE VarsGGJ
// #define CHOSEN_SCENE VarsExegfx
// #define CHOSEN_SCENE VarsExegfxPT
// #define CHOSEN_SCENE VarsNordlicht24
// #define CHOSEN_SCENE VarsCloudTracer
// #define CHOSEN_SCENE VarsStippling
// #define CHOSEN_SCENE VarsAbstractExpressionism
// #define CHOSEN_SCENE VarsExegfx
// #define CHOSEN_SCENE VarsOpflow
// #define CHOSEN_SCENE VarsRevision2025B
// #define CHOSEN_SCENE VarsGGJ2025
// #define CHOSEN_SCENE VarsDeadlineBiclor
// #define CHOSEN_SCENE VarsSessions24
// #define CHOSEN_SCENE VarsDeadline
// #define CHOSEN_SCENE VarsNordlicht24
// #define CHOSEN_SCENE VarsCat
#define CHOSEN_SCENE VarsJYRevis25


#include "Engine.h"

#include "audio/AudioMixer.h"
#include "Camera.h"
#include "EditorNew.h"
#include "engine/model/Model.h"
#include "engine/model/Mesh.h"
#include "Physics.h"
#include "gl/Buffer.h"
#include "gl/Framebuffer.h"
#include "gl/Shader.h"
#include "gl/ShaderManager.h"
#include "gl/ShaderProgram.h"
#include "gl/WGL.h"
// #include "passes/FXPassBloom.h"

#include "DebugDrawer.h"
#include "IO.h"
#include "WEntity.h"
#include "Drawer2D.h"
#include "Drawer3D.h"
#include "DrawerMDI.h"
// #include "ImSequencer.h"
#include "Light.h"
#include "WAlloc.h"
#include "game/Text.h"
// #include "..\scenes\SceneRevision2024B.cpp"
// #include "scenes/SceneRevision2025.h"
// #include "..\scenes\Bruh.cpp"

#ifdef LIVEPP_ENABLED
	#include "../LivePP/API/x64/LPP_API_x64_CPP.h"
#endif

// #define min(a,b) (a < b ? a : b)

#include "scenes/SceneTracer.h"
// #include "..\scenes\SceneGGJ.cpp"
#include "DrawerNew.h"
#include "MIDI.h"
#include "TexturePool.h"
#include "scenes/Scene.h"
// #include "..\scenes\SceneCloudTracer.cpp"
// #include "..\scenes\SceneCloudTracer.cpp"
// #include "scenes/SceneReSTIR.h"




void Engine::run() {
	//FrameMark("main");

	// Globals::log->log();
	auto engine = this;

	auto dbg_tex = WEngine->alloc_textures.push(TextureDesc{
		.resx = this->RESX,
		.resy = this->RESY,
		.resz = 1,
		.internal_format = InternalFormat::R32UI,
		.name = "dbg tex"
	}).ptr;

	
	CHOSEN_SCENE vars_chosen_scene;
	vars_chosen_scene.dbg_tex = dbg_tex;
	
	vars_chosen_scene.init();

	audio_mixer->set_master_volume(audio_mixer->master_vol);

	// ---------- CONFIG ---------- //
	glDisable(GL_CULL_FACE);
	do {
		FrameMarkNamed("main");
		ZoneScopedN("render");

		// listen to hot-reload and hot-restart requests

		{
			ZoneScopedN("THINGS");

			#ifdef LIVEPP_ENABLED
			if ( ( (lpp::LppSynchronizedAgent*)Globals::lppAgent)->WantsReload(lpp::LppReloadOption::LPP_RELOAD_OPTION_SYNCHRONIZE_WITH_RELOAD) ) {
				this->editor->gui_dbg_print_msg("L++ reload");
				((lpp::LppSynchronizedAgent*)Globals::lppAgent)->Reload(lpp::LppReloadBehaviour::LPP_RELOAD_BEHAVIOUR_WAIT_UNTIL_CHANGES_ARE_APPLIED);
			}

			if (((lpp::LppSynchronizedAgent*)Globals::lppAgent)->WantsRestart()) {
				// client code can do whatever it wants here, e.g. finish logging, abandon threads, etc.
				// ...
				((lpp::LppSynchronizedAgent*)Globals::lppAgent)->Restart(lpp::LPP_RESTART_BEHAVIOUR_INSTANT_TERMINATION, 0u, nullptr);
			}
			#endif
			
			this->start_frame();

			if(!editor->renderingPaused) {
				ZoneScopedN("SCENE");
				vars_chosen_scene.loop();
			}
			
			this->end_frame();
		}
		
		if(!editor->renderingPaused) {
			SwapBuffers(wgl->hDC);
		}

		// TracyGpuCollect;
	} while (!WEngine->editor->finished);

	//LeakTracker::memTracker.~MemTracker();
	DestroyWindow(wgl->hwnd);
}



void Engine::reset() {
	for (auto entity : this->entitties) {
		this->objects_to_destroy.push_back(entity.second);
	}

	for (auto entity : this->objects_to_destroy) {
		this->destroy_entity(entity);
	}
	this->objects_to_destroy.resize(0);

	this->alloc_game_object.clear();

	// TO POP FROM TEXTURES
	std::vector<Texture*> textures_to_destroy;

	for (int i = 0; i <= this->alloc_lights.max_slot; i++) {
		if (this->alloc_lights.slot_active[i]) {
			Light& light = this->alloc_lights.arr[i];
			textures_to_destroy.push_back(light.fb->depth_texture);
			light.fb->destroy();
			// light.fb->depth_texture->destroy();
		}
	}

	for (int i = 0; i <= this->alloc_textures.max_slot; i++) {
		if (this->alloc_textures.slot_active[i]) {
			Texture* tex = &this->alloc_textures.arr[i];
			if (tex->is_model_texture) {
				textures_to_destroy.push_back(tex);
			}
		}
	}

	for (Texture* tex : textures_to_destroy) {
		tex->destroy();
		this->alloc_textures.pop(*tex);
	}

	this->alloc_lights.clear();

	auto try_delete_mesh = [](Mesh* mesh) {
		if (mesh) {
			delete mesh;
		}
	};
	for (auto mesh : WEngine->scene_model->meshes) {
		try_delete_mesh(mesh);
	}
	for (auto mesh : WEngine->scene_model->mesh_outlines) {
		try_delete_mesh(mesh);
	}

	delete[] WEngine->scene_model->buff_normals->cpu_data;
	delete[] WEngine->scene_model->buff_tex_coords->cpu_data;
	delete[] WEngine->scene_model->buff_vert_indices->cpu_data;
	delete[] WEngine->scene_model->buff_verts->cpu_data;

	WEngine->scene_model->buff_normals->destroy();
	WEngine->scene_model->buff_tex_coords->destroy();
	WEngine->scene_model->buff_vert_indices->destroy();
	WEngine->scene_model->buff_verts->destroy();

	delete WEngine->scene_model->buff_normals;
	delete WEngine->scene_model->buff_tex_coords;
	delete WEngine->scene_model->buff_vert_indices;
	delete WEngine->scene_model->buff_verts;

	WEngine->scene_model->textures.resize(0);
	WEngine->scene_model->nodes.resize(0);
	WEngine->scene_model->meshes.resize(0);
	WEngine->scene_model->mesh_outlines.resize(0);

	WEngine->scene_model->lights.resize(0);
	WEngine->scene_model->nodes.resize(0);


	WEngine->scene_model->re_create();

	initializing = true;
}

// for(Texture& tex : this->textures) {
//
// }

// for(auto light : this->alloc_lights) {
// 	// this->destroy_entity(light.se);
// }

// objects
// lights
// physics
// editor

// models/meshes
// textures
// buffers

// pre-init
// init
// loop
// - logic
// - render


#define IDC_RESOLUTION  101
#define IDC_FULLSCREEN  102
#define IDC_ONTOP       103
#define IDC_VSYNC       104
#define IDC_MULTI       105
#define IDOK            106
#define IDCANCEL        107

static int selectedWidth = 0;
static int selectedHeight = 0;
static HWND hwndPicker;
static bool pressed_start = false;

static int resolutions_count = 0;
static int selected_res_idx = 0;

typedef struct {
	int w,h;
} RES;

static constexpr int MSAA_SAMPLES_CNT = 6;
static constexpr int MAXNUMRES = 4096;
static RES resolutions[MAXNUMRES];
// static int msaa_samples[MSAA_SAMPLES_CNT] = {1,2,4,8,16,32};

Engine::Engine() {
	this->texture_pool = new TexturePool();
	// collided_game_objects = WArray<GameObject*>({}, 10000);
	// collisions
	this->lights.reserve(100);
	collisions.reserve(10000);
	objects_to_destroy.reserve(5000);

	// this->framebuffers = new WArray<Framebuffer*>({}, 100);
	// this->textures = new WArray<Texture*>({}, 100);
	
	// selectedWidth = 1280;
	// selectedHeight = 720;
	
	selectedWidth = 1920;
	selectedHeight = 1080;
	
	Globals::global = new Globals();
	Globals::engine = this;
	// this->RESX = XRES;
	// this->RESY = YRES;
	#if defined(DISTRIB) && defined(RES_PICKER)
		// -- Resolution picker
		{
			HINSTANCE hInstance = GetModuleHandle(NULL);
			// Register the window class
			WNDCLASS wc = {0};
		
			// constexpr bool msaa_enabled = true;
			constexpr bool msaa_enabled = false;
			
			WEngine->fullscreen = true;

		
			#define DEMO_NAME "座標轉移"
			#define DEMO_NAME_L L"座標轉移"
		
			#define DEMO_NAME "Chuliau"
			#define DEMO_NAME_L L"Chuliau"
			// #define DEMO_NAME "bruh i ain't naming things"
			// #define DEMO_NAME_L L"bruh i ain't naming things"
			#define fontname "Tahoma"
			// #define fontname "Meiryo"
		
			#define charset SHIFTJIS_CHARSET
			// #define charset CHARSET 
			// #define charset DEFAULT_CHARSET
		
		
		
			wc.lpfnWndProc = [](HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT {
				static HWND combo_box_resolutions;  // Static variable to persist between messages
				static HWND combo_box_msaa;  // Static variable to persist between messages

				glm::ivec2 resolutions_combobox_pos = ivec2(120, 80);
				glm::ivec2 msaa_combobox_pos = ivec2(120, 60);
				glm::ivec2 title_pos = ivec2(150/2 - 22, 26);
				
				
				if(msaa_enabled){
					resolutions_combobox_pos.y += 5;
				} else {
					title_pos = ivec2(150/2, 30);
					title_pos.x -= 24;
				}

				
				switch (uMsg) {
					case WM_CREATE: {
						// Create font
						HFONT hFont = CreateFont(15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, charset, OUT_DEFAULT_PRECIS,
								CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, fontname);
						
						HFONT hFontBold = CreateFont(20, 0, 0, 0, 
						// FW_BOLD,
						FW_NORMAL,
						 FALSE, FALSE, FALSE, charset, OUT_DEFAULT_PRECIS,
						// HFONT hFontBold = CreateFont(18, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, charset, OUT_DEFAULT_PRECIS,
								CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, 
								// "Meiryo"
								// "Microsoft YaHei"
								fontname
							);

					
						if (hFont == NULL) {
							MessageBox(NULL, "Font Creation Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);
						}

						
						// HWND hTitle = CreateWindowExA(0 ,"STATIC",
						// 	// "Compowinner name",  WS_VISIBLE | WS_CHILD | SS_CENTER,
						// 	// "lai -> khi",  WS_VISIBLE | WS_CHILD | SS_CENTER,
						// 	// "khromos group",  WS_VISIBLE | WS_CHILD | SS_CENTER,
						// 	// "bruh i ain't naming things",  WS_VISIBLE | WS_CHILD | SS_CENTER,
						// 	"座標轉移",  WS_VISIBLE | WS_CHILD | SS_CENTER,
						// 	// 150/2, 30, 150, 150,
						// 	// 150/2 - 22, 30, 200, 150,
						// 	title_pos.x, title_pos.y, 200, 150,
						// 	
						// 	hwnd, (HMENU)1, NULL, NULL
						// );
						HWND hTitle = CreateWindowExW(0 ,L"STATIC",
							// "Compowinner name",  WS_VISIBLE | WS_CHILD | SS_CENTER,
							// "lai -> khi",  WS_VISIBLE | WS_CHILD | SS_CENTER,
							// "khromos group",  WS_VISIBLE | WS_CHILD | SS_CENTER,
							// "bruh i ain't naming things",  WS_VISIBLE | WS_CHILD | SS_CENTER,
							// L"座標轉移",  WS_VISIBLE | WS_CHILD | SS_CENTER,
							DEMO_NAME_L,  WS_VISIBLE | WS_CHILD | SS_CENTER,
							// 150/2, 30, 150, 150,
							// 150/2 - 22, 30, 200, 150,
							title_pos.x, title_pos.y, 200, 150,
							
							hwnd, (HMENU)1, NULL, NULL
						);
						
						SendMessage(hTitle, WM_SETFONT, (WPARAM)hFontBold, MAKELPARAM(TRUE, 0));


						
						// Create a dropdown (combo box)
						combo_box_resolutions = CreateWindowEx(0 ,"COMBOBOX",
							"ResPick",  WS_VISIBLE | WS_CHILD | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS | WS_EX_CLIENTEDGE,
							resolutions_combobox_pos.x, resolutions_combobox_pos.y, 150, 150,
							hwnd, (HMENU)6, NULL, NULL
						);
						if(msaa_enabled){
							combo_box_msaa = CreateWindowEx(0 ,"COMBOBOX",
								"MsaaPick",  WS_VISIBLE | WS_CHILD | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS | WS_EX_CLIENTEDGE,
								msaa_combobox_pos.x, msaa_combobox_pos.y, 150, 150,
								hwnd, (HMENU)7, NULL, NULL
							);
						}

						
						HWND hComboBoxTitle = CreateWindowEx(0 ,"STATIC",
							"Resolution:",  WS_VISIBLE | WS_CHILD | SS_LEFT,
							24, resolutions_combobox_pos.y + 2, 70, 20,
							hwnd, (HMENU)11, NULL, NULL
						);

						
						if(msaa_enabled) {
							HWND combo_box_msaa_title = CreateWindowEx(0 ,"STATIC",
								"Msaa:",  WS_VISIBLE | WS_CHILD | SS_LEFT,
								30, msaa_combobox_pos.y + 2, 70, 20,
								hwnd, (HMENU)21, NULL, NULL
							);
							SendMessage(combo_box_msaa_title, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
							SendMessage(combo_box_msaa, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
						}
						
						
						SendMessage(hComboBoxTitle, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
						SendMessage(combo_box_resolutions, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
						
						{
							char s[500];
							int i=0;

							DEVMODE d;
							EnumDisplaySettings(NULL,0,&d);
							while(1) {
								BOOL h = EnumDisplaySettings(NULL,i++,&d);
								if (!h || resolutions_count>MAXNUMRES) break;
        
								/*** You can use this following line to select only certain aspect ratios ***/
								// if ((d.dmPelsWidth * 3) / 4 != d.dmPelsHeight) continue;
								
								if(
									abs(float(d.dmPelsWidth) / float(d.dmPelsHeight) - 16./9.) > 0.002 ){
									continue;
								}

								/*** You can use this following line to avoid "rotated" pictures on e.g. laptops ***/
								//        if (d.dmDisplayOrientation != DMDO_DEFAULT) continue;

								// d.dmDisplayFrequency
								if (d.dmBitsPerPel != 32) continue; // 32bit required? ok?
								if (!resolutions_count || resolutions[resolutions_count-1].w != d.dmPelsWidth || resolutions[resolutions_count-1].h != d.dmPelsHeight) {
									
									resolutions[resolutions_count].w = d.dmPelsWidth;
									resolutions[resolutions_count].h = d.dmPelsHeight;
									resolutions_count++;
									_snprintf(s,500,"%d * %d",d.dmPelsWidth,d.dmPelsHeight);
									// SendDlgItemMessage(hWnd, IDC_RESOLUTION, CB_ADDSTRING, 0, (LPARAM)s);
									
									// SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)_T("1080p (1920x1080)\0"));
									SendMessage(combo_box_resolutions, CB_ADDSTRING, 0, (LPARAM)s);
								}
							}
							
							selected_res_idx = resolutions_count - 1;
							for (i=0; i<resolutions_count; i++){
								if (resolutions[i].w==1920 && resolutions[i].h==1080){
									selected_res_idx = i;
								}
							}
							
							SendMessage(combo_box_resolutions, CB_SETCURSEL, selected_res_idx, 0);
								// if (ress[i].w==setupcfg->scrWidth && ress[i].h==setupcfg->scrHeight)
						}
						if(msaa_enabled) {
							int samples = 1;
							for(int i = 0; i < MSAA_SAMPLES_CNT; i++){
								std::string msaa_sample_string = std::to_string(samples);
								SendMessage(combo_box_msaa, CB_ADDSTRING, 0, (LPARAM)msaa_sample_string.c_str());
								// msaa_samples
								samples *= 2;
							}
							SendMessage(combo_box_msaa, CB_SETCURSEL, 4, 10);
						}
						// Add resolutions to the combo box
						// SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)_T("720p (1280x720)\0"));
						// SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)_T("1080p (1920x1080)\0"));
						// SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)_T("1440p (2560x1440)\0"));
						// SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)_T("4k (3840x2160)\0"));
						// SendMessage(hComboBox, CB_ADDSTRING, 0, L"720p (1280x720)");
						// SendMessage(hComboBox, CB_ADDSTRING, 0, "1080p (1920x1080)");

						// ------------------------ Set the default selection ------------------------ //
						// ------------------------ Set the default selection ------------------------ //
						// ------------------------ Set the default selection ------------------------ //
						// ------------------------ Set the default selection ------------------------ //
						// ------------------------ Set the default selection ------------------------ //
						// SendMessage(hComboBox, CB_SETCURSEL, 1, 0);

						
						// Create a checkbox
						HWND hCheckbox = CreateWindowEx(0,"BUTTON", "Fullscreen", WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX | WS_TABSTOP | BS_FLAT,
								23, 110, 150, 30, hwnd, (HMENU)3, NULL, NULL);

						
						HWND hCheckboxB = CreateWindowEx(0,"BUTTON", "Borderless", WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX | WS_TABSTOP | BS_FLAT,
								190, 110, 150, 30, hwnd, (HMENU)5, NULL, NULL);
						
						SendMessage(hCheckbox, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
						SendMessage(hCheckboxB, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));

						SendMessage(hCheckbox, BM_SETCHECK, WEngine->fullscreen ? BST_CHECKED : BST_UNCHECKED, 0);
						SendMessage(hCheckboxB, BM_SETCHECK, WEngine->borderless ? BST_CHECKED : BST_UNCHECKED, 0);
						// 

						// Check the checkbox by default
						// SendMessage(hCheckbox, BM_SETCHECK, BST_CHECKED, 0);

						// Create a "Start" button
						HWND hQuit = CreateWindowEx(0,"BUTTON", "Quit", WS_VISIBLE | WS_CHILD | BS_FLAT, 20, 150, 120, 30, hwnd, (HMENU)4, NULL, NULL);
						HWND hStart = CreateWindowEx(0,"BUTTON", "Start", WS_VISIBLE | WS_CHILD | BS_FLAT, 150, 150, 120, 30, hwnd, (HMENU)2, NULL, NULL);
						
						SendMessage(hStart, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
						SendMessage(hQuit, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));

						
						break;
					}
					case WM_COMMAND: {
						if (LOWORD(wParam) == 3) {
							WEngine->fullscreen = IsDlgButtonChecked(hwnd, 3) == BST_CHECKED;
						} else if (LOWORD(wParam) == 5) {
							WEngine->borderless = IsDlgButtonChecked(hwnd, 5) == BST_CHECKED;
						}  else if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == 6) {
							int index = SendMessage(combo_box_resolutions, CB_GETCURSEL, 0, 0);

							// Set the selected resolutions based on the index
							selected_res_idx = index;

							selectedWidth = resolutions[selected_res_idx].w;
							selectedHeight = resolutions[selected_res_idx].h;
						}  else if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == 7) {
							int samples = 1;
							for(int i = 0; i < SendMessage(combo_box_msaa, CB_GETCURSEL, 0, 0); i++){
								samples *= 2;
							}
							WEngine->msaa_samples = samples;
						} else if (LOWORD(wParam) == 4 && HIWORD(wParam) == BN_CLICKED) {
							ExitProcess(0);
						} else if (LOWORD(wParam) == 2 && HIWORD(wParam) == BN_CLICKED) {
							// "Start" button clicked
							// Close the window and return control to the main program
							pressed_start = true;
							DestroyWindow(hwnd);
							// DestroyWindow(hwndPicker);
						}
						break;
					}
					case WM_QUIT: {
						ExitProcess(0);
					}
					case WM_DESTROY: {
						if(!pressed_start) {
								ExitProcess(0);
						}
						// Quit the application when the window is closed
						// ExitProcess(0);
						// PostQuitMessage(0);
						break;
					}
					default:
						return DefWindowProc(hwnd, uMsg, wParam, lParam);
				}
				return 0;
			};
		


		
			// Adjust window dimensions based on DPI scaling
			SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
		
			int screenWidth = GetSystemMetrics(SM_CXSCREEN);
			int screenHeight = GetSystemMetrics(SM_CYSCREEN);
			int windowWidth = 300; // Adjust as needed
			int windowHeight = 200; // Adjust as needed

			int xPos = (screenWidth - windowWidth) / 2;
			int yPos = (screenHeight - windowHeight) / 2;
		
			wc.hInstance = hInstance;
			wc.lpszClassName = "ResolutionPicker";
			wc.hbrBackground = (HBRUSH)(COLOR_WINDOW );
			// wc.hbrBackground = CreateSolidBrush(0x000000ff); 
			RegisterClass(&wc);


			// Create the window
			// hwndPicker = CreateWindowW(
			// 	L"ResolutionPicker", L" ",  WS_OVERLAPPEDWINDOW | CS_HREDRAW | CS_VREDRAW,
			// 	xPos, yPos, windowWidth, windowHeight, NULL, NULL, hInstance, NULL
			//  );
			hwndPicker = CreateWindowEx(
				0,
				"ResolutionPicker", " ",  WS_SYSMENU | WS_POPUP | WS_MINIMIZEBOX,
				xPos, yPos, windowWidth, windowHeight, NULL, NULL, hInstance, NULL
			 );
			 
			// Create font
			HFONT hFont = CreateFont(8, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, charset, OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, fontname);

		
			if (hFont == NULL) {
				MessageBox(NULL, "Font Creation Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);
				return;
			}

			SendMessage(hwndPicker, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
		


			// Show the window
			ShowWindow(hwndPicker, SW_SHOWNORMAL);
			UpdateWindow(hwndPicker);

			// Run the message loop
			MSG msg = {0};
			while (GetMessage(&msg, NULL, 0, 0) && IsWindow(hwndPicker)) {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			
			this->RESX = selectedWidth;
			this->RESY = selectedHeight;
		}
	#else
		this->RESX = XRES;
		this->RESY = YRES;
	#endif
	

	this->shader_manager = new ShaderManager();


	this->io = new IO();
	this->editor = new Editor();
	this->wgl = new WGL();

	auto map = WDumbMap<SIMDString<>, 32>();
	map.set("SHADOW_MAP", "");
	this->passthrough_frag_shader = new Shader("engine/passthru.frag", map);
	// ????
	// this->shadowmap_geom_shader = new Shader("engine/geom_shader.geom", map);
	this->vert_sh_fullscreen_quad = new Shader("engine/fullscreen_quad.vert");
	this->prog_clean_dbg = new ShaderProgram( {
			new Shader("engine/clean_dbg.comp")
	});
	this->prog_blit_texture = new ShaderProgram( {
			new Shader("engine/blit_texture.comp")
	});
	this->prog_gif = new ShaderProgram( {
		this->vert_sh_fullscreen_quad,
		new Shader("engine/gif.frag")
	});

	// #ifdef EDITOR
	this->editor->init();
	// #endif

	this->default_fb = Framebuffer::create_default_framebuffer();

	this->camera = new Camera();


	uint8_t val[] = {0, 0, 0};

	// this->shared_ssbo = new Buffer(BuffDesc{.byte_len = 2048 * 16, .immutable_subdata = false, .name = "Buff shared"});
	this->shared_ssbo = new Buffer(BuffDesc{
		.byte_len = megabyte_in_bytes*2, 
		.subdata_disabled = false, 
		.name = "Buff shared"
	});
	
	this->lights_ssbo = new Buffer(BuffDesc{.byte_len = megabyte_in_bytes * 8, .subdata_disabled = false, .name = "Buff lights"});


	// glClearNamedBufferSubData( this->shared_ssbo->pid, GL_R8I, 0, 2048 * 4, GL_RED_INTEGER, GL_BYTE, &val[0] );
	// glClearNamedBufferSubData( this->lights_ssbo->pid, GL_R8I, 0, 2048 * 4, GL_RED_INTEGER, GL_BYTE, &val[0] );

	this->shared_ssbo->bind_as_SSBO(0);
	this->lights_ssbo->bind_as_SSBO(2);


	this->debug_drawer = new DebugDrawer();
	this->drawer_2d = new Drawer2D();
	this->drawer_3d = new Drawer3D();
	this->drawer_mdi = new DrawerMDI();
	this->drawer_new = new Drawer();


	// delta_time = (double)(clock()) / CLOCKS_PER_SEC;
	this->timer = clock();

	audio_mixer = new AudioMixer();
}



void Engine::update_time() {
	float deltaTime_f = static_cast<float>(delta_time);
	// deltaTime_f = min(deltaTime_f, 0.1f);
	if (deltaTime_f > 0.1) {
		deltaTime_f = 0.1f;
	}
	static clock_t newClock;
	newClock = clock();
	delta_time = double(newClock - timer) / double(CLOCKS_PER_SEC);
	delta_time_internal = delta_time;
	// this->editor->music_time += delta_time;
	
	if(this->editor->renderingPaused) {
		delta_time = 0.0;
	} else if(this->editor->timePaused) {
		delta_time = 0.0;
		frame += 1;
	}else {
		frame += 1;
	}
	
	timer = newClock;
	// time = double(newClock - this->timerStart) / double(CLOCKS_PER_SEC);
	delta_time = glm::min(delta_time, 0.1);
	time += delta_time;
	
	this->editor->music_time = WEngine->time / 60.0f * this->editor->timeline.bpm;
	this->editor->verse_time = WEngine->editor->music_time / 32.0f;
}

void Engine::update_shared_uniforms() {
	ZoneScopedN("UPDATE SHARED UNIFORMS");
	// float* buff_ptr = (float*)this->shared_ssbo->mapped_buff;
	float* buff_ptr = (float*)this->shared_ssbo->cpu_data;

	const int offs_from_start = 256*4;
	// const int offs_from_start = 64*2;

	for (int i = 0; i < 16; i++) {
		(buff_ptr)[offs_from_start + i] = (&this->camera->V[0][0])[i];
		(buff_ptr)[offs_from_start + 16 + i] = (&this->camera->P[0][0])[i];
		(buff_ptr)[offs_from_start + 32 + i] = (&this->camera->V_prev[0][0])[i];
		(buff_ptr)[offs_from_start + 48 + i] = (&this->camera->P_prev[0][0])[i];
		(buff_ptr)[offs_from_start + 64 + i] = (&this->camera->V_inv[0][0])[i];
		(buff_ptr)[offs_from_start + 80 + i] = (&this->camera->P_inv[0][0])[i];
	}
	int offs = offs_from_start + 64 + 16 + 16;

	// vec4
	(buff_ptr)[offs++] = camera->pos.x;
	(buff_ptr)[offs++] = camera->pos.y;
	(buff_ptr)[offs++] = camera->pos.z;
	(buff_ptr)[offs++] = (float)this->delta_time;
	// offs++; // padding

	// vec3
	(buff_ptr)[offs++] = camera->dir.x;
	(buff_ptr)[offs++] = camera->dir.y;
	(buff_ptr)[offs++] = camera->dir.z;
	(buff_ptr)[offs++] = camera->fov;

	// vec2
	(buff_ptr)[offs++] = camera->curr_jitt_x;
	(buff_ptr)[offs++] = camera->curr_jitt_y;
	(buff_ptr)[offs++] = camera->prev_jitt_x;
	(buff_ptr)[offs++] = camera->prev_jitt_y;

	// vec2
	(buff_ptr)[offs++] = (float)this->RESX;
	(buff_ptr)[offs++] = (float)this->RESY;
	(buff_ptr)[offs++] = this->io->mouse_ndc.x;
	(buff_ptr)[offs++] = this->io->mouse_ndc.y;

	// vec2 + 2x float
	(buff_ptr)[offs++] = this->io->mouse_uv.x;
	(buff_ptr)[offs++] = this->io->mouse_uv.y;
	(buff_ptr)[offs++] = (float)time;
	(buff_ptr)[offs++] = this->camera->_near;

	// 2x float + int
	(buff_ptr)[offs++] = this->camera->_far;
	(buff_ptr)[offs++] = this->ambient_light_intensity;
	(buff_ptr)[offs++] = std::bit_cast<float>(uint32_t(this->frame));
	(buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse_lmb_down));

	// 4x int
	(buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse_lmb_just_pressed));
	(buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse_rmb_down));
	(buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse_rmb_just_pressed));
	(buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse_mmb_just_pressed));
	// offs++;
	// (buff_ptr)[offs++] = std::bit_cast<float>(int(this->io->mouse));

	if(WEngine->scene_model) {
		// 4x uint64
		*(uint64_t*)(&buff_ptr[offs]) = WEngine->scene_model->buff_vert_indices->bindless_address;
		offs++;
		offs++;
		*(uint64_t*)(&buff_ptr[offs]) = WEngine->scene_model->buff_verts->bindless_address;
		offs++;
		offs++;
		*(uint64_t*)(&buff_ptr[offs]) = WEngine->scene_model->buff_normals->bindless_address;
		offs++;
		offs++;
		*(uint64_t*)(&buff_ptr[offs]) = WEngine->scene_model->buff_tex_coords->bindless_address;
		offs++;
		offs++;
	}


	// int* indices_buff;
	// vec3* verts_buff;
	// vec3* normals_buff;
	// vec2* texcoords_buff;

	// *(uint64_t*)(&buff_ptr[offs]) = WEngine->scene_model->buff_vert_indices->bindless_address;
	// offs++;
	// offs++;

	// (buff_ptr)[offs++] = WEngine->scene_model->buff_vert_indices->bindless_address;
	// (buff_ptr)[offs++] = std::bit_cast<float>(
	// 	static_cast<uint32_t>(WEngine->scene_model->buff_vert_indices->bindless_address >> 32)
	// );
	// (buff_ptr)[offs++] = std::bit_cast<float>(
	// 	static_cast<uint32_t>(WEngine->scene_model->buff_vert_indices->bindless_address & 0xFFFFFFFF)
	// );

	// shader_p->setUniform("indices_buff", WEngine->scene_model->buff_vert_indices);
	// shader_p->setUniform("verts_buff", WEngine->scene_model->buff_verts);
	// shader_p->setUniform("texcoords_buff", WEngine->scene_model->buff_tex_coords);
	// shader_p->setUniform("normals_buff", WEngine->scene_model->buff_normals);


	{
		float* lights_ssbo_ptr = (float*)this->lights_ssbo->cpu_data;
		int lights_ssbo_offs = 0;
		(lights_ssbo_ptr)[lights_ssbo_offs++] = std::bit_cast<float>(this->alloc_lights.alloc_cnt);
		(lights_ssbo_ptr)[lights_ssbo_offs++] = 0;
		(lights_ssbo_ptr)[lights_ssbo_offs++] = 0;
		(lights_ssbo_ptr)[lights_ssbo_offs++] = 0;

		for (int i = 0; i < 64; i++) {
			if (i <= this->alloc_lights.max_slot && this->alloc_lights.slot_active[i]) {
				((int*)lights_ssbo_ptr)[lights_ssbo_offs++] = this->alloc_lights.arr[i].fb->depth_texture->bindless_idx;
			} else {
				lights_ssbo_offs++;
			}
		}

		auto write_mat = [](float* buff_ptr, glm::mat4 mat, int& offs) {
			float* mat_ptr = &mat[0][0];
			for (int i = 0; i < 16; i++) {
				(buff_ptr)[offs++] = *mat_ptr++;
			}
		};
		auto write_light = [&](Light& light) {
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.pos.x;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.pos.y;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.pos.z;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = std::bit_cast<float>((int)light.type);
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.near_plane;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.far_plane;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.attenuation;
			lights_ssbo_offs++;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.colour.x * light.intensity;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.colour.y * light.intensity;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.colour.z * light.intensity;
			lights_ssbo_offs++;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.dir.x;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.dir.y;
			(lights_ssbo_ptr)[lights_ssbo_offs++] = light.dir.z;
			lights_ssbo_offs++;


			write_mat(lights_ssbo_ptr, light.view_mat, lights_ssbo_offs);
			write_mat(lights_ssbo_ptr, light.proj_mat, lights_ssbo_offs);
			if (light.type == LightType::POINT) {
				for (auto mat : light.view_mats_pointlight) {
					write_mat(lights_ssbo_ptr, mat, lights_ssbo_offs);
				}
			} else {
				lights_ssbo_offs += 16 * 6;
			}
		};


		{
			ZoneScopedN("LIGHT MATS");
			for (int i = 0; i <= this->alloc_lights.max_slot; i++) {
				if (this->alloc_lights.slot_active[i]) {
					Light& light = this->alloc_lights.arr[i];
					light.calc_matrices();
					write_light(light);
				}
			}
		}
		// for(Light& light : this->lights) {
		// 	write_light(light);
		// }

		glNamedBufferSubData(this->lights_ssbo->pid, 0, lights_ssbo_offs * 4, (void*)this->lights_ssbo->cpu_data);
	}

	// glNamedBufferSubData(this->shared_ssbo->pid,256*4, (offs-256)*4, (void*)buff_ptr);
	glNamedBufferSubData(this->shared_ssbo->pid, 0, offs * 4, (void*)buff_ptr);
}

void Engine::handle_winapi_start_frame() {
	MSG msg;
	ZeroMemory(&msg, sizeof(msg)); // TODO: move this outside of while()

	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		if (msg.message == WM_CLOSE || msg.message == WM_QUIT) {
			//editor->done = true;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}


void Engine::start_frame() {
	ZoneScopedN("START FRAME");
	// if(!)

	
	this->update_time();
	this->handle_winapi_start_frame();

	if(editor->renderingPaused) {
		Sleep(40);
	} 
		
	
	if(this->io->mouse_lmb_just_pressed) {
		this->editor->toEndPausedRendering = true;
	}

	// state.delta_time = ImGui::GetIO().DeltaTime;
	//Editor::editor->handleEvents();
	//trackPosition = wavTrack->getTime();


	editor->start_frame();


	this->io->handle_mouse();


	this->camera->update();
	this->update_shared_uniforms();
	
	for(MidiFile* midi_file : this->midi_files){
		midi_file->get_note_info_internal(WEngine->editor->music_time, true);
	}
	
	#ifdef DBG_PRINT
	if(!editor->renderingPaused && this->frame > 5) {
		this->prog_clean_dbg->use();
		this->prog_clean_dbg->dispatch_compute(1, 1, 1);
		glMemoryBarrier(GL_ALL_BARRIER_BITS);
	}
	#endif
}

void Engine::start_timer_query(const char* name) {
	#ifdef EDITOR
	this->editor->start_timer_query(name);
	// glPushDebugGroup()
	#endif
}

Texture* Engine::get_tex(TextureDesc target_desc) {
	return this->texture_pool->get_tex(target_desc);
}

Framebuffer* Engine::get_framebuffer(TextureDesc target_desc) {
	return this->texture_pool->get_framebuffer(target_desc);
}


void Engine::destroy_entity(WEntity* entity) {
	if (entity->physics_active) {
		// WEngine->physics.body_interface->DeactivateBody(game_object->jph_body_id);
		WEngine->physics.body_interface->RemoveBody(entity->jph_body_id);
		// WEngine->physics.body_interface->DeactivateBody(game_object->jph_body_id);
		WEngine->physics.body_interface->DestroyBody(entity->jph_body_id);
	}
	WEngine->body_to_game_object.erase(entity->jph_body_id);
	WEngine->entitties.erase(entity->game_obj_id);

	alloc_game_object.pop(*entity);
}

void Engine::end_frame() {
	ZoneScopedN("END FRAME");

	// BlendState{
	// 	.equation = BlendEquation::Add,
	// 	.src_factor = BlendFactor::SrcAlpha,
	// 	.dst_factor = BlendFactor::OneMinusSrcAlpha
	// }.use();
	
	drawer_new->reset();
	
	
	glBlendEquationSeparate((GL_FUNC_ADD), (GL_FUNC_ADD));
	glBlendFuncSeparate(
		GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
		GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
	);
	// glBlendEquation(GLenum(this->equation));
	
	this->texture_pool->end_frame();
	this->drawer_2d->reset();
	this->drawer_3d->reset();
	// if(this->text_drawer){
	// 	this->text_drawer->reset_end_of_frame();
	// }
	
	this->shader_manager->shader_just_reloaded = false;
	this->camera->camera_moved_this_frame = false;
	
	#ifdef EDITOR
		this->editor->end_frame();
	#endif

	if (this->io->get_key(Key::LAlt).down && this->io->get_key(Key::F4).just_pressed) {
		this->editor->finished = true;
	}

	this->io->end_frame();

	// if(this->io->get_key(Key::LAlt).down && this->io->get_key(Key::F4).just_pressed) {


	for (WEntity* game_object : Globals::engine->objects_to_destroy) {
		destroy_entity(game_object);
	}
	this->objects_to_destroy.resize(0);


	if (this->to_reset_whole_engine || this->scene_reloaded.load()) {
		this->reset();
		this->to_reset_whole_engine = false;
		this->scene_reloaded.store(false);
	}

	if(!editor->renderingPaused) {
		for (Framebuffer* fb : Globals::engine->framebuffers) {
			if (fb->autopong && fb->pongable) {
				fb->pong_idx = 1 - fb->pong_idx;
			}
		}
	}

	if(editor->toStartPausedRendering) {
		SetWindowTextA(this->wgl->hwnd, "--------- PAUSED RENDERING ---------");
		editor->renderingPaused = true;
	} else if(editor->toEndPausedRendering) {
		#ifdef DISTRIB
			SetWindowTextA(this->wgl->hwnd, DEMO_NAME);
		#else
			SetWindowTextA(this->wgl->hwnd, "Levwrightian");
		#endif
		editor->renderingPaused = false;
	}
	#ifdef DISTRIB
		if(this->fullscreen){
			SetCursor(NULL);
		}
	#endif
	editor->toStartPausedRendering = false;
	editor->toEndPausedRendering = false;
	bytes_upload_this_frame = 0;
}
