///////////////////////////////////////////////////////////////////////////////
//
// File      : Asura_RingBuffer.h
// Author    : Philip Hall
// Created   : 23/10/2007
//
// Copyright (C) 2007 Rebellion. All Rights Reserved
//
///////////////////////////////////////////////////////////////////////////////

#if !defined __ASURA_RINGBUFFER_H__
#define __ASURA_RINGBUFFER_H__


#include "Core/Asura_Maths.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"


////PGH////////////////////////////////////////////////////////////////////////
// Structure sizing macros:-
//
//   Use these in the declaration of your ring-buffer to size and align the 
//   individual elements to specific byte boundaries
//
//   Asura_RingBuffer<TheClass, ASURA_SIZE_TO_32_BYTE(sizeof(TheClass))> xRingBuffer;
//
///////////////////////////////////////////////////////////////////////////////
#define ASURA_SIZE_TO_4_BYTE(size)   ( ((size) + 3) & ~3 )
#define ASURA_SIZE_TO_8_BYTE(size)   ( ((size) + 7) & ~7 )
#define ASURA_SIZE_TO_16_BYTE(size)  ( ((size) + 15) & ~15 )
#define ASURA_SIZE_TO_32_BYTE(size)  ( ((size) + 31) & ~31 )
#define ASURA_SIZE_TO_64_BYTE(size)  ( ((size) + 63) & ~63 )
#define ASURA_SIZE_TO_128_BYTE(size) ( ((size) + 127) & ~127 )
#define ASURA_SIZE_TO_256_BYTE(size) ( ((size) + 255) & ~255 )
#define ASURA_SIZE_TO_512_BYTE(size) ( ((size) + 511) & ~511 )
#define ASURA_SIZE_TO_1024_BYTE(size) ( ((size) + 1023) & ~1023 )


////PGH/////////////////////////////////////////
// General purpose ring buffer
////////////////////////////////////////////////
template< typename T, u_int uPITCH = 0 >
class Asura_RingBuffer
{

#define GET_PITCH() (uPITCH ? uPITCH : sizeof(T))

public:

	Asura_RingBuffer( u_int uCapacity = 0, ASURA_ALIGN_ID uAlignment = ASURA_ALIGN_8_BYTE )
	{
		m_pucBuffer = NULL; 
		m_uCapacity = 0; 
		m_uRead = m_uWrite = 0;

        if( uCapacity )
        {
            Create( uCapacity, uAlignment );
        }
	}

