#include "../../idlib/precompiled.h"
#pragma hdrstop

#include "../Game_local.h"

/*
================
rvScriptFuncUtility::rvScriptFuncUtility
================
*/
rvScriptFuncUtility::rvScriptFuncUtility() {
	func = NULL;
	parms.Clear();
}

/*
================
rvScriptFuncUtility::rvScriptFuncUtility
================
*/
rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility* sfu ) {
	Assign( sfu );
}

/*
================
rvScriptFuncUtility::rvScriptFuncUtility
================
*/
rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility& sfu ) {
	Assign( &sfu );
}

/*
================
rvScriptFuncUtility::rvScriptFuncUtility
================
*/
rvScriptFuncUtility::rvScriptFuncUtility( const char* source ) {
	Init( source );
}

/*
================
rvScriptFuncUtility::rvScriptFuncUtility
================
*/
rvScriptFuncUtility::rvScriptFuncUtility( const idCmdArgs& args ) {
	Init( args );
}

/*
================
rvScriptFuncUtility::Init
================
*/
sfuReturnType rvScriptFuncUtility::Init( const char* source ) {
	Clear();

	if( !source || !source[0] ) {
		return SFU_NOFUNC;
	}

	idStr::Split( source, parms, ' ' );
	return Init();
}

/*
================
rvScriptFuncUtility::Init
================
*/
sfuReturnType rvScriptFuncUtility::Init( const idCmdArgs& args ) {
	Clear();

	//Start at index 1 so we ignore 'call'
	for( int ix = 1; ix < args.Argc(); ++ix ) {
		InsertString( args.Argv(ix), ix );
	}

	return Init();
}

/*
================
rvScriptFuncUtility::Init
================
*/
sfuReturnType rvScriptFuncUtility::Init() {
	assert( parms.Num() );

	func = FindFunction( GetParm(0) );
	if( func ) {
		RemoveIndex( 0 );// remove Function name
		returnKey.Clear();
	} else if( parms.Num() >= 2 ) {
		returnKey = GetParm( 0 );
		RemoveIndex( 0 );// remove key name
		func = FindFunction( GetParm(0) );
		RemoveIndex( 0 );// remove Function name
	} else {
		gameLocal.Warning( "Unable to find function %s in rvScriptFuncUtility::Init\n", parms[0].c_str() );
	}

	return func != NULL ? SFU_OK : SFU_ERROR;
}

/*
================
rvScriptFuncUtility::Clear
================
*/
void rvScriptFuncUtility::Clear() {
	func = NULL;
	parms.Clear();
}

/*
================
rvScriptFuncUtility::Save
================
*/
void rvScriptFuncUtility::Save( idSaveGame *savefile ) const {
	bool validFunc = GetFunc() != NULL;
	savefile->WriteBool( validFunc );
	if( !validFunc ) {
		return;
	}
	
	savefile->WriteString( GetReturnKey() );

	savefile->WriteString( GetFunc()->Name() );

	savefile->WriteInt( NumParms() );
	for( int ix = 0; ix < NumParms(); ++ix ) {
		savefile->WriteString( func->Name() );
	}
}

/*
================
rvScriptFuncUtility::Restore
================
*/
void rvScriptFuncUtility::Restore( idRestoreGame *savefile ) {
	idStr value;
	idStr element;
	int numParms = 0;
	bool validFunc = false;

	savefile->ReadBool( validFunc );
	if( !validFunc ) {
		return;
	}

	savefile->ReadString( element );
	if( element.Length() ) {
		value += element;
		value += ' ';
	}

	savefile->ReadString( element );
	if( element.Length() ) {
		value += element;
		value += ' ';
	}

	savefile->ReadInt( numParms );
	if ( numParms < 0 || numParms > MAX_SAVEGAME_SCRIPT_FUNC_PARMS ) {
		savefile->Error( "rvScriptFuncUtility::Restore: invalid parameter count %d", numParms );
	}
	for( int ix = 0; ix < numParms; ++ix ) {
		savefile->ReadString( element );
		value += element;
		value += ' ';
	}

	value.StripTrailing( ' ' );
	sfuReturnType status = Init(value.c_str());
	if ( status != SFU_OK ) {
		assert( 0 );
	}
}

/*
================
rvScriptFuncUtility::NumParms
================
*/
int	rvScriptFuncUtility::NumParms() const {
	return (func && func->type) ? func->type->NumParameters() : 0;
}

/*
================
rvScriptFuncUtility::ReturnsAVal
================
*/
bool rvScriptFuncUtility::ReturnsAVal() const {
	return GetReturnType() != &type_void;
}

/*
================
rvScriptFuncUtility::GetParm
================
*/
idTypeDef* rvScriptFuncUtility::GetParmType( int index ) const {
	return (func && func->type) ? func->type->GetParmType( index ) : NULL;
}

/*
================
rvScriptFuncUtility::GetReturnType
================
*/
idTypeDef* rvScriptFuncUtility::GetReturnType() const {
	return (func && func->type) ? func->type->ReturnType() : &type_void;
}

/*
================
rvScriptFuncUtility::SetFunction
================
*/
void rvScriptFuncUtility::SetFunction( const function_t* func ) {
	this->func = func;
}

/*
================
rvScriptFuncUtility::SetParms
================
*/
void rvScriptFuncUtility::SetParms( const idList<idStr>& parms ) {
	this->parms = parms;
}

