#include "quakedef.h"
#ifdef HEADLESSQUAKE
#ifndef SERVERONLY
#include "gl_draw.h"
#include "shader.h"

#ifdef _WIN32
#include "winquake.h"
#include "resource.h"
#else
#include <unistd.h>
#endif
#if defined(FTE_SDL3)
	#include <SDL3/SDL.h>
#elif defined(FTE_SDL)
	#include <SDL.h>
#endif

static void Headless_Draw_Init(void)
{
	R2D_Init();
}
static void Headless_Draw_Shutdown(void)
{
	Shader_Shutdown();
}
static void		Headless_IMG_UpdateFiltering	(image_t *imagelist, int filtermip[3], int filterpic[3], int mipcap[2], float lodbias, float anis)
{
}
static qboolean	Headless_IMG_LoadTextureMips	(texid_t tex, const struct pendingtextureinfo *mips)
{
	return true;
}
static void		Headless_IMG_DestroyTexture		(texid_t tex)
{
}
static void	Headless_R_Init					(void)
{
}
static void	Headless_R_DeInit					(void)
{
}
static void	Headless_R_RenderView				(void)
{
}
#ifdef FTE_SDL
	#if SDL_VERSION_ATLEAST(3,0,0)
static SDL_Tray *thetray;
static void SDLCALL Headless_Restore(void *userdata, SDL_TrayEntry *entry)
{
	extern cvar_t vid_renderer;
	if (!Q_strcasecmp(vid_renderer.string, "headless"))
		Cbuf_AddText("vid_renderer \"\";vid_restart\n", RESTRICT_LOCAL);
	else
		Cbuf_AddText("vid_restart\n", RESTRICT_LOCAL);
}
	#endif
#elif defined(_WIN32)
//tray icon crap, so the user can still restore the game.
LRESULT CALLBACK HeadlessWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	extern cvar_t vid_renderer;
	switch(msg)
	{
	case WM_USER:
		switch(LOWORD(lparam))
		{
		case WM_CONTEXTMENU:
		case WM_USER+0:
		case WM_RBUTTONUP:
			if (!Q_strcasecmp(vid_renderer.string, "headless"))
				Cbuf_AddText("vid_renderer \"\";vid_restart\n", RESTRICT_LOCAL);
			else
				Cbuf_AddText("vid_restart\n", RESTRICT_LOCAL);
			break;
		default:
			break;
		}
		return 0;
	default:
		if (WinNT)
			return DefWindowProcW(wnd, msg, wparam, lparam);
		else
			return DefWindowProcA(wnd, msg, wparam, lparam);
	}
}
#endif

