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

*/
#include <ctype.h>

#include "ui_local.h"

char *menu_in_sound		= S_UI_MENU_IN_SOUND;
char *menu_move_sound	= S_UI_MENU_MOVE_SOUND;
char *menu_out_sound	= S_UI_MENU_OUT_SOUND;

void M_Menu_Main_f( void );
	void M_Menu_JoinServer_f( void );
	void M_Menu_Setup_f( void );
		void M_Menu_PlayerConfig_f( void );
		void M_Menu_TeamConfig_f( void );
		void M_Menu_StartServer_f( void );
	void M_Menu_Options_f( void );
		void M_Menu_Keys_f( void );
		void M_Menu_Vsays_f( void );
	void M_Menu_Sound_f( void );
	void M_Menu_Gfx_f( void );
	void M_Menu_Video_f( void );
		void M_Menu_GLExt_f( void );
	void M_Menu_Quit_f( void );
	void M_Menu_Demos_f( void );
	void M_Menu_Mods_f( void );

void M_Menu_Game_f( void );
void M_Menu_Failed_f( void );
void M_Menu_MsgBox_f( void );

ui_local_t	uis;

qboolean	m_entersound;		// play after drawing a frame, so caching
								// won't disrupt the sound
struct mempool_s *uipool;

menuframework_s *m_active;
void	*m_cursoritem;
void	(*m_drawfunc) (void);
const char *(*m_keyfunc) (int key);
const char *(*m_chareventfunc) (int key);

//======================================================================

char *gametype_names[] =
{
	"dm",
	"duel",
	"tdm",
	"ctf",
	"race",
	"midair",
	0
};

char *noyes_names[] =
{
	"no", "yes", 0
};

char *offon_names[] =
{
	"off", "on", 0
};

//======================================================================

/*
============
UI_API
============
*/
int UI_API (void) {
	return UI_API_VERSION;
}

/*
============
UI_Error
============
*/
void UI_Error ( char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;

	va_start ( argptr, fmt );
	if ( vsprintf (msg, fmt, argptr) > sizeof(msg) ) {
		trap_Error ( "CG_Error: Buffer overflow" );
	}
	va_end ( argptr );

	trap_Error ( msg );
}

/*
============
UI_Printf
============
*/
void UI_Printf ( char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;

	va_start ( argptr, fmt );
	if ( vsprintf (msg, fmt, argptr) > sizeof(msg) ) {
		trap_Error ( "CG_Print: Buffer overflow" );
	}
	va_end ( argptr );

	trap_Print ( msg );
}

//=============================================================================
/* Support Routines */

#define	MAX_MENU_DEPTH	8


typedef struct
{
	menuframework_s *m;
	void	(*draw) (void);
	const char *(*key) (int k);
	const char *(*charevent) (int k);
} menulayer_t;

menulayer_t	m_layers[MAX_MENU_DEPTH];
int		m_menudepth;

void UI_UpdateMousePosition (void);


//=================
//UI_RegisterFonts
//=================
void UI_RegisterFonts( void ) 
{
	cvar_t	*con_fontSystemSmall = trap_Cvar_Get( "con_fontSystemSmall", DEFAULT_FONT_SMALL, CVAR_ARCHIVE );
	cvar_t	*con_fontSystemMedium = trap_Cvar_Get( "con_fontSystemMedium", DEFAULT_FONT_MEDIUM, CVAR_ARCHIVE );
	cvar_t	*con_fontSystemBig = trap_Cvar_Get( "con_fontSystemBig", DEFAULT_FONT_BIG, CVAR_ARCHIVE );

	uis.fontSystemSmall = trap_SCR_RegisterFont( con_fontSystemSmall->string );
	if( !uis.fontSystemSmall ) {
		uis.fontSystemSmall = trap_SCR_RegisterFont( DEFAULT_FONT_SMALL );
		if( !uis.fontSystemSmall )
			UI_Error( "Couldn't load default font \"%s\"", DEFAULT_FONT_SMALL );
	}
	uis.fontSystemMedium = trap_SCR_RegisterFont( con_fontSystemMedium->string );
	if( !uis.fontSystemMedium )
		uis.fontSystemMedium = trap_SCR_RegisterFont( DEFAULT_FONT_MEDIUM );

	uis.fontSystemBig = trap_SCR_RegisterFont( con_fontSystemBig->string );
	if( !uis.fontSystemBig )
		uis.fontSystemBig = trap_SCR_RegisterFont( DEFAULT_FONT_BIG );

}

