/*
cl_menu.c - menu dlls interaction
Copyright (C) 2010 Uncle Mike

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 3 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.
*/

#include "common.h"
#include "client.h"
#include "const.h"
#include "library.h"
#include "input.h"
#include "server.h" // !!svgame.hInstance
#include "vid_common.h"

static void 	UI_UpdateUserinfo( void );

gameui_static_t	gameui;

void UI_UpdateMenu( float realtime )
{
	if( !gameui.hInstance ) return;

	// if some deferred cmds is waiting
	if( UI_IsVisible() && COM_CheckString( host.deferred_cmd ))
	{
		Cbuf_AddText( host.deferred_cmd );
		host.deferred_cmd[0] = '\0';
		Cbuf_Execute();
		return;
	}

	// don't show menu while level is loaded
	if( GameState->nextstate != STATE_RUNFRAME && !GameState->loadGame )
		return;

	// menu time (not paused, not clamped)
	gameui.globals->time = host.realtime;
	gameui.globals->frametime = host.realframetime;
	gameui.globals->demoplayback = cls.demoplayback;
	gameui.globals->demorecording = cls.demorecording;
	gameui.globals->developer = host.allow_console;

	gameui.dllFuncs.pfnRedraw( realtime );
	UI_UpdateUserinfo();
}

void UI_KeyEvent( int key, qboolean down )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnKeyEvent( key, down );
}

void UI_MouseMove( int x, int y )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnMouseMove( x, y );
}

void UI_SetActiveMenu( qboolean fActive )
{
	movie_state_t	*cin_state;

	if( !gameui.hInstance )
	{
		if( !fActive )
			Key_SetKeyDest( key_game );
		return;
	}

	gameui.drawLogo = fActive;
	gameui.dllFuncs.pfnSetActiveMenu( fActive );

	if( !fActive )
	{
		// close logo when menu is shutdown
		cin_state = AVI_GetState( CIN_LOGO );
		AVI_CloseVideo( cin_state );
	}
}

void UI_AddServerToList( netadr_t adr, const char *info )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnAddServerToList( adr, info );
}

void UI_GetCursorPos( int *pos_x, int *pos_y )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnGetCursorPos( pos_x, pos_y );
}

void UI_SetCursorPos( int pos_x, int pos_y )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnSetCursorPos( pos_x, pos_y );
}

void UI_ShowCursor( qboolean show )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnShowCursor( show );
}

qboolean UI_CreditsActive( void )
{
	if( !gameui.hInstance ) return 0;
	return gameui.dllFuncs.pfnCreditsActive();
}

void UI_CharEvent( int key )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnCharEvent( key );
}

qboolean UI_MouseInRect( void )
{
	if( !gameui.hInstance ) return 1;
	return gameui.dllFuncs.pfnMouseInRect();
}

qboolean UI_IsVisible( void )
{
	if( !gameui.hInstance ) return 0;
	return gameui.dllFuncs.pfnIsVisible();
}

/*
=======================
UI_AddTouchButtonToList

send button parameters to menu
=======================
*/
void UI_AddTouchButtonToList( const char *name, const char *texture, const char *command, unsigned char *color, int flags )
{
	if( gameui.dllFuncs2.pfnAddTouchButtonToList )
	{
		gameui.dllFuncs2.pfnAddTouchButtonToList( name, texture, command, color, flags );
	}
}

/*
=================
UI_ResetPing

notify gameui dll about latency reset
=================
*/
void UI_ResetPing( void )
{
	if( gameui.dllFuncs2.pfnResetPing )
	{
		gameui.dllFuncs2.pfnResetPing( );
	}
}

/*
=================
UI_ShowConnectionWarning

show connection warning dialog implemented by gameui dll
=================
*/
void UI_ShowConnectionWarning( void )
{
	if( cls.state != ca_connected )
		return;

	if( Host_IsLocalClient() )
		return;

	if( ++cl.lostpackets == 8 )
	{
		CL_Disconnect();
		if( gameui.dllFuncs2.pfnShowConnectionWarning )
		{
			gameui.dllFuncs2.pfnShowConnectionWarning();
		}
		Con_DPrintf( S_WARN "Too many lost packets! Showing Network options menu\n" );
	}
}