/*
================
rvScriptFuncUtility::GetParm
================
*/
const char* rvScriptFuncUtility::GetParm( int index ) const {
	return parms[ index ].c_str();
}

/*
================
rvScriptFuncUtility::GetFuncName
================
*/
const char* rvScriptFuncUtility::GetFuncName() const {
	if( !func ) {
		return NULL;
	}

	return func->Name();
}

/*
================
rvScriptFuncUtility::InsertInt
================
*/
void rvScriptFuncUtility::InsertInt( int parm, int index ) {
	InsertString( va("%d", parm), index );
}

/*
================
rvScriptFuncUtility::InsertFloat
================
*/
void rvScriptFuncUtility::InsertFloat( float parm, int index ) {
	InsertString( va("%f", parm), index );
}

/*
================
rvScriptFuncUtility::InsertVec3
================
*/
void rvScriptFuncUtility::InsertVec3( const idVec3& parm, int index ) {
	InsertString( parm.ToString(), index );
}

/*
================
rvScriptFuncUtility::InsertEntity
================
*/
void rvScriptFuncUtility::InsertEntity( const idEntity* parm, int index ) {
	assert( parm );

	InsertString( parm->GetName(), index );
}

/*
================
rvScriptFuncUtility::InsertString
================
*/
void rvScriptFuncUtility::InsertString( const char* parm, int index ) {
	assert( parm );

	parms.Insert( parm, index );
}

/*
================
rvScriptFuncUtility::InsertBool
================
*/
void rvScriptFuncUtility::InsertBool( bool parm, int index ) {
	InsertInt( (int)parm, index );
}

/*
================
rvScriptFuncUtility::RemoveIndex
================
*/
void rvScriptFuncUtility::RemoveIndex( int index ) {
	parms.RemoveIndex( index );
}

/*
================
rvScriptFuncUtility::FindFunction
================
*/
const function_t* rvScriptFuncUtility::FindFunction( const char* name ) const {
	idTypeDef* type = gameLocal.program.FindType( name );
	if( type ) {//Find based on type
		return gameLocal.program.FindFunction( name, type );
	}

	//Find based on scope
	return gameLocal.program.FindFunction( name );
}

/*
================
rvScriptFuncUtility::CallFunc
================
*/
void rvScriptFuncUtility::CallFunc( idDict* returnDict ) const {
	idTypeDef* type = NULL;

	if( !Valid() ) {
		return;
	}

	idThread* thread = new idThread();
	if( !thread ) {
		return;
	}

	thread->ClearStack();
	for( int ix = 0; ix < NumParms(); ++ix ) {
		type = GetParmType( ix );
		type->PushOntoStack( thread, GetParm(ix) );
	}

	thread->CallFunction( func, false );

	if( thread->Execute() && returnDict && ReturnsAVal() ) {
		returnDict->Set( GetReturnKey(), GetReturnType()->GetReturnedValAsString(gameLocal.program) );
	}
}

/*
================
rvScriptFuncUtility::Valid
================
*/
bool rvScriptFuncUtility::Valid() const {
//#if _DEBUG
	idTypeDef* type = NULL;

	if( !GetFunc() ) {
		return false;
	}

	if( GetFunc()->eventdef ) {
		gameLocal.Warning( "Function, %s, is an event\n", GetFunc()->Name() );
		return false;
	}

	// FIXME: designers call functions with no parms even though parms are pushed on stack
	//if( NumParms() != parms.Num() ) {
	//	gameLocal.Warning( "Number of parms doesn't equal the num required by function %s\n", func->Name() );
	//	return false;
	//}

	for( int ix = 0; ix < NumParms(); ++ix ) {
		type = GetParmType( ix );
		if( !type->IsValid( GetParm(ix) ) ) {
			gameLocal.Warning( "(Func: %s) Parm '%s' doesn't match expected type '%s'\n", GetFunc()->Name(), GetParm(ix), type->Name() );
			return false;
		}
	}

	if( returnKey.Length() && !ReturnsAVal() ) {
		gameLocal.Warning( "Expecting return val from function %s which doesn't return one\n", func->Name() );
		return false;
	}
//#endif

	return true;
}

/*
================
rvScriptFuncUtility::Assign
================
*/
rvScriptFuncUtility& rvScriptFuncUtility::Assign( const rvScriptFuncUtility* sfu ) {
	assert( sfu );
	func = sfu->func;
	parms = sfu->parms;
	returnKey = sfu->returnKey;

	return *this;
}

/*
================
rvScriptFuncUtility::operator=
================
*/
rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility* sfu ) {
	return Assign( sfu );
}

/*
================
rvScriptFuncUtility::operator=
================
*/
rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility& sfu ) {
	return Assign( &sfu );
}

/*
================
rvScriptFuncUtility::IsEqualTo
================
*/
bool rvScriptFuncUtility::IsEqualTo( const rvScriptFuncUtility* sfu ) const {
	assert( sfu );
	return GetFunc() == sfu->GetFunc();
}

/*
================
rvScriptFuncUtility::operator==
================
*/
bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility* sfu ) const {
	return IsEqualTo( sfu );
}

/*
================
rvScriptFuncUtility::operator==
================
*/
bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility& sfu ) const {
	return IsEqualTo( &sfu );
}
