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

*/
// cl.input.c  -- builds an intended movement command to send to the server

#include "client.h"

cvar_t	*cl_nodelta;

extern	unsigned	sys_frame_time;
unsigned	frame_msec;
unsigned	old_sys_frame_time;

static vec3_t	oldAngles;

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

MOUSE

===============================================================================
*/
extern cvar_t *in_grabinconsole;
extern cvar_t *m_filter;
extern cvar_t *m_accel;
extern cvar_t *in_minMsecs;

static qboolean mlooking = qfalse;
static int mouse_x, mouse_y, old_mouse_x, old_mouse_y;

// wsw : pb : moved mousemove to cl_input (q3 like)
void CL_MouseMove( usercmd_t *cmd, int mx, int my )
{
	float	accelSensitivity;
	float	rate;

	if( cls.key_dest == key_menu ) {
		CL_UIModule_MouseMove( mx, my );
		return;
	}

	if( (cls.key_dest == key_console) && !in_grabinconsole->integer )
		return;
	
	if( m_filter->integer )
	{
		mouse_x = (mx + old_mouse_x) * 0.5;
		mouse_y = (my + old_mouse_y) * 0.5;
	}
	else
	{
		mouse_x = mx;
		mouse_y = my;
	}

	old_mouse_x = mx;
	old_mouse_y = my;

	
	rate = sqrt( mouse_x * mouse_x + mouse_y * mouse_y ) / (float)frame_msec;
	accelSensitivity = sensitivity->value + rate * m_accel->value;

	// wsw : pb : to be added later when +zoom support
	// scale by FOV 
	//accelSensitivity *= cl.cgameSensitivity;

	/*
	if ( rate && cl_showMouseRate->integer ) {
		Com_Printf( "%f : %f\n", rate, accelSensitivity );
	}
	*/

	/* OLD
	mouse_x *= sensitivity->value;
	mouse_y *= sensitivity->value;
	*/
	// add mouse X/Y movement to cmd
	if( (in_strafe.state & 1) || (lookstrafe->integer && mlooking ) )
		cmd->sidemove += (accelSensitivity * m_side->value) * mouse_x;
	else
		cl.viewangles[YAW] -= (accelSensitivity * m_yaw->value) * mouse_x;

	if( (mlooking || cl_freelook->integer) && !(in_strafe.state & 1) )
		cl.viewangles[PITCH] += (accelSensitivity * m_pitch->value) * mouse_y;
	else
		cmd->forwardmove -= (accelSensitivity * m_forward->value) * mouse_y;
}

void IN_MLookDown (void)
{
	mlooking = qtrue;
}

void IN_MLookUp (void) {
	mlooking = qfalse;
	if( !cl_freelook->integer && lookspring->integer )
		IN_CenterView ();
}


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

KEY BUTTONS

Continuous button event tracking is complicated by the fact that two different
input sources (say, mouse button 1 and the control key) can both press the
same button, but the button should only be released when both of the
pressing key have been released.

When a key event issues a button command (+forward, +attack, etc), it appends
its key number as a parameter to the command so it can be matched up with
the release.

state bit 0 is the current state of the key
state bit 1 is edge triggered on the up to down transition
state bit 2 is edge triggered on the down to up transition


Key_Event (int key, qboolean down, unsigned time);

  +mlook src time

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

kbutton_t	in_klook;
kbutton_t	in_left, in_right, in_forward, in_back;
kbutton_t	in_lookup, in_lookdown, in_moveleft, in_moveright;
kbutton_t	in_strafe, in_speed, in_use, in_attack;
kbutton_t	in_up, in_down;
kbutton_t	in_special; // wsw : MiK