/*
=================
UI_ShowConnectionWarning

show update dialog
=================
*/
void UI_ShowUpdateDialog( qboolean preferStore )
{
	if( gameui.dllFuncs2.pfnShowUpdateDialog )
	{
		gameui.dllFuncs2.pfnShowUpdateDialog( preferStore );
	}

	Con_Printf( S_WARN "This version is not supported anymore. To continue, install latest engine version\n" );
}

/*
=================
UI_ShowConnectionWarning

show message box
=================
*/
void UI_ShowMessageBox( const char *text )
{
	if( gameui.dllFuncs2.pfnShowMessageBox )
	{
		gameui.dllFuncs2.pfnShowMessageBox( text );
	}
}

void UI_ConnectionProgress_Disconnect( void )
{
	if( gameui.dllFuncs2.pfnConnectionProgress_Disconnect )
	{
		gameui.dllFuncs2.pfnConnectionProgress_Disconnect( );
	}
}

void UI_ConnectionProgress_Download( const char *pszFileName, const char *pszServerName, const char *pszServerPath, int iCurrent, int iTotal, const char *comment )
{
	if( !gameui.dllFuncs2.pfnConnectionProgress_Download )
		return;

	if( pszServerPath )
	{
		char serverpath[MAX_SYSPATH];

		Q_snprintf( serverpath, sizeof( serverpath ), "%s%s", pszServerName, pszServerPath );
		gameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, serverpath, iCurrent, iTotal, comment );
	}
	else
	{
		gameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, pszServerName, iCurrent, iTotal, comment );
	}
}

void UI_ConnectionProgress_DownloadEnd( void )
{
	if( gameui.dllFuncs2.pfnConnectionProgress_DownloadEnd )
	{
		gameui.dllFuncs2.pfnConnectionProgress_DownloadEnd( );
	}
}

void UI_ConnectionProgress_Precache( void )
{
	if( gameui.dllFuncs2.pfnConnectionProgress_Precache )
	{
		gameui.dllFuncs2.pfnConnectionProgress_Precache( );
	}
}

void UI_ConnectionProgress_Connect( const char *server ) // NULL for local server
{
	if( gameui.dllFuncs2.pfnConnectionProgress_Connect )
	{
		gameui.dllFuncs2.pfnConnectionProgress_Connect( server );
	}
}

void UI_ConnectionProgress_ChangeLevel( void )
{
	if( gameui.dllFuncs2.pfnConnectionProgress_ChangeLevel )
	{
		gameui.dllFuncs2.pfnConnectionProgress_ChangeLevel( );
	}
}

void UI_ConnectionProgress_ParseServerInfo( const char *server )
{
	if( gameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo )
	{
		gameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo( server );
	}
}

static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, float width, float height )
{
	static float	cin_time;
	static int	last_frame = -1;
	byte		*cin_data = NULL;
	movie_state_t	*cin_state;
	int		cin_frame;
	qboolean		redraw = false;

	if( !gameui.drawLogo ) return;
	cin_state = AVI_GetState( CIN_LOGO );

	if( !AVI_IsActive( cin_state ))
	{
		string		path;
		const char	*fullpath;

		// run cinematic if not
		Q_snprintf( path, sizeof( path ), "media/%s", filename );
		COM_DefaultExtension( path, ".avi", sizeof( path ));
		fullpath = FS_GetDiskPath( path, false );

		if( FS_FileExists( path, false ) && !fullpath )
		{
			Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", path );
			gameui.drawLogo = false;
			return;
		}

		AVI_OpenVideo( cin_state, fullpath, false, true );
		if( !( AVI_GetVideoInfo( cin_state, &gameui.logo_xres, &gameui.logo_yres, &gameui.logo_length )))
		{
			AVI_CloseVideo( cin_state );
			gameui.drawLogo = false;
			return;
		}

		cin_time = 0.0f;
		last_frame = -1;
	}

	if( width <= 0 || height <= 0 )
	{
		// precache call, don't draw
		cin_time = 0.0f;
		last_frame = -1;
		return;
	}

	// advances cinematic time (ignores maxfps and host_framerate settings)
	cin_time += host.realframetime;

	// restarts the cinematic
	if( cin_time > gameui.logo_length )
		cin_time = 0.0f;

	// read the next frame
	cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time );

	if( cin_frame != last_frame )
	{
		cin_data = AVI_GetVideoFrame( cin_state, cin_frame );
		last_frame = cin_frame;
		redraw = true;
	}

	ref.dllFuncs.R_DrawStretchRaw( x, y, width, height, gameui.logo_xres, gameui.logo_yres, cin_data, redraw );
}

