#if INTELLISENSE_DIRECTIVES
#include "platform/platform.hpp"
#include "engine/engine.hpp"
#include "engine/engine_to_platform_api.hpp"
#include "engine/gen/engine_symbols.gen.hpp"
#include "win32.hpp"
#include "jsl.hpp"
#endif
/*
	TODO : This is not a final platform layer

	- Saved game locations
	- Getting a handle to our own executable file
	- Asset loading path
	- Threading (launch a thread)
	- Raw Input (support for multiple keyboards)
	- ClipCursor() (for multimonitor support)
	- QueryCancelAutoplay
	- WM_ACTIVATEAPP (for when not active)
	- Blit speed improvemnts (BitBlt)
	- Hardware acceleration ( OpenGL or Direct3D or both )
	- GetKeyboardLayout (for French keyboards, international WASD support)
*/

NS_PLATFORM_BEGIN
using namespace win32;

struct PlatformContext
{
	using_context()


};

// This is the "backbuffer" data related to the windowing surface provided by the operating system.
struct OffscreenBuffer
{
	BITMAPINFO info;
	char       _PAD_[4];
	void*      memory; // Lets use directly mess with the "pixel's memory buffer"
	s32        width;
	s32        height;
	s32        pitch;
	s32        bytes_per_pixel;
};

struct WinDimensions
{
	u32 width;
	u32 height;
};

#pragma region Static Data
global PlatformContext Platform_Context;

global StrPath Path_Root;
global StrPath Path_Binaries;
global StrPath Path_Content;
global StrPath Path_Scratch;

// TODO(Ed) : This is a global for now.
global b32 Running = false;

global b32             Show_Windows_Cursor;
global HCURSOR         Windows_Cursor;
global WINDOWPLACEMENT Window_Position;

global WinDimensions   Window_Dimensions;
global OffscreenBuffer Surface_Back_Buffer;

constexpr u64 Tick_To_Millisecond = 1000;
constexpr u64 Tick_To_Microsecond = 1000 * 1000;

global u64 Performance_Counter_Frequency;

// As of 2023 the highest refreshrate on the market is 500 hz. I'll just make this higher if something comes out beyond that...
constexpr u32 Monitor_Refresh_Max_Supported = 500;

// Anything at or below the high performance frame-time is too low latency to sleep against the window's scheduler.
constexpr f32 High_Perf_Frametime_MS = 1000.f / 240.f;

global u32 Monitor_Refresh_Hz     = 60;
global u32 Engine_Refresh_Hz      = Monitor_Refresh_Hz;
global f32 Engine_Frame_Target_MS = 1000.f / scast(f32, Engine_Refresh_Hz);
#pragma endregion Static Data


void impl_congrats( char const* message )
{
	JslSetLightColour( 0, (255 << 16) | (215 << 8) );
	MessageBoxA( 0, message, "Congratulations!", MB_OK | MB_ICONEXCLAMATION );
	JslSetLightColour( 0, (255 << 8 ) );
}

internal
FILETIME file_get_last_write_time( char const* path )
{
	WIN32_FILE_ATTRIBUTE_DATA engine_dll_file_attributes = {};
	GetFileAttributesExA( path, GetFileExInfoStandard, & engine_dll_file_attributes );

	return engine_dll_file_attributes.ftLastWriteTime;
}

#pragma region Timing
inline f32
timing_get_ms_elapsed( u64 start, u64 end )
{
	assert( end - start );
	u64 delta  = (end - start) * Tick_To_Millisecond;
	f32 result = scast(f32, delta) / scast(f32, Performance_Counter_Frequency);
	return result;
}

inline f32
timing_get_seconds_elapsed( u64 start, u64 end )
{
	assert( end > start );
	u64 delta = end - start;
	f32 result = scast(f32, delta) / scast(f32, Performance_Counter_Frequency);
	return result;
}

inline f32
timing_get_us_elapsed( u64 start, u64 end )
{
	assert( end - start );
	u64 delta = (end - start) * Tick_To_Microsecond;
	f32 result = scast(f32, delta) / scast(f32, Performance_Counter_Frequency);
	return result;
}

inline u64
timing_get_wall_clock()
{
	u64 clock;
	QueryPerformanceCounter( rcast( LARGE_INTEGER*, & clock) );
	return clock;
}
#pragma endregion Timing
NS_PLATFORM_END

#include "win32_audio.cpp"
#include "win32_input.cpp"
#include "platform/win32/win32_platform_api.cpp"

NS_PLATFORM_BEGIN
#pragma region Windows Sandbox Interface

