/*
Copyright (C) 2003-2006 Andrey Nazarov

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 "client.h"
#include <setjmp.h>
#include "q_lex.h"

//
// cl_draw.c - draw all 2D elements during gameplay
//

cvar_t *scr_draw2d;

cvar_t		*ch_crosshair_names;
cvar_t		*ch_following;
cvar_t		*ch_alpha;

#define SCR_DrawString( x, y, flags, string ) \
	UIS_DrawStringEx( x, y, flags, MAX_STRING_CHARS, string, scr_font )

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

/*
==============
SCR_LoadingString
==============
*/
void SCR_LoadingString( const char *string ) {
    if( cls.state != ca_loading ) {
        return;
    }
	Q_strncpyz( cl.loadingString, string, sizeof( cl.loadingString ) );
	
	if( string[0] ) {
		Con_Printf( "Loading %s...\r", string );
	} else {
		Con_Printf( "\n" );
	}

	SCR_UpdateScreen();
	Com_ProcessEvents();	
}

/*
===============================================================================

LAGOMETER

===============================================================================
*/

#define LAG_SAMPLES 1024
#define LAG_MASK	( LAG_SAMPLES - 1 )

#define LAG_HISTORY			16
#define LAG_HISTORY_MASK	( LAG_HISTORY - 1 )

#define SUPRESSED_PACKET_MASK	0x4000
#define DROPPED_PACKET_MASK		0x8000

typedef struct lagometer_s {
	uint16 ping[LAG_SAMPLES];
	uint16 inSize[LAG_SAMPLES];
	uint8 delta[LAG_SAMPLES];
	int inTime[LAG_HISTORY];
	int inPacketNum;

	uint16 outSize[LAG_SAMPLES];
	int outTime[LAG_HISTORY];
	int outPacketNum;
} lagometer_t;

static lagometer_t		scr_lagometer;

/*
==============
SCR_ClearLagometer
==============
*/
void SCR_ClearLagometer( void ) {
	memset( &scr_lagometer, 0, sizeof( scr_lagometer ) );
}

/*
==============
SCR_AddLagometerPacketInfo
==============
*/
void SCR_AddLagometerPacketInfo( void ) {
	uint16 ping;
	int i, j;

	i = cls.netchan->incoming_acknowledged & CMD_MASK;
	ping = ( cls.realtime - cl.history[i].realtime ) & 0x3FFF;

	if( cl.surpressCount ) {
		ping |= SUPRESSED_PACKET_MASK;
	}
	if( cls.netchan->dropped ) {
		ping |= DROPPED_PACKET_MASK;
	}
	
	i = scr_lagometer.inPacketNum & LAG_MASK;
	j = scr_lagometer.inPacketNum & LAG_HISTORY_MASK;
	scr_lagometer.inTime[j] = cls.realtime;
	scr_lagometer.ping[i] = ping;
	scr_lagometer.inSize[i] = msg_read.cursize;
	scr_lagometer.delta[i] = cl.frame.deltaFrame > 0 ? ( cl.frame.serverFrame - cl.frame.deltaFrame ) : 0;

	scr_lagometer.inPacketNum++;

}

/*
==============
SCR_AddLagometerOutPacketInfo
==============
*/
void SCR_AddLagometerOutPacketInfo( int size ) {
	int i, j;

	i = scr_lagometer.outPacketNum & LAG_MASK;
	j = scr_lagometer.outPacketNum & LAG_HISTORY_MASK;
	scr_lagometer.outTime[j] = cls.realtime;
	scr_lagometer.outSize[i] = size;

	scr_lagometer.outPacketNum++;
}

#if 0
static void SCR_DrawPingGraph( vrect_t *rc, int baseColor, int surpessedColor,
							  int droppedColor, int maxPing )
{
	int i, j, v;
	int color;

	for( i = 0; i < rc->width; i++ ) {
		j = scr_lagometer.inPacketNum - i - 1;
		if( j < 0 ) {
			break;
		}

		v = scr_lagometer.ping[j & LAG_MASK];

		color = baseColor;
		if( v & SUPRESSED_PACKET_MASK ) {
			v &= ~SUPRESSED_PACKET_MASK;
			color = surpessedColor;
		}
		if( v & DROPPED_PACKET_MASK ) {
			v &= ~DROPPED_PACKET_MASK;
			color = droppedColor;
		}

		v *= ( float )rc->height / maxPing;
		if( v > rc->height ) {
			v = rc->height;
		}

		ref.DrawFill( rc->x + rc->width - i - 1, rc->y + rc->height - v, 1, v, color );
		
	}
}

