/*
	ZPropertyBuffer.hpp
	Author: author_name <author_email@762studios.com>
	Created: 8/30/2015

	Purpose: 

	The property buffer system is an allocator which holds entity property data in
	contiguous blocks of memory that can then be synchronized across the network when
	properties change, though properties can be kept in non-synchronized buffers for
	local use.

	The buffer keeps both a read and write copy of the properties which can be updated
	on call.

	This allocator does not lock, so any locking that would be required must be handled
	by the caller.
		
	In order to have proper synchronization, each entity that has properties allocated
	should have it's synchronized variables created in the same order on all machines.  
	This will ensure that the data layouts are identical and proper synchronization can
	occur.

	License:

	TODO

*/

#pragma once

#ifndef _ZPROPERTYBUFFER_HPP
#define _ZPROPERTYBUFFER_HPP

#include "ZSimulationDefs.hpp"
#include "ZPropertyBufferUpdate.hpp"

// forward decl
struct PropertyPage;
class ZNetworkUpdateStream;

// class decl
class ZPropertyBuffer
{
public:
	// key used to look up allocated data in the property buffer
	typedef uint64_t PropertyKey;

	// used to flag synchronization type (none, send, or receive)
	enum SyncMode {
		SYNC_NONE = 0,
		SYNC_SEND = 1,
		SYNC_RECV = 2,

		SYNC_ENUM_SIZE
	};

	// data struct for property data
	struct Property {
		void*		Read;	// pointer to the read property
		void*		Write;	// pointer to the write property
		size_t		Size;	// size of the property
		PropertyKey Key;	// the property key
	};

	// c'tor
	ZPropertyBuffer();

	// d'tor
	~ZPropertyBuffer();
		
	/*
	Allocates a chunk of memory in the property buffer given the entity id, the entity size, 
	and whether or not the property should be synchronized.  The read buffer for the property 
	is returned.

	Allocations from these methods return static sized properties - they will not ever be able 
	to increase in size - alloc a new property and remove the old one in order to increase
	size.  The size limit on a synchronized property is 1024 bytes.

	In order to have proper synchronization, each entity that has properties allocated
	should have its synchronized variables created in the same order on all machines.  
	This will ensure that the data layouts are identical and proper synchronization can
	occur.
	*/
	Property AllocProperty(eID id, size_t size, SyncMode mode = SYNC_NONE );

	/*
	Deallocation methods, which deallocate previously allocated property data.  The version 
	which takes an entity id deallocates all data associated with that entity id.  The 
	version which takes a property key merely deallocates that particular property.

	Deallocating individual properties is not terribly fast, so avoid it where possible.
	*/
	void DeallocEntity(eID id);
	void DeallocProperty(PropertyKey key, size_t size);

	/*
	Flags a previously allocated property as modified.  The size (in bytes) of the property
	is required.
	*/
	void FlagModified(PropertyKey key, size_t size);

	/*
	Updates the read properties from the write properties where modifications have happened.

	The version which takes an entity id will update a single entities properties.

	If told to synchronize, the buffer will queue up the network updates needed to sync
	the buffer state.
	*/
	void UpdateModified(bool sync, ZNetworkUpdateStream& stream);
	void UpdateModified(eID id, bool sync, ZNetworkUpdateStream& stream);
	
	/*
	Applies buffer updates that have been read from the network.  These are applied directly
	to the read value of the property.
	*/
	void ApplyBufferUpdates(const ZArray<ZPropertyBufferUpdate*>& updates);

private:
	/* synchronized and non-synchronized data storage */
	ZArray<PropertyPage*>		  Pages[2];			// the individual buffer pages (synchronized and local)
	ZHashMap<eID, uint32_t>		  IdPageMap;		// maps the entity id to its page number
	ZArray<uint32_t>			  Avail;			// available indices from dealloc'd entities
	
	// given the property key will get the data, page, and synchronization flag
	void* GetData(PropertyKey key, int rw, PropertyPage** page_out = NULL, SyncMode* mode_out = NULL);
	
	// will alloc data for the property (searches page, otherwise makes new pages)
	Property AllocData(PropertyPage* page, eID id, uint32_t page_idx, size_t size, SyncMode mode);
};

#endif