/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * This is the "heart" of the id Tech 2 refresh engine. This file
 * implements the main window in which Quake II is running. The window
 * itself is created by the SDL backend, but here the refresh module is
 * loaded, initialized and it's interaction with the operating system
 * implemented. This code is also the interconnect between the input
 * system (the mouse) and the keyboard system, both are here tied
 * together with the refresher. The direct interaction between the
 * refresher and those subsystems are the main cause for the very
 * acurate and precise input controls of the id Tech 2.
 *
 * This implementation works for Windows and unixoid systems, but
 * other platforms may need an own implementation!
 *
 * =======================================================================
 */

#include <assert.h>
#include <errno.h>

#include "../../client/header/client.h"
#include "header/input.h"

/* Structure containing functions exported from refresh DLL */
refexport_t re;

/* Console variables that we need to access from this module */
cvar_t *vid_gamma;
cvar_t *vid_ref;                /* Name of Refresh DLL loaded */
cvar_t *vid_xpos;               /* X coordinate of window position */
cvar_t *vid_ypos;               /* Y coordinate of window position */
cvar_t *vid_fullscreen;

/* Global variables used internally by this module */
viddef_t viddef;                /* global video state; used by other modules */
void *reflib_library;           /* Handle to refresh DLL */
qboolean reflib_active = 0;

#define VID_NUM_MODES (sizeof(vid_modes) / sizeof(vid_modes[0]))

void Do_Key_Event(int key, qboolean down);

void (*IN_Update_fp)(void);
void (*IN_KeyboardInit_fp)(Key_Event_fp_t fp);
void (*IN_Close_fp)(void);
void (*IN_BackendInit_fp)(in_state_t *in_state_p);
void (*IN_BackendShutdown_fp)(void);
void (*IN_BackendMouseButtons_fp)(void);
void (*IN_BackendMove_fp)(usercmd_t *cmd);

in_state_t in_state;

#define MAXPRINTMSG 4096

void
VID_Printf(int print_level, char *fmt, ...)
{
	va_list argptr;
	char msg[MAXPRINTMSG];

	va_start(argptr, fmt);
	vsnprintf(msg, MAXPRINTMSG, fmt, argptr);
	va_end(argptr);

	if (print_level == PRINT_ALL)
	{
		Com_Printf("%s", msg);
	}
	else
	{
		Com_DPrintf("%s", msg);
	}
}

void
VID_Error(int err_level, char *fmt, ...)
{
	va_list argptr;
	char msg[MAXPRINTMSG];

	va_start(argptr, fmt);
	vsnprintf(msg, MAXPRINTMSG, fmt, argptr);
	va_end(argptr);

	Com_Error(err_level, "%s", msg);
}

/*
 * Console command to re-start the video mode and refresh DLL. We do this
 * simply by setting the modified flag for the vid_ref variable, which will
 * cause the entire video mode and refresh DLL to be reset on the next frame.
 */
void
VID_Restart_f(void)
{
	vid_ref->modified = true;
}

typedef struct vidmode_s
{
	const char *description;
	int width, height;
	int mode;
} vidmode_t;

/* This must be the same as in videomenu.c! */
vidmode_t vid_modes[] = {
	{"Mode 0: 320x240", 320, 240, 0},
	{"Mode 1: 400x300", 400, 300, 1},
	{"Mode 2: 512x384", 512, 384, 2},
	{"Mode 3: 640x400", 640, 400, 3},
	{"Mode 4: 640x480", 640, 480, 4},
	{"Mode 5: 800x500", 800, 500, 5},
	{"Mode 6: 800x600", 800, 600, 6},
	{"Mode 7: 960x720", 960, 720, 7},
	{"Mode 8: 1024x480", 1024, 480, 8},
	{"Mode 9: 1024x640", 1024, 640, 9},
	{"Mode 10: 1024x768", 1024, 768, 10},
	{"Mode 11: 1152x768", 1152, 768, 11},
	{"Mode 12: 1152x864", 1152, 864, 12},
	{"Mode 13: 1280x800", 1280, 800, 13},
	{"Mode 14: 1280x854", 1280, 854, 14},
	{"Mode 15: 1280x960", 1280, 960, 15},
	{"Mode 16: 1280x1024", 1280, 1024, 16},
	{"Mode 17: 1366x768", 1366, 768, 17}, 
	{"Mode 18: 1440x900", 1440, 900, 18},
	{"Mode 19: 1600x1200", 1600, 1200, 19},
	{"Mode 20: 1680x1050", 1680, 1050, 20},
	{"Mode 21: 1920x1080", 1920, 1080, 21},
	{"Mode 22: 1920x1200", 1920, 1200, 22},
	{"Mode 23: 2048x1536", 2048, 1536, 23},
};

