#include "common_headers.h"

#ifdef _IMMERSION

#define INITGUID	// this will need removing if already defined in someone else's module. Only one must exist in whole game

//#include "ff.h"
//#include "ff_ffset.h"
//#include "ff_compound.h"
//#include "ff_system.h"

FFSystem gFFSystem;

cvar_t	*use_ff;
cvar_t	*ensureShake;
cvar_t	*ff_developer;
#ifdef FF_DELAY
cvar_t	*ff_delay;
#endif
cvar_t	*ff_channels;

static const char *_pass = "SUCCEEDED";
static const char *_fail = "FAILED";

const char *gChannelName[ FF_CHANNEL_MAX ] =
{	"FF_CHANNEL_WEAPON"
,	"FF_CHANNEL_MENU"
,	"FF_CHANNEL_TOUCH"
,	"FF_CHANNEL_DAMAGE"
,	"FF_CHANNEL_BODY"
,	"FF_CHANNEL_FORCE"
,	"FF_CHANNEL_FOOT"
};

// Enable/Disable Com_Printf in FF_* functions
#if( 1 )
#ifdef FF_PRINT
#define FF_PROLOGUE( name, string )			qboolean result = qfalse; if ( FF_IsAvailable() ) { if ( ff_developer && ff_developer->integer ) Com_Printf( "%s: \"%s\" ", #name, string );
#define FF_PROLOGUE_NOQUOTE( name, string )	qboolean result = qfalse; if ( FF_IsAvailable() ) { if ( ff_developer && ff_developer->integer ) Com_Printf( "%s: %s ", #name, string );
#define FF_EPILOGUE							FF_EPILOGUE_NORETURN; return result;
#define FF_EPILOGUE_NORETURN				} if ( ff_developer && ff_developer->integer ) Com_Printf( "[%s]\n", ( result ? _pass : _fail ) );
#define FF_RESULT( function )				result = qboolean( function );
#else
#define FF_PROLOGUE( name, string )			qboolean result = qfalse; if ( FF_IsAvailable() ) { 
#define FF_PROLOGUE_NOQUOTE( name, string )	qboolean result = qfalse; if ( FF_IsAvailable() ) { 
#define FF_EPILOGUE							FF_EPILOGUE_NORETURN; return result;
#define FF_EPILOGUE_NORETURN				}
#define FF_RESULT( function )				result = qboolean( function );
#endif
#else
#define FF_PROLOGUE( name, string )			qboolean result = qfalse;
#define FF_PROLOGUE_NOQUOTE( name, string )	qboolean result = qfalse;
#define FF_EPILOGUE							return result;
#define FF_EPILOGUE_NORETURN
#define FF_RESULT( function )				result = qboolean( function );
#endif

////--------------
/// FF_IsAvailable
//------------------
//	Test to see if force feedback is currently operating. This is almost useless.
//	The only good it does currently is temporarily toggle effects on/off for users
//	amusement... feedback on, feedback off, feedback on, feedback off. Results are
//	instantaneous. FF_* calls basically skip themselves harmlessly.
//
//	Assumptions:
//	*	External system unloads this module if no device is present.
//	*	External system unloads this module if feedback is disabled when system (re)starts
//
//	Parameters:
//		None
//
//	Returns:
//	-	true: feedback currently enabled
//	-	false: feedback currently disabled
//
qboolean FF_IsAvailable(void)
{
	return (use_ff && use_ff->integer && gFFSystem.IsInitialized()) ? qtrue : qfalse;
}

qboolean FF_IsInitialized(void)
{
	return gFFSystem.IsInitialized();
}