static void KeyDown( kbutton_t *b )
{
	int		k;
	char	*c;
	
	c = Cmd_Argv(1);
	if (c[0])
		k = atoi(c);
	else
		k = -1;		// typed manually at the console for continuous down

	if( k == b->down[0] || k == b->down[1] )
		return;		// repeating key
	
	if( !b->down[0] )
		b->down[0] = k;
	else if( !b->down[1] )
		b->down[1] = k;
	else
	{
		Com_Printf( "Three keys down for a button!\n" );
		return;
	}
	
	if( b->state & 1 )
		return;		// still down

	// save timestamp
	c = Cmd_Argv(2);
	b->downtime = atoi(c);
	if( !b->downtime )
		b->downtime = sys_frame_time - 100;

	b->state |= 1 + 2;	// down + impulse down
}

static void KeyUp( kbutton_t *b )
{
	int		k;
	char	*c;
	unsigned	uptime;

	c = Cmd_Argv(1);
	if( c[0] )
		k = atoi(c);
	else
	{ // typed manually at the console, assume for unsticking, so clear all
		b->down[0] = b->down[1] = 0;
		b->state = 4;	// impulse up
		return;
	}

	if( b->down[0] == k )
		b->down[0] = 0;
	else if( b->down[1] == k )
		b->down[1] = 0;
	else
		return;		// key up without coresponding down (menu pass through)
	if( b->down[0] || b->down[1] )
		return;		// some other key is still holding it down

	if( !(b->state & 1) )
		return;		// still up (this should not happen)

	// save timestamp
	c = Cmd_Argv(2);
	uptime = atoi(c);
	if( uptime )
		b->msec += uptime - b->downtime;
	else
		b->msec += 10;

	b->state &= ~1;		// now up
	b->state |= 4; 		// impulse up
}

void IN_KLookDown( void )		{KeyDown(&in_klook);}
void IN_KLookUp( void )			{KeyUp(&in_klook);}
void IN_UpDown( void )			{KeyDown(&in_up);}
void IN_UpUp( void )			{KeyUp(&in_up);}
void IN_DownDown( void )		{KeyDown(&in_down);}
void IN_DownUp( void )			{KeyUp(&in_down);}
void IN_LeftDown( void )		{KeyDown(&in_left);}
void IN_LeftUp( void )			{KeyUp(&in_left);}
void IN_RightDown( void )		{KeyDown(&in_right);}
void IN_RightUp( void )			{KeyUp(&in_right);}
void IN_ForwardDown( void )		{KeyDown(&in_forward);}
void IN_ForwardUp( void )		{KeyUp(&in_forward);}
void IN_BackDown( void )		{KeyDown(&in_back);}
void IN_BackUp( void )			{KeyUp(&in_back);}
void IN_LookupDown( void )		{KeyDown(&in_lookup);}
void IN_LookupUp( void )		{KeyUp(&in_lookup);}
void IN_LookdownDown( void )	{KeyDown(&in_lookdown);}
void IN_LookdownUp( void )		{KeyUp(&in_lookdown);}
void IN_MoveleftDown( void )	{KeyDown(&in_moveleft);}
void IN_MoveleftUp( void )		{KeyUp(&in_moveleft);}
void IN_MoverightDown( void )	{KeyDown(&in_moveright);}
void IN_MoverightUp( void )		{KeyUp(&in_moveright);}

void IN_SpeedDown( void )		{KeyDown(&in_speed);}
void IN_SpeedUp( void )			{KeyUp(&in_speed);}
void IN_StrafeDown( void )		{KeyDown(&in_strafe);}
void IN_StrafeUp( void )		{KeyUp(&in_strafe);}

void IN_AttackDown( void )		{KeyDown(&in_attack);}
void IN_AttackUp( void )		{KeyUp(&in_attack);}

void IN_UseDown( void )			{KeyDown(&in_use);}
void IN_UseUp( void )			{KeyUp(&in_use);}

//wsw
void IN_SpecialDown( void )		{KeyDown(&in_special);}
void IN_SpecialUp( void )		{KeyUp(&in_special);}