static int GAME_EXPORT UI_GetLogoWidth( void )
{
	return gameui.logo_xres;
}

static int GAME_EXPORT UI_GetLogoHeight( void )
{
	return gameui.logo_yres;
}

static float GAME_EXPORT UI_GetLogoLength( void )
{
	return gameui.logo_length;
}

static void UI_UpdateUserinfo( void )
{
	player_info_t	*player;

	if( !host.userinfo_changed )
		return;

	player = &gameui.playerinfo;

	Q_strncpy( player->userinfo, cls.userinfo, sizeof( player->userinfo ));
	Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name ));
	Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model ));
	player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
	player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
	host.userinfo_changed = false; // we got it
}

void Host_Credits( void )
{
	if( !gameui.hInstance ) return;
	gameui.dllFuncs.pfnFinalCredits();
}

static void UI_ConvertGameInfo( GAMEINFO *out, gameinfo_t *in )
{
	Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder ));
	Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap ));
	Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap ));
	Q_strncpy( out->title, in->title, sizeof( out->title ));
	Q_snprintf( out->version, sizeof( out->version ), "%g", in->version );

	Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url ));
	Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url ));
	Q_strncpy( out->size, Q_pretifymem( in->size, 0 ), sizeof( out->size ));
	Q_strncpy( out->type, in->type, sizeof( out->type ));
	Q_strncpy( out->date, in->date, sizeof( out->date ));

	out->gamemode = in->gamemode;

	if( in->nomodels )
		SetBits( out->flags, GFL_NOMODELS );
	if( in->noskills )
		SetBits( out->flags, GFL_NOSKILLS );
	if( in->render_picbutton_text )
		SetBits( out->flags, GFL_RENDER_PICBUTTON_TEXT );
	if( in->hd_background )
		SetBits( out->flags, GFL_HD_BACKGROUND );
	if( in->animated_title )
		SetBits( out->flags, GFL_ANIMATED_TITLE );
}

/*
====================
PIC_DrawGeneric

draw hudsprite routine
====================
*/
static void PIC_DrawGeneric( float x, float y, float width, float height, const wrect_t *prc )
{
	float	s1, s2, t1, t2;
	int	w, h;

	// assume we get sizes from image
	R_GetTextureParms( &w, &h, gameui.ds.gl_texturenum );

	if( prc )
	{
		// calc user-defined rectangle
		s1 = prc->left / (float)w;
		t1 = prc->top / (float)h;
		s2 = prc->right / (float)w;
		t2 = prc->bottom / (float)h;

		if( width == -1 && height == -1 )
		{
			width = prc->right - prc->left;
			height = prc->bottom - prc->top;
		}
	}
	else
	{
		s1 = t1 = 0.0f;
		s2 = t2 = 1.0f;
	}

	if( width == -1 && height == -1 )
	{
		width = w;
		height = h;
	}

	// pass scissor test if supposed
	if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))
		return;

	ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, gameui.ds.gl_texturenum );
	ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
}

/*
===============================================================================
	MainUI Builtin Functions

===============================================================================
*/
/*
=========
pfnPIC_Load

=========
*/
static HIMAGE GAME_EXPORT pfnPIC_Load( const char *szPicName, const byte *image_buf, int image_size, int flags )
{
	HIMAGE	tx;

	if( !COM_CheckString( szPicName ))
	{
		Con_Reportf( S_ERROR "CL_LoadImage: refusing to load image with empty name\n" );
		return 0;
	}

	// add default parms to image
	SetBits( flags, TF_IMAGE );

	Image_SetForceFlags( IL_LOAD_DECAL ); // allow decal images for menu
	tx = ref.dllFuncs.GL_LoadTexture( szPicName, image_buf, image_size, flags );
	Image_ClearForceFlags();

	return tx;
}

