// JSCollection.h
//
// A Collection object for JavaScript to hold one specific type of 
// object.

#include "scripting/ScriptingHost.h"
#include "simulation/ScriptObject.h"
#include "scripting/JSConversions.h"

#ifndef JS_COLLECTION_INCLUDED
#define JS_COLLECTION_INCLUDED

template<typename T, JSClass* ScriptType> class CJSCollection
{
public:
	class CJSCollectionData
	{
	public:
		std::vector<T>* m_Data;
		bool m_EngineOwned;
		CJSCollectionData() { m_Data = new std::vector<T>; m_EngineOwned = false; }
		CJSCollectionData( const std::vector<T>& Copy ) { m_Data = new std::vector<T>( Copy ); m_EngineOwned = false; }
		CJSCollectionData( std::vector<T>* Reference ) { m_Data = Reference; m_EngineOwned = true; }
		~CJSCollectionData() { if( !m_EngineOwned ) delete( m_Data ); }
	};
	static JSClass JSI_class;
private:
	
	static JSPropertySpec JSI_props[];
	static JSFunctionSpec JSI_methods[];

	static JSBool ToString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
	static JSBool Subset( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
	static JSBool Push( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
	static JSBool Pop( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
	static JSBool Remove( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
	static JSBool GetLength( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static JSBool IsEmpty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static JSBool Clear( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
	static JSBool Equals( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
	static JSBool AddProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static JSBool RemoveProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static JSBool SetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
	static void Finalize( JSContext* cx, JSObject* obj );
	static bool GetNative( jsval m, T& Storage );

public:
	static void Init( const char* ClassName );
	static JSObject* Create( const std::vector<T>& Copy );
	static JSObject* CreateReference( std::vector<T>* Reference );
	static std::vector<T>* RetrieveSet( JSContext* cx, JSObject* obj );
};

template<typename T, JSClass* ScriptType> JSClass CJSCollection<T, ScriptType>::JSI_class =
{
	0, JSCLASS_HAS_PRIVATE,
	AddProperty, RemoveProperty,
	GetProperty, SetProperty,
	JS_EnumerateStub, JS_ResolveStub,
	JS_ConvertStub, Finalize,
	NULL, NULL, NULL, NULL
};

template<typename T, JSClass* ScriptType> JSPropertySpec CJSCollection<T, ScriptType>::JSI_props[] =
{
	{ "length", 0, JSPROP_PERMANENT | JSPROP_READONLY, GetLength },
	{ "empty", 0, JSPROP_PERMANENT | JSPROP_READONLY, IsEmpty },
	{ 0 }
};

template<typename T, JSClass* ScriptType> JSFunctionSpec CJSCollection<T, ScriptType>::JSI_methods[] = 
{
	{ "toString", ToString, 0, 0, 0 },
	{ "subset", Subset, 1, 0, 0 },
	{ "push", Push, 1, 0, 0 },
	{ "pop", Pop, 0, 0, 0 },
	{ "remove", Remove, 1, 0, 0 },
	{ "clear", Clear, 0, 0, 0 },
	{ "equals", Equals, 1, 0, 0 },
	{ 0 },
};

template<typename T, JSClass* ScriptType> std::vector<T>* CJSCollection<T, ScriptType>::RetrieveSet( JSContext* cx, JSObject* obj )
{
	CJSCollectionData* Info = (CJSCollectionData*)JS_GetInstancePrivate( cx, obj, &JSI_class, NULL );
	if( !Info ) return( NULL );
	return( Info->m_Data );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::AddProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
	if( !JSVAL_IS_INT( id ) )
		return( JS_TRUE ); // Accessing a named property; nothing interesting.
	 
	int index = JSVAL_TO_INT( id ); 

	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_TRUE ); // That's odd; we've lost the pointer.

	if( ( index >= 0 ) && ( index < (int)set->size() ) )
		return( JS_TRUE );

	if( index != (int)set->size() )
	{
		// If you add something to the collection, it must be at the
		// next empty array element; i.e. set->size()
		JS_ReportError( cx, "Cannot create a property at that subscript" );
		return( JS_FALSE );
	}
	
	T m;

	if( !GetNative( *vp, m ) ) 
		return( JS_TRUE );

	set->resize( index + 1 );
	set->at( index ) = m;

	return( JS_TRUE );
}


template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::RemoveProperty (
	JSContext* cx, JSObject* obj, jsval id, jsval* UNUSED(vp) )
{
	if( !JSVAL_IS_INT( id ) )
		return( JS_TRUE ); // Accessing a named property; nothing interesting.
	 
	int index = JSVAL_TO_INT( id ); 

	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_TRUE ); // That's odd; we've lost the pointer.

	if( ( index < 0 ) || ( index >= (int)set->size() ) )
	{
		JS_ReportError( cx, "Index out of bounds." );
		return( JS_TRUE );
	}

	set->erase( set->begin() + index );

	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::GetProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
	if( !JSVAL_IS_INT( id ) )
		return( JS_TRUE ); // Accessing a named property; nothing interesting.
	 
	int index = JSVAL_TO_INT( id ); 
	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_FALSE ); // That's odd; we've lost the pointer.

	if( ( index < 0 ) || ( index >= (int)set->size() ) )
	{
		JS_ReportError( cx, "Index out of bounds." );
		return( JS_TRUE );
	}

	*vp = ToJSVal( set->at( index ) );
	
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::SetProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
	if( !JSVAL_IS_INT( id ) )
		return( JS_TRUE ); // Accessing a named property; nothing interesting.
	 
	int index = JSVAL_TO_INT( id ); 

	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_FALSE ); // That's odd; we've lost the pointer.

	if( ( index < 0 ) || ( index >= (int)set->size() ) )
	{
		JS_ReportError( cx, "Index out of bounds." );
		return( JS_TRUE );
	}

	T m;

	if( !GetNative( *vp, m ) ) 
		return( JS_TRUE );

	set->at( index ) = m;
	
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> void CJSCollection<T, ScriptType>::Finalize ( JSContext* cx, JSObject* obj )
{
	CJSCollectionData* info = (CJSCollectionData*)JS_GetPrivate( cx, obj );
	if( info )
		delete( info );
}

template<typename T, JSClass* ScriptType> JSObject* CJSCollection<T, ScriptType>::Create( const std::vector<T>& Copy )
{	
	JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
	JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, new CJSCollectionData( Copy ) );

	return( Collection );
}

template<typename T, JSClass* ScriptType> JSObject* CJSCollection<T, ScriptType>::CreateReference( std::vector<T>* Reference )
{
	JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
	JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, new CJSCollectionData( Reference ) );

	return( Collection );
}

template<typename T, JSClass* ScriptType> void CJSCollection<T, ScriptType>::Init( const char* ClassName )
{
	JSI_class.name = ClassName;
	g_ScriptingHost.DefineCustomObjectType( &JSI_class, NULL, 0, JSI_props, JSI_methods, NULL, NULL );	
}

template<typename T, JSClass* ScriptType> bool CJSCollection<T, ScriptType>::GetNative( jsval m, T& Storage )
{
	if( ToPrimitive( g_ScriptingHost.GetContext(), m, Storage ) )
		return( true );

	JS_ReportError( g_ScriptingHost.GetContext(), "Only objects of type %s can be stored in this collection.", ScriptType->name );
	return( false );
}


template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::ToString(
	JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_FALSE ); // That's odd; we've lost the pointer.

	wchar_t buffer[256];
	int len=swprintf( buffer, 256, L"[object Collection: %hs: %d members]", ScriptType->name, set->size() );
	buffer[255] = 0;
	if (len < 0 || len > 255) len=255;
	utf16string u16str(buffer, buffer+len);
	*rval = STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, u16str.c_str() ) );
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::GetLength(
	JSContext* cx, JSObject* obj, jsval UNUSED(id), jsval* vp )
{
	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_FALSE ); // That's odd; we've lost the pointer.

	*vp = INT_TO_JSVAL( set->size() );
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::IsEmpty(
	JSContext* cx, JSObject* obj, jsval UNUSED(id), jsval* vp )
{
	std::vector<T>* set = RetrieveSet( cx, obj );

	if( !set )
		return( JS_FALSE ); // That's odd; we've lost the pointer.

	*vp = BOOLEAN_TO_JSVAL( set->empty() );
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Equals( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	std::vector<T>* a = RetrieveSet( cx, obj );
	if( !a )
		return( JS_FALSE );
	if( ( argc == 0 ) || ( !JSVAL_IS_OBJECT( argv[0] ) ) ) return( JS_FALSE );
	std::vector<T>* b = RetrieveSet( cx, JSVAL_TO_OBJECT( argv[0] ) );
	if( !b )
		return( JS_FALSE );
	typename std::vector<T>::iterator ita, itb;
	
	size_t seek = a->size();
	for( ita = a->begin(); ita != a->end(); ita++ )
		for( itb = b->begin(); itb != b->end(); itb++ )
			if( *ita == *itb ) { seek--; break; }
		
	if( seek )
	{
		*rval = JSVAL_FALSE;
		return( JS_TRUE );
	}

	seek = b->size();
	for( itb = b->begin(); itb != b->end(); itb++ )
		for( ita = a->begin(); ita != a->end(); ita++ )
			if( *ita == *itb ) { seek--; break; }
		
	if( seek )
	{
		*rval = JSVAL_FALSE;
		return( JS_TRUE );
	}

	*rval = JSVAL_TRUE;
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Subset(
	JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	debug_assert( argc > 0 );

	std::vector<T>* Set = RetrieveSet( cx, obj );
	if( !Set )
		return( JS_FALSE );

	CJSCollectionData* CollectionData = new CJSCollectionData();
	CollectionData->m_EngineOwned = false;

	typename std::vector<T>::iterator it;

	CScriptObject Predicate( argv[0] );

	JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
	JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, CollectionData );

	for( it = Set->begin(); it != Set->end(); it++ )
		if( Predicate.Run( ToScript( (T*)&( *it ) ) ) )
			CollectionData->m_Data->push_back( *it );

	*rval = OBJECT_TO_JSVAL( Collection );

	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Clear(
	JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
	std::vector<T>* Set = RetrieveSet( cx, obj );
	if( !Set )
		return( JS_FALSE );

	Set->clear();

	*rval = JSVAL_TRUE;

	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Push( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
	debug_assert( argc > 0 );

	std::vector<T>* Set = RetrieveSet( cx, obj );
	if( !Set )
		return( JS_FALSE );

	for( int i = 0; i < (int)argc; i++ )
	{
		T m;
		if( GetNative( argv[i], m ) )
			Set->push_back( m );
	}

	*rval = INT_TO_JSVAL( Set->size() );
	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Pop(
	JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
	std::vector<T>* Set = RetrieveSet( cx, obj );
	if( !Set )
		return( JS_FALSE );

	if( !Set->size() )
	{
		JS_ReportError( cx, "Can't pop members from an empty Collection." );
		return( JS_TRUE );
	}

	*rval = ToJSVal( Set->back() );
	Set->pop_back();

	return( JS_TRUE );
}

template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Remove(
	JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* UNUSED(rval) )
{
	debug_assert( argc > 0 );

	
	std::vector<T>* Set = RetrieveSet( cx, obj );

	if( !Set )
		return( JS_TRUE ); // That's odd; we've lost the pointer.

	int index;
	try
	{
		index = ToPrimitive<int>( argv[0] );
	}
	catch( PSERROR_Scripting_ConversionFailed )
	{
		JS_ReportError( cx, "Index must be an integer." );
		return( JS_TRUE );
	}

	if( ( index < 0 ) || ( index >= (int)Set->size() ) )
	{
		JS_ReportError( cx, "Index out of bounds." );
		return( JS_TRUE );
	}

	Set->erase( Set->begin() + index );

	return( JS_TRUE );
}

#define EntityCollection CJSCollection<HEntity, &CEntity::JSI_class>
#define PlayerCollection CJSCollection<CPlayer*, &CPlayer::JSI_class>
#define JSObjectCollection
#endif