/*
===============
CL_KeyState

Returns the fraction of the frame that the key was down
===============
*/
float CL_KeyState( kbutton_t *key )
{
	float		val;
	int			msec;

	key->state &= 1;		// clear impulses

	msec = key->msec;
	key->msec = 0;

	if( key->state )
	{	// still down
		msec += sys_frame_time - key->downtime;
		key->downtime = sys_frame_time;
	}

	val = (float)msec / frame_msec;

	return bound( 0, val, 1 );
}




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

cvar_t	*cl_upspeed;
cvar_t	*cl_forwardspeed;
cvar_t	*cl_sidespeed;

cvar_t	*cl_yawspeed;
cvar_t	*cl_pitchspeed;

cvar_t	*cl_run;

cvar_t	*cl_anglespeedkey;


/*
================
CL_AdjustAngles

Moves the local angle positions
================
*/
void CL_AdjustAngles( void )
{
	float	speed;
	float	up, down;

	if( in_speed.state & 1 )
		speed = (frame_msec * 0.001) * cl_anglespeedkey->value;
	else
		speed = frame_msec * 0.001;

	if( !(in_strafe.state & 1) )
	{
		cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState( &in_right );
		cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState( &in_left );
	}
	if( in_klook.state & 1 )
	{
		cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState( &in_forward );
		cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState( &in_back );
	}

	up = CL_KeyState( &in_lookup );
	down = CL_KeyState( &in_lookdown );

	cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up;
	cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down;
}

/*
================
CL_BaseMove

Send the intended movement message to the server
================
*/
void CL_BaseMove ( usercmd_t *cmd )
{
	VectorCopy( cl.viewangles, oldAngles );

	CL_AdjustAngles();

	memset( cmd, 0, sizeof(*cmd) );

	VectorCopy( cl.viewangles, cmd->angles );
	if( !frame_msec )
		return;

	if( in_strafe.state & 1 )
	{
		cmd->sidemove += cl_sidespeed->value * CL_KeyState( &in_right );
		cmd->sidemove -= cl_sidespeed->value * CL_KeyState( &in_left );
	}

	cmd->sidemove += cl_sidespeed->value * CL_KeyState( &in_moveright );
	cmd->sidemove -= cl_sidespeed->value * CL_KeyState( &in_moveleft );

	cmd->upmove += cl_upspeed->value * CL_KeyState( &in_up );
	cmd->upmove -= cl_upspeed->value * CL_KeyState( &in_down );

	if( !(in_klook.state & 1) )
	{
		cmd->forwardmove += cl_forwardspeed->value * CL_KeyState( &in_forward );
		cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState( &in_back );
	}
}

/*
==============
CL_FinishMove
==============
*/
void CL_FinishMove( usercmd_t *cmd )
{
	static double extramsec = 0;
	int		ms;
	int		i;

	// figure button bits

	if( in_attack.state & 3 )
		cmd->buttons |= BUTTON_ATTACK;
	in_attack.state &= ~2;

	if(in_special.state & 3) // wsw : ByMiK : useful to have 'special' working like attack...
		cmd->buttons |= BUTTON_SPECIAL;
	in_special.state &= ~2;

	if(in_use.state & 3)
		cmd->buttons |= BUTTON_USE;
	in_use.state &= ~2;

	if(anykeydown && cls.key_dest == key_game)
		cmd->buttons |= BUTTON_ANY;

	// wsw : jal : decide walk in server side
	if( (in_speed.state & 1) ^ !cl_run->integer )
		cmd->buttons |= BUTTON_WALK;

	// wsw : jal : add chat/console/ui icon as a button
	if( cls.key_dest != key_game )
		cmd->buttons |= BUTTON_BUSYICON;

	extramsec += frame_msec;
	ms = extramsec;
	extramsec -= ms;	// fractional part is left for next frame
	if( ms > 250 )
		ms = 100;		// time was unreasonable
	cmd->msec = ms;

	// from Q3: allow at most 90 degree moves per frame
	// wsw :mdr: I'm not sure about this
	if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) {
		cl.viewangles[PITCH] = oldAngles[PITCH] + 90;
	} else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) {
		cl.viewangles[PITCH] = oldAngles[PITCH] - 90;
	}

	for( i = 0; i < 3; i++ )
		cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
}

