#ifndef ps_GameAttributes_H
#define ps_GameAttributes_H

#include "Player.h"

#include "scripting/SynchedJSObject.h"

class CNetServerSession;
class CGameAttributes;
class CPlayerSlot;

enum EPlayerSlotAssignment
{
	SLOT_CLOSED,
	SLOT_OPEN,
	SLOT_SESSION,
	SLOT_AI
};

typedef void (PlayerSlotAssignmentCB)(void *data, CPlayerSlot *);

class CPlayerSlot: public CJSObject<CPlayerSlot>
{
	int m_SlotID;
	EPlayerSlotAssignment m_Assignment;

	CNetServerSession *m_pSession;
	int m_SessionID;
	CPlayer *m_pPlayer;
	
	PlayerSlotAssignmentCB *m_Callback;
	void *m_CallbackData;
	
	bool JSI_AssignClosed(JSContext *cx, uintN argc, jsval *argv);
	
	// Assign to a session, takes one argument (a NetSession object)
	bool JSI_AssignToSession(JSContext *cx, uintN argc, jsval *argv);
	// Assign to the local player in SP or Server Player in MP
	bool JSI_AssignLocal(JSContext *cx, uintN argc, jsval *argv);
	
	bool JSI_AssignOpen(JSContext *cx, uintN argc, jsval *argv);

// TODO This will wait until there actually is AI to set up
//	bool JSI_AssignAI(JSContext *cx, uintN argc, jsval *argv);

	jsval JSI_GetSession(JSContext* cx);
	jsval JSI_GetAssignment(JSContext* cx);

	void CallCallback();
	void SetAssignment(EPlayerSlotAssignment, CNetServerSession *pSession, int sessionID);
	
	static void ScriptingInit();

protected:
	friend class CGameAttributes;
	inline void SetSlotID(int slotID)
	{	m_SlotID=slotID; }

public:
	CPlayerSlot(int slotID, CPlayer *pPlayer);
	~CPlayerSlot();
	
	inline CPlayer *GetPlayer()
	{	return m_pPlayer; }
	inline int GetSessionID()
	{	return m_SessionID; }
	inline int GetSlotID()
	{	return m_SlotID; }
	
	// Only applicable on the server host, and may return NULL if the slot
	// is not assigned to a server session.
	inline CNetServerSession *GetSession()
	{	return m_pSession; }
	
	
	inline void SetCallback(PlayerSlotAssignmentCB *callback, void *data)
	{
		m_Callback=callback;
		m_CallbackData=data;
	}
	
	inline EPlayerSlotAssignment GetAssignment()
	{	return m_Assignment; }

	// Reset any assignment the slot might have had before and mark the slot as
	// closed.
	void AssignClosed();

	// [Server] Assign the slot to a connected session
	void AssignToSession(CNetServerSession *pSession);

	// [Client] The slot has been assigned by the server to a session ID, mirror
	// the assignment
	void AssignToSessionID(int sessionID);

	// Reset any assignment the slot might have before and mark the slot as free
	void AssignOpen();

	// Assign to the local player in SP or Server Player in MP
	void AssignLocal();

// TODO This will wait until there actually is AI to set up
//	void AssignAI();
};

namespace PlayerSlotArray_JS
{
	JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );	
};

class CGameAttributes:
	public CSynchedJSObject<CGameAttributes>,
	public Singleton<CGameAttributes>
{
public:
	typedef void (UpdateCallback)(CStrW name, CStrW newValue, void *data);

	CStrW m_MapFile;
	CStrW m_ResourceLevel;
	CStrW m_StartingPhase;
	uint m_LOSSetting;

	// Note: we must use the un-internationalized name of the resource level and starting phase

private:
	friend JSBool PlayerSlotArray_JS::GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );

	uint m_NumSlots;
	
	// All players in the game. m_Players[0] is the Gaia Player, like in CGame.
	// m_Players[1..n] have a corresponding player slot in m_PlayerSlots[0..n-1]
	std::vector <CPlayer *> m_Players;

	std::vector <CPlayerSlot *> m_PlayerSlots;
	JSObject *m_PlayerSlotArrayJS;

	UpdateCallback *m_UpdateCB;
	void *m_UpdateCBData;
	
	CPlayer::UpdateCallback *m_PlayerUpdateCB;
	void *m_PlayerUpdateCBData;
	
	PlayerSlotAssignmentCB *m_PlayerSlotAssignmentCB;
	void *m_PlayerSlotAssignmentCBData;
	
	virtual void Update(CStrW name, ISynchedJSProperty *attrib);	
	static void OnNumSlotsUpdate(CSynchedJSObjectBase *owner);

	jsval JSI_GetPlayerSlots(JSContext* cx);
	jsval JSI_GetOpenSlot(JSContext *cx, uintN argc, jsval *argv);
	static void ScriptingInit();

public:
	CGameAttributes();
	virtual ~CGameAttributes();
	
	void SetValue(CStrW name, CStrW value);
	
	inline void SetUpdateCallback(UpdateCallback *cb, void *userdata)
	{
		m_UpdateCB=cb;
		m_UpdateCBData=userdata;
	}
	
	inline uint GetSlotCount()
	{	return m_NumSlots; }

	// Remove all slots that are either opened or closed, so that all slots have
	// an assignment and a player. Player IDs will be assigned in the same order
	// as the slot indexes, but without holes in the numbering.
	void FinalizeSlots();
	
	// Get the player object for the passed Player ID
	CPlayer *GetPlayer(int id);
	// Get the slot object with the specified index
	CPlayerSlot *GetSlot(int index);
	
	void SetPlayerUpdateCallback(CPlayer::UpdateCallback *cb, void *userdata);
	void SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCB *cb, void *userdata);
};
#define g_GameAttributes CGameAttributes::GetSingleton()

#endif
