/*
* Warsow hud functions
*/

#include "cg_local.h"

extern cvar_t		*cg_weaponlist;
extern cvar_t		*cg_showWeaponSelect;
extern cvar_t		*cg_debug_HUD;
extern cvar_t		*cg_clientHUD;

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

typedef struct {
	char	*name;
	int		value;
} constant_integer_t;

static const constant_integer_t cg_integer_constants[] = {
	{ "NOTSET", STAT_NOTSET },

	// teams
	{ "TEAM_SPECTATOR", TEAM_SPECTATOR },
	{ "TEAM_PLAYERS", TEAM_PLAYERS },
	{ "TEAM_RED", TEAM_RED },
	{ "TEAM_BLUE", TEAM_BLUE },
	{ "TEAM_GREEN", TEAM_GREEN },
	{ "TEAM_YELLOW", TEAM_YELLOW },

	// align
	{ "LEFT", 1 },
	{ "CENTER", 2 },
	{ "RIGHT", 3 },
	{ "TOP", 1 },
	{ "MIDDLE", 2 },
	{ "BOTTOM", 3 },

	{ "WIDTH", 800 },
	{ "HEIGHT", 600 },

	// gametypes
	{ "GAMETYPE_DM", GAMETYPE_DM},
	{ "GAMETYPE_DUEL", GAMETYPE_DUEL},
	{ "GAMETYPE_TDM", GAMETYPE_TDM},
	{ "GAMETYPE_CTF", GAMETYPE_CTF},
	{ "GAMETYPE_RACE", GAMETYPE_RACE},
	{ "GAMETYPE_MIDAIR", GAMETYPE_MIDAIR},

	// ctf
	{ "FLAG_SAFE", FLAG_STATUS_SAFE },
	{ "FLAG_STOLEN", FLAG_STATUS_STOLEN },
	{ "FLAG_DROPPED", FLAG_STATUS_DROPPED },

	{ NULL, 0 }
};

#if 0 // not used yet atleast
typedef struct {
	char	*name;
	char	*value;
} constant_string_t;

static const constant_string_t cg_string_constants[] = {
	{ NULL, 0 }
};
#endif

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

int cgWeaponListData[WEAP_TOTAL][3];
void SCR_UpdateWeaponListData( void )
{
	int	i;

	memset( &cgWeaponListData, 0, sizeof(cgWeaponListData) );
	for( i = 0; (i < WEAP_TOTAL-1) && (i < MAX_WEAPLIST_STATS); i++ )
	{
		cgWeaponListData[i+1][0] = cg.frame.playerState.weaponlist[i][0];
		cgWeaponListData[i+1][1] = cg.frame.playerState.weaponlist[i][1];
		cgWeaponListData[i+1][2] = cg.frame.playerState.weaponlist[i][2];
	}
}

//================
//SCR_DrawWeaponList
//================
void SCR_DrawWeaponList( int x, int y, int ix, int iy, int wx, int wy, int sx, int sy ) 
{
	int	width;
	int xi[WEAP_TOTAL], yi[WEAP_TOTAL], xw[WEAP_TOTAL], yw[WEAP_TOTAL], xs[WEAP_TOTAL], ys[WEAP_TOTAL];
	int xmin, xmax, ymin, ymax;
	int i, j;
	vec4_t color;
	qboolean selected_weapon;

	if ( !cg_weaponlist || !cg_weaponlist->integer )
		return;

	SCR_UpdateWeaponListData(); //jalfixme: remove me

	xi[0] = yi[0] = xmin = ymin = xmax = ymax = 0;
	xw[0] = xi[0] + wx;
	yw[0] = yi[0] + wy;
	xs[0] = xi[0] + sx;
	ys[0] = yi[0] + sy;
	if ( xmin > xw[0] ) xmin = xw[0];
	if ( xmin > xs[0] ) xmin = xs[0];
	if ( ymin > yw[0] ) ymin = yw[0];
	if ( ymin > ys[0] ) ymin = ys[0];
	if ( xmax < xi[0] + 32 ) xmax = xi[0] + 32;
	if ( xmax < xw[0] + 32 ) xmax = xw[0] + 32;
	if ( xmax < xs[0] + 32 ) xmax = xs[0] + 32;
	if ( ymax < yi[0] + 32 ) ymax = yi[0] + 32;
	if ( ymax < yw[0] + 16 ) ymax = yw[0] + 16;
	if ( ymax < ys[0] + 16 ) ymax = ys[0] + 16;

	j = 1;
	for( i = WEAP_GUNBLADE ; i < WEAP_TOTAL ; i++ )
	{
		// if player doesnt have this weapon, skip it
		if ( !(cgWeaponListData[i][0]
		|| cgWeaponListData[i][1]
		|| cgWeaponListData[i][2]) )
		{   
			j++;
			continue;
		}

		xi[i] = xi[i-j] + ix;       yi[i] = yi[i-j] + iy;
		xw[i] = xi[i] + wx;         yw[i] = yi[i] + wy;
		xs[i] = xi[i] + sx;         ys[i] = yi[i] + sy;
		if ( xmin > xi[i] ) xmin = xi[i];
		if ( xmin > xw[i] ) xmin = xw[i];
		if ( xmin > xs[i] ) xmin = xs[i];
		if ( ymin > yi[i] ) ymin = yi[i];
		if ( ymin > yw[i] ) ymin = yw[i];
		if ( ymin > ys[i] ) ymin = ys[i];
		if ( xmax < xi[i] + 32 ) xmax = xi[i] + 32;
		if ( xmax < xw[i] + 32 ) xmax = xw[i] + 32;
		if ( xmax < xs[i] + 32 ) xmax = xs[i] + 32;
		if ( ymax < yi[i] + 32 ) ymax = yi[i] + 32;
		if ( ymax < yw[i] + 16 ) ymax = yw[i] + 16;
		if ( ymax < ys[i] + 16 ) ymax = ys[i] + 16;
		j = 1;
	}

	VectorCopy( colorWhite, color );

	for( i = WEAP_GUNBLADE ; i < WEAP_TOTAL ; i++ )
	{
		// if player doesnt have this weapon, skip it
		if( !(cgWeaponListData[i][0]
		|| cgWeaponListData[i][1]
		|| cgWeaponListData[i][2]) )
			continue;

		if( cg.demoPlaying && cg.frame.playerState.pmove.pm_type == PM_CHASECAM ) {
			if( i == cg.frame.playerState.stats[STAT_SELECTED_ITEM] )
				selected_weapon = qtrue;
			else
				selected_weapon = qfalse;
		} else {
			if( (cg.latched_weapon != WEAP_NONE && i == cg.latched_weapon) ||
					(cg.latched_weapon == WEAP_NONE && i == cg.frame.playerState.stats[STAT_SELECTED_ITEM]) )
				selected_weapon = qtrue;
			else
				selected_weapon = qfalse;
		}
		if( selected_weapon )
			color[3] = 1.0;
		else
			color[3] = 0.5;

		if( cgWeaponListData[i][0] )
		{
			// wsw : pb : display a little box around selected weapon in weaponlist (cg_showWeaponSelect)
			if( selected_weapon && cg_showWeaponSelect->integer )
			{
				// selected weapon
				trap_R_DrawStretchPic( (int)x+xi[i]+(xmin-xmax)/2-xmin, (int)y+yi[i]+(ymin-ymax)/2-ymin,
					(int)(32 * cgs.vidWidth/800 ), (int)(32 * cgs.vidHeight/600 ), 0, 0, 1, 1,
					color, CG_MediaShader( cgs.media.shaderSelect ) );
			}

			trap_R_DrawStretchPic ( (int)x+xi[i]+(xmin-xmax)/2-xmin, (int)y+yi[i]+(ymin-ymax)/2-ymin,
				(int)(32 * cgs.vidWidth/800 ), (int)(32 * cgs.vidHeight/600 ), 0, 0, 1, 1,
				color, CG_MediaShader( cgs.media.shaderWeaponIcon[i-1] ) );
		}
		else
		{
			trap_R_DrawStretchPic( (int)x+xi[i]+(xmin-xmax)/2-xmin, (int)y+yi[i]+(ymin-ymax)/2-ymin,
				(int)(32 * cgs.vidWidth/800 ), (int)(32 * cgs.vidHeight/600 ), 0, 0, 1, 1,
				color, CG_MediaShader( cgs.media.shaderNoGunWeaponIcon[i-1] ) );
		}

		width = cgWeaponListData[i][2] >= 100 ? 3 : 2;
		CG_DrawHUDField( x+xw[i]+(xmin-xmax)/2-xmin, y+yw[i]+(ymin-ymax)/2-ymin, ALIGN_LEFT_TOP, color, 12, width, cgWeaponListData[i][2] );

		if( cgWeaponListData[i][1] > 0 )
		{
			width = cgWeaponListData[i][1] >= 100 ? 3 : 2;
			CG_DrawHUDField( x+xs[i]+(xmin-xmax)/2-xmin, y+ys[i]+(ymin-ymax)/2-ymin, ALIGN_LEFT_TOP, color, 12, width, cgWeaponListData[i][1] );
		}
	}		
}