qboolean
VID_GetModeInfo(int *width, int *height, int mode)
{
	if ((mode < 0) || (mode >= VID_NUM_MODES))
	{
		return false;
	}

	*width = vid_modes[mode].width;
	*height = vid_modes[mode].height;

	return true;
}

void
VID_NewWindow(int width, int height)
{
	viddef.width = width;
	viddef.height = height;
}

void
VID_FreeReflib(void)
{
	if (reflib_library)
	{
		if (IN_Close_fp)
		{
			IN_Close_fp();
		}

		if (IN_BackendShutdown_fp)
		{
			IN_BackendShutdown_fp();
		}

		Sys_FreeLibrary(reflib_library);
	}

	IN_KeyboardInit_fp = NULL;
	IN_Update_fp = NULL;
	IN_Close_fp = NULL;
	IN_BackendInit_fp = NULL;
	IN_BackendShutdown_fp = NULL;
	IN_BackendMouseButtons_fp = NULL;
	IN_BackendMove_fp = NULL;

	memset(&re, 0, sizeof(re));
	reflib_library = NULL;
	reflib_active = false;
}

qboolean
VID_LoadRefresh(char *name)
{
	refimport_t ri;
	R_GetRefAPI_t GetRefAPI;

	char fn[MAX_OSPATH];
	char *path;

	if (reflib_active)
	{
		if (IN_Close_fp)
		{
			IN_Close_fp();
		}

		if (IN_BackendShutdown_fp)
		{
			IN_BackendShutdown_fp();
		}

		IN_Close_fp = NULL;
		IN_BackendShutdown_fp = NULL;
		re.Shutdown();
		VID_FreeReflib();
	}

	Com_Printf("----- refresher initialization -----\n");

	path = Cvar_Get("basedir", ".", CVAR_NOSET)->string;
	snprintf(fn, MAX_OSPATH, "%s/%s", path, name);

	Sys_LoadLibrary(fn, NULL, &reflib_library);

	if (reflib_library == 0)
	{
		return false;
	}


	ri.Cmd_AddCommand = Cmd_AddCommand;
	ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
	ri.Cmd_Argc = Cmd_Argc;
	ri.Cmd_Argv = Cmd_Argv;
	ri.Cmd_ExecuteText = Cbuf_ExecuteText;
	ri.Con_Printf = VID_Printf;
	ri.Sys_Error = VID_Error;
	ri.Sys_Mkdir = Sys_Mkdir;
	ri.Sys_LoadLibrary = Sys_LoadLibrary;
	ri.Sys_FreeLibrary = Sys_FreeLibrary;
	ri.Sys_GetProcAddress = Sys_GetProcAddress;
	ri.FS_LoadFile = FS_LoadFile;
	ri.FS_FreeFile = FS_FreeFile;
	ri.FS_Gamedir = FS_Gamedir;
	ri.Cvar_Get = Cvar_Get;
	ri.Cvar_Set = Cvar_Set;
	ri.Cvar_SetValue = Cvar_SetValue;
	ri.Vid_GetModeInfo = VID_GetModeInfo;
	ri.Vid_MenuInit = VID_MenuInit;
	ri.Vid_NewWindow = VID_NewWindow;

	if ((GetRefAPI = (void *)Sys_GetProcAddress(reflib_library, "R_GetRefAPI")) == 0)
	{
		Com_Error(ERR_FATAL, "Sys_GetProcAddress failed on %s", name);
	}

	re = GetRefAPI(ri);

	if (re.api_version != API_VERSION)
	{
		VID_FreeReflib();
		Com_Error(ERR_FATAL, "%s has incompatible api_version", name);
	}

	/* Init IN (Mouse) */
	in_state.IN_CenterView_fp = IN_CenterView;
	in_state.Key_Event_fp = Do_Key_Event;
	in_state.viewangles = cl.viewangles;
	in_state.in_strafe_state = &in_strafe.state;
	in_state.in_speed_state = &in_speed.state;

	if (((IN_BackendInit_fp = Sys_GetProcAddress(reflib_library, "IN_BackendInit")) == NULL) ||
		((IN_BackendShutdown_fp = Sys_GetProcAddress(reflib_library, "IN_BackendShutdown")) == NULL) ||
		((IN_BackendMouseButtons_fp = Sys_GetProcAddress(reflib_library, "IN_BackendMouseButtons")) == NULL) ||
		((IN_BackendMove_fp = Sys_GetProcAddress(reflib_library, "IN_BackendMove")) == NULL))
	{
		Com_Error(ERR_FATAL, "No input backend init functions in REF.\n");
	}

	if (IN_BackendInit_fp)
	{
		IN_BackendInit_fp(&in_state);
	}

	if (re.Init(0, 0) == -1)
	{
		re.Shutdown();
		VID_FreeReflib();
		return false;
	}

	/* Init IN */
	if (((IN_KeyboardInit_fp = Sys_GetProcAddress(reflib_library, "IN_KeyboardInit")) == NULL) ||
		((IN_Update_fp = Sys_GetProcAddress(reflib_library, "IN_Update")) == NULL) ||
		((IN_Close_fp = Sys_GetProcAddress(reflib_library, "IN_Close")) == NULL))
	{
		Com_Error(ERR_FATAL, "No keyboard input functions in REF.\n");
	}

	IN_KeyboardInit_fp(Do_Key_Event);
	Key_ClearStates();

	Com_Printf("------------------------------------\n\n");
	reflib_active = true;
	return true;
}

