#include "precompiled.h"

#include "JSInterface_Entity.h"
#include "JSInterface_BaseEntity.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "simulation/EntityHandles.h"
#include "simulation/Entity.h"
#include "simulation/EntityManager.h"
#include "simulation/BaseEntityCollection.h"
#include "ps/CConsole.h"
#include "simulation/Scheduler.h"

/*
JSClass JSI_Entity::JSI_class = {
	"Entity", JSCLASS_HAS_PRIVATE,
	JS_PropertyStub, JS_PropertyStub,
	JSI_Entity::getProperty, JSI_Entity::setProperty,
	JS_EnumerateStub, JS_ResolveStub,
	JS_ConvertStub, JSI_Entity::finalize,
	NULL, NULL, NULL, NULL 
};

JSPropertySpec JSI_Entity::JSI_props[] = 
{
	{ 0 }
};

JSFunctionSpec JSI_Entity::JSI_methods[] = 
{
	{ "toString", JSI_Entity::toString, 0, 0, 0 },
	{ "order", JSI_Entity::orderSingle, 1, 0, 0 },
	{ "orderQueued", JSI_Entity::orderQueued, 1, 0, 0 },
	{ 0 }
};

JSBool JSI_Entity::getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
	HEntity* e = (HEntity*)JS_GetPrivate( cx, obj );
	if( !e )
	{
		*vp = JSVAL_NULL;
		return( JS_TRUE );
	}
	CStrW propName = g_ScriptingHost.ValueToUCString( id );
	
	if( (*e)->m_properties.find( propName ) != (*e)->m_properties.end() )
	{
		*vp = (*e)->m_properties[propName]->tojsval();
		return( JS_TRUE );
	}
	else
	{
		// Don't show the message if we're using a function defined by the engine.
		JSFunctionSpec* fnSpec = JSI_Entity::JSI_methods;
		while( fnSpec->name )
		{
			if( CStr8( propName ) == CStr8( fnSpec->name ) )
				return( JS_TRUE );
			fnSpec++;
		}
		JS_ReportError( cx, "No such property on %s: %s", CStr8((*e)->m_name).c_str(), CStr8(propName).c_str() );
		return( JS_TRUE );
	}
}

JSBool JSI_Entity::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
	HEntity* e = (HEntity*)JS_GetPrivate( cx, obj );
	CStrW propName = g_ScriptingHost.ValueToUCString( id );
	
	if( (*e)->m_properties.find( propName ) != (*e)->m_properties.end() )
	{
		(*e)->m_properties[propName]->fromjsval( *vp );
		(*e)->rebuild( propName );
	}
	else
	{
		(*e)->addProperty( propName, *vp );
	}
	return( JS_TRUE );
}

JSBool JSI_Entity::construct( JSContext* cx, JSObject* obj, uint argc, jsval* argv, jsval* rval )
{
	debug_assert( argc >= 2 );
	CBaseEntity* baseEntity = NULL;
	CVector3D position;
	float orientation = 0.0f;
	JSObject* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] );
	CStrW templateName;

	if( !JSVAL_IS_OBJECT( argv[0] ) || !( baseEntity = CBaseEntity::GetNative( cx, jsBaseEntity ) ) )
	{	
		try
		{
			templateName = g_ScriptingHost.ValueToUCString( argv[0] );
		}
		catch( PSERROR_Scripting_ConversionFailed )
		{
			*rval = JSVAL_NULL;
			JS_ReportError( cx, "Invalid template identifier" );
			return( JS_TRUE );
		}
		baseEntity = g_EntityTemplateCollection.getTemplate( templateName );
	}
	if( !baseEntity )
	{
		*rval = JSVAL_NULL;
		JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() );
		return( JS_TRUE );
	}
	JSI_Vector3D::Vector3D_Info* jsVector3D = NULL;
	if( JSVAL_IS_OBJECT( argv[1] ) && ( jsVector3D = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( argv[1] ), &JSI_Vector3D::JSI_class, NULL ) ) )
	{
		position = *( jsVector3D->vector );
	}
	if( argc >= 3 )
	{
		try
		{
			orientation = ToPrimitive<float>( argv[2] );
		}
		catch( PSERROR_Scripting_ConversionFailed )
		{
			// TODO: Net-safe random for this parameter.
			orientation = 0.0f;
		}
	}

	HEntity* handle = new HEntity( g_EntityManager.create( baseEntity, position, orientation ) );
	CMessage message( CMessage::EMSG_INIT );
	(*handle)->dispatch( &message );
	JSObject* entity = JS_NewObject( cx, &JSI_Entity::JSI_class, NULL, NULL );
	JS_SetPrivate( cx, entity, handle );
	*rval = OBJECT_TO_JSVAL( entity );
	return( JS_TRUE );
}

void JSI_Entity::finalize( JSContext* cx, JSObject* obj )
{
	JSClass* DebugInfo = JS_GetClass( obj );
	delete( (HEntity*)JS_GetPrivate( cx, obj ) );
}

void JSI_Entity::init()
{
	g_ScriptingHost.DefineCustomObjectType( &JSI_class, construct, 2, JSI_props, JSI_methods, NULL, NULL );
}

JSBool JSI_Entity::toString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	HEntity* e = (HEntity*)JS_GetPrivate( cx, obj );

	wchar_t buffer[256];
	swprintf( buffer, 256, L"[object Entity: \"%ls\" (%ls)]", (*e)->m_name.c_str(), (*e)->m_base->m_name.c_str() );
	buffer[255] = 0;
	utf16_t utfbuf[256];
	std::copy(buffer, buffer+256, utfbuf);
	*rval = STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, utfbuf ) );
	return( JS_TRUE );
}

JSBool JSI_Entity::orderSingle( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	return( order( cx, obj, argc, argv, rval, false ) );
}

JSBool JSI_Entity::orderQueued( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	return( order( cx, obj, argc, argv, rval, true ) );
}

JSBool JSI_Entity::order( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval, bool queueOrder )
{
	HEntity* e = (HEntity*)JS_GetPrivate( cx, obj );

	// This needs to be sorted (uses Scheduler rather than network messaging)
	debug_assert( argc >= 1 );

	int orderCode;

	try
	{
		orderCode = ToPrimitive<int>( argv[0] );
	}
	catch( PSERROR_Scripting_ConversionFailed )
	{
		*rval = JSVAL_FALSE;
		JS_ReportError( cx, "Invalid order type" );
		return( JS_TRUE );
	}

	CEntityOrder newOrder;

	(int&)newOrder.m_type = orderCode;

	switch( orderCode )
	{
	case CEntityOrder::ORDER_GOTO:
	case CEntityOrder::ORDER_PATROL:
		if( argc < 3 )
		{
			*rval = JSVAL_FALSE;
			JS_ReportError( cx, "Too few parameters" );
			return( JS_TRUE );
		}
		try
		{
			newOrder.m_data[0].location.x = g_ScriptingHost.ValueToDouble( argv[1] );
			newOrder.m_data[0].location.y = g_ScriptingHost.ValueToDouble( argv[2] );
		}
		catch( PSERROR_Scripting_ConversionFailed )
		{
			*rval = JSVAL_FALSE;
			JS_ReportError( cx, "Invalid location" );
			return( JS_TRUE );
		}
		g_Scheduler.pushFrame( ORDER_DELAY, (*e)->me, new CMessageOrder( newOrder, queueOrder ) );
		*rval = JSVAL_TRUE;
		return( JS_TRUE );
	default:
		*rval = JSVAL_FALSE;
		JS_ReportError( cx, "Invalid order type" );
		return( JS_TRUE );
	}
}

*/