//=============================================================================
//	STATUS BAR PROGRAMS
//=============================================================================


// we will always operate with floats so we don't have to code 2 different numeric paths
// it's not like using float or ints would make a difference in this simple-scripting case.

static float CG_OpFuncAdd( const float a, const float b ) {
	return a + b;
}

static float CG_OpFuncSubtract( const float a, const float b ) {
	return a - b;
}

static float CG_OpFuncMultiply( const float a, const float b ) {
	return a * b;
}

static float CG_OpFuncDivide( const float a, const float b ) {
	return a / b;
}

static float CG_OpFuncAND( const float a, const float b ) {
	return (int)a & (int)b;
}

static float CG_OpFuncOR( const float a, const float b ) {
	return (int)a | (int)b;
}

static float CG_OpFuncXOR( const float a, const float b ) {
	return (int)a ^ (int)b;
}

static float CG_OpFuncCompareEqual( const float a, const float b ) {
	return (a == b);
}

static float CG_OpFuncCompareNotEqual( const float a, const float b ) {
	return (a != b);
}

static float CG_OpFuncCompareGreater( const float a, const float b ) {
	return (a > b);
}

static float CG_OpFuncCompareGreaterOrEqual( const float a, const float b ) {
	return (a >= b);
}

float CG_OpFuncCompareSmaller( const float a, const float b ) {
	return (a < b);
}

static float CG_OpFuncCompareSmallerOrEqual( const float a, const float b ) {
	return (a <= b);
}

typedef struct cg_layoutoperators_s {
	char	*name;
	float	(*opFunc)( const float a, const float b );
}cg_layoutoperators_t;

static cg_layoutoperators_t cg_LayoutOperators[] = 
{
	{
		"+",
			CG_OpFuncAdd
	},

	{
		"-",
			CG_OpFuncSubtract
	},

	{
		"*",
		CG_OpFuncMultiply
	},

	{
		"/",
		CG_OpFuncDivide
	},

	{
		"&",
		CG_OpFuncAND
	},

	{
		"|",
		CG_OpFuncOR
	},

	{
		"^",
		CG_OpFuncXOR
	},

	{
		"==",
		CG_OpFuncCompareEqual
	},

	{
		"!=",
		CG_OpFuncCompareNotEqual
	},

	{
		">",
		CG_OpFuncCompareGreater
	},

	{
		">=",
		CG_OpFuncCompareGreaterOrEqual
	},

	{
		"<",
		CG_OpFuncCompareSmaller
	},

	{
		"<=",
		CG_OpFuncCompareSmallerOrEqual
	},

	{
		NULL,
		NULL
	},
};

//================
//CG_OperatorFuncForArgument
//================
void *CG_OperatorFuncForArgument( char *token ) {
	cg_layoutoperators_t *op; 

	while( *token == ' ' )
		token++;

	for( op = cg_LayoutOperators; op->name; op++ ) {
		if( !Q_stricmp( token, op->name ) )
			return op->opFunc;
	}

	return NULL;
}

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

static char *CG_GetStringArg( struct cg_layoutnode_s **argumentsnode );
static float CG_GetNumericArg( struct cg_layoutnode_s **argumentsnode );

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

static int layout_cursor_x = 400;
static int layout_cursor_y = 300;
static int layout_cursor_width = 100;
static int layout_cursor_height = 100;
static int layout_cursor_align = ALIGN_LEFT_TOP;
static vec4_t layout_cursor_color = { 1, 1, 1, 1 };
static vec4_t layout_cursor_flashcolor = { 1, 1, 1, 1 };
static vec3_t layout_cursor_rotation = { 0, 0, 0 };
static struct mufont_s *layout_cursor_font;
qboolean layout_cursor_flash = qfalse;

enum {
	LNODE_NUMERIC,
	LNODE_STAT,
	LNODE_STRING,
	LNODE_COMMAND
};

