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

	Purpose: 

	Simulation base class for the ZSimulation project.  Simulation is intended to be a server
	that handles the simulation of a large number of interacting entities.  Entities will 
	interact with each other via a messaging queue which will have handlers defined per 
	message type.

	Per this simulation, Actors are simply Entities with a 'think' function defined.

	The nominal difference between server and client instances is that client instances
	can only ever connect to a single server instance, whereas server instances listen
	for client connections and can connect to other server instances to share the 
	workload.

	License:

	Copyright 2013, 762 Studios
*/

#pragma once

#ifndef ZSIMULATION_HPP
#define ZSIMULATION_HPP

#include <SST/SST_Concurrency.h>

#include <ZSTL/ZArray.hpp>
#include <ZSTL/ZArrayAlgo.hpp>

#include <ZUtil/ZUtil.hpp>

#include "ZSimulationDefs.hpp"
#include "ZMessageStream.hpp"
#include "ZEntity.hpp"
#include "ZPropertyBuffer.hpp"
#include "ZNetworkService.hpp"

/*
Simulation class, which handles the following sphere of responsibilities:

  * Fixed Timestep and Entity Update
  * Entity Message Passing
  * Entity Lifetime and Memory Management
  * Entity Property Allocation
  * Entity Property Network Synchronization
  * Entity Type Declaration
  * Entity State Declaration
*/
class ZSimulation : public ZThread
{
public:
	// c'tor
	ZSimulation();

	// d'tor
	virtual ~ZSimulation();

	// initialization methods
	bool InitAsServer(bool start = true);
	bool InitAsClient(bool start = true);

	// shutdown method (this will deallocate entities)
	void Shutdown();

	// getters
	uint32_t			  GetCurrentEntityCount() const		{ return CurrentEntityCount; }
	const ZEntity*		  GetEntity(eID entityId) const		{ return Entities[entityId]; }
	ZEntity*			  GetEntity(eID entityId)			{ return Entities[entityId]; }
	ZArray<ZEntity*>&	  GetBaseEntities()					{ return BaseEntities; }
	ZArray<ZEntity*>&	  GetActorEntities()				{ return ActorEntities; }
	uint64_t			  GetCurrentFrame() const			{ return CurrentFrame; }
	double				  GetCurrentFrameStartTime() const	{ return CurrentFrameStartTime; }
	int16_t				  GetSimulationHz() const			{ return SimulationHz; }
	double				  GetSimulationTimestep() const		{ return 1.0 / (double)SimulationHz; }
	size_t				  GetSimulationThreadCount() const	{ return ThreadCount; }
	ZPropertyBuffer&	  GetPropertyBuffer()				{ return PropertyBuffer; }
	ZRandomGenerator&	  GetPRNG()							{ return PRNG; }
	uint16_t			  GetListenPort()					{ return ListenPort; }
	uint8_t				  GetSyncRate()						{ return SyncRate; }
	ZNetworkService&	  GetNetworkService()				{ return NetworkService; }
	ZNetworkUpdateStream& GetNetworkUpdateStream()			{ return NetworkUpdateStream; }
	uint8_t				  GetNetworkSendRate()				{ return NetSendRate; }
	ZString&			  GetNetworkName()					{ return NetName; }
	bool				  IsServer()						{ return bIsServer; }

	// function getters (can return NULL)
	ZEntity::InitFunc	  GetEntityInitFunc(const ZName& type);
	ZEntity::InitFunc	  GetEntityInitFunc(ZHashValue type);
	ZEntity::StateFunc	  GetEntityStateFunc(const ZName& name);
	ZEntity::StateFunc	  GetEntityStateFunc(ZHashValue name);

	// setters
	void SetHz(int16_t simHz)					{ SimulationHz = simHz; }
	void SetThreadCount(int16_t threadCount)	{ ThreadCount = threadCount; }
	void SetListenPort(uint16_t port)			{ ListenPort = port; }
	void SetSyncRate(uint8_t rate)				{ SyncRate = rate; }
	void SetNetworkName(const ZString& name)	{ NetName = name; }

	/*
	Connection method.  Connects to another simulation instance.  Returns the network request
	object which can be used to check status of the connection attempt.  Delete the request
	when done.
	*/
	ZNetworkRequest* Connect(const char* remoteHost);

	/*
	Declares an entity and initialization function.  All entity initialization functions
	must be declared in order to ensure proper allocation of properties on all simulations, 
	client and server.

	It is recommended that all mapping be done during simulation initialization - no locking
	or concurrent access mechanisms are used when looking up mapped data.

	The initialization function can be NULL, which will create an empty entity.

	Ensure when declaring entity types that the name provided has its string representation.

	@assert - on name collision (name hash resolves to same or entry already exists)
	*/
	void DeclareEntityType(const ZName& type, ZEntity::InitFunc init);
	