void M_Cache( void )
{
	// precache sounds
	trap_S_RegisterSound( S_UI_MENU_IN_SOUND );
	trap_S_RegisterSound( S_UI_MENU_MOVE_SOUND );
	trap_S_RegisterSound( S_UI_MENU_OUT_SOUND );

	uis.whiteShader = trap_R_RegisterPic( "gfx/ui/white" );
	trap_R_RegisterPic( UI_SHADER_FXBACK );
	trap_R_RegisterPic( UI_SHADER_BIGLOGO );
	trap_R_RegisterPic( UI_SHADER_CURSOR );

	UI_RegisterFonts();
}

void M_PushMenu( menuframework_s *m, void (*draw) (void), const char *(*key) (int k), const char *(*charevent) (int k) )
{
	int		i;

	// if this menu is already present, drop back to that level
	// to avoid stacking menus by hotkeys
	for( i=0 ; i<m_menudepth ; i++ ) {
		if( m_layers[i].m == m && m_layers[i].draw == draw && m_layers[i].key == key ) {
			m_menudepth = i;
		}
	}

	if( i == m_menudepth )
	{
		if( m_menudepth >= MAX_MENU_DEPTH ) {
			UI_Error ("M_PushMenu: MAX_MENU_DEPTH");
			return;
		}

		m_layers[m_menudepth].m = m_active;
		m_layers[m_menudepth].draw = m_drawfunc;
		m_layers[m_menudepth].key = m_keyfunc;
		m_layers[m_menudepth].charevent = m_chareventfunc;
		m_menudepth++;

		M_Cache();
	}

	m_drawfunc = draw;
	m_keyfunc = key;
	m_chareventfunc = charevent;
	m_active = m;

	m_entersound = qtrue;

	UI_UpdateMousePosition();

	trap_CL_SetKeyDest( key_menu );
}

void M_ForceMenuOff( void )
{
	m_active = 0;
	m_drawfunc = 0;
	m_keyfunc = 0;
	trap_CL_SetKeyDest( key_game );
	m_menudepth = 0;
	trap_Key_ClearStates();
}

void M_PopMenu( void )
{
	if( m_menudepth == 1 ) {
		// start the demo loop again
		if( uis.clientState < CA_CONNECTING )
//			trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" );
			return;
		M_ForceMenuOff();
		return;
	}

	trap_S_StartLocalSound( menu_out_sound );

	if( m_menudepth < 1 ) {
		UI_Error( "M_PopMenu: depth < 1" );
		return;
	}

	m_menudepth--;

	m_drawfunc = m_layers[m_menudepth].draw;
	m_keyfunc = m_layers[m_menudepth].key;
	m_active = m_layers[m_menudepth].m;

	M_Cache();

	UI_UpdateMousePosition();
}

void M_genericBackFunc( menucommon_t *menuitem ) {
	M_PopMenu();
}


const char *Default_MenuKey( menuframework_s *m, int key )
{
	const char *sound = NULL;
	menucommon_t *item = NULL;

	if( m )
	{
		item = Menu_ItemAtCursor( m );
		if( item != NULL )
		{
			if( item->type == MTYPE_FIELD )
			{
				if( Field_Key( item, key ) )
					return NULL;
			}
		}
	}

	switch( key )
	{
	case K_ESCAPE:
		M_PopMenu();
		return menu_out_sound;

	case K_MOUSE1:
		if( m && (m_cursoritem == item) && Menu_SlideItem( m, 1, key ) )
		{
			sound = menu_move_sound;
		}
		else
		{
			if( m )
				Menu_SelectItem( m );
			sound = menu_move_sound;
		}
		break;

	case K_MOUSE2:
		// wsw : pb : fix ui slider bug
		if( m && (m_cursoritem == item) && Menu_SlideItem( m, -1, key ) )
		{
			sound = menu_move_sound;
		}
		else
		{
			M_PopMenu ();
			sound = menu_out_sound;
	}
		break;

	case K_MWHEELUP:
	case KP_UPARROW:
	case K_UPARROW:
		if ( m )
		{
			m->cursor--;
			Menu_AdjustCursor( m, -1 );
			sound = menu_move_sound;
		}
		break;
	case K_TAB:
		if ( m )
		{
			m->cursor++;
			Menu_AdjustCursor( m, 1 );
			sound = menu_move_sound;
		}
		break;

	case K_MWHEELDOWN:
	case KP_DOWNARROW:
	case K_DOWNARROW:
		if ( m )
		{
			m->cursor++;
			Menu_AdjustCursor( m, 1 );
			sound = menu_move_sound;
		}
		break;
	case KP_LEFTARROW:
	case K_LEFTARROW:
		if ( m )
		{
			Menu_SlideItem( m, -1, key );
			sound = menu_move_sound;
		}
		break;
	case KP_RIGHTARROW:
	case K_RIGHTARROW:
		if ( m )
		{
			Menu_SlideItem( m, 1, key );
			sound = menu_move_sound;
		}
		break;

	case K_MOUSE3:
	case K_JOY1:
	case K_JOY2:
	case K_JOY3:
	case K_JOY4:
	case K_AUX1:
	case K_AUX2:
	case K_AUX3:
	case K_AUX4:
	case K_AUX5:
	case K_AUX6:
	case K_AUX7:
	case K_AUX8:
	case K_AUX9:
	case K_AUX10:
	case K_AUX11:
	case K_AUX12:
	case K_AUX13:
	case K_AUX14:
	case K_AUX15:
	case K_AUX16:
	case K_AUX17:
	case K_AUX18:
	case K_AUX19:
	case K_AUX20:
	case K_AUX21:
	case K_AUX22:
	case K_AUX23:
	case K_AUX24:
	case K_AUX25:
	case K_AUX26:
	case K_AUX27:
	case K_AUX28:
	case K_AUX29:
	case K_AUX30:
	case K_AUX31:
	case K_AUX32:

	case KP_ENTER:
	case K_ENTER:
		if( m )
			Menu_SelectItem( m );
		sound = menu_move_sound;
		break;
	}

	return sound;
}