//=============================================================================
// Commands' Functions
//=============================================================================
static int CG_LFuncDrawRaceTimer( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char time[64];
	int min, sec, milli;

	milli = (int)CG_GetNumericArg( &argumentnode );
	if( milli == STAT_NOTSET )
		return qfalse;

	// stat is in milliseconds/100.0f
	min=milli/600;
	milli-=min*600;
	sec=milli/10;
	milli-=sec*10;
	// we want MM:SS:m
	Q_snprintfz( time, sizeof(time), "%02d:%02d.%1d", min, sec, milli ); 
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, time, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawPicByIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	int x, y;

	if( value >= 0 && value < MAX_IMAGES ) {
		if( cgs.configStrings[CS_IMAGES + value][0] ) {
			x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
			y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
			trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic(cgs.configStrings[CS_IMAGES+value]) );
			return qtrue;
		}
	}

	return qfalse;
}

static int CG_LFuncDrawPicByItemIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int itemindex = (int)CG_GetNumericArg( &argumentnode );
	int x, y;
	gitem_t	*item;

	item = GS_FindItemByTag( itemindex );
	if( !item )
		return qfalse;
	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic(item->icon) );
	return qtrue;
}

static int CG_LFuncDrawPicByItemName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int x, y;
	gitem_t	*item = GS_FindItemByName( CG_GetStringArg(&argumentnode) );
	if( !item )
		return qfalse;

	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic(item->icon) );
	return qtrue;
}

static int CG_LFuncDrawPicByName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int x, y;

	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic( CG_GetStringArg(&argumentnode) ) );
	return qtrue;
}

static int CG_LFuncDrawModelByIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct model_s *model;
	int value = (int)CG_GetNumericArg( &argumentnode );

	if( value >= 0 && value < MAX_MODELS ) {
		model = value > 1 ? CG_RegisterModel( cgs.configStrings[CS_MODELS+value] ) : NULL;
		CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, NULL, layout_cursor_rotation[YAW] );
		return qtrue;
	}

	return qfalse;
}

static int CG_LFuncDrawModelByName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct model_s *model;
	struct shader_s *shader;
	char			*shadername;

	model = CG_RegisterModel( CG_GetStringArg(&argumentnode) );
	shadername = CG_GetStringArg( &argumentnode );
	shader = Q_stricmp( shadername, "NULL" ) ? trap_R_RegisterPic( shadername ) : NULL;
	CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, shader, layout_cursor_rotation[YAW] );
	return qtrue;
}

static int CG_LFuncDrawModelByItemIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int i;
	gitem_t	*item;
	struct model_s *model;
	int itemindex = (int)CG_GetNumericArg( &argumentnode );

	item = GS_FindItemByTag( itemindex );
	if( !item )
		return qfalse;
	for( i = 0; i < MAX_ITEM_MODELS; i++ ) {
		if( item->world_model[i] != NULL ) {
			model = itemindex >= 1 ? CG_RegisterModel( item->world_model[i] ) : NULL;
			CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, NULL, layout_cursor_rotation[YAW] );
		}
	}
	return qtrue;
}

static int CG_LFuncDrawModelByItemName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int x, y, i;
	struct model_s *model;
	gitem_t	*item = GS_FindItemByName( CG_GetStringArg(&argumentnode) );
	if( !item )
		return qfalse;

	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );

	for( i = 0; i < MAX_ITEM_MODELS; i++ ) {
		model = item->world_model[i] ? CG_RegisterModel( item->world_model[i] ) : NULL;
		CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, NULL, layout_cursor_rotation[YAW] );
	}
	return qtrue;
}

static int CG_LFuncCursor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_x = (int)(CG_GetNumericArg( &argumentnode )*cgs.vidWidth/800);
	layout_cursor_y = (int)(CG_GetNumericArg( &argumentnode )*cgs.vidHeight/600);
	return qtrue;
}

static int CG_LFuncSize( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_width = (int)(CG_GetNumericArg( &argumentnode )*cgs.vidWidth/800);
	layout_cursor_height = (int)(CG_GetNumericArg( &argumentnode )*cgs.vidHeight/600);
	return qtrue;
}

static int CG_LFuncColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int		i;
	for( i = 0; i < 4; i++ ) {
		layout_cursor_color[i] = CG_GetNumericArg( &argumentnode );
		clamp( layout_cursor_color[i], 0, 1 );
	}
	return qtrue;
}
static int CG_LFuncFlashColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int		i;
	for( i = 0; i < 4; i++ ) {
		layout_cursor_flashcolor[i] = CG_GetNumericArg( &argumentnode );
		clamp( layout_cursor_flashcolor[i], 0, 1 );
	}
	return qtrue;
}

static int CG_LFuncColorToTeamColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	GS_TeamColor( CG_GetNumericArg( &argumentnode ), layout_cursor_color );
	return qtrue;
}
static int CG_LFuncFlashColorToTeamColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	GS_TeamColor( CG_GetNumericArg( &argumentnode ), layout_cursor_flashcolor );
	return qtrue;
}

static int CG_LFuncColorAlpha( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_color[3] = CG_GetNumericArg( &argumentnode );
	return qtrue;
}

static int CG_LFuncFlashColorAlpha( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_flashcolor[3] = CG_GetNumericArg( &argumentnode );
	return qtrue;
}

static int CG_LFuncRotationSpeed( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int		i;
	for( i = 0; i < 3; i++ ) {
		layout_cursor_rotation[i] = CG_GetNumericArg( &argumentnode );
		clamp( layout_cursor_rotation[i], 0, 999 );
	}
	return qtrue;
}

static int CG_LFuncAlign( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int v, h;

	h = (int)CG_GetNumericArg( &argumentnode );
	v = (int)CG_GetNumericArg( &argumentnode );
	if( h < 1 )	h = 1;
	if( v < 1 )	v = 1;
	layout_cursor_align = (h-1)+(3*(v-1));
	return qtrue;
}

static int CG_LFuncFont( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct mufont_s *font;
	char *fontname = CG_GetStringArg(&argumentnode);

	if( !Q_stricmp(fontname, "con_fontsystemsmall") ) {
		font = cgs.fontSystemSmall;
	}
	else if( !Q_stricmp(fontname, "con_fontsystemmedium") ) {
		font = cgs.fontSystemMedium;
	}
	else if( !Q_stricmp(fontname, "con_fontsystembig") ) {
		font = cgs.fontSystemBig;
	}
	else {
		font = trap_SCR_RegisterFont( fontname );
	}

	if( font ) {
		layout_cursor_font = font;
		return qtrue;
	}

	return qfalse;
}