	/*
	Declares an entity state function.  Entity states are looked up by name to ensure recognition
	on all simulations, client and server.

	It is recommended that all state mapping be done during simulation initialization - no locking
	or concurrent access mechanisms are used when looking up mapped data.

	State functions cannot be NULL.

	@assert - on name collision (name hash resolves to same or entry already exists)
	*/
	void DeclareEntityState(const ZName& name, ZEntity::StateFunc state);
	
	/*
	Given the name, gives a string name for an entity type or state.  These exist because names 
	can be constructed without a string representation (such as when used for network communication).
	*/
	ZString LookupEntityTypeName(const ZName& type);
	ZString LookupEntityStateName(const ZName& name);
	
	/*
	Spawn methods which add an entity to the simulation, returning the eID (0 if failure).

	Variant which takes an eID request that an entity with a specific eID be added (returns 
	false if unable to create).

	@assert - if the entity type is undefined
	*/
	eID	 SpawnEntity(const ZName& type);
	bool SpawnEntity(const ZName& type, eID id);

	/*
	Kill method, which removes entities from the simulation.  This call cleans up the entity and any 
	memory it has allocated.
	*/
	void KillEntity(eID id);

	/*
	Sends a message through the simulation message queue, which will copy data from 
	a struct into the message for you (MT must be a POD type).  Local messages are not
	reflected across the network.
	*/
	template <typename MT> void SendLocalMessage(mID type, MT payload) const 
		{ const_cast<ZSimulation*>(this)->MessageQueue.Send(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload);	}

	template <typename MT> void SendLocalMessage(mID type, eID sender, eID target, MT payload) const
		{ const_cast<ZSimulation*>(this)->MessageQueue.Send(type, sender, target, payload); }

	/*
	Sends a message across the network, which will be processed by the message queue of 
	another connected simulation.  MT must be POD type and in order to ensure proper
	processing it should not contain pointers as these will be invalid on the other side.
	*/
	template <typename MT> void SendNetworkMessage(mID type, MT payload, cID connection) const
		{ const_cast<ZSimulation*>(this)->NetworkUpdateStream.EnqueueOutgoingEvent(new (std::nothrow) ZMessageEvent(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload, connection)); }
	
	template <typename MT> void SendNetworkMessage(mID type, eID sender, eID target, MT payload, cID connection) const
		{ const_cast<ZSimulation*>(this)->NetworkUpdateStream.EnqueueOutgoingEvent(new (std::nothrow) ZMessageEvent(type, sender, target, payload, connection)); }

	/*
	Pushes a task onto the task stream.  This task will be executed by the simulation after 
	simulation tasks have completed.
	*/
	void PushTask(ZPtr<ZTask> task);
	void PushTasks(ZArray<ZPtr<ZTask>>& tasks);

	/*
	Pushes a future onto the task stream, which can be used to evaluate task progress.
	*/
	template <typename RT, typename AT>	void PushFuture(ZPtr<ZFuture<RT, AT>> future) {
		TaskStream.PushFuture(future);
	}
	
	/*
	Pushes or Pops a message handler onto the simulation.  These message handlers are used for
	messages that are directed at the simulation or for messages that do not have a valid 
	target entity.
	*/
	void PopHandler(mID type);
	void PushHandler(mID type, ZMessageHandler handler);

	/*
	These methods notify the simulation that a targeted message was handled correctly or was 
	unhandled.  The simulation will return these messages to the message pool.  These functions
	are primarily used by entities as they handle their own message queues.
	*/
	void HandledMessage(ZMessage* msg) const;
	void UnhandledMessage(ZMessage* msg) const;

	/*
	This method notifies the simulation that an entity has had one or more of it's properties 
	deleted.  It's not terribly efficient to delete entity properties - use sparingly.
	*/
	void PropertyDeleted(eID id);

protected:
	int16_t				 SimulationHz;					// current simulation Hz (tick rate per second)
	size_t				 ThreadCount;					// number of concurrent simulation threads

	double				 CurrentTime;					// current tick count (most recent)
	double				 TotalTime;						// total number of ticks that have been processed
	double				 Accumulator;					// tick accumulator, used to help fix the timestep
	double				 LastSync;						// the time we last did a network sync

	uint64_t			 CurrentFrame;					// number of simulation frames that have passed
	double				 CurrentFrameStartTime;			// time at the start of the current frame

	uint32_t			 CurrentEntityCount;			// number of entities we have spawned
	size_t				 CurrentEntityIndex;			// next entity we will hand out for simulation entities
	ZEntity*			 Entities[ZSIM_EID_MAX + 1];	// array mapping of entity id -> entity
	
	ZArray<ZEntity*>	 BaseEntities;					// base entities that have been created (no think state)
	ZArray<ZEntity*>	 ActorEntities;					// actor entities that have been created (has a think state)