const char *Default_MenuCharEvent( menuframework_s *m, int key )
{
	menucommon_t *item = NULL;

	if( m )
	{
		item = Menu_ItemAtCursor( m );
		if( item != NULL )
		{
			if( item->type == MTYPE_FIELD )
			{
				if( Field_CharEvent( item, key ) )
					return NULL;
			}
		}
	}

	return NULL;
}


float M_ClampCvar( float min, float max, float value )
{
	if( value < min ) return min;
	if( value > max ) return max;
	return value;
}

//=============================================================================

/*
=================
UI_CopyString
=================
*/
char *UI_CopyString( const char *in )
{
	char	*out;
	
	out = UI_Malloc(strlen(in)+1);
	strcpy (out, in);
	return out;
}

//=============================================================================

//=============================================================================
/* User Interface Subsystem */

/*
=================
UI_Init
=================
*/
void UI_Init ( int vidWidth, int vidHeight )
{
	uipool = UI_MemAllocPool ( "UI" );

	m_active = NULL;
	m_cursoritem = NULL;
	m_drawfunc = NULL;
	m_keyfunc = NULL;
	m_entersound = qfalse;

	memset( &uis, 0, sizeof( uis ) );

	uis.vidWidth = vidWidth;
	uis.vidHeight = vidHeight;

#if 0
	uis.scaleX = UI_WIDTHSCALE;
	uis.scaleY = UI_HEIGHTSCALE;
#else
	uis.scaleX = 1;
	uis.scaleY = 1;
#endif

	uis.cursorX = uis.vidWidth / 2;
	uis.cursorY = uis.vidHeight / 2;

	trap_Cmd_AddCommand( "menu_main", M_Menu_Main_f );
	trap_Cmd_AddCommand( "menu_setup", M_Menu_Setup_f );
	trap_Cmd_AddCommand( "menu_joinserver", M_Menu_JoinServer_f );
	trap_Cmd_AddCommand( "menu_playerconfig", M_Menu_PlayerConfig_f );
	trap_Cmd_AddCommand( "menu_startserver", M_Menu_StartServer_f );
	trap_Cmd_AddCommand( "menu_sound", M_Menu_Sound_f );
	trap_Cmd_AddCommand( "menu_gfx", M_Menu_Gfx_f );
	trap_Cmd_AddCommand( "menu_video", M_Menu_Video_f );
	trap_Cmd_AddCommand( "menu_glext", M_Menu_GLExt_f );
	trap_Cmd_AddCommand( "menu_options", M_Menu_Options_f );
	trap_Cmd_AddCommand( "menu_keys", M_Menu_Keys_f );
	trap_Cmd_AddCommand( "menu_vsays", M_Menu_Vsays_f );
	trap_Cmd_AddCommand( "menu_quit", M_Menu_Quit_f );
	trap_Cmd_AddCommand( "menu_demos", M_Menu_Demos_f );
	trap_Cmd_AddCommand( "menu_mods", M_Menu_Mods_f );
	trap_Cmd_AddCommand( "menu_game", M_Menu_Game_f );
	trap_Cmd_AddCommand( "menu_failed", M_Menu_Failed_f );
	trap_Cmd_AddCommand( "menu_msgbox", M_Menu_MsgBox_f );
	trap_Cmd_AddCommand( "menu_teamconfig", M_Menu_TeamConfig_f );

	M_Cache();
	UI_Playermodel_Init(); // create a list with the available player models
	UI_InitTemporaryBoneposesCache();	// skelmod

	trap_S_StartBackgroundTrack( S_MUSIC_MENU, S_MUSIC_MENU );
}

