/*************************************************************************
	Crytek Source File.
	Copyright (C), Crytek Studios, 2001-2004.
	-------------------------------------------------------------------------
	$Id$
	$DateTime$
	Description: Helper classes for implementing serialization in script

	-------------------------------------------------------------------------
	History:
	- 24:11:2004   11:30 : Created by Craig Tiller

*************************************************************************/
#include "StdAfx.h"
#include "ScriptSerialize.h"

//
// CScriptSerializeAny
//

CScriptSerializeAny::CScriptSerializeAny( EScriptSerializeType type ) :
	m_type(type)
{
	switch (m_type)
	{
	case eSST_StringTable:
	case eSST_String:
		new (Ptr<string>()) string();
		break;
	}
}

CScriptSerializeAny::CScriptSerializeAny( const CScriptSerializeAny& other )
{
	m_type = other.m_type;
	switch (m_type)
	{
	case eSST_StringTable:
	case eSST_String:
		new (Ptr<string>()) string(*other.Ptr<string>());
		break;

	default:
		memcpy(m_buffer, other.m_buffer, BUFFER_SIZE);
	}
}

CScriptSerializeAny::~CScriptSerializeAny()
{
	switch (m_type)
	{
	case eSST_StringTable:
	case eSST_String:
		Ptr<string>()->~string();
		break;
	}
}

CScriptSerializeAny& CScriptSerializeAny::operator =( const CScriptSerializeAny& other )
{
	// this isn't general enough to be used in every place... but for what
	// we have for now it should be fine
	if (&other == this)
		return *this;
	this->~CScriptSerializeAny();
	new (this) CScriptSerializeAny(other);
	return *this;
}

bool CScriptSerializeAny::SetFromFunction( 
	IFunctionHandler * pFH, 
	int param )
{
	switch (m_type)
	{
	case eSST_Bool:
		if (!pFH->GetParam( param, *Ptr<bool>() ))
			return false;
		break;
	case eSST_Float:
		if (!pFH->GetParam( param, *Ptr<float>() ))
			return false;
		break;
	case eSST_Int8:
		{
			int temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<int8>() = temp;
		}
		break;
	case eSST_Int16:
		{
			int temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<int16>() = temp;
		}
		break;
	case eSST_Int32:
		{
			int temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<int32>() = temp;
		}
		break;
	case eSST_StringTable:
	case eSST_String:
		{
			const char * temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<string>() = temp;
		}
		break;

	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			ScriptHandle temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<EntityId>() = temp.n;
		}
		break;
	case eSST_Vec3:
		{
			Vec3 temp;
			if (!pFH->GetParam( param, temp ))
				return false;
			*Ptr<Vec3>() = temp;
		}
	case eSST_Void:
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}
	return true;
}

void CScriptSerializeAny::PushFuncParam( IScriptSystem * pSS ) const
{
	switch (m_type)
	{
	case eSST_Bool:
		pSS->PushFuncParam( *Ptr<bool>() );
		break;
	case eSST_Float:
		pSS->PushFuncParam( *Ptr<float>() );
		break;
	case eSST_Int8:
		pSS->PushFuncParam( (int)*Ptr<int8>() );
		break;
	case eSST_Int16:
		pSS->PushFuncParam( (int)*Ptr<int16>() );
		break;
	case eSST_Int32:
		pSS->PushFuncParam( (int)*Ptr<int32>() );
		break;
	case eSST_StringTable:
	case eSST_String:
		pSS->PushFuncParam( Ptr<string>()->c_str() );
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			ScriptHandle temp;
			temp.n = *Ptr<EntityId>();
			pSS->PushFuncParam( temp );
		}
		break;
	case eSST_Vec3:
		pSS->PushFuncParam( *Ptr<Vec3>() );
		break;
	case eSST_Void:
		pSS->PushFuncParam( NULL );
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}
}

void CScriptSerializeAny::SerializeWith( TSerialize ser, const char * name )
{
#define SER_NAME(n) (name? name : n)

	switch (m_type)
	{
	case eSST_Bool:
		ser.Value( SER_NAME("bool"), *Ptr<bool>() );
		break;
	case eSST_Float:
		ser.Value( SER_NAME("float"), *Ptr<float>() );
		break;
	case eSST_Int8:
		ser.Value( SER_NAME("int8"), *Ptr<int8>() );
		break;
	case eSST_Int16:
		ser.Value( SER_NAME("int16"), *Ptr<int16>() );
		break;
	case eSST_Int32:
		ser.Value( SER_NAME("int32"), *Ptr<int32>() );
		break;
	case eSST_String:
		ser.Value( SER_NAME("string"), *Ptr<string>() );
		break;
	case eSST_StringTable:
		ser.Value( SER_NAME("string"), *Ptr<string>(), 'stab' );
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		ser.Value( SER_NAME("entityid"), *Ptr<EntityId>(), 'eid' );
		break;
	case eSST_Vec3:
		ser.Value( SER_NAME("vec3"), *Ptr<Vec3>() );
		break;
	case eSST_Void:
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}

#undef SER_NAME
}