/*
=========
pfnPIC_Width

=========
*/
static int GAME_EXPORT pfnPIC_Width( HIMAGE hPic )
{
	int	picWidth;

	R_GetTextureParms( &picWidth, NULL, hPic );

	return picWidth;
}

/*
=========
pfnPIC_Height

=========
*/
static int GAME_EXPORT pfnPIC_Height( HIMAGE hPic )
{
	int	picHeight;

	R_GetTextureParms( NULL, &picHeight, hPic );

	return picHeight;
}

/*
=========
pfnPIC_Set

=========
*/
void GAME_EXPORT pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a )
{
	gameui.ds.gl_texturenum = hPic;
	r = bound( 0, r, 255 );
	g = bound( 0, g, 255 );
	b = bound( 0, b, 255 );
	a = bound( 0, a, 255 );
	ref.dllFuncs.Color4ub( r, g, b, a );
}

/*
=========
pfnPIC_Draw

=========
*/
void GAME_EXPORT pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc )
{
	ref.dllFuncs.GL_SetRenderMode( kRenderNormal );
	PIC_DrawGeneric( x, y, width, height, prc );
}

/*
=========
pfnPIC_DrawTrans

=========
*/
void GAME_EXPORT pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc )
{
	ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
	PIC_DrawGeneric( x, y, width, height, prc );
}

/*
=========
pfnPIC_DrawHoles

=========
*/
void GAME_EXPORT pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc )
{
	ref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha );
	PIC_DrawGeneric( x, y, width, height, prc );
}

/*
=========
pfnPIC_DrawAdditive

=========
*/
void GAME_EXPORT pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc )
{
	ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );
	PIC_DrawGeneric( x, y, width, height, prc );
}

/*
=========
pfnPIC_EnableScissor

=========
*/
static void GAME_EXPORT pfnPIC_EnableScissor( int x, int y, int width, int height )
{
	// check bounds
	x = bound( 0, x, gameui.globals->scrWidth );
	y = bound( 0, y, gameui.globals->scrHeight );
	width = bound( 0, width, gameui.globals->scrWidth - x );
	height = bound( 0, height, gameui.globals->scrHeight - y );

	CL_EnableScissor( &gameui.ds.scissor, x, y, width, height );
}

/*
=========
pfnPIC_DisableScissor

=========
*/
static void GAME_EXPORT pfnPIC_DisableScissor( void )
{
	CL_DisableScissor( &gameui.ds.scissor );
}

/*
=============
pfnFillRGBA

=============
*/
static void GAME_EXPORT pfnFillRGBA( int x, int y, int width, int height, int r, int g, int b, int a )
{
	r = bound( 0, r, 255 );
	g = bound( 0, g, 255 );
	b = bound( 0, b, 255 );
	a = bound( 0, a, 255 );
	ref.dllFuncs.Color4ub( r, g, b, a );
	ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
	ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) );
	ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
}

/*
=============
pfnCvar_RegisterVariable

=============
*/
static cvar_t *GAME_EXPORT pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags )
{
	return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_GAMEUIDLL ));
}

/*
=============
pfnClientCmd

=============
*/
static void GAME_EXPORT pfnClientCmd( int exec_now, const char *szCmdString )
{
	if( !szCmdString || !szCmdString[0] )
		return;

	Cbuf_AddText( szCmdString );
	Cbuf_AddText( "\n" );

	// client command executes immediately
	if( exec_now ) Cbuf_Execute();
}

/*
=============
pfnPlaySound

=============
*/
static void GAME_EXPORT pfnPlaySound( const char *szSound )
{
	if( !COM_CheckString( szSound )) return;
	S_StartLocalSound( szSound, VOL_NORM, false );
}