/*
=================
UI_Shutdown
=================
*/
void UI_Shutdown( void )
{
	trap_S_StopBackgroundTrack();

	trap_Cmd_RemoveCommand( "menu_main" );
	trap_Cmd_RemoveCommand( "menu_setup" );
	trap_Cmd_RemoveCommand( "menu_joinserver" );
	trap_Cmd_RemoveCommand( "menu_playerconfig" );
	trap_Cmd_RemoveCommand( "menu_startserver" );
	trap_Cmd_RemoveCommand( "menu_gfx" );
	trap_Cmd_RemoveCommand( "menu_sound" );
	trap_Cmd_RemoveCommand( "menu_video" );
	trap_Cmd_RemoveCommand( "menu_glext" );
	trap_Cmd_RemoveCommand( "menu_options" );
	trap_Cmd_RemoveCommand( "menu_keys" );
	trap_Cmd_RemoveCommand( "menu_vsays" );
	trap_Cmd_RemoveCommand( "menu_quit" );
	trap_Cmd_RemoveCommand( "menu_demos" );
	trap_Cmd_RemoveCommand( "menu_mods" );
	trap_Cmd_RemoveCommand( "menu_game" );
	trap_Cmd_RemoveCommand( "menu_failed" );
	trap_Cmd_RemoveCommand( "menu_msgbox" );
	trap_Cmd_RemoveCommand( "menu_teamconfig" );

	UI_MemFreePool( &uipool );
}

/*
=================
UI_UpdateMousePosition
=================
*/
void UI_UpdateMousePosition (void)
{
	int i;
	menucommon_t *menuitem;

	if ( !m_active || !m_active->nitems ) {
		return;
	}

	/*
	** check items
	*/
	m_cursoritem = NULL;
	for( i = 0; i < m_active->nitems; i++ )
	{
		menuitem = m_active->items[i]; 
		if ( uis.cursorX > menuitem->maxs[0] || 
			uis.cursorY > menuitem->maxs[1] ||
			uis.cursorX < menuitem->mins[0] || 
			uis.cursorY < menuitem->mins[1] )
			continue;

		m_cursoritem = m_active->items[i];

		if( m_active->cursor == i ) {
			break;
		}

		Menu_AdjustCursor( m_active, i - m_active->cursor );
		m_active->cursor = i;

		//trap_S_StartLocalSound( ( char * )menu_move_sound );
		break;
	}
}

/*
=================
UI_MouseMove
=================
*/
void UI_MouseMove( int dx, int dy )
{
	uis.cursorX += dx;
	uis.cursorY += dy;

	clamp( uis.cursorX, 0, uis.vidWidth );
	clamp( uis.cursorY, 0, uis.vidHeight );

	if( dx || dy ) {
		UI_UpdateMousePosition();
	}
}