bool CScriptSerializeAny::CopyFromTable( SmartScriptTable& tbl, const char * name )
{
	bool ok = false;

	switch (m_type)
	{
	case eSST_Bool:
		ok = tbl->GetValue( name, *Ptr<bool>() );
		break;
	case eSST_Float:
		ok = tbl->GetValue( name, *Ptr<float>() );
		break;
	case eSST_Int8:
		{
			int temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<int8>() = temp;
		}
		break;
	case eSST_Int16:
		{
			int temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<int16>() = temp;
		}
		break;
	case eSST_Int32:
		{
			int temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<int32>() = temp;
		}
		break;
	case eSST_StringTable:
	case eSST_String:
		{
			const char * temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<string>() = temp;
		}
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			ScriptHandle temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<EntityId>() = temp.n;
		}
		break;
	case eSST_Vec3:
		{
			Vec3 temp;
			ok = tbl->GetValue( name, temp );
			*Ptr<Vec3>() = temp;
		}
		break;
	case eSST_Void:
		ok = true;
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}

	if (!ok)
		GameWarning( "Unable to read property %s of type %c", name, (char)m_type );

	return ok;
}

void CScriptSerializeAny::CopyToTable( SmartScriptTable& tbl, const char * name )
{
	switch (m_type)
	{
	case eSST_Bool:
		tbl->SetValue( name, *Ptr<bool>() );
		break;
	case eSST_Float:
		tbl->SetValue( name, *Ptr<float>() );
		break;
	case eSST_Int8:
		tbl->SetValue( name, (int)*Ptr<int8>() );
		break;
	case eSST_Int16:
		tbl->SetValue( name, (int)*Ptr<int16>() );
		break;
	case eSST_Int32:
		tbl->SetValue( name, (int)*Ptr<int32>() );
		break;
	case eSST_StringTable:
	case eSST_String:
		tbl->SetValue( name, Ptr<string>()->c_str() );
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			ScriptHandle temp;
			temp.n = *Ptr<EntityId>();
			tbl->SetValue( name, temp );
		}
		break;
	case eSST_Vec3:
		tbl->SetValue( name, *Ptr<Vec3>() );
		break;
	case eSST_Void:
		tbl->SetToNull( name );
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}
}

string CScriptSerializeAny::DebugInfo() const
{
	string output;

	switch (m_type)
	{
	case eSST_Bool:
		output = *Ptr<bool>()? "true" : "false";
		break;
	case eSST_Float:
		output.Format( "%f", *Ptr<float>() );
		break;
	case eSST_Int8:
		output.Format( "%i", (int)*Ptr<int8>() );
		break;
	case eSST_Int16:
		output.Format( "%i", (int)*Ptr<int16>() );
		break;
	case eSST_Int32:
		output.Format( "%i", (int)*Ptr<int32>() );
		break;
	case eSST_StringTable:
	case eSST_String:
		output = *Ptr<string>();
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			EntityId id = *Ptr<EntityId>();
			IEntity * pEntity = gEnv->pEntitySystem->GetEntity(id);
			const char * name = pEntity? pEntity->GetName() : "<no such entity>";
			output.Format( "entity[%d]:%s", id, name );
		}
		break;
	case eSST_Vec3:
		{
			Vec3 temp = *Ptr<Vec3>();
			output.Format( "%f,%f,%f", temp.x, temp.y, temp.z );
		}
		break;
	case eSST_Void:
		output = "void";
		break;
	default:
		assert(!"need to add the type enum to this switch");
	}

	return output;
}

//
// CScriptRMISerialize
//

CScriptRMISerialize::CScriptRMISerialize( const char * format )
{
	while (*format)
	{
		m_values.push_back( (EScriptSerializeType)*format );
		format++;
	}
}

void CScriptRMISerialize::SerializeWith( TSerialize ser )
{
	for (TValueVec::iterator i = m_values.begin(); i != m_values.end(); ++i)
	{
		i->SerializeWith( ser );
	}
}

bool CScriptRMISerialize::SetFromFunction( 
		IFunctionHandler * pFH, 
		int firstParam )
{
	for (size_t i=0; i<m_values.size(); i++)
	{
		if (!m_values[i].SetFromFunction( pFH, firstParam+i ))
			return false;
	}
	return true;
}

void CScriptRMISerialize::PushFunctionParams( IScriptSystem * pSS )
{
	for (TValueVec::iterator i = m_values.begin(); i != m_values.end(); ++i)
	{
		i->PushFuncParam( pSS );
	}
}