/*
=============
pfnDrawCharacter

quakefont draw character
=============
*/
static void GAME_EXPORT pfnDrawCharacter( int ix, int iy, int iwidth, int iheight, int ch, int ulRGBA, HIMAGE hFont )
{
	rgba_t	color;
	float	row, col, size;
	float	s1, t1, s2, t2;
	float	x = ix, y = iy;
	float	width = iwidth;
	float	height = iheight;

	ch &= 255;

	if( ch == ' ' ) return;
	if( y < -height ) return;

	color[3] = (ulRGBA & 0xFF000000) >> 24;
	color[0] = (ulRGBA & 0xFF0000) >> 16;
	color[1] = (ulRGBA & 0xFF00) >> 8;
	color[2] = (ulRGBA & 0xFF) >> 0;
	ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );

	col = (ch & 15) * 0.0625f + (0.5f / 256.0f);
	row = (ch >> 4) * 0.0625f + (0.5f / 256.0f);
	size = 0.0625f - (1.0f / 256.0f);

	s1 = col;
	t1 = row;
	s2 = s1 + size;
	t2 = t1 + size;

	// pass scissor test if supposed
	if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))
		return;

	ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
	ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, hFont );
	ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
}

/*
=============
UI_DrawConsoleString

drawing string like a console string
=============
*/
static int GAME_EXPORT UI_DrawConsoleString( int x, int y, const char *string )
{
	int	drawLen;

	if( !string || !*string ) return 0; // silent ignore
	drawLen = Con_DrawString( x, y, string, gameui.ds.textColor );
	MakeRGBA( gameui.ds.textColor, 255, 255, 255, 255 );

	return (x + drawLen); // exclude color prexfixes
}

/*
=============
pfnDrawSetTextColor

set color for anything
=============
*/
static void GAME_EXPORT UI_DrawSetTextColor( int r, int g, int b, int alpha )
{
	// bound color and convert to byte
	gameui.ds.textColor[0] = r;
	gameui.ds.textColor[1] = g;
	gameui.ds.textColor[2] = b;
	gameui.ds.textColor[3] = alpha;
}

/*
====================
pfnGetPlayerModel

for drawing playermodel previews
====================
*/
static cl_entity_t* GAME_EXPORT pfnGetPlayerModel( void )
{
	return &gameui.playermodel;
}

/*
====================
pfnSetPlayerModel

for drawing playermodel previews
====================
*/
static void GAME_EXPORT pfnSetPlayerModel( cl_entity_t *ent, const char *path )
{
	ent->model = Mod_ForName( path, false, false );
	ent->curstate.modelindex = MAX_MODELS; // unreachable index
}

/*
====================
pfnClearScene

for drawing playermodel previews
====================
*/
static void GAME_EXPORT pfnClearScene( void )
{
	ref.dllFuncs.R_PushScene();
	ref.dllFuncs.R_ClearScene();
}

/*
====================
pfnRenderScene

for drawing playermodel previews
====================
*/
static void GAME_EXPORT pfnRenderScene( const ref_viewpass_t *rvp )
{
	ref_viewpass_t copy;

	// to avoid division by zero
	if( !rvp || rvp->fov_x <= 0.0f || rvp->fov_y <= 0.0f )
		return;

	copy = *rvp;

	// don't allow special modes from menu
	copy.flags = 0;

	ref.dllFuncs.R_Set2DMode( false );
	GL_RenderFrame( &copy );
	ref.dllFuncs.R_Set2DMode( true );
	ref.dllFuncs.R_PopScene();
}

/*
====================
pfnAddEntity

adding player model into visible list
====================
*/
static int GAME_EXPORT pfnAddEntity( int entityType, cl_entity_t *ent )
{
	if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
		return false;
	return true;
}

/*
====================
pfnClientJoin

send client connect
====================
*/
static void GAME_EXPORT pfnClientJoin( const netadr_t adr )
{
	Cbuf_AddTextf( "connect %s\n", NET_AdrToString( adr ));
}

/*
====================
pfnKeyGetOverstrikeMode

get global key overstrike state
====================
*/
static int GAME_EXPORT pfnKeyGetOverstrikeMode( void )
{
	return host.key_overstrike;
}

/*
====================
pfnKeySetOverstrikeMode

set global key overstrike mode
====================
*/
static void GAME_EXPORT pfnKeySetOverstrikeMode( int fActive )
{
	host.key_overstrike = fActive;
}