void CG_DrawFPS( int x, int y, int align, struct mufont_s *font, vec4_t color );
static int CG_LFuncDrawFPS( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawFPS( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

void CG_DrawSpeed( int x, int y, int align, struct mufont_s *font, vec4_t color );
static int CG_LFuncDrawSpeed( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawSpeed( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

void CG_DrawClock( int x, int y, int align, struct mufont_s *font, vec4_t color );
static int CG_LFuncDrawClock( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawClock( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawClockText( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char *message = NULL;
	qboolean abbr = (CG_GetNumericArg(&argumentnode) != 0);

	if( cg.frame.match.state == MATCH_STATE_WARMUP )
	{
		message = (abbr ? "WA" : "Warmup");
	}
	else if( cg.frame.match.state == MATCH_STATE_COUNTDOWN )
	{
		message = (abbr ? "CD" : "Countdown");
	}
	else if( cg.frame.match.state == MATCH_STATE_PLAYTIME )
	{
		if( cg.frame.match.extendedtime && cg.frame.match.timelimit )
			message = (abbr ? "OT" : "Overtime");
		else if( cg.frame.match.extendedtime && !cg.frame.match.timelimit )
			message = (abbr ? "SD" : "Suddendeath");
	}

	if( message ) {
		trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, message, layout_cursor_font,
			layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	}
	return qtrue;
}

static int CG_LFuncDrawHelpMessage( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	// hide this one when scoreboard is up
	if( !(cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD) ) {
		if( cg_showhelp->integer && !cg.demoPlaying ) {
			char *helpmessage = cg.matchmessage;
			int y = layout_cursor_y;
			size_t len;
			while( (len = trap_SCR_StrlenForWidth( helpmessage, layout_cursor_font, layout_cursor_width )) ) {
				trap_SCR_DrawStringLen( layout_cursor_x, y, layout_cursor_align, helpmessage, len, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
				helpmessage += len;
				if( helpmessage[0] == '\n' ) {
					y += trap_SCR_strHeight( layout_cursor_font );
					helpmessage++;
				}
			}
		}
	}
	return qtrue;
}

void CG_DrawPointed( int x, int y, int align, struct mufont_s *font, vec4_t color );
static int CG_LFuncDrawPointed( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawPointed( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawString( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char *string = CG_GetStringArg(&argumentnode);

	if( !string || !string[0] )
		return qfalse;
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, string, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawItemNameFromIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	gitem_t	*item;
	int itemindex = CG_GetNumericArg(&argumentnode);

	item = GS_FindItemByTag( itemindex );
	if( !item || !item->pickup_name )
		return qfalse;
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, item->pickup_name, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawConfigstring( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int index = (int)CG_GetNumericArg( &argumentnode );

	if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
		CG_Printf( "WARNING 'CG_LFuncDrawConfigstring' Bad stat_string index" );
		return qfalse;
	}
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, cgs.configStrings[index], layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawPlayername( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int index = (int)CG_GetNumericArg( &argumentnode ) - 1;

	if( index >= 0 && index < MAX_CLIENTS && cgs.clientInfo[index].name ) {
		trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, cgs.clientInfo[index].name, layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
		return qtrue;
	}
	return qfalse;
}

void CG_DrawHUDNumeric( int x, int y, int align, float *color, int charwidth, int charheight, int value );
static int CG_LFuncDrawNumeric( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color, layout_cursor_width, layout_cursor_height, value );
	return qtrue;
}

static int CG_LFuncDrawStretchNum( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	static char	num[16];
	int	len;
	int value = (int)CG_GetNumericArg( &argumentnode );

	Q_snprintfz( num, sizeof(num), "%i", value );
	len = strlen( num );
	if( len * layout_cursor_height <= layout_cursor_width ) {
		CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color, layout_cursor_height, layout_cursor_height, value );
	} else { //stretch numbers
		CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color, layout_cursor_width / len, layout_cursor_height, value );
	}
	return qtrue;
}

static int CG_LFuncDrawNumeric2( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );

	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, va("%i", value), layout_cursor_font, layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawBar( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int	value = (int)CG_GetNumericArg( &argumentnode );
	int maxvalue = (int)CG_GetNumericArg( &argumentnode );
	CG_DrawHUDRect( layout_cursor_x, layout_cursor_y, layout_cursor_align,
		layout_cursor_width, layout_cursor_height, value, maxvalue,
		layout_cursor_flash ? layout_cursor_flashcolor : layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawWeaponList( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int ix, iy, wx, wy, sx, sy;

	// TODO: align support
	ix = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidWidth/800 );
	iy = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidHeight/600 );
	wx = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidWidth/800 );
	wy = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidHeight/600 );
	sx = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidWidth/800 );
	sy = (int)( CG_GetNumericArg(&argumentnode) * cgs.vidHeight/600 );
	SCR_DrawWeaponList( layout_cursor_x, layout_cursor_y, ix, iy, wx, wy, sx, sy );
	return qtrue;
}

void SCR_DrawTeamInfo( int x, int y, int align, struct mufont_s *font, vec4_t color );
static int CG_LFuncDrawTeamInfo( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	SCR_DrawTeamInfo( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawCrossHair( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	SCR_DrawCrosshair( layout_cursor_x, layout_cursor_y, layout_cursor_align );
	return qtrue;
}

void SCR_DrawNet( int x, int y, int w, int h, int align, vec4_t color );
static int CG_LFuncDrawNet( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	SCR_DrawNet( layout_cursor_x, layout_cursor_y, layout_cursor_width, layout_cursor_height, layout_cursor_align, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncIf( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	return (int)CG_GetNumericArg(&argumentnode);
}


typedef struct cg_layoutcommand_s {
	char	*name;
	int		(*func)( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments );
	int		numparms;
	char	*help;
}cg_layoutcommand_t;

static cg_layoutcommand_t	cg_LayoutCommands[] = 
{
	{
		"setCursor",
		CG_LFuncCursor,
		2,
		"Moves the cursor to x and y coordinates."
	},

	{
		"setAlign",
		CG_LFuncAlign,
		2,
		"Changes align setting. Parameters: horizontal alignement, vertical alignement"
	},

	{
		"setSize",
		CG_LFuncSize,
		2,
		"Sets width and height. Used for pictures and models."
	},

	{
		"setFont",
		CG_LFuncFont,
		1,
		"Sets font by font name. Accepts 'system_small' and 'system_medium' as shortcut to default game fonts."
	},

	{
		"setColor",
		CG_LFuncColor,
		4,
		"Sets color setting in RGBA mode. Used for text and pictures"
	},

	{
		"setFlashColor",
		CG_LFuncFlashColor,
		4,
		"Sets flash color setting in RGBA mode. Used for text and pictures when they flash"
	},

	{
		"setColorToTeamColor",
		CG_LFuncColorToTeamColor,
		1,
		"Sets cursor color to the color of the team provided in the argument"
	},

	{
		"setFlashColorToTeamColor",
		CG_LFuncFlashColorToTeamColor,
		1,
		"Sets cursor flash-color to the color of the team provided in the argument"
	},

	{
		"setColorAlpha",
		CG_LFuncColorAlpha,
		1,
		"Changes the alpha value of the current color"
	},

	{
		"setFlashColorAlpha",
		CG_LFuncFlashColorAlpha,
		1,
		"Changes the alpha value of the current flash-color"
	},

	{
		"setRotationSpeed",
		CG_LFuncRotationSpeed,
		3,
		"Sets rotation speeds as vector. Used for models"
	},

	{
		"drawFPS",
		CG_LFuncDrawFPS,
		0,
		"Draws renderer frames per second"
	},

	{
		"drawSpeed",
		CG_LFuncDrawSpeed,
		0,
		"Draws current player speed"
	},

	{
		"drawClock",
		CG_LFuncDrawClock,
		0,
		"Draws clock"
	},

	{
		"drawClockText",
		CG_LFuncDrawClockText,
		1,
		"Draws text associated with current clock time"
	},

	{
		"drawHelpString",
		CG_LFuncDrawHelpMessage,
		0,
		"Draws the help message"
	},

	{
		"drawPlayername",
		CG_LFuncDrawPlayername,
		1,
		"Draws the name of the player with id provided by the argument"
	},

	{
		"drawPointing",
		CG_LFuncDrawPointed,
		0,
		"Draws the name of the player in the crosshair"
	},

	{
		"drawStatString",
		CG_LFuncDrawConfigstring,
		1,
		"Draws configstring of argument id"
	},

	{
		"drawItemName",
		CG_LFuncDrawItemNameFromIndex,
		1,
		"Draws the name of the item with given item index"
	},

	{
		"drawString",
		CG_LFuncDrawString,
		1,
		"Draws the string in the argument"
	},
		
	{
		"drawStringNum",
		CG_LFuncDrawNumeric2,
		1,
		"Draws numbers as text"
	},

	{
		"drawNum",
		CG_LFuncDrawNumeric,
		1,
		"Draws numbers of given character size"
	},

	{
		"drawStretchNum",
		CG_LFuncDrawStretchNum,
		1,
		"Draws numbers stretch inside a given size"
	},

	{
		"drawBar",
		CG_LFuncDrawBar,
		2,
		"Draws a bar of size setting, the bar is filled in proportion to the arguments"
	},

	{
		"drawCrosshair",
		CG_LFuncDrawCrossHair,
		0,
		"Draws the game crosshair"
	},

	{
		"drawNetIcon",
		CG_LFuncDrawNet,
		0,
		"Draws the disconnection icon"
	},

	{
		"drawPicByIndex",
		CG_LFuncDrawPicByIndex,
		1,
		"Draws a pic with argument as imageIndex"
	},

	{
		"drawPicByItemindex",
		CG_LFuncDrawPicByItemIndex,
		1,
		"Draws a item icon pic with argument as itemIndex"
	},

	{
		"drawPicByItemname",
		CG_LFuncDrawPicByItemName,
		1,
		"Draws a item icon pic with argument being the item name"
	},

	{
		"drawPicByName",
		CG_LFuncDrawPicByName,
		1,
		"Draws a pic with argument being the file path"
	},

	{
		"drawModelByIndex",
		CG_LFuncDrawModelByIndex,
		1,
		"Draws a model with argument being the modelIndex"
	},

	{
		"drawModelByName",
		CG_LFuncDrawModelByName,
		2,
		"Draws a model with argument being the path to the model file"
	},

	{
		"drawModelByItemindex",
		CG_LFuncDrawModelByItemIndex,
		1,
		"Draws a item model with argument being the item index"
	},

	{
		"drawModelByItemname",
		CG_LFuncDrawModelByItemName,
		1,
		"Draws a model with argument being the item name"
	},

	{
		"drawWeaponList",
		CG_LFuncDrawWeaponList,
		6,
		"Draws the weapon list"
	},

	{
		"drawTeamInfo",
		CG_LFuncDrawTeamInfo,
		0,
		"Draws the Team Info (locations) box"
	},

	{
		"if",
		CG_LFuncIf,
		1,
		"Conditional expression. Argument accepts operations >, <, ==, >=, <=, etc"
	},

	{
		"endif",
		NULL,
		0,
		"End of conditional expression block"
	},

	{
		"drawRaceTimer",
		CG_LFuncDrawRaceTimer,
		1,
		"Draws a timer clock for the race gametype"
	},

	{
		NULL,
		NULL,
		0,
		NULL
	}
};

void Cmd_CG_PrintHudHelp_f( void ) {
	cg_layoutcommand_t	*cmd;
	cg_layoutoperators_t	*op;
	int	i;

	CG_Printf( "- %sHUD scripts commands\n---------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( cmd = cg_LayoutCommands; cmd->name; cmd++ ) {
		CG_Printf( "- cmd: %s%s%s expected arguments: %s%i%s\n- desc: %s%s%s\n",
			S_COLOR_YELLOW, cmd->name, S_COLOR_WHITE,
			S_COLOR_YELLOW, cmd->numparms, S_COLOR_WHITE,
			S_COLOR_BLUE, cmd->help, S_COLOR_WHITE );
	}
	CG_Printf( "\n" );

	CG_Printf( "- %sHUD scripts operators\n---------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	CG_Printf( "- " );
	for( op = cg_LayoutOperators; op->name; op++ ) {
		CG_Printf( "%s%s%s, ", S_COLOR_YELLOW, op->name, S_COLOR_WHITE );
	}
	CG_Printf( "\n\n" );

	CG_Printf( "- %sHUD scripts STATS names\n---------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( i = 0; gs_stat_names[i] != NULL; i++ ) {
		CG_Printf( "- %s%s%s\n", S_COLOR_YELLOW, gs_stat_names[i], S_COLOR_WHITE );
	}
	CG_Printf( "\n" );

	CG_Printf( "- %sHUD scripts CONSTANT names\n---------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( i = 0; cg_integer_constants[i].name != NULL; i++ ) {
		CG_Printf( "%s%s%s, ", S_COLOR_YELLOW, cg_integer_constants[i].name, S_COLOR_WHITE );
	}
	CG_Printf( "\n", S_COLOR_WHITE );
}


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

typedef struct cg_layoutnode_s {
	int		(*func)( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments );
	int		type;
	char	*string;
	int		integer;
	float	value;
	float	(*opFunc)( const float a, float b );
	struct cg_layoutnode_s *parent;
	struct cg_layoutnode_s *next;
	struct cg_layoutnode_s *ifthread;
}cg_layoutnode_t;

//================
//CG_GetStringArg
//================
static char *CG_GetStringArg( struct cg_layoutnode_s **argumentsnode ) {
	struct cg_layoutnode_s *anode = *argumentsnode;

	if( !anode || anode->type == LNODE_COMMAND )
		CG_Error( "'CG_LayoutGetIntegerArg': bad arg count" );

	// we can return anything as string
	*argumentsnode = anode->next;
	return anode->string;
}

//================
//CG_GetNumericArg
//can use recursion for mathematical operations
//================
static float CG_GetNumericArg( struct cg_layoutnode_s **argumentsnode ) {
	struct cg_layoutnode_s *anode = *argumentsnode;
	float	value;

	if( !anode || anode->type == LNODE_COMMAND )
		CG_Error( "'CG_LayoutGetIntegerArg': bad arg count" );

	if( anode->type != LNODE_NUMERIC && anode->type != LNODE_STAT )
		CG_Printf( "WARNING: 'CG_LayoutGetIntegerArg': arg %s is not numeric", anode->string );

	*argumentsnode = anode->next;
	if( anode->type == LNODE_STAT )
		value = cg.frame.playerState.stats[anode->integer];
	else
		value = anode->value;

	// recurse if there are operators
	if( anode->opFunc != NULL ) {
		value = anode->opFunc( value, CG_GetNumericArg(argumentsnode) );
	}

	return value;
}

//================
//CG_LayoutParseCommandNode
//alloc a new node for a command
//================
static cg_layoutnode_t *CG_LayoutParseCommandNode( char *token ) {

	int	i = 0;
	cg_layoutcommand_t *command = NULL;
	cg_layoutnode_t *node;

	for( i = 0; cg_LayoutCommands[i].name; i++ ) {
		if( !Q_stricmp(token, cg_LayoutCommands[i].name) ) {
			command = &cg_LayoutCommands[i];
			break;
		}
	}

	if( command == NULL )
		return NULL;

	node = CG_Malloc( sizeof(cg_layoutnode_t) );
	node->type = LNODE_COMMAND;
	node->integer = command->numparms;
	node->value = 0.0f;
	node->string = CG_CopyString( command->name );
	node->func = command->func;
	node->ifthread = NULL;

	return node;
}

//================
//CG_LayoutParseArgumentNode
//alloc a new node for an argument
//================
static cg_layoutnode_t *CG_LayoutParseArgumentNode( char *token ) {
	cg_layoutnode_t *node;
	int				type = LNODE_NUMERIC;
	char			*valuetok;
	static char		tmpstring[8];

	// find what's it
	if( !token )
		return NULL;

	valuetok = token;
	
	if( token[0] == '%' ) { // it's a stat parm
		int i;
		type = LNODE_STAT;
		valuetok++; // skip %

		// replace stat names by values
		for( i = 0; gs_stat_names[i] != NULL; i++ ) {
			if( !Q_stricmp( valuetok, gs_stat_names[i] ) ) {
				Q_snprintfz( tmpstring, sizeof(tmpstring), "%i", i );
				valuetok = tmpstring;
				break;
			}
		}
		
	} else if( token[0] == '#' ) { // it's a integer constant
		int i;
		type = LNODE_NUMERIC;
		valuetok++; // skip #

		// replace stat names by values
		for( i = 0; cg_integer_constants[i].name != NULL; i++ ) {
			if( !Q_stricmp( valuetok, cg_integer_constants[i].name ) ) {
				Q_snprintfz( tmpstring, sizeof(tmpstring), "%i", cg_integer_constants[i].value );
				valuetok = tmpstring;
				break;
			}
		}
		if( cg_integer_constants[i].name == NULL )
			valuetok = "0";
		
#if 0 // not used yet atleast
	} else if( token[0] == '$' ) { // it's a string constant
		int i;
		type = LNODE_STRING;
		valuetok++; // skip $

		// replace stat names by values
		for( i = 0; cg_string_constants[i].name != NULL; i++ ) {
			if( !Q_stricmp( valuetok, cg_string_constants[i].name ) ) {
				Q_snprintfz( tmpstring, sizeof(tmpstring), "%s", cg_string_constants[i].value );
				valuetok = tmpstring;
				break;
			}
		}
#endif
		
	} else if( token[0] < '0' && token[0] > '9' && token[0] != '.' ) {
		type = LNODE_STRING;
	}

	// alloc
	node = CG_Malloc( sizeof(cg_layoutnode_t) );
	node->type = type;
	node->integer = atoi( valuetok );
	node->value = atof( valuetok );
	node->string = CG_CopyString( token );
	node->func = NULL;
	node->ifthread = NULL;

	// return it
	return node;
}

//================
//CG_LayoutCathegorizeToken
//================
static int CG_LayoutCathegorizeToken( char *token ) {
	int	i = 0;

	for( i = 0; cg_LayoutCommands[i].name; i++ ) {
		if( !Q_stricmp(token, cg_LayoutCommands[i].name) )
			return LNODE_COMMAND;
	}

	if( token[0] == '%' ) { // it's a stat parm
		return LNODE_STAT;
	} else if( token[0] == '#' ) { // it's a numerical constant
		return LNODE_NUMERIC;
#if 0
	} else if( token[0] == '$' ) { // it's a string constant
		return LNODE_STRING;
#endif
	} else if( token[0] < '0' && token[0] > '9' && token[0] != '.' ) {
		return LNODE_STRING;
	}

	return LNODE_NUMERIC;
}

//================
//CG_RecurseFreeLayoutThread
//recursive for freeing "if" subtrees
//================
static void CG_RecurseFreeLayoutThread( cg_layoutnode_t *rootnode ) {
	cg_layoutnode_t *node;

	if( !rootnode )
		return;

	while( rootnode ) {
		node = rootnode;
		rootnode = rootnode->parent;

		if( node->ifthread )
			CG_RecurseFreeLayoutThread( node->ifthread );

		if( node->string )
			CG_Free( node->string );

		CG_Free( node );
	}
}

//================
//CG_LayoutFixCommasInToken
//commas are accepted in the scripts. They actually do nothing, but are good for readability
//================
static qboolean CG_LayoutFixCommasInToken( char **ptr, char **backptr ) {
	char *token;
	char *back;
	int		offset, count;
	qboolean stepback = qfalse;

	token = *ptr;
	back = *backptr;

	if( !token || !strlen(token) )return qfalse;

	// check that sizes match (quotes are removed from tokens)
	offset = count = strlen(token);
	back = *backptr;
	while( count-- ) {
		if( *back == '"' ) {
			count++;
			offset++;
		}
		back--;
	}

	back = *backptr - offset;
	while( offset ) {
		if( *back == '"' ) {
			offset--;
			back++;
			continue;
		}

		if( *token != *back )
			CG_Printf( "Token and Back mismatch %c - %c\n", *token, *back );

		if( *back == ',' ) {
			*back = ' ';
			stepback = qtrue;
		}

		offset--;
		token++;
		back++;
	}

	return stepback;
}

//================
//CG_RecurseParseLayoutScript
//recursive for generating "if" subtrees
//================
/*static */cg_layoutnode_t *CG_RecurseParseLayoutScript( char **ptr, int level )
{
	cg_layoutnode_t		*command = NULL;
	cg_layoutnode_t		*node = NULL;
	cg_layoutnode_t		*rootnode = NULL;
	int					expecArgs = 0, numArgs = 0;
	int					token_type;
	qboolean			add;
	char				*token, *s_tokenback;

	if( !ptr )
		return NULL;

	if( !*ptr || !*ptr[0] )
		return NULL;

	while( *ptr ) {
		s_tokenback = *ptr;

		token = COM_Parse( ptr );
		while( *token == ' ' ) token++; // eat up whitespaces
		if( !Q_stricmp( ",", token) ) continue; // was just a comma
		if( CG_LayoutFixCommasInToken( &token, ptr ) ) {
			*ptr = s_tokenback; // step back
			continue;
		}
		
		if( !*token ) continue;
		if( !strlen(token) ) continue;

		add = qfalse;
		token_type = CG_LayoutCathegorizeToken( token );

		// if it's an operator, we don't create a node, but add the operation to the last one
		if( CG_OperatorFuncForArgument(token) != NULL ) {
			if( !node ) {
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" Operator hasn't any prior argument\n", level, token );
				continue;
			}
			if( node->type == LNODE_COMMAND || node->type == LNODE_STRING )
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" Operator was assigned to a command node\n", level, token );
			else
				expecArgs++; // we now expect one extra argument (not counting the operator one)

			node->opFunc = CG_OperatorFuncForArgument( token );
			continue; // skip and continue
		}

		if( expecArgs > numArgs ) {	// we are expecting an argument
			switch( token_type ) {
			case LNODE_NUMERIC:
			case LNODE_STRING:
			case LNODE_STAT:
				break;
			case LNODE_COMMAND:
				{
					CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid argument for \"%s\"\n", level, token, command ? command->string : "" );
					continue;
				}
				break;
			default:
				{
					CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i) skip and continue: Unrecognized token \"%s\"\n", level, token );
					continue;
				}
				break;
			}
		} else {
			if( token_type != LNODE_COMMAND ) { // we are expecting a command
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): unrecognized command \"%s\"\n", level, token );
				continue;
			}

			//special case: endif commands interrupt the thread and are not saved
			if( !Q_stricmp( token, "endif" ) ) {
				//finish the last command properly
				if( command )
					command->integer = expecArgs;
				return rootnode;
			}

			//special case: last command was "if", we create a new sub-thread and ignore the new command
			if( command && !Q_stricmp( command->string, "if" ) ) {
				*ptr = s_tokenback; // step back one token
				command->ifthread = CG_RecurseParseLayoutScript( ptr, level + 1 );
			}
		}

		// things look fine, proceed creating the node
		switch( token_type ) {
			case LNODE_NUMERIC:
			case LNODE_STRING:
			case LNODE_STAT:
				{
					node = CG_LayoutParseArgumentNode( token );
					if( !node ) {
						CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid argument for \"%s\"\n", level, token, command ? command->string : "" );
						break;
					}
					numArgs++;
					add = qtrue;
				}
				break;
			case LNODE_COMMAND:
				{
					node = CG_LayoutParseCommandNode( token );
					if( !node ) {
						CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid command\n", level, token );
						break; // skip and continue
					}

					// expected arguments could have been extended by the operators
					if( command )
						command->integer = expecArgs;

					 // move on into the new command
					command = node;
					numArgs = 0;
					expecArgs = command->integer;
					add = qtrue;
				}
				break;
			default:
				break;
		}

		if( add == qtrue ) {
			if( rootnode )
				rootnode->next = node;
			node->parent = rootnode;
			rootnode = node;
		}
	}

	if( level > 0 )
		CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): If without endif\n", level );

	return rootnode;
}

