#define _WIN32_WINNT 0x4000
#include <windows.h>
#include <crtdbg.h>
#include <stdio.h>

#define SDL_MAIN_USE_CALLBACKS
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_main.h"

#include "imgui.h"
#include "backends/imgui_impl_sdl3.h"
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_opengl3_loader.h"

// video drivers pick these up and make sure the game runs on the good GPU
extern "C" __declspec( dllexport ) DWORD NvOptimusEnablement = 1;
extern "C" __declspec( dllexport ) int AmdPowerXpressRequestHighPerformance = 1;

void ShowErrorMessage( const char * msg, const char * file, int line ) {
#if NDEBUG
	MessageBoxA( NULL, msg, "Error", MB_OK );
#else
	if( IsDebuggerPresent() != 0 ) {
		__debugbreak();
	}
	else if( _CrtDbgReport( _CRT_ERROR, file, line, NULL, "%s", msg ) == 1 ) {
		_CrtDbgBreak();
	}
#endif
}

void FatalImpl( const char * file, int line, const char * format, ... ) {
	va_list argptr;
	char msg[ 1024 ];

	va_start( argptr, format );
	vsnprintf( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	ShowErrorMessage( msg, file, line );
	abort();
}

#define Fatal( format, ... ) FatalImpl( __FILE__, __LINE__, format, ##__VA_ARGS__ )

#define CONCAT_HELPER( a, b ) a##b
#define CONCAT( a, b ) CONCAT_HELPER( a, b )
#define COUNTER_NAME( x ) CONCAT( x, __COUNTER__ )

template< typename F >
struct ScopeExit {
	ScopeExit( F f_ ) : f( f_ ) { }
	~ScopeExit() { f(); }
	F f;
};

struct DeferHelper {
	template< typename F > ScopeExit< F > operator+( F f ) { return f; }
};

#define defer [[maybe_unused]] const auto & COUNTER_NAME( DEFER_ ) = DeferHelper() + [&]()

template< typename R = bool, typename F, typename... Rest >
static R TrySDLImpl( const char * function_name, F f, const Rest & ... args ) {
	R res = f( args... );
	if( !res ) {
		Fatal( "%s: %s", function_name, SDL_GetError() );
	}
	return res;
}

#define TrySDL( f, ... ) TrySDLImpl( #f, f, ##__VA_ARGS__ )
#define TrySDLR( R, f, ... ) TrySDLImpl< R >( #f, f, ##__VA_ARGS__ )

static SDL_Window * window;
static SDL_GLContext gl_context;

SDL_AppResult SDL_AppInit( void ** appstate, int argc, char ** argv ) {
	/*
	 * SDL
	 */

	TrySDL( SDL_SetAppMetadata, "Cocaine Diesel", "1", "fun.cocainediesel" );
	TrySDL( SDL_Init, SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD );

	TrySDL( SDL_GL_SetAttribute, SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
	TrySDL( SDL_GL_SetAttribute, SDL_GL_CONTEXT_MAJOR_VERSION, 4 );
	TrySDL( SDL_GL_SetAttribute, SDL_GL_CONTEXT_MINOR_VERSION, 5 );
	TrySDL( SDL_GL_SetAttribute, SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, true );

	SDL_GLContextFlag gl_flags = SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
	TrySDL( SDL_GL_SetAttribute, SDL_GL_CONTEXT_NO_ERROR, true );
	TrySDL( SDL_GL_SetAttribute, SDL_GL_CONTEXT_FLAGS, gl_flags );

	SDL_PropertiesID props = TrySDLR( SDL_PropertiesID, SDL_CreateProperties );
	defer { SDL_DestroyProperties( props ); };

	TrySDL( SDL_SetStringProperty, props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Cocaine Diesel" );
	TrySDL( SDL_SetBooleanProperty, props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true );
	TrySDL( SDL_SetBooleanProperty, props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true );
	TrySDL( SDL_SetBooleanProperty, props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true );
	TrySDL( SDL_SetNumberProperty, props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 1440 );
	TrySDL( SDL_SetNumberProperty, props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 900 );

	TrySDL( SDL_SetNumberProperty, props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED );
	TrySDL( SDL_SetNumberProperty, props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED );

	window = TrySDLR( SDL_Window *, SDL_CreateWindowWithProperties, props );
	gl_context = TrySDLR( SDL_GLContext, SDL_GL_CreateContext, window );

	float content_scale = TrySDLR( float, SDL_GetWindowDisplayScale, window );

	/*
	 * imgui
	 */

	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGui_ImplSDL3_InitForOpenGL( window, gl_context );
	ImGui_ImplOpenGL3_Init();
	ImGui::GetIO().IniFilename = NULL;
	ImGui::GetStyle().ScaleAllSizes( content_scale );

	return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent( void * appstate, SDL_Event * event ) {
	ImGui_ImplSDL3_ProcessEvent( event );
	return event->type == SDL_EVENT_QUIT ? SDL_APP_SUCCESS : SDL_APP_CONTINUE;
}

void Goochie();
SDL_AppResult SDL_AppIterate( void * appstate ) {
	ImGui_ImplSDL3_NewFrame();
	ImGui_ImplOpenGL3_NewFrame();
	ImGui::NewFrame();

	Goochie();

	ImGui::Render();
	glClear( GL_COLOR_BUFFER_BIT );
	ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() );
	TrySDL( SDL_GL_SwapWindow, window );

	return SDL_APP_CONTINUE;
}

void SDL_AppQuit( void * appstate, SDL_AppResult result ) {
	ImGui_ImplOpenGL3_Shutdown();
	ImGui_ImplSDL3_Shutdown();
	ImGui::DestroyContext();
	TrySDL( SDL_GL_DestroyContext, gl_context );
	SDL_DestroyWindow( window );
	SDL_Quit();
}
