#include "common_headers.h"

#ifdef _IMMERSION

//#include "ff_ConfigParser.h"
//#include "ifc.h"
//#include "ff_utils.h"

////--------------------
/// FFConfigParser::Init
//------------------------
//	Reads the force feedback configuration file. Call this once after the device
//	is initialized.
//
//	Parameters:
//	*	filename
//
//	Returns:
//	*	qtrue - the effects set directory has been set according to the initialized
//			device. (See base/fffx/fffx.cfg)
//	*	qfalse - no effects set could be determined for this device.
//
qboolean FFConfigParser::Init( const char *filename )
{
	Clear();	// Always cleanup

	return qboolean( filename && Parse( LoadFile( filename ) ) );
}

////---------------------
/// FFConfigParser::Clear
//-------------------------
//
//
//	Parameters:
//
//	Returns:
//
void FFConfigParser::Clear( void )
{
	mMap.clear();
	mDefaultSet.clear();
}

////---------------------
/// FFConfigParser::Parse
//-------------------------
//
//
//	Parameters:
//
//	Returns:
//
qboolean FFConfigParser::Parse( void *file )
{
	qboolean result = qboolean( file != NULL );

	if ( file )
	{
		const char *token = 0, *pos = (const char*)file;
		for
		(	token = COM_ParseExt( &pos, qtrue )
		;	token[ 0 ]
		&&	result // fail if any problem
		;	token = COM_ParseExt( &pos, qtrue )
		){
			if ( !Q_stricmp( token, "ffdefaults" ) )
			{
				result &= ParseDefaults( &pos );
			}
			else
			if ( !Q_stricmp( token, "ffsets" ) )
			{
				result &= ParseSets( &pos );
			}
			else
			{
				// unexpected field
				result = qfalse;
			}
		}

		FS_FreeFile( file );
	}

	return result;
}

////---------------------------------
/// FFConfigParser::ParseDefaultBlock
//-------------------------------------
//
//
//	Parameters:
//
//	Returns:
//
qboolean FFConfigParser::ParseDefault( const char **pos, TDeviceType &defaultSet )
{
	qboolean result = qboolean( pos != NULL );

	if ( pos )
	{
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				int device = 0;

				if ( sscanf( token, "%d", &device ) )
				{
					string &str = defaultSet[ device ];
					if ( !str.size() )
					{
						str = COM_ParseExt( pos, qfalse );
						result &= qboolean( str.size() > 0 );
					}
					else
					{
						result = qfalse;
#ifdef FF_PRINT
						ConsoleParseError
						(	"Redefinition of DeviceType index"
						,	token
						);
#endif
					}
				}
				else
				{
					result = qfalse;
#ifdef FF_PRINT
					ConsoleParseError
					(	"DeviceType field should begin with an integer"
					,	token
					);
#endif
				}
			}
		}
	}

	return result;
}



////----------------------------
/// FFConfigParser::ParseDefault
//--------------------------------
//
//
//	Parameters:
//
//	Returns:
//
qboolean FFConfigParser::ParseDefaults( const char **pos )
{
	qboolean result = qboolean( pos != NULL );

	if ( pos )
	{
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				int techType = 0;

				if ( sscanf( token, "%d", &techType ) )
				{
					TDeviceType &deviceType = mDefaultSet[ techType ];
					if ( !deviceType.size() )
					{
						result &= ParseDefault( pos, deviceType );
						mDefaultPriority.push_back( techType );
					}
					else
					{
						result = qfalse;
#ifdef FF_PRINT
						ConsoleParseError
						(	"Redefinition of TechType index"
						,	token
						);
#endif
					}
				}
				else
				{
					result = qfalse;
#ifdef FF_PRINT
					ConsoleParseError
					(	"TechType fields should begin with integers"
					,	token
					);
#endif
				}
			}
		}
		else
		{
			// expected '{'
			result = qfalse;
		}
	}

	return result;
}

////--------------------------
/// FFConfigParser::RightOfSet
//------------------------------
//
//
//	Parameters:
//
//	Returns:
//
const char* FFConfigParser::RightOfSet( const char *effectname )
{
	const char *s = effectname;

	// Check through all set names and test effectname against it
	for
	(	TMap::iterator itMap = mMap.begin()
	;	itMap != mMap.end() && s == effectname
	;	itMap++
	){
		s = RightOf( effectname, (*itMap).first.c_str() );
	}

	return s ? s : effectname;
}