#if 0
static void CG_RecursePrintLayoutThread( cg_layoutnode_t *rootnode, int level ) {
	int	i;
	cg_layoutnode_t *node;

	node = rootnode;
	while(node->parent)
		node = node->parent;

	while( node ) {
		for( i = 0; i < level; i++ )
			CG_Printf( "   " );
		CG_Printf( "%s\n", node->string );

		if( node->ifthread )
			CG_RecursePrintLayoutThread( node->ifthread, level+1 );

		node = node->next;
	}
}
#endif

//================
//CG_ParseLayoutScript
//================
void CG_ParseLayoutScript( char *string, cg_layoutnode_t *rootnode ) {

	CG_RecurseFreeLayoutThread( cg.statusBar );
	cg.statusBar = CG_RecurseParseLayoutScript( &string, 0 );

#if 0
	CG_RecursePrintLayoutThread( cg_layoutRootNode, 0 );
#endif
}

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

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

//================
//CG_RecurseExecuteLayoutThread
// Execution works like this: First node (on backwards) is expected to be the command, followed by arguments nodes.
// we keep a pointer to the command and run the tree counting arguments until we reach the next command,
// then we call the command function sending the pointer to first argument and the pointer to the command.
// At return we advance one node (we stopped at last argument node) so it starts again from the next command (if any).
//
// When finding an "if" command with a subtree, we execute the "if" command. In the case it
// returns any value, we recurse execute the subtree
//================
static void CG_RecurseExecuteLayoutThread( cg_layoutnode_t *rootnode ) {
	cg_layoutnode_t		*argumentnode = NULL;
	cg_layoutnode_t		*commandnode = NULL;
	int					numArguments;

	if( !rootnode )
		return;

	// run until the real root
	commandnode = rootnode;
	while( commandnode->parent ) {
		commandnode = commandnode->parent;
	}

	// now run backwards up to the next command node
	while( commandnode ) {
		argumentnode = commandnode->next;

		// we could trust the parser, but I prefer counting the arguments here
		numArguments = 0;
		while( argumentnode ) {
			if( argumentnode->type == LNODE_COMMAND )
				break;

			argumentnode = argumentnode->next;
			numArguments++;
		}

		// reset
		argumentnode = commandnode->next;

		// Execute the command node
		if( commandnode->integer != numArguments ) {
			CG_Printf( "ERROR: Layout command %s: invalid argument count (expecting %i, found %i)\n", commandnode->string, commandnode->integer, numArguments );
			return;
		}
		if( commandnode->func ) {
			//special case for if commands
			if( commandnode->func( commandnode, argumentnode, numArguments ) ) {
				// execute the "if" thread when command returns a value
				if( commandnode->ifthread )
					CG_RecurseExecuteLayoutThread( commandnode->ifthread );
			}
		}

		//move up to next command node
		commandnode = argumentnode;
		if( commandnode == rootnode )
			return;

		while( commandnode && commandnode->type != LNODE_COMMAND ) {
			commandnode = commandnode->next;
		}
	}
}