	ZMessageStream		 MessageQueue;					// message queue for the simulation
	ZTaskStream			 TaskStream;					// task stream for the simulation, which concurrently processes actors
	ZRandomGenerator	 PRNG;							// PRNG for the simulation

	ZNetworkService		 NetworkService;				// network service for this simulation
	ZNetworkUpdateStream NetworkUpdateStream;			// the network update stream that processes sync and events
	bool				 bIsServer;						// set by which init method we call
	uint16_t			 ListenPort;					// listen port for this simulation server (0 = don't listen)
	uint8_t				 SyncRate;						// the number of times per second we should send network updates
	uint8_t				 NetSendRate;					// the rate at which the buffers are synchronized (updates per second)
	ZString				 NetName;						// the name of this server / client on the network

	ZPropertyBuffer		 PropertyBuffer;				// allocator and sync manager for entity properties
	ZArray<eID>			 PropertyDeletedStack;			// list of entities that have had a property deleted
	ZMutex				 PropertyDeletedLock;			// lock for manipulating PropertyDeletedStack

	ZHashMap<ZName, ZEntity::InitFunc>  EntityTypeMap;	// map of entity name to initialization functions
	ZHashMap<ZName, ZEntity::StateFunc> EntityStateMap;	// map of entity name to initialization functions

	// function to get the next entity id available
	eID GetNextEntityId()
	{
		size_t startIndex = CurrentEntityIndex;

		ZEntity* ent = Entities[CurrentEntityIndex];

		while (ent != NULL)
		{
			CurrentEntityIndex++;

			if (CurrentEntityIndex > ZSIM_EID_MAX)
				CurrentEntityIndex = 0;

			ZASSERT_RUNTIME(CurrentEntityIndex != startIndex, "No available entity Id's!  Also, wow, that's a lot of entities.");

			ent = Entities[CurrentEntityIndex];
		}

		return (eID)CurrentEntityIndex;
	}

	// Entity allocation methods (override in subclass to use specialized memory management)
	virtual ZEntity* AllocEntity(eID entityId);	
	virtual ZEntity* AllocEntity(eID entityId, ZEntity::StateFunc startState);
	virtual void	 DeallocEntity(ZEntity* entity);

	// Thread initialization method and shutdown method (be sure to call ZSimulation::initThread and ZSimulation::shutdownThread if subclassed)
	virtual void initThread();
	virtual void shutdownThread();

	// Overridden 'Run' Method
	virtual ZThreadReturn run(uint64_t dt);

	// Exposed Init and Shutdown methods for subclass simulations to implement
	virtual bool SubInitServer() { return true; }
	virtual bool SubInitClient() { return true; }
	virtual void SubShutdown() { }

	// Exposed 'Simulation Tick' method for subclass simulations to implement
	virtual void Tick(double dt) { URFP(dt); }

	// Some private static functions for handling the base message types
	static void HandleNetworkSystemShutdown(eID sender, eID target, void* payload, ZSimulation& sim);	// 
	static void HandleConnectionEstablished(eID sender, eID target, void* payload, ZSimulation& sim);	// 
	static void HandleConnectionFailed(eID sender, eID target, void* payload, ZSimulation& sim);		// 
	static void HandleConnectionLost(eID sender, eID target, void* payload, ZSimulation& sim);			//
	static void HandleServerNodeGained(eID sender, eID target, void* payload, ZSimulation& sim);		//
	static void HandleServerNodeLost(eID sender, eID target, void* payload, ZSimulation& sim);			// 

	static void HandleSpawnEntity(eID sender, eID target, void* payload, ZSimulation& sim);				// spawns an entity of a specific type
	static void HandleGainState(eID sender, eID target, void* payload, ZSimulation& sim);				// transitions entity to 'actor' status
	static void HandleLoseState(eID sender, eID target, void* payload, ZSimulation& sim);				// transitions from 'actor' to 'base entity' status
	static void HandleKill(eID sender, eID target, void* payload, ZSimulation& sim);					// removes an entity from the simulation

	static void HandleShutdownSimulation(eID sender, eID target, void* payload, ZSimulation& sim);		// handles a shutdown message (shuts down this simulation)

	static void HandleSetInt(eID sender, eID target, void* payload, ZSimulation& sim);					// sets the integer value of a property
	static void HandleSetDbl(eID sender, eID target, void* payload, ZSimulation& sim);					// sets the real number value of a property
	static void HandleSetVec(eID sender, eID target, void* payload, ZSimulation& sim);					// sets the vector value of a property
	static void HandleSetMat(eID sender, eID target, void* payload, ZSimulation& sim);					// sets the matrix value of a property
	static void HandleSetStr(eID sender, eID target, void* payload, ZSimulation& sim);					// sets the string value of a property

private:
	DISABLE_COPY_AND_ASSIGN(ZSimulation);
};

#endif