	~Asura_RingBuffer()
	{
		Destroy();
	}
	
	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Create
	// Description : Allocate and initialise a circular buffer
	//
	// Parameters  : 
	//   u_int uCapacity - Number of elements to store (maximum+1)
	//   ASURA_ALIGN_ID uAlignBytes - Buffer alignment (in bytes)
	//
	// Returns     : bool - Returns true if allocated successfully, false on failure.
	///////////////////////////////////////////////////////////////////////////////
	bool Create(u_int uCapacity, ASURA_ALIGN_ID uAlignBytes = ASURA_ALIGN_8_BYTE)
	{
		Asura_Assert(m_pucBuffer == NULL, "Create called twice without destroy called inbetween");
		m_pucBuffer = reinterpret_cast<u_char*>( Asura_AllocateAlign(uCapacity * GET_PITCH(), uAlignBytes) );
		if ( m_pucBuffer != NULL )
		{

#include <Core/Asura_MemoryManagement_DebugOff.h>

			// make sure the constructor is called for each item
			for (u_int i = 0; i < uCapacity; ++i)
			{
	            new (m_pucBuffer + i * GET_PITCH()) T;
			}

#include <Core/Asura_MemoryManagement_DebugOn.h>

    		m_uRead = m_uWrite = 0;
			m_uCapacity = uCapacity; 
			return true;
		}
	    return false;
	}
	
	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Destroy
	// Description : Deallocate ring buffer memory and reset the pointers
	//
	// Parameters  : none
	//
	// Returns     : nothing
	///////////////////////////////////////////////////////////////////////////////
	void Destroy()
	{
		if ( m_pucBuffer != NULL )
		{
			// make sure the destructor is called for each item
			for (u_int i = 0; i < m_uCapacity; ++i)
			{
				reinterpret_cast<T*>(&m_pucBuffer[i * GET_PITCH()])->~T();
			}

			Asura_DeallocateAlign(m_pucBuffer);
			m_pucBuffer = NULL;
			m_uCapacity = 0;
		}
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Reset
	// Description : Reset the read & write pointers to zero without deallocating 
	//               the buffer.
	//
	// Parameters  : none
	//
	// Returns     : nothing
	///////////////////////////////////////////////////////////////////////////////
	void Reset()
	{
		m_uRead = m_uWrite = 0;
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Alloc
	// Description : Add an element to the front of the ring (if possible)
	//
	// Parameters  : none
	//
	// Returns     : T* - Returns a pointer to a new element (or NULL on failure)
	///////////////////////////////////////////////////////////////////////////////
	T* Alloc()
	{
		Asura_Assert(m_pucBuffer != NULL, "[Asura_RingBuffer::Alloc] - Buffer not created\n");

		u_int uTestNext = m_uWrite + 1;
		if ( uTestNext >= m_uCapacity ) { uTestNext = 0; }
		if ( uTestNext != m_uRead )     // Can't have (m_uWrite == m_uRead) post write because the list would 'look' empty.
		{
			T* pNext = reinterpret_cast<T*>( m_pucBuffer + (m_uWrite * GET_PITCH()) );
			m_uWrite++;
			if ( m_uWrite >= m_uCapacity ) { m_uWrite = 0; }
			return pNext;
		}
		
		return NULL;
	}

    // Add an element to the front of the ring. However, if it's full then
    // drop one from the back first (ie. effectively overwrite it).
    T* Alloc_Overwrite( void )
    {
        if( IsFull() )
        {
            Drop();
        }

        return Alloc();
    }

	////JCT////////////////////////////////////////////////////////////////////////
	// Function    : Write
	// Description : Writes multiple elements to the current write position.
	//					This also advances the write position.
	//
	// Parameters  :
	//		pReadPtr - pointer to copy from
	//		uWriteCount - number of elements to write to buffer
	//		bWrap - Wrap at bottom if we need to.
	// Returns     : Number of elements written to buffer
	///////////////////////////////////////////////////////////////////////////////
	u_int Write(const T* pReadPtr, u_int uWriteCount, bool bWrap=true)
	{
		u_int uWrittenCount = 0;
		if (uWriteCount > 0)
		{
			if (uWriteCount <= GetUnusedSpace())
			{
				T* pWritePos = BeginAlloc();
				if (pWritePos)
				{
					u_int uFirstWriteSize = m_uCapacity - m_uWrite;
					// no need to wrap
					if (uWriteCount <= uFirstWriteSize)
					{
						Asura_Mem::Copy(pWritePos, pReadPtr, uWriteCount * GET_PITCH());
						uWrittenCount += uWriteCount;
					}
					else
					{
						// copy first half
						Asura_Mem::Copy(pWritePos, pReadPtr, uFirstWriteSize * GET_PITCH());
						uWrittenCount += uFirstWriteSize;
						if (bWrap)
						{
							// copy second half
							Asura_Mem::Copy(m_pucBuffer, pReadPtr + uFirstWriteSize, (uWriteCount - uFirstWriteSize) * GET_PITCH());
							uWrittenCount += uWriteCount - uFirstWriteSize;
						}
					}
					EndAlloc(uWrittenCount);
				}
			}
		}
		return uWrittenCount;
	}

	////KOR////////////////////////////////////////////////////////////////////////
	// Function    : Read
	// Description : Reads multiple elements from the current read position.
	//					This also advances the read position.
	//
	// Parameters  :
	//		pBuffer - pointer to copy to
	//		uElementsToRead - number of elements to read from buffer
	//
	// Returns     : Number of elements written to buffer
	///////////////////////////////////////////////////////////////////////////////
	u_int Read(T* pBuffer, u_int uElementsToRead)
	{
		u_int uReadCount = 0;
		// Only read as many elements as we have available.
		uElementsToRead = Asura_Maths::Min(GetCount(), uElementsToRead);
		if(uElementsToRead)
		{
			const u_int uFirstReadSize = m_uCapacity - m_uRead;
			if(uElementsToRead <= uFirstReadSize)
			{
				Asura_Mem::Copy(pBuffer, m_pucBuffer + m_uRead, uElementsToRead * GET_PITCH());
			}
			else
			{
				// Copy from end of buffer.
				Asura_Mem::Copy(pBuffer, m_pucBuffer + m_uRead, uFirstReadSize * GET_PITCH());
				// Copy from start of buffer.
				Asura_Mem::Copy(pBuffer + uFirstReadSize, m_pucBuffer, (uElementsToRead - uFirstReadSize) * GET_PITCH());
			}
			m_uRead = (m_uRead + uElementsToRead) % m_uCapacity;

			uReadCount = uElementsToRead;
		}
		return uReadCount;
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : BeginAlloc
	// Description : Two-part allocation - this adds an element to the front of 
	//               the ring, but doesn't advance the write position. Call
	//               EndAlloc to commit the element.
	//
	// Parameters  : none
	//
	// Returns     : T* - Returns a pointer to the next element (or NULL on failure)
	///////////////////////////////////////////////////////////////////////////////
	T* BeginAlloc()
	{
		Asura_Assert(m_pucBuffer != NULL, "[Asura_RingBuffer::BeginAlloc] - Buffer not created\n");

		u_int uTestNext = m_uWrite + 1;
		if ( uTestNext >= m_uCapacity ) { uTestNext = 0; }
		if ( uTestNext != m_uRead )     // Can't have (m_uWrite == m_uRead) post write because the list would 'look' empty.
		{
			T* pNext = reinterpret_cast<T*>( m_pucBuffer + (m_uWrite * GET_PITCH()) );
			return pNext;
		}
		
		return NULL;
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : EndAlloc
	// Description : End a two-part allocation. Should be called after BeginAlloc
	//               to commit an element, to discard just ignore.
	//
	// Parameters  : 
	//		u_int uCount - Number of elements we've written
	//
	// Returns     : nothing
	///////////////////////////////////////////////////////////////////////////////
	void EndAlloc(u_int uCount=1)
	{
		m_uWrite += uCount;
		// wrap around into buffer
		if ( m_uWrite >= m_uCapacity ) { m_uWrite -= m_uCapacity; }
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Peek
	// Description : Read the oldest element in the ring, or some distance ahead of
	//               it. Peek only inspects an element, it is safe to inspect it
	//               while in the buffer until it has been dropped.
	//
	// Parameters  : 
	//   u_int uOffset - Offset to allow peeking ahead of the current read point
	//
	// Returns     : T* - Pointer to element to inspect (NULL on failure)
	///////////////////////////////////////////////////////////////////////////////
	T* Peek(u_int uOffset=0) const
	{
		Asura_Assert(m_pucBuffer != NULL, "[Asura_RingBuffer::Peek] - Buffer not created\n");

		u_int uRead = m_uRead + uOffset; // No protection for PASSING the current write point
		if ( uRead >= m_uCapacity ) { uRead -= m_uCapacity; }

		if ( uRead != m_uWrite )
		{
			return reinterpret_cast<T*>( m_pucBuffer + (uRead * GET_PITCH()) );
		}

		return NULL;
	}

	////PGH////////////////////////////////////////////////////////////////////////
	// Function    : Drop
	// Description : Drop (remove) one or more elements from the back of the ring.
	//
	// Parameters  : 
	//   u_int uCount - Number of elements to drop, zero drops no elements
	//
	// Returns     : nothing
	///////////////////////////////////////////////////////////////////////////////
	void Drop(u_int uCount=1)
	{
		Asura_Assert(m_pucBuffer != NULL, "[Asura_RingBuffer::Drop] - Buffer not created\n");

		if ( m_uRead != m_uWrite )
		{
			m_uRead += uCount;	   // There is NO protection for PASSING the current write point
			if ( m_uRead >= m_uCapacity ) { m_uRead = m_uRead - m_uCapacity; }
		}
	}

	// Get the current read position (limited use)
	u_int GetReadPos() const
	{
		return m_uRead;
	}

	// Get the current write position (limited use)
	u_int GetWritePos() const 
	{
		return m_uWrite;
	}

	// Get the allocated capacity
	u_int GetCapacity() const
	{
		return m_uCapacity;
	}

	// Get the total number of allocated elements within the buffer  
	u_int GetCount() const
	{
		return (m_uRead <= m_uWrite) ? (m_uWrite - m_uRead) : (m_uCapacity - m_uRead + m_uWrite);
	}

	// Get the total number of free elements within the buffer  
	// Note that we cannot use the full capacity due to the way we store the positions;
	// see Mantis issue 68528.
	u_int GetUnusedSpace() const
	{
		return (GetCapacity() - GetCount() - 1);
	}

	// Determine if the buffer is empty	(i.e. contains no items where read==write)
	bool  IsEmpty() const
	{
		return (m_uRead == m_uWrite);
	}

	// Determine if the ring buffer is full (more useful than you'd think)
	bool  IsFull() const
	{
		u_int uTestNext = m_uWrite + 1;
		if ( uTestNext >= m_uCapacity ) { uTestNext = 0; }
		return (uTestNext == m_uRead);
	}

protected:

    u_char* m_pucBuffer;
    u_int   m_uCapacity;
    u_int   m_uRead;
    u_int   m_uWrite;

#undef GET_PITCH

};

#endif