/*
 * This function gets called once just before drawing each frame, and
 * it's sole purpose in life is to check to see if any of the video mode
 * parameters have changed, and if they have to update the rendering DLL
 * and/or video mode to match.
 */
void
VID_CheckChanges(void)
{
	char name[100];

	if (vid_ref->modified)
	{
		S_StopAllSounds();
	}

	while (vid_ref->modified)
	{
		/* refresh has changed */
		vid_ref->modified = false;
		vid_fullscreen->modified = true;
		cl.refresh_prepped = false;
		cl.cinematicpalette_active = false;
		cls.disable_screen = true;

#ifdef _WIN32
		sprintf(name, "ref_%s.dll", vid_ref->string);
#else
		sprintf(name, "ref_%s.so", vid_ref->string);
#endif

		if (!VID_LoadRefresh(name))
		{
			Cvar_Set("vid_ref", "gl");
		}

		cls.disable_screen = false;
	}
}

void
VID_Init(void)
{
	/* Create the video variables so we know how to start the graphics drivers */
	vid_ref = Cvar_Get("vid_ref", "gl", CVAR_ARCHIVE);

	vid_xpos = Cvar_Get("vid_xpos", "3", CVAR_ARCHIVE);
	vid_ypos = Cvar_Get("vid_ypos", "22", CVAR_ARCHIVE);
	vid_fullscreen = Cvar_Get("vid_fullscreen", "0", CVAR_ARCHIVE);
	vid_gamma = Cvar_Get("vid_gamma", "1", CVAR_ARCHIVE);

	/* Add some console commands that we want to handle */
	Cmd_AddCommand("vid_restart", VID_Restart_f);

	/* Start the graphics mode and load refresh DLL */
	VID_CheckChanges();
}

void
VID_Shutdown(void)
{
	if (reflib_active)
	{
		if (IN_Close_fp)
		{
			IN_Close_fp();
		}

		if (IN_BackendShutdown_fp)
		{
			IN_BackendShutdown_fp();
		}

		IN_Close_fp = NULL;
		IN_BackendShutdown_fp = NULL;
		re.Shutdown();
		VID_FreeReflib();
	}
}

/* INPUT */

void
IN_Shutdown(void)
{
	if (IN_BackendShutdown_fp)
	{
		IN_BackendShutdown_fp();
	}
}

void
IN_Commands(void)
{
	if (IN_BackendMouseButtons_fp)
	{
		IN_BackendMouseButtons_fp();
	}
}

void
IN_Move(usercmd_t *cmd)
{
	if (IN_BackendMove_fp)
	{
		IN_BackendMove_fp(cmd);
	}
}

void
Do_Key_Event(int key, qboolean down)
{
	Key_Event(key, down, Sys_Milliseconds());
}

