//////////////////////////////////////////////////////////////////////////////
//
// Asura_Mem_ResetOnlyAllocator.h
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     A very simple and restrictive allocator that does not permit true
//     deallocations in the sense of freeing up the memory in question, only
///    a complete reset that makes all reserved memory available again.
//
//     A method for deallocation is provided but this is really only for
//     debugging purposes and can be suppressed by passing false to the
//     bRequireDeallocation parameters of Initialise() and Wipe().
//
//     Allocation alignment must be a power of two.
//
//////////////////////////////////////////////////////////////////////////////

// B E G I N   F I L E   W R A P P E R ///////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef ASURA_MEM_RESETONLYALLOCATOR_H___
#define ASURA_MEM_RESETONLYALLOCATOR_H___ 1

// I N C L U D E S ///////////////////////////////////////////////////////////

#include <stdint.h>

#include "Debugging/Asura_Assert.h"

// C L A S S E S /////////////////////////////////////////////////////////////

class Asura_Mem_ResetOnlyAllocator
{

public:

    Asura_Mem_ResetOnlyAllocator( void );

    // This method may be called at any time to reinitialise the allocator.
    inline void Initialise( void* pvBfr, u_int uBfrSize, bool bRequireDeallocation = true );
    // Forget all allocations and lose reference to buffer.
    inline void Reset( void );
    // Forget all allocations but retain reference to buffer.
    inline void Wipe( bool bRequireDeallocation = true );

    inline void* Allocate( u_int uNumBytes, u_int uAlignment = 4 );
    inline void Deallocate( void* pvMemory );

    inline u_int GetNumFreeBytes( void ) const { return( (m_pcBfrStart + m_uBfrSize) - m_pcNextFreeByte ); }
    inline u_int GetNumUsedBytes( void ) const { return( m_pcNextFreeByte - m_pcBfrStart ); }
    inline u_int GetBufferSize( void ) const { return m_uBfrSize; }

private:

    char* m_pcBfrStart;
    u_int m_uBfrSize;
    char* m_pcNextFreeByte;
    u_int m_uNumAllocs;

};


template< u_int uBUFFER_SIZE >
class Asura_Mem_ResetOnlyAllocatorWithInternalBfr : public Asura_Mem_ResetOnlyAllocator
{

public:

    Asura_Mem_ResetOnlyAllocatorWithInternalBfr( void );

private:

    char m_acInternalBfr[uBUFFER_SIZE];

};

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Asura_Mem_ResetOnlyAllocator
//

Asura_Mem_ResetOnlyAllocator::Asura_Mem_ResetOnlyAllocator( void )
{
    Reset();
}


inline void Asura_Mem_ResetOnlyAllocator::Wipe( bool bRequireDeallocation )
{
    if( bRequireDeallocation )
    {
        Asura_Assert( !m_uNumAllocs, "Missed a deallocation" );
    }

    m_pcNextFreeByte = m_pcBfrStart;
    m_uNumAllocs     = 0;
}


inline void Asura_Mem_ResetOnlyAllocator::Initialise( void* pvBfr, u_int uBfrSize, bool bRequireDeallocation )
{
    m_pcBfrStart = static_cast<char*>(pvBfr);
    m_uBfrSize   = uBfrSize;

    Wipe( bRequireDeallocation );
}


inline void* Asura_Mem_ResetOnlyAllocator::Allocate( u_int uNumBytes, u_int uAlignment )
{
    char* pcAddr = m_pcNextFreeByte;
    
    // Align.
    {
        uintptr_t uPtrValue = reinterpret_cast<uintptr_t>( pcAddr );
        u_int uAlignmentMask = uAlignment - 1;
        if( uPtrValue & uAlignmentMask )
        {
            uPtrValue = (uPtrValue + uAlignmentMask) & ~uAlignmentMask;
            pcAddr = reinterpret_cast<char*>( uPtrValue );
        }
    }

    char* pcNewNextFreeByte = pcAddr + uNumBytes;
    if( pcNewNextFreeByte > m_pcBfrStart + m_uBfrSize )
    {
        Asura_Assert( false, "Out of reserved memory!" );
        return NULL;
    }

    m_pcNextFreeByte = pcNewNextFreeByte;
    ++m_uNumAllocs;

    return pcAddr;
}


inline void Asura_Mem_ResetOnlyAllocator::Deallocate( void* pvMemory )
{
    if( pvMemory )
    {
        Asura_Assert( m_uNumAllocs, "Mis-matched alloc / dealloc calls?" );
        --m_uNumAllocs;
    }
}


inline void Asura_Mem_ResetOnlyAllocator::Reset( void )
{
    m_pcBfrStart     = NULL;
    m_uBfrSize       = 0;
    m_pcNextFreeByte = NULL;
    m_uNumAllocs     = 0;
}


//
// Asura_Mem_ResetOnlyAllocatorWithInternalBfr
//

template< u_int uBUFFER_SIZE >
Asura_Mem_ResetOnlyAllocatorWithInternalBfr<uBUFFER_SIZE>::Asura_Mem_ResetOnlyAllocatorWithInternalBfr( void )
    : Asura_Mem_ResetOnlyAllocator()
{
    Initialise( m_acInternalBfr, uBUFFER_SIZE );
}


// E N D   F I L E   W R A P P E R ///////////////////////////////////////////

#endif // #ifndef ASURA_MEM_RESETONLYALLOCATOR_H___