static void SCR_DrawOutPacketGraph( vrect_t *rc, int baseColor, int maxSize ) {
	int i, j, v;

	for( i = 0; i < rc->width; i++ ) {
		j = scr_lagometer.outPacketNum - i - 1;
		if( j < 0 ) {
			break;
		}

		v = scr_lagometer.outSize[j & LAG_MASK];

		v *= ( float )rc->height / maxSize;
		if( v > rc->height ) {
			v = rc->height;
		}

		ref.DrawFill( rc->x + rc->width - i - 1, rc->y + rc->height - v, 1, v, baseColor );
		
	}
}

static void SCR_DrawInPacketGraph( vrect_t *rc, int baseColor, int maxSize ) {
	int i, j, v;

	for( i = 0; i < rc->width; i++ ) {
		j = scr_lagometer.inPacketNum - i - 1;
		if( j < 0 ) {
			break;
		}

		v = scr_lagometer.inSize[j & LAG_MASK];

		v *= ( float )rc->height / maxSize;
		if( v > rc->height ) {
			v = rc->height;
		}

		ref.DrawFill( rc->x + rc->width - i - 1, rc->y + rc->height - v, 1, v, baseColor );
		
	}
}

static void SCR_DrawDeltaGraph( vrect_t *rc, int baseColor, int nodeltaColor, int maxDelta ) {
	int i, j, v;
	int color;

	for( i = 0; i < rc->width; i++ ) {
		j = scr_lagometer.inPacketNum - i - 1;
		if( j < 0 ) {
			break;
		}

		v = scr_lagometer.delta[j & LAG_MASK];

		color = baseColor;
		if( !v ) {
			v = maxDelta;
			color = nodeltaColor;
		}

		v *= ( float )rc->height / maxDelta;
		if( v > rc->height ) {
			v = rc->height;
		}

		ref.DrawFill( rc->x + rc->width - i - 1, rc->y + rc->height - v, 1, v, color );
	}
}

#endif

static void SCR_DrawDisconnect( vrect_t *rc ) {
	if( !cls.netchan ) {
		return;
	}
	if( cls.netchan->outgoing_sequence - cls.netchan->incoming_acknowledged > CMD_BACKUP - 1 ) {
		if( ( cls.realtime >> 8 ) & 3 ) {
			ref.DrawStretchPic( rc->x, rc->y, rc->width, rc->height, scr_net );
		}
	}
}

static void CL_Ping_m( char *buffer, int bufferSize ) {
	int i, j;
	int start;
	int ping;

	start = scr_lagometer.inPacketNum - LAG_HISTORY + 1;
	if( start < 0 ) {
		start = 0;
	}

	ping = 0;
	if( scr_lagometer.inPacketNum > 1 ) {
		for( i = start; i < scr_lagometer.inPacketNum; i++ ) {
			j = scr_lagometer.ping[i & LAG_MASK];
			ping += j & ~( SUPRESSED_PACKET_MASK | DROPPED_PACKET_MASK );
		}

		ping /= i - start;
	}
	Com_sprintf( buffer, bufferSize, "%i", ping );
}

static void CL_UpRate_m( char *buffer, int bufferSize ) {
	int i;
	float size;
	int startTime, endTime;

	i = scr_lagometer.outPacketNum - LAG_HISTORY + 1;
	if( i < 0 ) {
		i = 0;
	}

	startTime = scr_lagometer.outTime[i & LAG_HISTORY_MASK];
	endTime = scr_lagometer.outTime[( scr_lagometer.outPacketNum - 1 ) &
        LAG_HISTORY_MASK];

	size = 0;
	if( startTime != endTime ) {
		for( ; i < scr_lagometer.outPacketNum; i++ ) {
			size += scr_lagometer.outSize[i & LAG_MASK];
		}

		size /= endTime - startTime;
	}
	Com_sprintf( buffer, bufferSize, "%1.2f", size );
}

static void CL_DnRate_m( char *buffer, int bufferSize ) {
	int i;
	float size;
	int startTime, endTime;

	i = scr_lagometer.inPacketNum - LAG_HISTORY + 1;
	if( i < 0 ) {
		i = 0;
	}

	startTime = scr_lagometer.inTime[i & LAG_HISTORY_MASK];
	endTime = scr_lagometer.inTime[( scr_lagometer.inPacketNum - 1 ) &
        LAG_HISTORY_MASK];

	size = 0;
	if( startTime != endTime ) {
		for( ; i < scr_lagometer.inPacketNum; i++ ) {
			size += scr_lagometer.inSize[i & LAG_MASK];
		}

		size /= endTime - startTime;
	}
	Com_sprintf( buffer, bufferSize, "%1.2f", size );
}