//================
//CG_ExecuteLayoutProgram
//================
void CG_ExecuteLayoutProgram( struct cg_layoutnode_s *rootnode ) {
	CG_RecurseExecuteLayoutThread( rootnode );
}

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

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


//================
//CG_OptimizeStatusBarFile
//================
char *CG_OptimizeStatusBarFile( char *path, qboolean skip_include )
{
	int length, f;
	char *temp_buffer;
	char *opt_buffer;

	char *parse, *token, *toinclude;
	int optimized_length, included_length;
	int fi, fi_length;
	char fipath[MAX_QPATH], shortname[MAX_QPATH];

	// load the file
	length = trap_FS_FOpenFile( path, &f, FS_READ );
	if( length == -1 )
		return NULL;
	if( !length ) {
		trap_FS_FCloseFile( f );
		return NULL;
	}
	
	// alloc a temp buffer according to size
	temp_buffer = CG_Malloc( length + 1 );
	
	// load layout file in memory
	trap_FS_Read( temp_buffer, length, f );
	trap_FS_FCloseFile( f );

	// first pass: scan buffer line by line and check for include lines
	// if found compute needed length for included files
	// else count token length as Com_Parse as skipped comments and stuff like that
	parse=temp_buffer;
	optimized_length=0;
	included_length=0;
	while ( parse )
	{
		token=COM_ParseExt2( &parse, qtrue, qfalse );

		if( (!Q_stricmp( token, "include" )) && skip_include == qfalse) 
		{
			toinclude=COM_ParseExt2( &parse, qtrue, qfalse );
			//if( cg_debug_HUD && cg_debug_HUD->integer )
			//CG_Printf( "included: %s \n", toinclude );

			Q_strncpyz( shortname, toinclude, sizeof(shortname) );
			Q_snprintfz( fipath, sizeof(fipath), "huds/inc/%s", shortname );
			COM_ReplaceExtension( fipath, ".hud", sizeof(fipath) );
			fi_length = trap_FS_FOpenFile( fipath, &fi, FS_READ );

			if( fi_length == -1 )
			{
				// failed to include file
				CG_Printf( "HUD: Failed to include hud subfile: %s \n", path );
			}

			if( fi_length > 0) 
			{
				// not an empty file
				// we have the size we can close it
				included_length+=fi_length;
			}
			trap_FS_FCloseFile( fi );
		}
		else
		{
			// not an include line
			// simply count token size for optimized hud layout
			optimized_length+=strlen( token ) + 1 /*for spaces */;				
		}
	}
	
	// second pass: we now have the needed size
	// alloc optimized buffer
	opt_buffer = CG_Malloc( optimized_length + included_length + 1 );

	// reparse all file and copy it
	parse=temp_buffer;
	while ( parse )
	{
		token=COM_ParseExt2( &parse, qtrue, qfalse );

		if( (!Q_stricmp( token, "include" )) && skip_include == qfalse) 
		{
			toinclude=COM_ParseExt2( &parse, qtrue, qfalse );

			Q_strncpyz( shortname, toinclude, sizeof(shortname) );
			Q_snprintfz( fipath, sizeof(fipath), "huds/inc/%s", shortname );
			COM_ReplaceExtension( fipath, ".hud", sizeof(fipath) );
			fi_length = trap_FS_FOpenFile( fipath, &fi, FS_READ );

			if( fi_length == -1 )
			{
				// failed to include file
				CG_Printf( "HUD: Failed to include hud subfile: %s \n", path );
			}

			if( fi_length > 0) 
			{
				char *fi_parse;
				char *include_buffer;

				// not an empty file
				if( cg_debug_HUD && cg_debug_HUD->integer )
					CG_Printf( "HUD: Including sub hud file: %s \n", toinclude );

				// reparse all lines from included file to skip include commands

				// alloc a temp buffer according to size
				include_buffer = CG_Malloc( fi_length + 1 );

				// load included layout file in memory
				trap_FS_Read( include_buffer, fi_length, fi );

				fi_parse=include_buffer;
				while ( fi_parse )
				{
					token=COM_ParseExt2( &fi_parse, qtrue, qfalse );

					if( !Q_stricmp( token, "include" ) ) 
					{
						// skip recursive include
						toinclude=COM_ParseExt2( &fi_parse, qtrue, qfalse );
						CG_Printf( "HUD: No recursive include allowed: huds/inc/%s \n", toinclude );
					}
					else
					{
						// normal token
						strcat( opt_buffer, token );
						strcat( opt_buffer, " ");
					}
				}

				// release memory
				CG_Free( include_buffer );
			}

			// close included file
			trap_FS_FCloseFile( fi );
		}
		else
		{
			// normal token
			strcat( opt_buffer, token );
			strcat( opt_buffer, " ");
		}
	}

	// free temp buffer
	CG_Free( temp_buffer );

	return opt_buffer;
}