static qboolean Headless_VID_Init				(rendererstate_t *info, unsigned char *palette)
{
	memset(&sh_config, 0, sizeof(sh_config));

#ifdef FTE_SDL
	#if SDL_VERSION_ATLEAST(3,0,0)
		thetray = SDL_CreateTray(NULL, fs_manifest->formalname);
		if (thetray)
		{	//don't know how to respond to direct clicks, so create a menu.
			SDL_TrayMenu *menu = SDL_CreateTrayMenu(thetray);
			SDL_TrayEntry *entry = SDL_InsertTrayEntryAt(menu, -1, "Restore", SDL_TRAYENTRY_BUTTON);
			SDL_SetTrayEntryCallback(entry, Headless_Restore, NULL);
			return true;
		}
		Con_Printf(CON_ERROR"Headless: Unable to create system tray icon for restoring.\n");
		return false;
	#endif
#elif defined(_WIN32)
	//tray icon crap, so the user can still restore the game.
	extern HWND	mainwindow;
	extern HINSTANCE	global_hInstance;
	if (WinNT)
	{
		WNDCLASSW wc;
		NOTIFYICONDATAW data;

		//Shell_NotifyIcon requires a window to provide events etc.
		wc.style         = 0;
		wc.lpfnWndProc   = (WNDPROC)HeadlessWndProc;
		wc.cbClsExtra    = 0;
		wc.cbWndExtra    = 0;
		wc.hInstance     = global_hInstance;
		wc.hIcon         = LoadIcon (global_hInstance, MAKEINTRESOURCE (IDI_ICON1));
		wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
		wc.hbrBackground = NULL;
		wc.lpszMenuName  = 0;
		wc.lpszClassName = L"FTEHeadlessClass";
		RegisterClassW(&wc);

		mainwindow = CreateWindowExW(0L, wc.lpszClassName, L"FTE QuakeWorld", 0, 0, 0, 0, 0, NULL, NULL, global_hInstance, NULL);
		data.cbSize = sizeof(data);
		data.hWnd = mainwindow;
		data.uID = 0;
		data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
		data.uCallbackMessage = WM_USER;
		data.hIcon = wc.hIcon;
		wcscpy(data.szTip, L"Right-click to restore");
		if (pShell_NotifyIconW)
			pShell_NotifyIconW(NIM_ADD, &data);
	}
	else
	{
		WNDCLASSA wc;
		NOTIFYICONDATAA data;

		//Shell_NotifyIcon requires a window to provide events etc.
		wc.style         = 0;
		wc.lpfnWndProc   = (WNDPROC)HeadlessWndProc;
		wc.cbClsExtra    = 0;
		wc.cbWndExtra    = 0;
		wc.hInstance     = global_hInstance;
		wc.hIcon         = LoadIcon (global_hInstance, MAKEINTRESOURCE (IDI_ICON1));
		wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
		wc.hbrBackground = NULL;
		wc.lpszMenuName  = 0;
		wc.lpszClassName = "FTEHeadlessClass";
		RegisterClassA(&wc);

		mainwindow = CreateWindowExA(0L, wc.lpszClassName, "FTE QuakeWorld", 0, 0, 0, 0, 0, NULL, NULL, global_hInstance, NULL);
		data.cbSize = sizeof(data);
		data.hWnd = mainwindow;
		data.uID = 0;
		data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
		data.uCallbackMessage = WM_USER;
		data.hIcon = wc.hIcon;
		//fixme: proper multibyte
		Q_strncpyz(data.szTip, "Right-click to restore", sizeof(data.szTip));
		Shell_NotifyIconA(NIM_ADD, &data);
	}
#endif
	return true;
}
static void	 Headless_VID_DeInit				(void)
{
#ifdef FTE_SDL
	#if SDL_VERSION_ATLEAST(3,0,0)
		SDL_DestroyTray(thetray);
		thetray = NULL;
	#endif
#elif defined(_WIN32)
	//tray icon crap, so the user can still restore the game.
	//FIXME: remove tray icon. win95 won't do this automagically.
	extern HWND	mainwindow;
	DestroyWindow(mainwindow);
	mainwindow = NULL;
#endif
}
static void	Headless_VID_SwapBuffers			(void)
{
}
static qboolean Headless_VID_ApplyGammaRamps		(unsigned int gammarampsize, unsigned short *ramps)
{
	return false;
}
static void	Headless_VID_SetWindowCaption		(const char *msg)
{
}
static int Headless_VID_GetPrioriy				(void)
{	//headless renderers are the lowest priority possible, due to how broken they'd be perceived to be.
	return -1;
}
static char	*Headless_VID_GetRGBInfo			(int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt)
{
	*fmt = TF_INVALID;
	*bytestride = *truevidwidth = *truevidheight = 0;
	return NULL;
}
static qboolean	Headless_SCR_UpdateScreen			(void)
{
	if (!cls.timedemo)
	{
#ifdef FTE_SDL
		SDL_Delay(100);		
#elif defined(_WIN32)
		Sleep(100);
#else
		usleep(100*1000);
#endif
	}
	return true;
}
static void	Headless_BE_SelectMode	(backendmode_t mode)
{
}
static void	Headless_BE_DrawMesh_List	(shader_t *shader, int nummeshes, struct mesh_s **mesh, struct vbo_s *vbo, struct texnums_s *texnums, unsigned int be_flags)
{
}
static void	Headless_BE_DrawMesh_Single	(shader_t *shader, struct mesh_s *meshchain, struct vbo_s *vbo, unsigned int be_flags)
{
}
static void	Headless_BE_SubmitBatch	(struct batch_s *batch)
{
}
static struct batch_s *Headless_BE_GetTempBatch	(void)
{
	return NULL;
}
static void	Headless_BE_DrawWorld	(struct batch_s **worldbatches)
{
}
static void	Headless_BE_Init	(void)
{
}
static void Headless_BE_GenBrushModelVBO	(struct model_s *mod)
{
}
static void Headless_BE_ClearVBO	(struct vbo_s *vbo, qboolean dataonly)
{
}
static void Headless_BE_UploadAllLightmaps	(void)
{
}
static void Headless_BE_SelectEntity	(struct entity_s *ent)
{
}
static qboolean Headless_BE_SelectDLight	(struct dlight_s *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode)
{
	return false;
}
static void Headless_BE_Scissor	(srect_t *rect)
{
}
static qboolean Headless_BE_LightCullModel	(vec3_t org, struct model_s *model)
{
	return false;
}
static void Headless_BE_VBO_Begin	(vbobctx_t *ctx, size_t maxsize)
{
}
static void Headless_BE_VBO_Data	(vbobctx_t *ctx, void *data, size_t size, vboarray_t *varray)
{
}
static void Headless_BE_VBO_Finish	(vbobctx_t *ctx, void *edata, size_t esize, vboarray_t *earray, void **vbomem, void **ebomem)
{
}
static void Headless_BE_VBO_Destroy	(vboarray_t *vearray, void *mem)
{
}
static void Headless_BE_RenderToTextureUpdate2d	(qboolean destchanged)
{
}