/*
=================
CL_CreateCmd
=================
*/
usercmd_t CL_CreateCmd( void )
{
	usercmd_t	cmd;

	frame_msec = sys_frame_time - old_sys_frame_time;
	 // wsw : jal : allow transparent calls.
	if( !frame_msec || ((frame_msec < (unsigned int)in_minMsecs->integer ) && !cls.demoplaying) ) { // [DEMOCAM] -- PLX
		cmd.msec = 0;
		return cmd;
	}

	clamp( frame_msec, 1, 200 );

	CL_BaseMove( &cmd );	// get basic movement from keyboard
	IN_Frame(); // reaquire the mouse
	IN_Move( &cmd );		// allow mice or other external controllers to add to the move
	CL_FinishMove( &cmd );

	old_sys_frame_time = sys_frame_time;

	return cmd;
}


void IN_CenterView( void )
{
	if( !cl.curFrame )
		return;

	cl.viewangles[PITCH] = -SHORT2ANGLE(cl.curFrame->playerState.pmove.delta_angles[PITCH]);
}

#ifdef DEMOCAM
void IN_CenterViewOnVec( void ) // wsw: [DEMOCAM] Used by democam to make player look at cam direction -- PLX
{
	if (Cmd_Argc() < 3) return;
	cl.viewangles[0] = atof(Cmd_Argv(1));
	cl.viewangles[1] = atof(Cmd_Argv(2));
	cl.viewangles[2] = atof(Cmd_Argv(3));
}
#endif

//==================
//CL_UpdateUserCommand
//==================
void CL_UpdateUserCommand( void )
{
	usercmd_t	tCmd, *uCmd;

/*	// get new key events
	Sys_SendKeyEvents();

	// allow mice or other external controllers to add commands
	IN_Commands();

	// process console commands
	Cbuf_Execute();
*/
	tCmd = CL_CreateCmd();
	uCmd = &cl.cmds[cls.netchan.outgoingSequence & CMD_MASK];
	if( cls.demoplaying ) { // outgoingsequence is not bumped when not connected, so use pure command
		*uCmd = tCmd;
		return;
	}

	uCmd->angles[0] = ANGLE2SHORT(cl.viewangles[0]);
	uCmd->angles[1] = ANGLE2SHORT(cl.viewangles[1]);
	uCmd->angles[2] = ANGLE2SHORT(cl.viewangles[2]);

	if ( !tCmd.msec ) // not time to update yet
		return;

	uCmd->buttons |= tCmd.buttons;
	uCmd->forwardmove += tCmd.forwardmove;
	uCmd->msec += tCmd.msec;
	uCmd->sidemove += tCmd.sidemove;
	uCmd->upmove += tCmd.upmove;
	uCmd->serverTimeStamp = cl.serverTime; // return the time stamp to the server

//	if( uCmd->msec > 250 ) {
//		uCmd->msec = 100;
//	}
}

void CL_UserInputFrame( void ) 
{
	// let the mouse activate or deactivate
	//IN_Frame();

	// get new key events
	Sys_SendKeyEvents();

	// allow mice or other external controllers to add commands
	IN_Commands();

	// process console commands
	Cbuf_Execute();

	// update client command up to the current msec for prediction
	if( !cls.demoplaying ) {
		CL_UpdateUserCommand();
	}
}