internal
void toggle_fullscreen( HWND window_handle )
{
	// Note(Ed) : Follows: https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
	DWORD style = GetWindowLongA( window_handle, GWL_STYLE );
	if ( style & WS_OVERLAPPEDWINDOW )
	{
		MONITORINFO info           = { sizeof(MONITORINFO) };
		HMONITOR    monitor_handle = MonitorFromWindow( window_handle, MONITOR_DEFAULTTOPRIMARY );

		if ( GetWindowPlacement( window_handle, & Window_Position )
		    && GetMonitorInfoA( monitor_handle, & info ) )
		{
			SetWindowLongA( window_handle, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW );
			SetWindowPos( window_handle, HWND_TOP
			             , info.rcMonitor.left,                     info.rcMonitor.top
			             , info.rcWork.right - info.rcMonitor.left, info.rcMonitor.bottom - info.rcMonitor.top
			             , SWP_NOOWNERZORDER | SWP_FRAMECHANGED
			);
		}
	}
	else
	{
		SetWindowLongA( window_handle, GWL_STYLE , style | WS_OVERLAPPEDWINDOW );
		SetWindowPlacement( window_handle, & Window_Position );
		SetWindowPos( window_handle, NULL
		             , 0, 0, 0, 0
		             , SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED );
	}
}

internal WinDimensions
get_window_dimensions( HWND window_handle )
{
	RECT client_rect;
	GetClientRect( window_handle, & client_rect );
	WinDimensions result;
	result.width  = client_rect.right  - client_rect.left;
	result.height = client_rect.bottom - client_rect.top;
	return result;
}

internal void
display_buffer_in_window( HDC device_context, s32 window_width, s32 window_height, OffscreenBuffer* buffer
	, s32 x, s32 y
	, s32 width, s32 height )
{
	if ( 	(window_width  % buffer->width ) == 0
	    &&	(window_height % buffer->height) == 0 )
	{
		// TODO(Ed) : Aspect ratio correction
		StretchDIBits( device_context
			, 0, 0, window_width, window_height
			, 0, 0, buffer->width, buffer->height
			, buffer->memory, & buffer->info
			, DIB_ColorTable_RGB, RO_Source_To_Dest );

		return;
	}

	s32 offset_x = 0;
	s32 offset_y = 0;

	if ( window_width > buffer->width )
		offset_x = (window_width - buffer->width) / 2;

	if ( window_height > buffer->height )
		offset_y = (window_height - buffer->height) / 2;

	PatBlt( device_context
		, 0, 0
		, window_width, offset_y
		, BLACKNESS );

	PatBlt( device_context
		, 0, 0
		, offset_x, window_height
		, BLACKNESS );

	PatBlt( device_context
		, offset_x + buffer->width, 0
		, window_width, window_height
		, BLACKNESS);

	PatBlt( device_context
		, 0, offset_y + buffer->height
		, window_width, window_height
		, BLACKNESS );

	// TODO(Ed) : Aspect ratio correction
	StretchDIBits( device_context
	#if 0
		, x, y, width, height
		, x, y, width, height
	#endif
		, offset_x, offset_y, buffer->width, buffer->height
		// , 0, 0, window_width, window_height
		, 0, 0, buffer->width, buffer->height
		, buffer->memory, & buffer->info
		, DIB_ColorTable_RGB, RO_Source_To_Dest );
}

internal void
resize_dib_section( OffscreenBuffer* buffer, u32 width, u32 height )
{
	// TODO(Ed) : Bulletproof memory handling here for the bitmap memory
	if ( buffer->memory )
	{
		VirtualFree( buffer->memory, 0, MEM_RELEASE );
	}

	buffer->width           = width;
	buffer->height          = height;
	buffer->bytes_per_pixel = 4;
	buffer->pitch           = buffer->width * buffer->bytes_per_pixel;

	// Negative means top-down in the context of the biHeight
#	define Top_Down -
	BITMAPINFOHEADER&
	header = buffer->info.bmiHeader;
	header.biSize        = sizeof( buffer->info.bmiHeader );
	header.biWidth       = buffer->width;
	header.biHeight      = Top_Down buffer->height;
	header.biPlanes      = 1;
	header.biBitCount    = 32; // Need 24, but want 32 ( alignment )
	header.biCompression = BI_RGB_Uncompressed;
	// header.biSizeImage     = 0;
	// header.biXPelsPerMeter = 0;
	// header.biYPelsPerMeter = 0;
	// header.biClrUsed	   = 0;
	// header.biClrImportant  = 0;
#	undef Top_Down

	// We want to "touch" a pixel on every 4-byte boundary
	u32 BitmapMemorySize = (buffer->width * buffer->height) * buffer->bytes_per_pixel;
	buffer->memory = VirtualAlloc( NULL, BitmapMemorySize, MEM_Commit_Zeroed | MEM_Reserve, Page_Read_Write );

	// TODO(Ed) : Clear to black
}