//================
//CG_LoadStatusBarFile
//================
void CG_LoadStatusBarFile( char *s )
{
	char path[MAX_QPATH], shortname[MAX_QPATH];
	char *opt;

	if( !s || !s[0] )
		return;

	// strip extension and add local path
	Q_strncpyz( shortname, s, sizeof(shortname) );
	Q_snprintfz( path, sizeof(path), "huds/%s", shortname );
	COM_ReplaceExtension( path, ".hud", sizeof(path) );

	opt=CG_OptimizeStatusBarFile( path, qfalse );

	if( opt == NULL )
	{
		CG_Printf("HUD: failed to load huds/%s file\n", shortname );
		return;
	}

	// load the new status bar program
	CG_ParseLayoutScript( opt, cg.statusBar );

	// set up layout font as default system font
	layout_cursor_font = trap_SCR_RegisterFont( DEFAULT_FONT_SMALL );
}

//================
//CG_LoadStatusBar
//================
void CG_LoadStatusBar( void )
{
	// always load default first. Custom second if needed

	if( cg_debug_HUD && cg_debug_HUD->integer )
		CG_Printf("HUD: Loading default clientHUD huds/%s\n", cg_clientHUD->dvalue );
	CG_LoadStatusBarFile( cg_clientHUD->dvalue );

	if( Q_stricmp( cg_clientHUD->string, cg_clientHUD->dvalue ) )
	{
		if( cg_debug_HUD && cg_debug_HUD->integer )
			CG_Printf("HUD: Loading custom clientHUD huds/%s\n", cg_clientHUD->string );
		CG_LoadStatusBarFile( cg_clientHUD->string );
	}
}