rendererinfo_t headlessrenderer =
{
	"Headless (Null)",
	{"headless"},
	QR_HEADLESS,

	Headless_Draw_Init,
	Headless_Draw_Shutdown,
	Headless_IMG_UpdateFiltering,
	Headless_IMG_LoadTextureMips,
	Headless_IMG_DestroyTexture,
	Headless_R_Init,
	Headless_R_DeInit,
	Headless_R_RenderView,
	Headless_VID_Init,
	Headless_VID_DeInit,
	Headless_VID_SwapBuffers,
	Headless_VID_ApplyGammaRamps,
	NULL,
	NULL,
	NULL,
	Headless_VID_SetWindowCaption,
	Headless_VID_GetRGBInfo,
	Headless_SCR_UpdateScreen,
	Headless_BE_SelectMode,
	Headless_BE_DrawMesh_List,
	Headless_BE_DrawMesh_Single,
	Headless_BE_SubmitBatch,
	Headless_BE_GetTempBatch,
	Headless_BE_DrawWorld,
	Headless_BE_Init,
	Headless_BE_GenBrushModelVBO,
	Headless_BE_ClearVBO,
	Headless_BE_UploadAllLightmaps,
	Headless_BE_SelectEntity,
	Headless_BE_SelectDLight,
	Headless_BE_Scissor,
	Headless_BE_LightCullModel,
	Headless_BE_VBO_Begin,
	Headless_BE_VBO_Data,
	Headless_BE_VBO_Finish,
	Headless_BE_VBO_Destroy,
	Headless_BE_RenderToTextureUpdate2d,
	"",
	Headless_VID_GetPrioriy
};




