/*
	ZMessageStream.hpp
	Author: James Russell <jcrussell@762studios.com>
	Created: 2/4/2013

	Purpose: 

	Messaging stream class, which acts as an event loop for the entire simulation.  Processes both
	untargeted (or 'simulation' targeted) messages and messages that are intended for a specific entity.
	Handles organization and sorting so that the messages can be processed in a thread safe fashion at
	a later time.

	License:

	Copyright 2013, 762 Studios

*/

#pragma once

#ifndef _ZMESSAGESTREAM_HPP
#define _ZMESSAGESTREAM_HPP

#include <ZUtil/ZConcurrency.hpp>
#include <ZUtil/ZSlabAllocator.hpp>

#include "ZSimulationDefs.hpp"

//Forward Declarations
class ZSimulation;

/*
Message structure.  Messages are in the form of a POD struct of a set size 
payload.  Message layout definitions (see ZSimulationDefs.hpp for examples) 
can be used to interpret the data.
*/
struct ZMessage {
	mID	 Type;		// type of the message
	eID	 Sender;	// the entity who sent the message (ZSIM_EID_SYSTEM means system or simulation)
	eID	 Target;	// the target of the message (ZSIM_EID_SYSTEM calls the default handler)

	char Payload[ZSIM_MESSAGE_SIZE];	// the message payload, which can be interpreted via layout definitions
};

/*
Message handler function profile.
*/
typedef void (*ZMessageHandler)(eID _sender, eID _target, void* _payload, ZSimulation& _sim);

/*
Message streaming class, used to send messages from one entity to another or to the simulation.
*/
class ZMessageStream
{
public:
	// c'tor
	ZMessageStream();

	// d'tor
	~ZMessageStream();
	
	// message 'Send' method, which will copy data from a struct into the message (MT must be a POD type)
	template <typename MT>
	void Send(mID _type, eID _sender, eID _target, MT _payload)
	{
		ZMessage* msg;

		#if SST_COMPILER == SST_COMPILER_MSVC
		#pragma warning(push)
		#pragma warning(disable:4127)
		#endif

		ZASSERT(sizeof(MT) <= ZSIM_MESSAGE_SIZE, "Message sent with payload too large!");

		#if SST_COMPILER == SST_COMPILER_MSVC
		#pragma warning(pop)
		#endif

		// Synchronized Section
		{
			ZLock lock(AllocatorMutex);

			msg = MessageAllocator.Allocate();
		}

		ZASSERT(msg != NULL, "Unable to allocate message!");
		
		msg->Type = _type;
		msg->Sender = _sender;
		msg->Target = _target;

		MemCopy(uint8_t, msg->Payload, &_payload, sizeof(MT));

		// Synchronized Section
		{
			ZLock lock(BufferMutex);

			MessageBuffers[CurrentBufferIndex].PushBack(msg);
		}
	}

	/*
	Pumps the message queue, processing the messages that have been sent since last time this method was 
	called.  Returns true if additional messages were generated during the processing of the current set
	of messages.

	Messages that are targeted at a specific entity will be delivered to that entity.  If that entity cannot
	be found, the message is dropped.  Messages that target the reserved eID of '0' will be handled by the 
	message stream handlers.

	Only one thread should ever be calling this method.  Multiple threads calling this method will result
	in that thread waiting until the previous has finished.

	Returns true if messages are still available for processing, false if the current queue is empty.
	*/
	bool ProcessMessages(ZSimulation& _sim);

	/*
	External message handlers.  Used to return an entity targeted message or process a targeted 
	message that had no installed handler.
	*/
	void HandleMessage(ZMessage* _msg, ZSimulation& _sim);
	void ReturnMessage(ZMessage* _msg);

	/*
	Push and Pop Methods for Handlers.
	*/
	void			PushHandler(mID _type, ZMessageHandler _handler);
	ZMessageHandler	PopHandler(mID _type);

protected:

	ZMutex AllocatorMutex;		// lock for allocators
	ZMutex BufferMutex;			// lock for buffers
	ZMutex HandlerMutex;		// lock for handlers
	ZMutex ProcessingMutex;		// lock for processing

	int	   CurrentBufferIndex;	// index to our current message buffer

	ZRingBuffer<ZMessage*>			MessageBuffers[2];	// the current set of messages (one receives messages while the other is processed)
	ZHashMap<mID, 
		ZArray<ZMessageHandler>>	MessageHandlers;	// set of handler functions

	ZSlabAllocator<ZMessage, 
				   ZSIM_MESSAGE_ALLOC>	MessageAllocator;	// slab allocator for messages


private:
	DISABLE_COPY_AND_ASSIGN(ZMessageStream);
};

#endif
 