/*
============
CL_InitInput
============
*/
void CL_InitInput( void )
{
	Cmd_AddCommand( "in_restart", IN_Restart );

	IN_Init();

#ifdef DEMOCAM
	Cmd_AddCommand( "centerviewonvec", IN_CenterViewOnVec ); // wsw: [DEMOCAM] -- PLX
#endif
	Cmd_AddCommand( "centerview", IN_CenterView );
	Cmd_AddCommand( "+moveup", IN_UpDown );
	Cmd_AddCommand( "-moveup", IN_UpUp );
	Cmd_AddCommand( "+movedown", IN_DownDown );
	Cmd_AddCommand( "-movedown", IN_DownUp );
	Cmd_AddCommand( "+left", IN_LeftDown );
	Cmd_AddCommand( "-left", IN_LeftUp );
	Cmd_AddCommand( "+right", IN_RightDown );
	Cmd_AddCommand( "-right", IN_RightUp );
	Cmd_AddCommand( "+forward", IN_ForwardDown );
	Cmd_AddCommand( "-forward", IN_ForwardUp );
	Cmd_AddCommand( "+back", IN_BackDown );
	Cmd_AddCommand( "-back", IN_BackUp );
	Cmd_AddCommand( "+lookup", IN_LookupDown );
	Cmd_AddCommand( "-lookup", IN_LookupUp );
	Cmd_AddCommand( "+lookdown", IN_LookdownDown );
	Cmd_AddCommand( "-lookdown", IN_LookdownUp );
	Cmd_AddCommand( "+strafe", IN_StrafeDown );
	Cmd_AddCommand( "-strafe", IN_StrafeUp );
	Cmd_AddCommand( "+moveleft", IN_MoveleftDown );
	Cmd_AddCommand( "-moveleft", IN_MoveleftUp );
	Cmd_AddCommand( "+moveright", IN_MoverightDown );
	Cmd_AddCommand( "-moveright", IN_MoverightUp );
	Cmd_AddCommand( "+speed", IN_SpeedDown );
	Cmd_AddCommand( "-speed", IN_SpeedUp );
	Cmd_AddCommand( "+attack", IN_AttackDown );
	Cmd_AddCommand( "-attack", IN_AttackUp );
	Cmd_AddCommand( "+use", IN_UseDown );
	Cmd_AddCommand( "-use", IN_UseUp );
	Cmd_AddCommand( "+klook", IN_KLookDown );
	Cmd_AddCommand( "-klook", IN_KLookUp );
	// wsw
	Cmd_AddCommand( "+mlook", IN_MLookDown );
	Cmd_AddCommand( "-mlook", IN_MLookUp );
	Cmd_AddCommand( "+special", IN_SpecialDown );
	Cmd_AddCommand( "-special", IN_SpecialUp );

	cl_nodelta = Cvar_Get( "cl_nodelta", "0", 0 );
}

/*
============
CL_ShutdownInput
============
*/
void CL_ShutdownInput( void )
{
	Cmd_RemoveCommand( "in_restart" );

	IN_Shutdown();

#ifdef DEMOCAM
	Cmd_RemoveCommand( "centerviewonvec" ); // wsw: [DEMOCAM] -- PLX
#endif
	Cmd_RemoveCommand( "centerview" );
	Cmd_RemoveCommand( "+moveup" );
	Cmd_RemoveCommand( "-moveup" );
	Cmd_RemoveCommand( "+movedown" );
	Cmd_RemoveCommand( "-movedown" );
	Cmd_RemoveCommand( "+left" );
	Cmd_RemoveCommand( "-left" );
	Cmd_RemoveCommand( "+right" );
	Cmd_RemoveCommand( "-right" );
	Cmd_RemoveCommand( "+forward" );
	Cmd_RemoveCommand( "-forward" );
	Cmd_RemoveCommand( "+back" );
	Cmd_RemoveCommand( "-back" );
	Cmd_RemoveCommand( "+lookup" );
	Cmd_RemoveCommand( "-lookup" );
	Cmd_RemoveCommand( "+lookdown" );
	Cmd_RemoveCommand( "-lookdown" );
	Cmd_RemoveCommand( "+strafe" );
	Cmd_RemoveCommand( "-strafe" );
	Cmd_RemoveCommand( "+moveleft" );
	Cmd_RemoveCommand( "-moveleft" );
	Cmd_RemoveCommand( "+moveright" );
	Cmd_RemoveCommand( "-moveright" );
	Cmd_RemoveCommand( "+speed" );
	Cmd_RemoveCommand( "-speed" );
	Cmd_RemoveCommand( "+attack" );
	Cmd_RemoveCommand( "-attack" );
	Cmd_RemoveCommand( "+use" );
	Cmd_RemoveCommand( "-use" );
	Cmd_RemoveCommand( "+klook" );
	Cmd_RemoveCommand( "-klook" );
	// wsw
	Cmd_RemoveCommand( "+mlook" );
	Cmd_RemoveCommand( "-mlook" );
	Cmd_RemoveCommand( "+special" );
	Cmd_RemoveCommand( "-special" );
}