/*
=================
UI_DrawConnectScreen
=================
*/
void UI_DrawConnectScreen( char *serverName, char *rejectmessage, char *downloadfilename,
		int connectCount, qboolean backGround )
{
	qboolean localhost;
	char str[MAX_QPATH], levelshot[MAX_QPATH];
	char mapname[MAX_QPATH], message[MAX_QPATH];

	trap_S_StopBackgroundTrack();

	localhost = !serverName || !serverName[0] || !Q_stricmp ( serverName, "localhost" );

	M_Cache();

	trap_GetConfigString ( CS_MAPNAME, mapname, sizeof(mapname) );
	if ( backGround ) {
		if ( mapname[0] ) {
			Q_snprintfz ( levelshot, sizeof(levelshot), "levelshots/%s.jpg", mapname );

			if ( trap_FS_FOpenFile( levelshot, NULL, FS_READ ) == -1 ) 
				Q_snprintfz ( levelshot, sizeof(levelshot), "levelshots/%s.tga", mapname );

			if ( trap_FS_FOpenFile( levelshot, NULL, FS_READ ) == -1 ) 
				Q_snprintfz ( levelshot, sizeof(levelshot), "gfx/ui/unknownmap" );

			trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 
				0, 0, 1, 1, colorWhite, trap_R_RegisterPic ( levelshot ) );
			trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 
				0, 0, 2.5, 2, colorWhite, trap_R_RegisterPic ( "levelShotDetail" ) );
		} else {
			UI_FillRect ( 0, 0, uis.vidWidth, uis.vidHeight, colorBlack );
		}
	}

	// draw server name if not local host
	if ( !localhost ) {
		Q_snprintfz ( str, sizeof(str), "Connecting to %s", serverName );
		trap_SCR_DrawString( uis.vidWidth/2, 64, ALIGN_CENTER_TOP, str, uis.fontSystemBig, colorWhite );
	}

	if( rejectmessage ) {
		Q_snprintfz ( str, sizeof(str), "Refused: %s", rejectmessage );
		trap_SCR_DrawString( uis.vidWidth/2, 86, ALIGN_CENTER_TOP, str, uis.fontSystemMedium, colorWhite );
	}

	if( downloadfilename ) {
		Q_snprintfz ( str, sizeof(str), "Downloading %s", downloadfilename );
		trap_SCR_DrawString( uis.vidWidth/2, 86, ALIGN_CENTER_TOP, str, uis.fontSystemMedium, colorWhite );
	}

	if( mapname[0] ) {
		// draw Warsow logo at bottom
		trap_R_DrawStretchPic( 0, uis.vidHeight - UI_SCALED_HEIGHT(64), UI_SCALED_WIDTH(640), UI_SCALED_HEIGHT(64), 
			0, 0, 1, 1, colorWhite, trap_R_RegisterPic( "gfx/ui/loadscreen_logo" ) );

		trap_GetConfigString( CS_MESSAGE, message, sizeof(message) );

		if( message[0] )	// level name ("message")
			trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite );
	} else {
		if( !localhost ) {
			Q_snprintfz ( message, sizeof(message), "Awaiting connection... %i", connectCount );
			trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite );
		} else {
			Q_strncpyz ( message, "Loading...", sizeof(message) );
			trap_SCR_DrawString( uis.vidWidth/2, 150, ALIGN_CENTER_TOP, message, uis.fontSystemBig, colorWhite );
		}
	}
}

/*
=================
UI_Refresh
=================
*/
void UI_Refresh( unsigned int time, int clientState, int serverState, qboolean backGround )
{
	uis.time = time;
	uis.clientState = clientState;
	uis.serverState = serverState;
	uis.backGround = backGround;

	if( !m_drawfunc ) // ui is inactive
		return;

	// draw background
	if( uis.backGround ) {
		trap_R_DrawStretchPic( 0, 0, uis.vidWidth, uis.vidHeight, 
			0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_VIDEOBACK ) );

		trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 
			0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_FXBACK ) );

		trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 
			0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_BIGLOGO ) );
	} else {
		//trap_R_DrawStretchPic( 0, 0, uis.vidWidth, uis.vidHeight, 
		//	0, 0, 1, 1, colorDkGrey, trap_R_RegisterPic( "gfx/ui/novideoback" ) );

		//trap_R_DrawStretchPic ( 0, 0, uis.vidWidth, uis.vidHeight, 
		//	0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_BIGLOGO ) );
	}

	m_drawfunc();

	// draw cursor
	trap_R_DrawStretchPic( uis.cursorX - 16, uis.cursorY - 16, 32, 32, 
		0, 0, 1, 1, colorWhite, trap_R_RegisterPic( UI_SHADER_CURSOR ) );

	// delay playing the enter sound until after the
	// menu has been drawn, to avoid delay while
	// caching images
	if (m_entersound)
	{
		trap_S_StartLocalSound( menu_in_sound );
		m_entersound = qfalse;
	}
}

/*
=================
UI_Keydown
=================
*/
void UI_Keydown (int key)
{
	const char *s;

	if( m_keyfunc )
		if( ( s = m_keyfunc( key ) ) != 0 )
			trap_S_StartLocalSound( ( char * ) s );
}

/*
=================
UI_CharEvent
=================
*/
void UI_CharEvent (int key)
{
	const char *s;

	if( m_chareventfunc )
		if( ( s = m_chareventfunc( key ) ) != 0 )
			trap_S_StartLocalSound( ( char * ) s );
}

//======================================================================

#ifndef UI_HARD_LINKED
// this is only here so the functions in q_shared.c and q_math.c can link
void Sys_Error (char *error, ...)
{
	va_list		argptr;
	char		text[1024];

	va_start (argptr, error);
	vsnprintf (text, sizeof(text), error, argptr);
	va_end (argptr);
	text[sizeof(text)-1] = 0;

	trap_Error (text);
}

void Com_Printf (char *fmt, ...)
{
	va_list		argptr;
	char		text[1024];

	va_start (argptr, fmt);
	vsnprintf (text, sizeof(text), fmt, argptr);
	va_end (argptr);
	text[sizeof(text)-1] = 0;

	trap_Print (text);
}
#endif