#if 0//def VKQUAKE
#include "../vk/vkrenderer.h"
static qboolean HeadlessVK_CreateSurface(void)
{
	vk.surface = VK_NULL_HANDLE;	//nothing to create, we're using offscreen rendering.
	vk.allowsubmissionthread = false;	//waste of threading
	return true;
}
static void HeadlessVK_Present(struct vkframe *theframe)
{
	//VK_DoPresent(theframe);

	if (!theframe)
		return;
	vk

	qglWaitVkSemaphoreNV(theframe->backbuf->presentsemaphore);
	//tell the gl driver to copy it over now
	qglDrawVkImageNV(theframe->backbuf->colour.image, theframe->backbuf->colour.sampler, 
		0, 0, vid.pixelwidth, vid.pixelheight,	//xywh (window coords)
		0,	//z
		0, 1, 1, 0);	//stst (remember that gl textures are meant to be upside down)

	//at this point the gl driver can signal the fence so our vk code can wake up and start drawing the next frame (if the gpu is slow)
	qglSignalVkFenceNV(vk.acquirefences[vk.aquirelast%ACQUIRELIMIT]);
	//and tell our code to expect it.
	vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT] = vk.aquirelast%vk.backbuf_count;
	barrier
	vk.aquirelast++;
}
static qboolean HeadlessVK_Init (rendererstate_t *info, unsigned char *palette)
{
	extern cvar_t vid_conautoscale;
#ifdef VK_NO_PROTOTYPES
	static dllhandle_t *hInstVulkan = NULL;
	dllfunction_t vkfuncs[] =
	{
		{(void**)&vkGetInstanceProcAddr, "vkGetInstanceProcAddr"},
		{NULL}
	};
	if (!hInstVulkan)
		hInstVulkan = *info->subrenderer?Sys_LoadLibrary(info->subrenderer, vkfuncs):NULL;
#ifdef _WIN32
	if (!hInstVulkan)
		hInstVulkan = Sys_LoadLibrary("vulkan-1.dll", vkfuncs);
#else
	if (!hInstVulkan)
		hInstVulkan = Sys_LoadLibrary("libvulkan.so.1", vkfuncs);
	if (!hInstVulkan)
		hInstVulkan = Sys_LoadLibrary("libvulkan.so", vkfuncs);
#endif
	if (!hInstVulkan)
	{
		Con_Printf("Unable to load vulkan library\nNo Vulkan drivers are installed\n");
		return false;
	}
#endif

	vid.pixelwidth = 1920;
	vid.pixelheight = 1080;
	if (!VK_Init(info, NULL,0, HeadlessVK_CreateSurface, NULL))
		return false;
	Cvar_ForceCallback(&vid_conautoscale);
	return true;
}

rendererinfo_t headlessvkrendererinfo =
{
	"Headless Vulkan",
	{
		"vkheadless"
	},
	QR_VULKAN,

	VK_Draw_Init,
	VK_Draw_Shutdown,

	VK_UpdateFiltering,
	VK_LoadTextureMips,
	VK_DestroyTexture,

	VK_R_Init,
	VK_R_DeInit,
	VK_R_RenderView,

	HeadlessVK_Init,
	GLVID_DeInit,
	GLVID_SwapBuffers,
	GLVID_ApplyGammaRamps,
	NULL,
	NULL,
	NULL,
	GLVID_SetCaption,
	VKVID_GetRGBInfo,

	VK_SCR_UpdateScreen,

	VKBE_SelectMode,
	VKBE_DrawMesh_List,
	VKBE_DrawMesh_Single,
	VKBE_SubmitBatch,
	VKBE_GetTempBatch,
	VKBE_DrawWorld,
	VKBE_Init,
	VKBE_GenBrushModelVBO,
	VKBE_ClearVBO,
	VKBE_UploadAllLightmaps,
	VKBE_SelectEntity,
	VKBE_SelectDLight,
	VKBE_Scissor,
	VKBE_LightCullModel,

	VKBE_VBO_Begin,
	VKBE_VBO_Data,
	VKBE_VBO_Finish,
	VKBE_VBO_Destroy,

	VKBE_RenderToTextureUpdate2d,

	"no more",

	Headless_VID_GetPrioriy
};
#endif
#endif
#endif
