// DOM-style event object
//
// Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com)

// Note: Cancellable [UK]? Cancelable [US]? DOM says one l, OED says 2.
//       JS interface uses 1.

// Entity and e.g. projectile classes derive from this and use it for
// sending/receiving events.

#ifndef DOMEVENT_INCLUDED
#define DOMEVENT_INCLUDED

#include "ScriptableObject.h"
#include "EventTypes.h"	// for EVENT_LAST

class CScriptObject;
class CScriptEvent;

typedef CScriptObject* DOMEventHandler;

class IEventTarget
{
	// Return 'true' if we should stop propagating.
	bool _DispatchEvent( CScriptEvent* evt, IEventTarget* target );

	// Events dispatched to this object are sent here before being processed.
	IEventTarget* before;
	// Events dispatched to this object are sent here after being processed.
	IEventTarget* after;

	typedef std::vector<DOMEventHandler> HandlerList;
	HandlerList m_Handlers_id[EVENT_LAST];
	typedef STL_HASH_MULTIMAP<CStrW, DOMEventHandler, CStrW_hash_compare> HandlerMap;
	HandlerMap m_Handlers_name;
	typedef std::pair<HandlerMap::iterator, HandlerMap::iterator> HandlerRange;
public:
	IEventTarget()
	{
		before = NULL;
		after = NULL;
	}
	virtual ~IEventTarget();
	// Set target that will receive each event after it is processed.
	// unused
	inline void SetPriorObject( IEventTarget* obj )
	{
		before = obj;
	}
	// Set target that will receive each event after it is processed.
	// used by Entity and EntityTemplate.
	inline void SetNextObject( IEventTarget* obj )
	{
		after = obj;
	}

	// Register a handler for the given event type.
	// Returns false if the handler was already present
	bool AddHandler( int TypeCode, DOMEventHandler handler );
	bool AddHandler( CStrW TypeString, DOMEventHandler handler );
	// Remove a previously registered handler for the specified event.
	// Returns false if the handler was not present
	bool RemoveHandler( int TypeCode, DOMEventHandler handler );
	bool RemoveHandler( CStrW TypeString, DOMEventHandler handler );

	// called by ScriptGlue.cpp for add|RemoveGlobalHandler
	bool AddHandlerJS( JSContext* cx, uintN argc, jsval* argv );
	bool RemoveHandlerJS( JSContext* cx, uintN argc, jsval* argv );

	// Return the JSObject* we'd like to be the 'this' object
	// when executing the handler. The argument is the object
	// to which the event is targeted.
	// It is passed to CScriptObject::DispatchEvent.
	virtual JSObject* GetScriptExecContext( IEventTarget* target ) = 0;

	// Dispatch an event to its handler.
	// returns: whether the event arrived (i.e. wasn't cancelled) [bool]
	bool DispatchEvent( CScriptEvent* evt );
};

class CScriptEvent : public CJSObject<CScriptEvent>
{
public:
	enum EPhaseType
	{
		CAPTURING_PHASE = 1,
		AT_TARGET = 2,
		BUBBLING_PHASE = 3
	};

	// Target (currently unused)
	IEventTarget* m_Target;

	// Listening object currently being processed (currently unused)
	IEventTarget* m_CurrentTarget;

	// Phase type (currently unused)
	// EPhaseType m_EventPhase;

	// Can bubble? (currently unused)
	// bool m_Bubbles;

	// Can be cancelled (default actions prevented)
	bool m_Cancelable;

	// Can be blocked (prevented from propogating along the handler chain)
	bool m_Blockable;

	// Timestamp (milliseconds since epoch (start of game?))
	i32 m_Timestamp;

	// Event type string
	CStrW m_Type;

	// Type code (to speed lookups)
	unsigned int m_TypeCode;

	// Has been cancelled?
	bool m_Cancelled;

	// Has it been blocked (won't be sent to any more handlers)
	bool m_Blocked;

// -- 

	jsval ToString( JSContext* cx, uintN argc, jsval* argv );
	jsval PreventDefault( JSContext* cx, uintN argc, jsval* argv );
	jsval StopPropagation( JSContext* cx, uintN argc, jsval* argv );

public:
	CScriptEvent( const CStrW& Type, unsigned int TypeCode = (unsigned int)-1, bool Cancelable = true, bool Blockable = true );
	static void ScriptingInit();
};

#endif