/*
===============================================================================

CHAT HUD

===============================================================================
*/

#define MAX_CHAT_LENGTH		128
#define MAX_CHAT_LINES		32
#define CHAT_MASK			( MAX_CHAT_LINES - 1 )

typedef struct chatMessage_s {
	char	text[MAX_CHAT_LENGTH];
	int		time;
} chatMessage_t;

static chatMessage_t	scr_chatMsgs[MAX_CHAT_LINES];
static int				scr_currentChatMsg;

/*
==============
SCR_ClearChatHUD_f
==============
*/
void SCR_ClearChatHUD_f( void ) {
	memset( scr_chatMsgs, 0, sizeof( scr_chatMsgs ) );
	scr_currentChatMsg = 0;
}

/*
==============
SCR_AddToChatHUD
==============
*/
void SCR_AddToChatHUD( const char *string ) {
	chatMessage_t *msg;
	char *p;

	scr_currentChatMsg++;
	msg = &scr_chatMsgs[scr_currentChatMsg & CHAT_MASK];

	Q_strncpyz( msg->text, string, sizeof( msg->text ) );
	msg->time = cls.realtime;

	// clamp to single line
	p = strchr( msg->text, '\n' );
	if( p ) {
		*p = 0;
	}
}

/*
===============================================================================

CROSSHAIR

===============================================================================
*/

/*
=================
SCR_ScanForCrosshairEntity
=================
*/
static void SCR_ScanForCrosshairEntity( void ) {
	trace_t		trace, worldtrace;
	vec3_t		end;
	entity_state_t	*ent;
	int			i, x, zd, zu;
	cnode_t		*headnode;
	vec3_t		bmins, bmaxs;
	vec3_t forward, light;

	AngleVectors( cl.refdef.viewangles, forward, NULL, NULL );
	VectorMA( cl.refdef.vieworg, 8192, forward, end );

	CM_BoxTrace( &worldtrace, cl.refdef.vieworg, end,
		vec3_origin, vec3_origin, cl.cm.cache->nodes, MASK_SOLID );

	for( i = 0; i < cl.numSolidEntities; i++ ) {
		ent = cl.solidEntities[i];

		if( ent->solid == 31 ) {
			continue;
		}

		if( ent->modelindex != 255 ) {
			continue; // not a player
		}

		if( ent->frame >= 173 || ent->frame <= 0 ) {
			continue; // player is dead
		}

		x = 8*(ent->solid & 31);
		zd = 8*((ent->solid>>5) & 31);
		zu = 8*((ent->solid>>10) & 63) - 32;

		bmins[0] = bmins[1] = -x;
		bmaxs[0] = bmaxs[1] = x;
		bmins[2] = -zd;
		bmaxs[2] = zu;

		headnode = CM_HeadnodeForBox( bmins, bmaxs );

		CM_TransformedBoxTrace( &trace, cl.refdef.vieworg, end,
			vec3_origin, vec3_origin, headnode, MASK_PLAYERSOLID,
				ent->origin, vec3_origin );

		if( trace.allsolid || trace.startsolid ||
                trace.fraction < worldtrace.fraction )
        {
            /* TODO: find nearest entity */
			ref.LightPoint( ent->origin, light );
			if( VectorLengthSquared( light ) > 0.02f ) {
				cl.crosshairPlayer = &cl.clientinfo[ent->skinnum & 0xFF];
				cl.crosshairTime = cls.realtime;
			}
			break;
		}
	}
}

/*
=================
SCR_FadeAlpha
=================
*/
float SCR_FadeAlpha( int startTime, int visTime, int fadeTime ) {
	float alpha;
	int timeLeft;

	timeLeft = visTime - ( cls.realtime - startTime );
	if( timeLeft <= 0 ) {
		return 0;
	}

	if( fadeTime > visTime ) {
		fadeTime = visTime;
	}

	alpha = 1;
	if( timeLeft < fadeTime ) {
		alpha = ( float )timeLeft / fadeTime;
	}

	return alpha;
}