//jalfixme: this isn't the right file for this function
/*
=================
CL_WritePacket

Send a packet to the server
=================
*/
void CL_WritePacket( usercmd_t *cmd )
{
	msg_t		message;
	qbyte		messageData[MAX_MSGLEN];
	
	if( cls.state == CA_DISCONNECTED || cls.state == CA_CONNECTING )
		return;
	
	if( cls.demoplaying )
		return;

	MSG_Init( &message, messageData, sizeof(messageData) );
	MSG_Clear( &message );

	// write the command ack
	MSG_WriteByte( &message, clc_svcack );
	MSG_WriteLong( &message, (unsigned long)cls.lastExecutedServerCommand );

	// send only reliable commands during conneting time
	if( cls.state == CA_CONNECTED )
	{
		if( cls.reliableSent < cls.reliableSequence || cls.realtime - cls.lastPacketSentTime > 1000 )
		{
			//write up the clc commands
			CL_UpdateClientCommandsToServer( &message );
			CL_Netchan_Transmit( &message );
		}
		return;
	}

	// send a userinfo update if needed
	if( userinfo_modified )
	{
		userinfo_modified = qfalse;
		CL_AddReliableCommand( va( "usri \"%s\"", Cvar_Userinfo() ) );
	}

	CL_UpdateClientCommandsToServer( &message );

	// wsw : jal : don't write user move commands unless we were given one
	if( cmd != NULL )
	{
		usercmd_t	*oldcmd;
		usercmd_t	nullcmd;
		int			checksumIndex;

		// begin a client move command
		MSG_WriteByte( &message, clc_move );
		
		// save the position for a checksum byte
		checksumIndex = message.cursize;
		MSG_WriteByte( &message, 0 );
		
		// (acknowledge server frame snap) 
		// let the server know what the last frame we
		// got was, so the next message can be delta compressed
		if( cl_nodelta->integer || !cl.curFrame || !cl.curFrame->valid || cls.demowaiting )
			MSG_WriteLong( &message, -1 );	// no compression
		else
			MSG_WriteLong( &message, cl.curFrame->serverFrame );
		
		// send this and the previous cmds in the message, so
		// if the last packet was dropped, it can be recovered
		cmd = &cl.cmds[(cls.netchan.outgoingSequence-2) & CMD_MASK];
		memset( &nullcmd, 0, sizeof(nullcmd) );
		MSG_WriteDeltaUsercmd( &message, &nullcmd, cmd );
		oldcmd = cmd;
		
		cmd = &cl.cmds[(cls.netchan.outgoingSequence-1) & CMD_MASK];
		MSG_WriteDeltaUsercmd( &message, oldcmd, cmd );
		oldcmd = cmd;
		
		cmd = &cl.cmds[(cls.netchan.outgoingSequence) & CMD_MASK];
		MSG_WriteDeltaUsercmd( &message, oldcmd, cmd );
		
		// calculate a checksum over the move commands
		message.data[checksumIndex] = COM_BlockSequenceCRCByte(
			message.data + checksumIndex + 1, message.cursize - checksumIndex - 1,
			cls.netchan.outgoingSequence );
	}

	CL_Netchan_Transmit( &message );
}