string CScriptRMISerialize::DebugInfo()
{
	bool first = true;
	string out;
	for (TValueVec::iterator i = m_values.begin(); i != m_values.end(); ++i)
	{
		if (!first)
			out += ", ";
		out += i->DebugInfo();
		first = false;
	}
	return out;
}

//
// CScriptSerialize
//

bool CScriptSerialize::ReadValue( const char * name, EScriptSerializeType type, TSerialize ser, IScriptTable * pTable )
{
	switch (type)
	{
	case eSST_Bool:
		{
			bool temp;
			ser.Value( name, temp /*, 'bool'*/ );
			pTable->SetValue( name, temp );
		}
		break;
	case eSST_Float:
		{
			float temp;
			ser.Value( name, temp );
			pTable->SetValue( name, temp );
		}
		break;
	case eSST_Int8:
		{
			int8 temp;
			ser.Value( name, temp, 'i8' );
			pTable->SetValue( name, (int)temp );
		}
		break;
	case eSST_Int16:
		{
			int16 temp;
			ser.Value( name, temp, 'i16' );
			pTable->SetValue( name, (int)temp );
		}
		break;
	case eSST_Int32:
		{
			int32 temp;
			ser.Value( name, temp, 'i32' );
			pTable->SetValue( name, (int)temp );
		}
		break;
	case eSST_String:
		{
			ser.Value( name, m_tempString );
			pTable->SetValue( name, m_tempString.c_str() );
		}
		break;
	case eSST_StringTable:
		{
			ser.Value( name, m_tempString, 'stab' );
			pTable->SetValue( name, m_tempString.c_str() );
		}
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			EntityId id;
			ScriptHandle hdl;
			ser.Value( name, id, 'eid' );
			hdl.n = id;
			pTable->SetValue( name, hdl );
		}
		break;
	case eSST_Vec3:
		{
			Vec3 temp;
			ser.Value( name, temp );
			pTable->SetValue( name, temp );
		}
		break;
	default:
		assert(!"type not added to ReadValue");
		return false;
	}
	return true;
}

bool CScriptSerialize::WriteValue( const char * name, EScriptSerializeType type, TSerialize ser, IScriptTable * pTable )
{
	bool ok = false;
	switch (type)
	{
	case eSST_Bool:
		{
			bool temp = false;
			if (ok = pTable->GetValue( name, temp ))
				ser.Value( name, temp /*, 'bool'*/ );
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp );
				ok = true;
			}
		}
		break;
	case eSST_Float:
		{
			float temp = 0;
			if (ok = pTable->GetValue( name, temp ))
				ser.Value( name, temp );
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp );
				ok = true;
			}
		}
		break;
	case eSST_Int8:
		{
			int temp1;
			int8 temp2 = 0;
			if (ok = pTable->GetValue( name, temp1 ))
			{
				if (temp1 < -128 || temp1 > 127)
					ok = false;
				else
				{
					temp2 = temp1;
					ser.Value( name, temp2, 'i8' );
				}
			}
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp2, 'i8' );
				ok = true;
			}
		}
		break;
	case eSST_Int16:
		{
			int temp1;
			int16 temp2 = 0;
			if (ok = pTable->GetValue( name, temp1 ))
			{
				if (temp1 < -32768 || temp1 > 32767)
					ok = false;
				else
				{
					temp2 = temp1;
					ser.Value( name, temp2, 'i16' );
				}
			}
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp2, 'i16' );
				ok = true;
			}
		}
		break;
	case eSST_Int32:
		{
			int32 temp1;
			int temp2 = 0;
			if (ok = pTable->GetValue( name, temp1 ))
			{
				temp2 = temp1;
				ser.Value( name, temp2, 'i32' );
			}
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp2, 'i32' );
				ok = true;
			}
		}
		break;
	case eSST_StringTable:
	case eSST_String:
		{
			const char * temp;
			if (ok = pTable->GetValue( name, temp ))
			{
				m_tempString = temp;
				ser.Value( name, m_tempString );
			}
			else if (!pTable->HaveValue( name ))
			{
				m_tempString = string();
				ser.Value( name, m_tempString );
				ok = true;
			}
		}
		break;
	case eSST_DepEntityId:
	case eSST_EntityId:
		{
			EntityId id = 0;
			ScriptHandle hdl;
			if (ok = pTable->GetValue( name, hdl ))
			{
				id = hdl.n;
				ser.Value( name, id, 'eid' );
			}
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, id, 'eid' );
				ok = true;
			}
		}
		break;
	case eSST_Vec3:
		{
			Vec3 temp = ZERO;
			if (ok = pTable->GetValue( name, temp ))
			{
				ser.Value( name, temp );
			}
			else if (!pTable->HaveValue( name ))
			{
				ser.Value( name, temp );
				ok = true;
			}
		}
	default:
		assert(!"type not added to WriteValue");
		ok = false;
	}
	return ok;
}