/*
====================
pfnKeyGetState

returns kbutton struct if found
====================
*/
static void *pfnKeyGetState( const char *name )
{
	if( clgame.dllFuncs.KB_Find )
		return clgame.dllFuncs.KB_Find( name );
	return NULL;
}

/*
=========
pfnMemAlloc

=========
*/
static void *pfnMemAlloc( size_t cb, const char *filename, const int fileline )
{
	return _Mem_Alloc( gameui.mempool, cb, true, filename, fileline );
}

/*
=========
pfnMemFree

=========
*/
static void GAME_EXPORT pfnMemFree( void *mem, const char *filename, const int fileline )
{
	_Mem_Free( mem, filename, fileline );
}

/*
=========
pfnGetGameInfo

=========
*/
static int GAME_EXPORT pfnGetGameInfo( GAMEINFO *pgameinfo )
{
	if( !pgameinfo ) return 0;

	*pgameinfo = gameui.gameInfo;
	return 1;
}

/*
=========
pfnGetGamesList

=========
*/
static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames )
{
	if( numGames ) *numGames = FI->numgames;
	return gameui.modsInfo;
}

/*
=========
pfnGetFilesList

release prev search on a next call
=========
*/
static char ** GAME_EXPORT pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly )
{
	static search_t	*t = NULL;

	if( t ) Mem_Free( t ); // release prev search

	t = FS_Search( pattern, true, gamedironly );
	if( !t )
	{
		if( numFiles ) *numFiles = 0;
		return NULL;
	}

	if( numFiles ) *numFiles = t->numfilenames;
	return t->filenames;
}

/*
=========
pfnGetClipboardData

pointer must be released in call place
=========
*/
static char *pfnGetClipboardData( void )
{
	return Sys_GetClipboardData();
}

/*
=========
pfnCheckGameDll

=========
*/
int GAME_EXPORT pfnCheckGameDll( void )
{
#ifdef XASH_INTERNAL_GAMELIBS
	return true;
#else
	string dllpath;

	if( svgame.hInstance )
		return true;

	COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath ));

	if( FS_FileExists( dllpath, false ))
		return true;

	return false;
#endif
}

/*
=========
pfnChangeInstance

=========
*/
static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage )
{
	Con_Reportf( S_ERROR "ChangeInstance menu call is deprecated!\n" );
}

/*
=========
pfnHostEndGame

=========
*/
static void GAME_EXPORT pfnHostEndGame( const char *szFinalMessage )
{
	if( !szFinalMessage ) szFinalMessage = "";
	Host_EndGame( false, "%s", szFinalMessage );
}

/*
=========
pfnStartBackgroundTrack

=========
*/
static void GAME_EXPORT pfnStartBackgroundTrack( const char *introTrack, const char *mainTrack )
{
	S_StartBackgroundTrack( introTrack, mainTrack, 0, false );
}

static void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )
{
	ref.dllFuncs.GL_ProcessTexture( texnum, gamma, topColor, bottomColor );
}


/*
=================
UI_ShellExecute
=================
*/
static void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, int shouldExit )
{
	Platform_ShellExecute( path, parms );

	if( shouldExit )
		Sys_Quit();
}

/*
==============
pfnParseFile

legacy wrapper
==============
*/
static char *pfnParseFile( char *buf, char *token )
{
	return COM_ParseFile( buf, token, INT_MAX );
}

/*
=============
pfnFileExists

legacy wrapper
=============
*/
static int pfnFileExists( const char *path, int gamedironly )
{
	return FS_FileExists( path, gamedironly );
}

/*
=============
pfnDelete

legacy wrapper
=============
*/
static int pfnDelete( const char *path )
{
	return FS_Delete( path );
}

