/*
	ZAllocWindow.hpp
	Author: James Russell <jcrussell@762studios.com>
	Created: 3/7/2013

	Purpose: 

	Buffer used to maintain a sliding window of allocations in a buffer.  Primary
	use case is sliding packet windows, but could also be used for other forms 
	of data.

	Able to allocate raw memory blocks or perform in-place construction using 
	default constructors.  Destructor calls can be omitted or performed.

	License:

	TODO

*/

#pragma once

#ifndef _ZALLOCWINDOW_HPP
#define _ZALLOCWINDOW_HPP

#include <new>	// for placement new

#include <ZUtil/ZUtilBuild.hpp>

/*
Allocation window class.  Operating on a contiguous array of backing memory,
this will allocate chunks of that memory, increasing the window until the
entire block would be used.

When deallocations are performed, no memory is actually freed from the buffer
unless the earliest allocation is deallocated, which will shrink the window.

As an example, consider the following:

  buffer.Allocate(A);
  buffer.Allocate(B);
  buffer.Allocate(C);
  buffer.Allocate(D);

A large enough buffer would return four pointers and would have the following 
layout (a = allocated flag, d = deallocated flag, f = free memory):

	  +-----------------------------------+
	  | a | A | a | B | a | C | a | D | f |
	  +-----------------------------------+
	  ^							      ^
	  buffer begin					  buffer end

  buffer.Deallocate(B);

	  +-----------------------------------+
	  | a | A | d | B | a | C | a | D | f |
	  +-----------------------------------+
	  ^							      ^
	  buffer begin					  buffer end

  buffer.Deallocate(A);
  
	  +-----------------------------------+
	  | f | f | f | f | a | C | a | D | f |
	  +-----------------------------------+
					  ^			      ^
					  buffer begin	  buffer end

Notice that when B was deallocated, no space was actually made available 
in the buffer until A was deallocated.  If a further attempt had been made
to allocate data, it would have returned NULL, indicating that the allocation
window was full.  The only way to free data at that point would have been to
deallocate the earliest allocation (A, in this case).

The allocation window will automatically handle wrapping around when the end
of the buffer is reached.
*/
class ZAllocWindow
{
public:
	// c'tor
	ZAllocWindow();

	// parameterized c'tor
	ZAllocWindow(size_t _buffersize);

	// d'tor
	~ZAllocWindow();

	/*
	void* ZAllocWindow::Allocate

	Allocates a contiguous block of memory from the buffer.  If this
	would wrap around such that the previously allocated data (that has
	not been deallocated) would be overwritten, then it returns NULL.

	An additional 8 bytes are used per allocation from the buffer for 
	allocation metadata.

	This will always return a word aligned pointer.

	The template version will default construct an object of type T.

	@param _size - number of bytes to allocate
	@return (void*) - the allocated block of memory (NULL if not possible)
	*/
	void* Allocate(size_t _size);
	template <typename T> T* Allocate()
		{ T* obj = (T*)Allocate(sizeof(T)); return (obj != NULL ? new (obj) T() : NULL); }

	/*
	void ZAllocWindow::Deallocate

	Deallocates a previously allocated block of memory from the binary buffer.  Since
	allocation is done in a sliding window, this only moves frees up memory if this
	is the oldest currently allocated block of data.  Any other data that is deallocated
	will be marked as freed to be made available when the oldest allocated block is freed.

	The template version will call the destructor for the object.

	@param _data - the data to be deallocated
	@param _obj - the object to deallocate (destructor is called)
	@return (void)
	*/
	void Deallocate(void* _data);
	template <typename T> void Deallocate(T* _obj)
		{ _obj->~T(); Deallocate((void*)_obj);  }

	/*
	size_t ZAllocWindow::GetAllocCount

	Gets the current number of allocations out on this allocation window.  

	@return (size_t) - the number of allocations
	*/
	size_t GetAllocCount() const
		{ return NumAllocs;	}

	/*
	void ZAllocWindow::Reset

	Completely resets the allocation window, completely invalidating all
	existing allocations.

	@return (void)
	*/
	void Reset();

	/*
	bool ZAllocWindow::Resize

	Attempts to resize the buffer.  This will fail if data is currently allocated in
	the buffer.

	The template version will set the buffer to enough room for N objects of type T.

	@param _newsize - the new desired size of the buffer
	@param _count - the number of objects of type T to resize the buffer for
	@return (bool) - true if able to resize, false otherwise
	*/
	bool Resize(size_t _newsize);
	template <typename T> bool Resize(size_t _count)
		{ return Resize((sizeof(T) + 8) * _count); }

	/*
	void ZAllocWindow::SetBuffer

	Sets the backing buffer for this binary buffer.  This will fail if data is currently
	allocated in the buffer.  If NULL, will release ownership of any previous set buffer
	and allocate a new one equal to the current size.

	Does not assume ownership of the buffer - will not delete it upon destruction of this
	buffer.

	@param _buffer - the buffer to use as backing
	@param _buffersize - the size of the buffer being set
	@return (bool) - true if able to use, false otherwise
	*/
	bool SetBuffer(void* _buffer, size_t _buffersize);

	/*
	size_t ZAllocWindow::Size

	Returns the current size of the buffer.

	@return (size_t) - size (in bytes) of the buffer
	*/
	size_t Size() const
		{ return BufferSize; }

private:
	DISABLE_COPY_AND_ASSIGN(ZAllocWindow);

	void*	Buffer;			// The allocated buffer
	void*	Begin;			// The (moving) front pointer of the buffer
	void*	End;			// The (moving) back pointer of the buffer
	
	size_t	BufferSize;		// The current allocated size of the buffer
	bool	bOwnsBuffer;	// Flag indicating we own the buffer (and should free when done)
	size_t	NumAllocs;		// The number of allocations we've done
};

#endif
 