////-------
/// FF_Init
//-----------
//	Initializes the force feedback system.
//
//	This function may be called multiple times to apply changes in cvars.
//
//	Assumptions:
//	*	If FF_Init returns qfalse, caller calls FF_Shutdown
//
//	Parameters:
//		None
//
//	Returns:
//	-	qtrue: module initialized properly.
//	-	qfalse: module experienced an error. Caller MUST call FF_Shutdown.
//
qboolean FF_Init( void )
{
	if ( !gFFSystem.IsInitialized() )
	{
		//
		//	Console variable setup
		//

#ifdef FF_CONSOLECOMMAND
		Cmd_AddCommand( "ff_stopall",	CMD_FF_StopAll		);
		Cmd_AddCommand( "ff_info",		CMD_FF_Info			);
#endif
		use_ff				= Cvar_Get( "use_ff",				"0",	CVAR_ARCHIVE /*| CVAR_LATCH*/); 
		ensureShake			= Cvar_Get( "ff_ensureShake",		"1",	CVAR_TEMP);
		ff_developer		= Cvar_Get( "ff_developer",			"0",	CVAR_TEMP);
		ff_channels			= Cvar_Get( "ff_channels",		FF_CHANNEL,	CVAR_ARCHIVE);
#ifdef FF_DELAY
		ff_delay			= Cvar_Get( "ff_delay",			FF_DELAY,	CVAR_ARCHIVE);
#endif
	}
	
	return qboolean // assumes external system will call FF_Shutdown in case of failure
	(	ff_channels != NULL
	&&	gFFSystem.Init( ff_channels->string )
	);
}

////-----------
/// FF_Shutdown
//---------------
//	Shut force feedback system down and free resources.
//
//	Assumptions:
//	*	Always called if FF_Init returns qfalse. ALWAYS. (Or memory leaks occur)
//	*	Never called twice in succession. (always in response to previous assumption)
//
//	Parameters:
//		None
//
//	Returns:
//		None
//
void FF_Shutdown(void)
{
#ifdef FF_CONSOLECOMMAND
	Cmd_RemoveCommand( "ff_stopall" );
	Cmd_RemoveCommand( "ff_info" );
#endif

	gFFSystem.Shutdown();
}

////-----------
/// FF_Register
//---------------
//	Loads a named effect from an .ifr file through the game's file system. The handle
//	returned is not tied to any particular device. The feedback system may even change
//	which device receives the effect without the need to restart the external system.
//	The is ONE EXCEPTION. If this module is not loaded when the registration phase
//	passes, the external system will need to be restarted to register effects properly.
//
//	Parameters:
//	*	name: effect name from .ifr (case-sensitive)
//	*	channel: channel to output effect. A channel may play on 0+ devices.
//	*	notfound: return a valid handle even if effect is not found
//		- Allows temporary disabling of a channel in-game without losing effects
//		- Only use with trusted effect names, not user input. See CMD_FF_Play.
//
//	Returns:
//		Handle to loaded effect
//
ffHandle_t FF_Register( const char *name, int channel, qboolean notfound )
{
	ffHandle_t ff = FF_HANDLE_NULL;

	// Removed console print... too much spam with AddLoopingForce.
/*
	FF_PROLOGUE( FF_Register, ( name ? name : "" ) );
	ff = gFFSystem.Register( name, channel, notfound );
	FF_RESULT( ff != FF_HANDLE_NULL );
	FF_EPILOGUE_NORETURN;
*/
	if ( FF_IsAvailable() )
		ff = gFFSystem.Register( name, channel, notfound );

	return ff;
}

////----------------
/// FF_EnsurePlaying
//--------------------
//	Starts an effect if the effect is not currently playing.
//	Does not restart currently playing effects.
//
//	Parameters:
//	*	ff: handle of an effect
//
//	Returns:
//	-	qtrue: effect started
//	-	qfalse: effect was not started
//
qboolean FF_EnsurePlaying(ffHandle_t ff)
{
	FF_PROLOGUE( FF_EnsurePlaying, gFFSystem.GetName( ff ) );
	FF_RESULT( gFFSystem.EnsurePlaying( ff ) );
	FF_EPILOGUE;
}

////-------
/// FF_Play
//-----------
//	Start an effect on its registered channel.
//
//	Parameters
//	*	ff: handle to an effect
//
//	Returns:
//	-	qtrue: effect started
//	-	qfalse: effect was not started
//
qboolean FF_Play(ffHandle_t ff)
{
	FF_PROLOGUE( FF_Play, gFFSystem.GetName( ff ) );
	FF_RESULT( gFFSystem.Play( ff ) );
	FF_EPILOGUE;
}