// engine callbacks
static ui_enginefuncs_t gEngfuncs =
{
	pfnPIC_Load,
	GL_FreeImage,
	pfnPIC_Width,
	pfnPIC_Height,
	pfnPIC_Set,
	pfnPIC_Draw,
	pfnPIC_DrawHoles,
	pfnPIC_DrawTrans,
	pfnPIC_DrawAdditive,
	pfnPIC_EnableScissor,
	pfnPIC_DisableScissor,
	pfnFillRGBA,
	pfnCvar_RegisterGameUIVariable,
	Cvar_VariableValue,
	Cvar_VariableString,
	Cvar_Set,
	Cvar_SetValue,
	Cmd_AddGameUICommand,
	pfnClientCmd,
	Cmd_RemoveCommand,
	Cmd_Argc,
	Cmd_Argv,
	Cmd_Args,
	Con_Printf,
	Con_DPrintf,
	UI_NPrintf,
	UI_NXPrintf,
	pfnPlaySound,
	UI_DrawLogo,
	UI_GetLogoWidth,
	UI_GetLogoHeight,
	UI_GetLogoLength,
	pfnDrawCharacter,
	UI_DrawConsoleString,
	UI_DrawSetTextColor,
	Con_DrawStringLen,
	Con_DefaultColor,
	pfnGetPlayerModel,
	pfnSetPlayerModel,
	pfnClearScene,
	pfnRenderScene,
	pfnAddEntity,
	Host_Error,
	pfnFileExists,
	pfnGetGameDir,
	Cmd_CheckMapsList,
	CL_Active,
	pfnClientJoin,
	COM_LoadFileForMe,
	pfnParseFile,
	COM_FreeFile,
	Key_ClearStates,
	Key_SetKeyDest,
	Key_KeynumToString,
	Key_GetBinding,
	Key_SetBinding,
	Key_IsDown,
	pfnKeyGetOverstrikeMode,
	pfnKeySetOverstrikeMode,
	pfnKeyGetState,
	pfnMemAlloc,
	pfnMemFree,
	pfnGetGameInfo,
	pfnGetGamesList,
	pfnGetFilesList,
	SV_GetSaveComment,
	CL_GetDemoComment,
	pfnCheckGameDll,
	pfnGetClipboardData,
	UI_ShellExecute,
	Host_WriteServerConfig,
	pfnChangeInstance,
	pfnStartBackgroundTrack,
	pfnHostEndGame,
	COM_RandomFloat,
	COM_RandomLong,
	IN_SetCursor,
	pfnIsMapValid,
	GL_ProcessTexture,
	COM_CompareFileTime,
	VID_GetModeString,
	(void*)COM_SaveFile,
	pfnDelete
};

static void pfnEnableTextInput( int enable )
{
	Key_EnableTextInput( enable, false );
}

static int pfnGetRenderers( unsigned int num, char *shortName, size_t size1, char *readableName, size_t size2 )
{
	if( num >= ref.numRenderers )
		return 0;

	if( shortName && size1 )
		Q_strncpy( shortName, ref.shortNames[num], size1 );

	if( readableName && size2 )
		Q_strncpy( readableName, ref.readableNames[num], size2 );

	return 1;
}

static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len )
{
	return COM_ParseFileSafe( data, buf, size, flags, len, NULL );
}

static ui_extendedfuncs_t gExtendedfuncs =
{
	pfnEnableTextInput,
	Con_UtfProcessChar,
	Con_UtfMoveLeft,
	Con_UtfMoveRight,
	pfnGetRenderers,
	Sys_DoubleTime,
	pfnParseFileSafe,
	NET_AdrToString,
	NET_CompareAdrSort,
};

void UI_UnloadProgs( void )
{
	if( !gameui.hInstance ) return;

	// deinitialize game
	gameui.dllFuncs.pfnShutdown();

	Cvar_FullSet( "host_gameuiloaded", "0", FCVAR_READ_ONLY );

	Cvar_Unlink( FCVAR_GAMEUIDLL );
	Cmd_Unlink( CMD_GAMEUIDLL );

	COM_FreeLibrary( gameui.hInstance );
	Mem_FreePool( &gameui.mempool );
	memset( &gameui, 0, sizeof( gameui ));
}