internal LRESULT CALLBACK
main_window_callback( HWND handle
	, UINT   system_messages
	, WPARAM w_param
	, LPARAM l_param )
{
	LRESULT result = 0;

	switch ( system_messages )
	{
		case WM_ACTIVATEAPP:
		{
			if ( scast( bool, w_param ) == true )
			{
				SetLayeredWindowAttributes( handle, RGB(0, 0, 0), 255, LWA_Alpha );
			}
			else
			{
//				 SetLayeredWindowAttributes( handle, RGB(0, 0, 0), 120, LWA_Alpha );
			}
		}
		break;

		case WM_CLOSE:
		{
			// TODO(Ed) : Handle with a message to the user
			Running = false;
		}
		break;

		case WM_DESTROY:
		{
			// TODO(Ed) : Handle with as an error and recreate the window
			Running = false;
		}
		break;

		case WM_PAINT:
		{
			PAINTSTRUCT info;
			HDC device_context = BeginPaint( handle, & info );
			u32 x 	   = info.rcPaint.left;
			u32 y 	   = info.rcPaint.top;
			u32 width  = info.rcPaint.right  - info.rcPaint.left;
			u32 height = info.rcPaint.bottom - info.rcPaint.top;

			WinDimensions dimensions = get_window_dimensions( handle );

			display_buffer_in_window( device_context, dimensions.width, dimensions.height, &Surface_Back_Buffer
				, x, y
				, width, height );
			EndPaint( handle, & info );
		}
		break;

		// TODO(Ed) : Expose cursor toggling to engine via platform api (lets game control it for its ux purposes)
		case WM_MOUSEMOVE:
		{

			while (ShowCursor(FALSE) >= 0);
		}
		break;

		case WM_NCMOUSEMOVE:
		{
			// Show the cursor when it's outside the window's client area (i.e., on the frame or elsewhere)
			while (ShowCursor(TRUE) < 0);
		}
		break;

		case WM_SETCURSOR:
		{
			if ( Show_Windows_Cursor )
			{
//				SetCursor( Windows_Cursor );
				result = DefWindowProc( handle, system_messages, w_param, l_param );
			}
			else
			{
				SetCursor(NULL);
			}
		}
		break;

		case WM_SIZE:
		{
		}
		break;

		default:
		{
			result = DefWindowProc( handle, system_messages, w_param, l_param );
		}
	}

	return result;
}

internal void
process_pending_window_messages( HWND window_handle, engine::KeyboardState* keyboard, engine::MousesState* mouse )
{
	MSG window_msg_info;
	while ( PeekMessageA( & window_msg_info, 0, 0, 0, PM_Remove_Messages_From_Queue ) )
	{
		if ( window_msg_info.message == WM_QUIT  )
		{
			OutputDebugStringA("WM_QUIT\n");
			Running = false;
		}

		// Keyboard input handling
		switch (window_msg_info.message)
		{
			// I rather do this with GetAsyncKeyState...
			case WM_SYSKEYDOWN:
			case WM_SYSKEYUP:
			{
				WPARAM vk_code  = window_msg_info.wParam;
				b32    is_down  = scast(b32, (window_msg_info.lParam >> 31) == 0 );
				b32    was_down = scast(b32, (window_msg_info.lParam >> 30) );
				b32    alt_down = scast(b32, (window_msg_info.lParam & (1 << 29)) );

				switch ( vk_code )
				{
					case VK_F4:
					{
						if ( alt_down )
							Running = false;
					}
					break;
					case VK_F10:
					{
						// TODO(Ed) : Expose this feature via platform_api to engine. Let the game toggle via the its action binds.
						if ( is_down )
							toggle_fullscreen( window_handle );
					}
					break;
				}
			}
			break;

			case WM_MOUSEWHEEL:
			{
				// This captures the vertical scroll value
				int verticalScroll = GET_WHEEL_DELTA_WPARAM(window_msg_info.wParam);
				mouse->vertical_wheel.end += scast(f32, verticalScroll);
			}
			break;

			case WM_MOUSEHWHEEL:
			{
				// This captures the horizontal scroll value
				int horizontalScroll = GET_WHEEL_DELTA_WPARAM(window_msg_info.wParam);
				mouse->horizontal_wheel.end += scast( f32, horizontalScroll);
			}
			break;

			default:
				TranslateMessage( & window_msg_info );
				DispatchMessageW( & window_msg_info );
		}
	}
}