////----------
/// FF_StopAll
//--------------
//	Stop all currently playing effects.
//
//	Parameters:
//		None
//
//	Returns:
//	-	qtrue: no errors occurred
//	-	qfalse: an error occurred
//
qboolean FF_StopAll(void)
{
	FF_PROLOGUE( FF_StopAll, "" );
	FF_RESULT( gFFSystem.StopAll() );
	FF_EPILOGUE;
}

////-------
/// FF_Stop
//-----------
//	Stop an effect. Only returns qfalse if there's an error
//
//	Parameters:
//	*	ff: handle to a playing effect
//
//	Returns:
//	-	qtrue: no errors occurred
//	-	qfalse: an error occurred
//
qboolean FF_Stop(ffHandle_t ff)
{
	FF_PROLOGUE( FF_Stop, gFFSystem.GetName( ff ) );
	FF_RESULT( gFFSystem.Stop( ff ) );
	FF_EPILOGUE;
}


////--------
/// FF_Shake
//------------
//	Shake the mouse (play the special "shake" effect) at a given strength
//	for a given duration. The shake effect can be a compound containing
//	multiple component effects, but each component effect's magnitude and
//	duration will be set to the parameters passed in this function.
//
//	Parameters:
//	*	intensity [0..10000] - Magnitude of effect
//	*	duration [0..MAXINT] - Length of shake in milliseconds
//
//	Returns:
//	-	qtrue: shake started
//	-	qfalse: shake did not start
//
qboolean FF_Shake(int intensity, int duration)
{
	char message[64];
	message[0] = 0;
	sprintf( message, "intensity=%d, duration=%d", intensity, duration );
	FF_PROLOGUE_NOQUOTE( FF_Shake, message );
	FF_RESULT( gFFSystem.Shake( intensity, duration, qboolean( ensureShake->integer != qfalse ) ) );
	FF_EPILOGUE;
}

#ifdef FF_CONSOLECOMMAND

////--------------
/// CMD_FF_StopAll
//------------------
//	Console function which stops all currently playing effects
//
//	Parameters:
//		None
//
//	Returns:
//		None
//
void CMD_FF_StopAll(void)
{
	// Display messages
	if ( FF_StopAll() )
	{
		Com_Printf( "stopping all effects\n" );
	}
	else
	{
		Com_Printf( "failed to stop all effects\n" );
	}
}

////-----------
/// CMD_FF_Info
//---------------
//	Console function which displays various ff-system information.
//
//	Parameters:
//	*	'devices'	display list of ff devices currently connected
//	*	'channels'	display list of support ff channels
//	*	'order'		display search order used by ff name-resolution system (ff_ffset.cpp)
//	*	'files'		display currently loaded .ifr files sorted by device
//	*	'effects'	display currently registered effects sorted by device
//
//	Returns:
//		None
//
void CMD_FF_Info(void)
{
	TNameTable Unprocessed, Processed;
	int i, max;

	for
	(	i = 1, max = Cmd_Argc()
	;	i < max
	;	i++
	){
		Unprocessed.push_back( Cmd_Argv( i ) );
	}

	if ( Unprocessed.size() == 0 )
	{
		
		if ( ff_developer->integer )
			Com_Printf( "Usage: ff_info [devices] [channels] [order] [files] [effects]\n" );
		else
			Com_Printf( "Usage: ff_info [devices] [channels]\n" );

		return;
	}

	gFFSystem.Display( Unprocessed, Processed );

	if ( Unprocessed.size() > 0 )
	{
		Com_Printf( "invalid parameters:" );
		for
		(	i = 0
		;	i < Unprocessed.size()
		;	i++
		){
			Com_Printf( " %s", Unprocessed[ i ].c_str() );
		}

		if ( ff_developer->integer )
			Com_Printf( "Usage: ff_info [devices] [channels] [order] [files] [effects]\n" );
		else
			Com_Printf( "Usage: ff_info [devices] [channels]\n" );
	}
}

#endif // FF_CONSOLECOMMAND

#endif // _IMMERSION