qboolean FFConfigParser::ParseSetDevices( const char **pos, TDevice &device )
{
	qboolean result = qboolean( pos != NULL );

	if ( pos )
	{
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				device.insert( token );
			}

			result = qboolean( token[ 0 ] != 0 );
		}
		else
		{
			// expected '{'
			result = qfalse;
		}
	}

	return result;
}

qboolean FFConfigParser::ParseSetIncludes( const char **pos, TInclude &include )
{
	qboolean result = qboolean( pos != NULL );

	if ( pos )
	{
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				include.push_back( token );
			}

			result = qboolean( token[ 0 ] != 0 );
		}
		else
		{
			// expected '{'
			result = qfalse;
		}
	}

	return result;
}

qboolean FFConfigParser::ParseSet( const char **pos, TData &data )
{
	qboolean result = qboolean( pos != NULL );

	if ( pos )
	{
		const char *oldpos = *pos;	// allows set declarations with no attributes to have no "{}" 
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				if ( !Q_stricmp( token, "includes" ) )
				{
					result &= ParseSetIncludes( pos, data.include );
				}
				else
				if ( !Q_stricmp( token, "devices" ) )
				{
					result &= ParseSetDevices( pos, data.device );
				}
				else
				{
					result = qfalse;
#ifdef FF_PRINT
					ConsoleParseError
					(	"Invalid set parameter. Should be 'includes' or 'devices'"
					,	token
					);
#endif
				}
			}
		}
		else
		{
			// expected '{'		(no longer expected!)
			//result = qfalse;	(no longer an error!)
			*pos = oldpos;
		}
	}

	return result;
}

////-------------------------
/// FFConfigParser::ParseSets
//-----------------------------
//
//
//	Parameters:
//
//	Returns:
//
qboolean FFConfigParser::ParseSets( const char **pos )
{
	qboolean result = qboolean( pos != NULL );
	string			groupName;

	if ( pos )
	{
		char *token = COM_ParseExt( pos, qtrue );
		if ( token[ 0 ] == '{' )
		{
			for
			(	token = COM_ParseExt( pos, qtrue )
			;	token[ 0 ]
			&&	token[ 0 ] != '}'
			&&	result // fail if any problem
			;	token = COM_ParseExt( pos, qtrue )
			){
				TData &data = mMap[ token ];
				result &= ParseSet( pos, data );
			}
		}
		else
		{
			// expected '{'
			result = qfalse;
		}
	}

	return result;
}

////---------------------------
/// FFConfigParser::GetIncludes
//-------------------------------
//
//
//	Parameters:
//
//	Returns:
//
FFConfigParser::TInclude& FFConfigParser::GetIncludes( const char *name )
{
	TMap::iterator itMap = mMap.find( name );
	if ( itMap != mMap.end() )
		return (*itMap).second.include;

	// No includes present
	static TInclude emptyInclude;
	return emptyInclude;
}

const char * FFConfigParser::GetFFSet( CImmDevice *Device )
{
	char devName[ FF_MAX_PATH ];
	const char *ffset = NULL;

	//
	//	Check explicit name
	//

	devName[0] = 0;
	Device->GetProductName( devName, FF_MAX_PATH - 1 );
	for
	(	TMap::iterator itmap = mMap.begin()
	;	itmap != mMap.end()
	;	itmap++
	){
		TDevice::iterator itdev;

		itdev = (*itmap).second.device.find( devName );
		if ( itdev != (*itmap).second.device.end() )
			ffset = (*itmap).first.c_str();
	}


	//
	//	Check device defaults
	//

	for
	(	int i = 0
	;	!ffset && i < mDefaultPriority.size()
	;	i++
	){
		int defaultTechType;
		DWORD	productType = Device->GetProductType();
		WORD	deviceType	= HIWORD( productType );
		WORD	techType	= LOWORD( productType );

		defaultTechType = mDefaultPriority[ i ];

		//
		//	Check for minimum required features
		//

		if ( (techType & defaultTechType) >= defaultTechType )
		{
			//
			//	Check that device exists in this technology section
			//

			TDeviceType::iterator itDeviceType = mDefaultSet[ defaultTechType ].find( deviceType );
			if ( itDeviceType != mDefaultSet[ defaultTechType ].end() )
			{
				ffset = (*itDeviceType).second.c_str();
			}
		}

		//
		//	If not, try next technology section
		//
	}

	return ffset;
}

#endif // _IMMERSION