LONG WINAPI unhandled_exeception( EXCEPTION_POINTERS* exception_info )
{
    if ( exception_info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION )
	{
        congrats("Access violation detected! \n");
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

#pragma endregion Windows Sandbox Interface

#pragma region Engine Module

constexpr const Str FName_Engine_DLL       = str_ascii("handmade_engine.dll");
constexpr const Str FName_Engine_DLL_InUse = str_ascii("handmade_engine_in_use.dll");
constexpr const Str FName_Engine_PDB_Lock  = str_ascii("handmade_engine.pdb.lock");

global HMODULE             Lib_Handmade_Engine = nullptr;
global StrFixed< S16_MAX > Path_Engine_DLL;
global StrFixed< S16_MAX > Path_Engine_DLL_InUse;

internal
engine::ModuleAPI load_engine_module_api()
{
	using ModuleAPI = engine::ModuleAPI;

	CopyFileA( Path_Engine_DLL, Path_Engine_DLL_InUse, FALSE );

	// Engine
	Lib_Handmade_Engine = LoadLibraryA( Path_Engine_DLL_InUse );
	if ( ! Lib_Handmade_Engine )
	{
		return {};
	}

	engine::ModuleAPI engine_api {};
	engine_api.on_module_reload  = get_procedure_from_library< engine::OnModuleRelaodFn > ( Lib_Handmade_Engine, engine::symbol_on_module_load );
	engine_api.startup           = get_procedure_from_library< engine::StartupFn >        ( Lib_Handmade_Engine, engine::symbol_startup );
	engine_api.shutdown          = get_procedure_from_library< engine::ShutdownFn >       ( Lib_Handmade_Engine, engine::symbol_shutdown );
	engine_api.update_and_render = get_procedure_from_library< engine::UpdateAndRenderFn >( Lib_Handmade_Engine, engine::symbol_update_and_render );
	engine_api.update_audio      = get_procedure_from_library< engine::UpdateAudioFn >    ( Lib_Handmade_Engine, engine::symbol_update_audio );

	engine_api.IsValid =
			engine_api.on_module_reload
		&&	engine_api.startup
		&&	engine_api.shutdown
		&&	engine_api.update_and_render
		&&	engine_api.update_audio;
	if ( engine_api.IsValid )
	{
		OutputDebugStringA( "Loaded engine module API\n" );
	}
	else {
		fatal( "Failed to load engine module API!\n" );
	}
	return engine_api;
}

internal
void unload_engine_module_api( engine::ModuleAPI* engine_api )
{
	if ( engine_api->IsValid )
	{
		FreeLibrary( Lib_Handmade_Engine );
		*engine_api = {};
		OutputDebugStringA( "Unloaded engine module API\n" );
	}
}
#pragma endregion Engine Module

NS_PLATFORM_END

int CALLBACK
WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int show_command )
{
	using namespace win32;
	using namespace platform;

	SetUnhandledExceptionFilter(unhandled_exeception);

#pragma region Startup
	// Timing
#if Build_Development
	u64 launch_clock = timing_get_wall_clock();
	u64 launch_cycle = __rdtsc();
#endif

	// Sets the windows scheduler granulaity for this process to 1 ms
	constexpr u32 desired_scheduler_ms = 1;
	b32 sleep_is_granular = ( timeBeginPeriod( desired_scheduler_ms ) == TIMERR_NOERROR );

	// If its a high-perofmrance frame-time (a refresh rate that produces a target frametime at or below 4.16~ ms, we cannot allow the scheduler to mess things up)
	b32 sub_ms_granularity_required = scast(f32, Engine_Refresh_Hz) <= High_Perf_Frametime_MS;

	QueryPerformanceFrequency( rcast(LARGE_INTEGER*, & Performance_Counter_Frequency) );

	// Setup pathing
	StrFixed< S16_MAX > path_pdb_lock {};
	{
		// TODO(Ed): This will not support long paths, NEEDS to be changed to support long paths.

		char path_buffer[S16_MAX];
		GetModuleFileNameA( 0, path_buffer, sizeof(path_buffer) );

		if ( GetCurrentDirectoryA( S16_MAX, Path_Binaries ) == 0 )
		{
			fatal( "Failed to get the root directory!" );
		}
		Path_Binaries.len = str_length( Path_Binaries );
		Path_Binaries[ Path_Binaries.len ] = '\\';
		++ Path_Binaries.len;

		if ( SetCurrentDirectoryA( ".." ) == 0 )
		{
			fatal( "Failed to set current directory to root!");
		}

		if ( GetCurrentDirectoryA( S16_MAX, Path_Root.ptr ) == 0 )
		{
			fatal( "Failed to get the root directory!" );
		}
		Path_Root.len = str_length(Path_Root.ptr);
		Path_Root.ptr[ Path_Root.len ] = '\\';
		++ Path_Root.len;

		Path_Engine_DLL.      concat( Path_Binaries, FName_Engine_DLL );
		Path_Engine_DLL_InUse.concat( Path_Binaries, FName_Engine_DLL_InUse );

		path_pdb_lock.concat( Path_Binaries, FName_Engine_PDB_Lock );

		Path_Scratch.concat( Path_Root, str_ascii("scratch") );
		Path_Scratch.ptr[ Path_Scratch.len ] = '\\';
		++ Path_Scratch.len;

		CreateDirectoryA( Path_Scratch, 0 );

		Path_Content.concat( Path_Root, str_ascii("content") );
		Path_Content.ptr[ Path_Content.len ] = '\\';
		++ Path_Content.len;
	}

	// Memory
	engine::Memory engine_memory {};
	{
		engine_memory.persistent_size = megabytes( 128 );
		// engine_memory.FrameSize	  = megabytes( 64 );
		engine_memory.transient_size  = gigabytes( 2 );

		u64 total_size = engine_memory.persistent_size
			// + engine_memory.FrameSize
			+ engine_memory.transient_size;

	#if Build_Debug
		void* base_address = rcast(void*, terabytes( 1 ));
	#else
		void* base_address = 0;
	#endif

		engine_memory.persistent = rcast( Byte*, VirtualAlloc( base_address, total_size , MEM_Commit_Zeroed | MEM_Reserve, Page_Read_Write ));
		engine_memory.transient  = rcast( Byte*, engine_memory.persistent ) + engine_memory.persistent_size;

	#if Build_Development
		// First slot is for restore
		for (u32 slot = 0; slot < engine_memory.Num_Snapshot_Slots; ++slot)
		{
			engine::MemorySnapshot& snapshot = engine_memory.snapshots[ slot ];

			snapshot.file_path.concat( Path_Scratch, str_ascii("snapshot_") );
			wsprintfA( snapshot.file_path.ptr, "%s%d.hm_snapshot", snapshot.file_path.ptr, slot );

			HANDLE snapshot_file = CreateFileA( snapshot.file_path
				, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0
				, OPEN_ALWAYS, 0, 0 );

			LARGE_INTEGER file_size {};
			file_size.QuadPart = total_size;

			HANDLE snapshot_mapping = CreateFileMappingA( snapshot_file, 0
				, Page_Read_Write
				, file_size.HighPart, file_size.LowPart
				, 0 );

			if (!snapshot_mapping)
			{
				// Handle the error, perhaps log it or display a message
				DWORD error = GetLastError();

				char text[256];
				wsprintfA(text, "FlushFileBuffers failed with error code: %lu\n", error);
				OutputDebugStringA(text);
				congrats( text );

				CloseHandle(snapshot_file); // Close the file handle before continuing
				continue; // Skip the current iteration
			}

			snapshot.memory = MapViewOfFile( snapshot_mapping, FILE_MAP_ALL_ACCESS, 0, 0, total_size );

			if (!snapshot.memory)
			{
				// Handle the error, perhaps log it or display a message
				DWORD error = GetLastError();

				char text[256];
				wsprintfA(text, "FlushFileBuffers failed with error code: %lu\n", error);
				OutputDebugStringA(text);
				congrats( text );

				CloseHandle(snapshot_mapping); // Close the mapping handle
				CloseHandle(snapshot_file);    // Close the file handle
				continue; // Skip the current iteration
			}

			snapshot.opaque_handle   = snapshot_file;
			snapshot.opaque_handle_2 = snapshot_mapping;
		}
	#endif

		if (	engine_memory.persistent == nullptr
			||	engine_memory.transient  == nullptr )
		{
			// TODO : Diagnostic Logging
			return -1;
		}
	}

	WNDCLASSW window_class {};
	HWND window_handle = nullptr;
	b32 window_in_foreground = false;
	{
		window_class.style = CS_Horizontal_Redraw | CS_Vertical_Redraw;
		window_class.lpfnWndProc = main_window_callback;
		// window_class.cbClsExtra  = ;
		// window_class.cbWndExtra  = ;
		window_class.hInstance   = instance;
		// window_class.hIcon = ;
		 window_class.hCursor = LoadCursorW( 0, IDC_ARROW );
		// window_class.hbrBackground = ;
		window_class.lpszMenuName  = L"Handmade Hero!";
		window_class.lpszClassName = L"HandmadeHeroWindowClass";

		Show_Windows_Cursor = true;
		Windows_Cursor      = LoadCursorW( 0, IDC_CROSS );
		Window_Position     = {sizeof(WINDOWPLACEMENT)};

		if ( ! RegisterClassW( & window_class ) )
		{
			// TODO : Diagnostic Logging
			return 0;
		}

		window_handle = CreateWindowExW(
			// WS_EX_LAYERED | WS_EX_TOPMOST,
			WS_EX_LAYERED,
			window_class.lpszClassName,
			L"Handmade Hero",
			WS_Overlapped_Window | WS_Initially_Visible,
			300, 300, // x, y
			1280, 720, // width, height
			0, 0,                         // parent, menu
			instance, 0                   // instance, param
		);

		if ( ! window_handle )
		{
			// TODO : Diagnostic Logging
			return 0;
		}

		// WinDimensions dimensions = get_window_dimensions( window_handle );
		resize_dib_section( &Surface_Back_Buffer, 1280, 720	 );

		// Setup monitor refresh and associated timers
		HDC refresh_dc = GetDC( window_handle );
		u32 monitor_refresh_hz = GetDeviceCaps( refresh_dc, VREFRESH );
		if ( monitor_refresh_hz > 1 )
		{
			Monitor_Refresh_Hz = monitor_refresh_hz;
		}
		ReleaseDC( window_handle, refresh_dc );

		Engine_Refresh_Hz      = monitor_refresh_hz;
		Engine_Frame_Target_MS = 1000.f / scast(f32, Engine_Refresh_Hz);
	}

	// Prepare platform API
	ModuleAPI platform_api {};
	{
		platform_api.path_root     = Path_Root;
		platform_api.path_binaries = Path_Binaries;
		platform_api.path_content  = Path_Content;
		platform_api.path_scratch  = Path_Scratch;

	#if Build_Development
		platform_api.debug_set_pause_rendering = & debug_set_pause_rendering;
	#endif

		platform_api.get_wall_clock = & timing_get_wall_clock;

		// Not implemented yet
		platform_api.get_monitor_refresh_rate = nullptr;
		platform_api.set_monitor_refresh_rate = nullptr;
		platform_api.get_engine_frame_target  = nullptr;
		platform_api.set_engine_frame_target  = nullptr;

		platform_api.load_binary_module	  = & load_binary_module;
		platform_api.unload_binary_module = & unload_binary_module;
		platform_api.get_module_procedure = & get_binary_module_symbol;

		platform_api.file_check_exists  = & file_check_exists;
		platform_api.file_close         = & file_close;
		platform_api.file_delete        = & file_delete;
		platform_api.file_read_content  = & file_read_content;
		platform_api.file_read_stream   = & file_read_stream;
		platform_api.file_rewind        = & file_rewind;
		platform_api.file_write_content = & file_write_content;
		platform_api.file_write_stream  = & file_write_stream;

		platform_api.memory_copy = & memory_copy;
	}

	// Load engine module
	FILETIME          engine_api_load_time = file_get_last_write_time( Path_Engine_DLL );
	engine::ModuleAPI engine_api           = load_engine_module_api();

	b32               sound_is_valid       = false;
	DWORD             ds_cursor_byte_delta = 0;
	f32               ds_latency_ms        = 0;
	DirectSoundBuffer ds_sound_buffer;
	u32               audio_marker_index = 0;
	AudioTimeMarker   audio_time_markers[ Monitor_Refresh_Max_Supported ] {};
	u32               audio_time_markers_size = Engine_Refresh_Hz / 2;
	assert( audio_time_markers_size <= Monitor_Refresh_Max_Supported )
	{
		ds_sound_buffer.is_playing         = 0;
		ds_sound_buffer.samples_per_second = 48000;
		ds_sound_buffer.bytes_per_sample   = sizeof(s16) * 2;

		ds_sound_buffer.secondary_buffer_size = ds_sound_buffer.samples_per_second * ds_sound_buffer.bytes_per_sample;
		init_sound( window_handle, & ds_sound_buffer );

		ds_sound_buffer.samples = rcast( s16*, VirtualAlloc( 0, 48000 * 2 * sizeof(s16)
			, MEM_Commit_Zeroed | MEM_Reserve, Page_Read_Write ));

		assert( ds_sound_buffer.samples );
		ds_sound_buffer.running_sample_index = 0;
		// ds_clear_sound_buffer( & sound_output );
		ds_sound_buffer.secondary_buffer->Play( 0, 0, DSBPLAY_LOOPING );

		ds_sound_buffer.bytes_per_second   = ds_sound_buffer.samples_per_second * ds_sound_buffer.bytes_per_sample;
		ds_sound_buffer.guard_sample_bytes = (ds_sound_buffer.bytes_per_second / Engine_Refresh_Hz) / 2;

		// TODO(Ed): When switching to core audio at minimum, this will be 1 ms of lag and guard samples wont really be needed.
		u32 min_guard_sample_bytes = 1540;
		if ( ds_sound_buffer.guard_sample_bytes < min_guard_sample_bytes )
		{
			ds_sound_buffer.guard_sample_bytes = min_guard_sample_bytes;
		}
	}

	engine::InputState input {};

	// There can be 4 of any of each input API type : KB & Mouse, XInput, JSL.
#if 0
	using EngineKeyboardStates = engine::KeyboardState[ Max_Controllers ];
	EngineKeyboardStates* old_keyboards = & keyboard_states[0];
	EngineKeyboardStates* new_keyboards = & keyboard_states[1];
	EngineKeyboardStates keyboard_states[2] {};
#endif

	engine::KeyboardState  keyboard_states[2] {};
	engine::KeyboardState* old_keyboard = & keyboard_states[0];
	engine::KeyboardState* new_keyboard = & keyboard_states[1];
	// Important: Assuming keyboard always connected for now, and assigning to first controller.

	engine::MousesState mouse_states[2] {};
	engine::MousesState* old_mouse = & mouse_states[0];
	engine::MousesState* new_mouse = & mouse_states[1];

	EngineXInputPadStates  xpad_states[2] {};
	EngineXInputPadStates* old_xpads = & xpad_states[0];
	EngineXInputPadStates* new_xpads = & xpad_states[1];

	EngineDSPadStates  ds_pad_states[2] {};
	EngineDSPadStates* old_ds_pads = & ds_pad_states[0];
	EngineDSPadStates* new_ds_pads = & ds_pad_states[1];

	u32 jsl_num_devices = JslConnectDevices();
	JSL_DeviceHandle jsl_device_handles[4] {};
	{
		xinput_load_library_bindings();

		u32 jsl_getconnected_found = JslGetConnectedDeviceHandles( jsl_device_handles, jsl_num_devices );
		{
			if ( jsl_getconnected_found != jsl_num_devices )
			{
				OutputDebugStringA( "Error: JSLGetConnectedDeviceHandles didn't find as many as were stated with JslConnectDevices\n");
			}

			if ( jsl_num_devices > 0 )
			{
				OutputDebugStringA( "JSL Connected Devices:\n" );
				for ( u32 jsl_device_index = 0; jsl_device_index < jsl_num_devices; ++ jsl_device_index )
				{
					JslSetLightColour( jsl_device_handles[ jsl_device_index ], (255 << 8) );
				}
			}
		}

		if ( jsl_num_devices > 4 )
		{
			jsl_num_devices = 4;
			MessageBoxA( window_handle, "More than 4 JSL devices found, this engine will only support the first four found."
				, "Warning", MB_ICONEXCLAMATION );
		}
	}

	// Populate an initial polling state for the inputs
	poll_input( window_handle, & input, jsl_num_devices, jsl_device_handles
		, old_keyboard, new_keyboard
		, old_mouse, new_mouse
		, old_xpads, new_xpads
		, old_ds_pads, new_ds_pads );

	engine_api.startup( rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.memory), & engine_memory, & platform_api );

	u64 last_frame_clock = timing_get_wall_clock();
	u64 last_frame_cycle = __rdtsc();
	u64 flip_wall_clock = last_frame_clock;

#if Build_Development
	u64 startup_cycles = last_frame_cycle - launch_cycle;
	f32 startup_ms     = timing_get_ms_elapsed( launch_clock, last_frame_clock );

	char text_buffer[256];
	sprintf_s( text_buffer, sizeof(text_buffer), "Startup MS: %f\n", startup_ms );
	OutputDebugStringA( text_buffer );
#endif
#pragma endregion Startup

	// Placeholder
	engine::ThreadContext thread_context_placeholder {};

	Running = true;
#if 0
// This tests the play & write cursor update frequency.
	while ( Running )
	{
		DWORD play_cursor;
		DWORD write_cursor;

		ds_sound_buffer.SecondaryBuffer->GetCurrentPosition( & play_cursor, & write_cursor );
		char text_buffer[256];
		sprintf_s( text_buffer, sizeof(text_buffer), "PC:%u WC:%u\n", (u32)play_cursor, (u32)write_cursor );
		OutputDebugStringA( text_buffer );
	}
#endif
	while( Running )
	{
		f32 delta_time = Engine_Frame_Target_MS / 1000.f;

		window_in_foreground = ( GetForegroundWindow() == window_handle );

		// Engine Module Hot-Reload
		do {
			FILETIME engine_api_current_time = file_get_last_write_time( Path_Engine_DLL );
			if ( CompareFileTime( & engine_api_load_time, & engine_api_current_time ) == 0 )
				break;

			WIN32_FIND_DATAA lock_file_info = {};
			for(;;)
			{
				HANDLE lock_file = FindFirstFileA( path_pdb_lock, & lock_file_info );
				if ( lock_file != INVALID_HANDLE_VALUE )
				{
					FindClose( lock_file );
					Sleep( 1 );
					continue;
				}
				break;
			}

			engine_api_load_time = engine_api_current_time;
			unload_engine_module_api( & engine_api );
			engine_api = load_engine_module_api();
		} while (0);


		if ( window_in_foreground )
		{
			// Swapping at the beginning of the input frame instead of the end.
			swap( old_keyboard, new_keyboard );
			swap( old_mouse,    new_mouse );
			swap( old_xpads,    new_xpads );
			swap( old_ds_pads,  new_ds_pads );

			poll_input( window_handle, & input, jsl_num_devices, jsl_device_handles
				, old_keyboard, new_keyboard
				, old_mouse, new_mouse
				, old_xpads, new_xpads
				, old_ds_pads, new_ds_pads );
		}
		else
		{
			keyboard_states[0] = {};
			keyboard_states[1] = {};

			mouse_states[0] = {};
			mouse_states[0] = {};

			for ( s32 id = 0; id < Max_Controllers; ++ id )
			{
				xpad_states[0][ id ] = {};
				xpad_states[1][ id ] = {};

				ds_pad_states[0][ id ] = {};
				ds_pad_states[1][ id ] = {};
			}
		}

		process_pending_window_messages( window_handle, new_keyboard, new_mouse );

		// f32 delta_time = timing_get_seconds_elapsed( last_frame_clock, timing_get_wall_clock() );

		// Engine's logical iteration and rendering process
		engine_api.update_and_render( delta_time, & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.memory )
			, & engine_memory, & platform_api, & thread_context_placeholder );

		u64   audio_frame_start = timing_get_wall_clock();
		f32   flip_to_audio_ms  = timing_get_ms_elapsed( flip_wall_clock, audio_frame_start );

		DWORD ds_play_cursor;
		DWORD ds_write_cursor;
		process_audio_frame( ds_sound_buffer, ds_play_cursor, ds_write_cursor, ds_latency_ms
			, sound_is_valid
			, audio_time_markers, audio_marker_index
			, flip_to_audio_ms, last_frame_clock
			, engine_api, & engine_memory, & platform_api, & thread_context_placeholder );

		// Timing Update
		{
			u64 work_frame_end_cycle = __rdtsc();
			u64 work_frame_end_clock = timing_get_wall_clock();

			f32 work_frame_ms  = timing_get_ms_elapsed( last_frame_clock, work_frame_end_clock );  // WorkSecondsElapsed
			f32 work_cycles    = timing_get_ms_elapsed( last_frame_cycle, work_frame_end_cycle );

			f32 frame_elapsed_ms = work_frame_ms; // SecondsElapsedForFrame
			if ( frame_elapsed_ms < Engine_Frame_Target_MS )
			{
				s32 sleep_ms = scast(DWORD, (Engine_Frame_Target_MS - frame_elapsed_ms)) - 1;
				if ( sleep_ms > 0 && ! sub_ms_granularity_required && sleep_is_granular )
				{
					Sleep( scast(DWORD, sleep_ms) );
				}

				u64 frame_clock  = timing_get_wall_clock();
				frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock );
				if ( frame_elapsed_ms < Engine_Frame_Target_MS )
				{
					// TODO(Ed) : Log missed sleep here.
				}

				while ( frame_elapsed_ms < Engine_Frame_Target_MS )
				{
					frame_clock      = timing_get_wall_clock();
					frame_elapsed_ms = timing_get_ms_elapsed( last_frame_clock, frame_clock );
				}
			}
			else
			{
				// TODO(Ed) : Missed the display sync window!
			}

			last_frame_clock = timing_get_wall_clock(); // LastCouner
			last_frame_cycle = __rdtsc();
		}

		// Update surface back buffer
		if ( ! Pause_Rendering )
		{
			WinDimensions dimensions     = get_window_dimensions( window_handle );
			HDC           device_context = GetDC( window_handle );

		#if Build_Development && 0
			// Note: debug_marker_index is wrong for the 0th index
			debug_sync_display( & ds_sound_buffer
				, audio_time_markers_size, audio_time_markers, audio_marker_index - 1
				, Engine_Frame_Target_MS );
		#endif

			display_buffer_in_window( device_context, dimensions.width, dimensions.height, &Surface_Back_Buffer
				, 0, 0
				, dimensions.width, dimensions.height );
			ReleaseDC( window_handle, device_context );
		}

		flip_wall_clock = timing_get_wall_clock();
		#if Build_Development
		{
			// Audio Debug
			DWORD play_cursor  = 0;
			DWORD write_cursor = 0;
			if ( SUCCEEDED( ds_sound_buffer.secondary_buffer->GetCurrentPosition( & play_cursor, & write_cursor ) ) )
			{
				if ( ! sound_is_valid )
				{
					ds_sound_buffer.running_sample_index = write_cursor / ds_sound_buffer.bytes_per_sample;
					sound_is_valid = true;
				}

				assert( audio_marker_index < audio_time_markers_size )
				AudioTimeMarker* marker = & audio_time_markers[ audio_marker_index ];

				marker->flip_play_curosr = play_cursor;
				marker->flip_write_cursor = write_cursor;
			}
		}
		#endif

		#if Build_Development
			audio_marker_index++;
			if ( audio_marker_index >= audio_time_markers_size )
				audio_marker_index = 0;
		#endif
	}

	engine_api.shutdown( & engine_memory, & platform_api );

	for ( s32 slot = 0; slot < engine_memory.Num_Snapshot_Slots; ++slot )
	{
		engine::MemorySnapshot& snapshot = engine_memory.snapshots[ slot ];

		UnmapViewOfFile( snapshot.memory );
		CloseHandle( snapshot.opaque_handle_2 );
		CloseHandle( snapshot.opaque_handle );
	}

	unload_engine_module_api( & engine_api );
	DeleteFileA( Path_Engine_DLL_InUse );

	if ( jsl_num_devices > 0 )
	{
		for ( u32 jsl_device_index = 0; jsl_device_index < jsl_num_devices; ++ jsl_device_index )
		{
			JslSetLightColour( jsl_device_handles[ jsl_device_index ], 0 );
		}
	}

	return 0;
}