qboolean UI_LoadProgs( void )
{
	static ui_enginefuncs_t	gpEngfuncs;
	static ui_extendedfuncs_t gpExtendedfuncs;
	static ui_globalvars_t	gpGlobals;
	UIEXTENEDEDAPI GetExtAPI;
	UITEXTAPI	GiveTextApi;
	MENUAPI	GetMenuAPI;
	string dllpath;
	int			i;

	if( gameui.hInstance ) UI_UnloadProgs();

	// setup globals
	gameui.globals = &gpGlobals;

	COM_GetCommonLibraryPath( LIBRARY_GAMEUI, dllpath, sizeof( dllpath ));

	if(!( gameui.hInstance = COM_LoadLibrary( dllpath, false, false )))
	{
		string path = OS_LIB_PREFIX "menu." OS_LIB_EXT;

		FS_AllowDirectPaths( true );

		// no use to load it from engine directory, as library loader
		// that implements internal gamelibs already knows how to load it
#ifndef XASH_INTERNAL_GAMELIBS
		if(!( gameui.hInstance = COM_LoadLibrary( path, false, true )))
#endif
		{
			FS_AllowDirectPaths( false );
			return false;
		}
	}

	FS_AllowDirectPaths( false );

	if(( GetMenuAPI = (MENUAPI)COM_GetProcAddress( gameui.hInstance, "GetMenuAPI" )) == NULL )
	{
		COM_FreeLibrary( gameui.hInstance );
		Con_Reportf( "UI_LoadProgs: can't init menu API\n" );
		gameui.hInstance = NULL;
		return false;
	}


	gameui.use_text_api = false;

	// make local copy of engfuncs to prevent overwrite it with user dll
	memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs ));

	gameui.mempool = Mem_AllocPool( "Menu Pool" );

	if( !GetMenuAPI( &gameui.dllFuncs, &gpEngfuncs, gameui.globals ))
	{
		COM_FreeLibrary( gameui.hInstance );
		Con_Reportf( "UI_LoadProgs: can't init menu API\n" );
		Mem_FreePool( &gameui.mempool );
		gameui.hInstance = NULL;
		return false;
	}

	// make local copy of engfuncs to prevent overwrite it with user dll
	memcpy( &gpExtendedfuncs, &gExtendedfuncs, sizeof( gExtendedfuncs ));
	memset( &gameui.dllFuncs2, 0, sizeof( gameui.dllFuncs2 ));

	// try to initialize new extended API
	if( ( GetExtAPI = (UIEXTENEDEDAPI)COM_GetProcAddress( gameui.hInstance, "GetExtAPI" ) ) )
	{
		Con_Reportf( "UI_LoadProgs: extended Menu API found\n" );
		if( GetExtAPI( MENU_EXTENDED_API_VERSION, &gameui.dllFuncs2, &gpExtendedfuncs ) )
		{
			Con_Reportf( "UI_LoadProgs: extended Menu API initialized\n" );
			gameui.use_text_api = true;
		}
	}
	else // otherwise, fallback to old and deprecated extensions
	{
		if( ( GiveTextApi = (UITEXTAPI)COM_GetProcAddress( gameui.hInstance, "GiveTextAPI" ) ) )
		{
			Con_Reportf( "UI_LoadProgs: extended text API found\n" );
			Con_Reportf( S_WARN "Text API is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" );
			if( GiveTextApi( &gpExtendedfuncs ) ) // they are binary compatible, so we can just pass extended funcs API to menu
			{
				Con_Reportf( "UI_LoadProgs: extended text API initialized\n" );
				gameui.use_text_api = true;
			}
		}

		gameui.dllFuncs2.pfnAddTouchButtonToList = (ADDTOUCHBUTTONTOLIST)COM_GetProcAddress( gameui.hInstance, "AddTouchButtonToList" );
		if( gameui.dllFuncs2.pfnAddTouchButtonToList )
		{
			Con_Reportf( "UI_LoadProgs: AddTouchButtonToList call found\n" );
			Con_Reportf( S_WARN "AddTouchButtonToList is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" );
		}
	}

	Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY );

	// setup gameinfo
	for( i = 0; i < FI->numgames; i++ )
	{
		gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO ));
		UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] );
	}

	UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo

	// setup globals
	gameui.globals->developer = host.allow_console;

	// initialize game
	gameui.dllFuncs.pfnInit();

	return true;
}