/*
=================
SCR_DrawCrosshair
=================
*/
static void SCR_DrawCrosshair( void ) {
	int x, y, w, h;
	float alpha;
	qhandle_t handle;

	w = h = 0;
	if( crosshair->integer ) {
		if( crosshair->modified ) {
			crosshair->modified = qfalse;
			SCR_TouchPics();
		}

        handle = crosshair_pic;
        w = crosshair_width;
        h = crosshair_height;

		x = ( scr_hudWidth - w ) / 2;
		y = ( scr_hudHeight - h ) / 2;
		ref.DrawPic( x, y, handle );
	}

	Cvar_ClampValue( ch_crosshair_names, 0, 5 );
	if( ch_crosshair_names->value ) {
		if( cl.numSolidEntities ) {
			SCR_ScanForCrosshairEntity();
		}
		if( cl.crosshairPlayer ) {
			alpha = SCR_FadeAlpha( cl.crosshairTime,
				ch_crosshair_names->value * 1000, 300 );
			if( alpha ) {
				x = ( scr_hudWidth - strlen( cl.crosshairPlayer->name ) *
					TINYCHAR_WIDTH ) / 2;
				y = ( scr_hudHeight + h ) / 2;

				ref.SetColor( DRAW_COLOR_ALPHA, ( byte * )&alpha );
				ref.DrawString( x, y, 0, MAX_STRING_CHARS,
					cl.crosshairPlayer->name, scr_font );
			}
		}
	}

    ref.SetColor( DRAW_COLOR_CLEAR, NULL );
}

/*
===============================================================================

FOLLOWING

===============================================================================
*/

/*
================
SCR_DrawFollowing
================
*/
static void SCR_DrawFollowing( void ) {
	char *string;
	int x;

	if( !ch_following->integer ) {
		return;
	}

	if( !cls.demoplayback && cl.frame.ps.clientNum == cl.clientNum ) {
		return;
	}

	string = cl.clientinfo[cl.frame.ps.clientNum].name;
	if( !string[0] ) {
		return;
	}

	x = ( scr_hudWidth - strlen( string ) * TINYCHAR_WIDTH ) / 2;

	ref.DrawString( x, 48, 0, MAX_STRING_CHARS, string, scr_font );
}

/*
=================
SCR_DrawDefaultHud
=================
*/
static void SCR_DrawDefaultHud( void ) {
	vrect_t rc;

//	SCR_DrawCrosshair();

	Cvar_ClampValue( ch_alpha, 0, 1 );
	ref.SetColor( DRAW_COLOR_ALPHA, ( byte * )&ch_alpha->value );

	// draw status bar
	if( cl.frame.ps.clientNum != CLIENTNUM_NONE ) {
		SCR_ExecuteLayoutString( cl.configstrings[CS_STATUSBAR] );
	}

	// draw layout
	if( cl.frame.ps.ps.stats[STAT_LAYOUTS] & 1 ) {
		SCR_ExecuteLayoutString( cl.layout );
	}

	// draw inventory
	if( cl.frame.ps.ps.stats[STAT_LAYOUTS] & 2 ) {
		SCR_DrawInventory();
	}

	if( cl.frame.ps.clientNum != CLIENTNUM_NONE ) {
		SCR_DrawFollowing();
	}

	SCR_DrawCenterString();

	// draw phone jack
	rc.x = scr_hudWidth - 48;
	rc.y = scr_hudHeight - 48;
	rc.width = 48;
	rc.height = 48;
	SCR_DrawDisconnect( &rc );

}

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

/*
=================
SCR_Draw2D
=================
*/
void SCR_Draw2D( void ) {
    clipRect_t rc;

	if( !scr_draw2d->integer || ( cls.key_dest & KEY_MENU ) ) {
		return;
	}
    
    // avoid DoS by making sure nothing is drawn out of bounds
    if( scr_glconfig.renderer == GL_RENDERER_SOFTWARE ) {
        rc.left = 0;
        rc.top = 0;
        rc.right = scr_hudWidth;
        rc.bottom = scr_hudHeight;

        ref.SetClipRect( DRAW_CLIP_MASK, &rc );
    }

	ref.SetColor( DRAW_COLOR_CLEAR, NULL );

	SCR_DrawCrosshair();

	SCR_DrawDefaultHud();

	ref.SetColor( DRAW_COLOR_CLEAR, NULL );
    if( scr_glconfig.renderer == GL_RENDERER_SOFTWARE ) {
        ref.SetClipRect( DRAW_CLIP_DISABLED, NULL );
    }
}
	

/*
================
SCR_InitDraw
================
*/
void SCR_InitDraw( void ) {
	scr_draw2d = Cvar_Get( "scr_draw2d", "1", 0 );

	ch_crosshair_names = Cvar_Get( "ch_crosshair_names", "0", 0 );
	ch_following = Cvar_Get( "ch_following", "1", 0 );
	ch_alpha = Cvar_Get( "ch_alpha", "1", 0 );

	Cmd_AddMacro( "cl_ping", CL_Ping_m );
	Cmd_AddMacro( "cl_uprate", CL_UpRate_m );
	Cmd_AddMacro( "cl_dnrate", CL_DnRate_m );
	
	Cmd_AddCommand( "clearchat", SCR_ClearChatHUD_f );
}

