#if !defined( _IOABUFFER_H )
#define _IOABUFFER_H

class IoABuffer
{
public:
    IoABuffer( int Elements = 0, int Size = 1 ) : m_pBuffer( NULL ), m_nElements( 0 ), m_nSize( 0 )
    {
        if ( Elements > 0 ) Allocate( Elements, Size );
    }

    ~IoABuffer( ) { Free( ); }

    int Allocate( int Elements, int Size = 1 );
    void Free( );

    int IsAllocated( ) const { return m_pBuffer != NULL; }

    void *Address( ) const { return m_pBuffer; }
    int ElementCount( ) const { return m_nElements; }
    int ElementSize( ) const { return m_nSize; }

    int SizeOf( ) const { return m_nElements * m_nSize; }

private:
    void *m_pBuffer;

    int m_nElements;
    int m_nSize;
};

class IoACharacterBuffer : public IoABuffer
{
public:
    IoACharacterBuffer( int Elements = 0 ) : IoABuffer( Elements, sizeof( char ) ), m_Null( 0 ) { }

    int Allocate( int Elements ) { return IoABuffer::Allocate( Elements, sizeof( char ) ); }

    char *Address( ) const { return (char *) IoABuffer::Address( ); }

    char& Element( int Index ) { return 0 <= Index && Index < ElementCount( ) ? Address( )[ Index ] : m_Null; }

    char& operator[ ]( int Index ) { return Element( Index ); }

private:
    char m_Null;
};

class IoAUnsignedCharacterBuffer : public IoABuffer
{
public:
    IoAUnsignedCharacterBuffer( int Elements = 0 ) : IoABuffer( Elements, sizeof( unsigned char ) ), m_Null( 0 ) { }

    int Allocate( int Elements ) { return IoABuffer::Allocate( Elements, sizeof( unsigned char ) ); }

    unsigned char *Address( ) const { return (unsigned char *) IoABuffer::Address( ); }

    unsigned char& Element( int Index ) { return 0 <= Index && Index < ElementCount( ) ? Address( )[ Index ] : m_Null; }

    unsigned char& operator[ ]( int Index ) { return Element( Index ); }

private:
    unsigned char m_Null;
};

class IoAIntegerBuffer : public IoABuffer
{
public:
    IoAIntegerBuffer( int Elements = 0 ) : IoABuffer( Elements, sizeof( int ) ), m_Null( 0 ) { }

    int Allocate( int Elements ) { return IoABuffer::Allocate( Elements, sizeof( int ) ); }

    int *Address( ) const { return (int *) IoABuffer::Address( ); }

    int& Element( int Index ) { return 0 <= Index && Index < ElementCount( ) ? Address( )[ Index ] : m_Null; }

    int& operator[ ]( int Index ) { return Element( Index ); }

private:
    int m_Null;
};

class IoAUnsignedIntegerBuffer : public IoABuffer
{
public:
    IoAUnsignedIntegerBuffer( int Elements = 0 ) : IoABuffer( Elements, sizeof( unsigned int ) ), m_Null( 0 ) { }

    int Allocate( int Elements ) { return IoABuffer::Allocate( Elements, sizeof( unsigned int ) ); }

    unsigned int *Address( ) const { return (unsigned int *) IoABuffer::Address( ); }

    unsigned int& Element( int Index ) { return 0 <= Index && Index < ElementCount( ) ? Address( )[ Index ] : m_Null; }

    unsigned int& operator[ ]( int Index ) { return Element( Index ); }

private:
    unsigned int m_Null;
};

class IoAFloatingPointBuffer : public IoABuffer
{
public:
    IoAFloatingPointBuffer( int Elements = 0 ) : IoABuffer( Elements, sizeof( double ) ), m_Null( 0.0 ) { }

    int Allocate( int Elements ) { return IoABuffer::Allocate( Elements, sizeof( double ) ); }

    double *Address( ) const { return (double *) IoABuffer::Address( ); }

    double& Element( int Index ) { return 0 <= Index && Index < ElementCount( ) ? Address( )[ Index ] : m_Null; }

    double& operator[ ]( int Index ) { return Element( Index ); }

private:
    double m_Null;
};

class IoARingBuffer : public IoAUnsignedCharacterBuffer
{
public:
    IoARingBuffer( int Elements = 0 ) : IoAUnsignedCharacterBuffer( Elements ), m_iHead( 0 ), m_iTail( 0 ), m_nCount( 0 ) { }

    int Allocate( int Elements )
    {
        Free( );
        
        return IoAUnsignedCharacterBuffer::Allocate( Elements );
    }

    void Free( )
    {
        Flush( );

        IoAUnsignedCharacterBuffer::Free( );
    }

    unsigned char *Address( ) const { return (unsigned char *) IoAUnsignedCharacterBuffer::Address( ); }
    int Size( ) const { return ElementCount( ); }

    int Head( ) const { return m_iHead; }
    int Tail( ) const { return m_iTail; }

    int Count( ) const { return m_nCount; }
    int Space( ) const { return Size( ) - Count( ); }  // => Count( ) + Space( ) = Size( )

    int IsEmpty( ) const { return Count( ) == 0; }
    int IsFull( ) const { return Count( ) == Size( ); }

    void Flush( ) { m_iHead = m_iTail = m_nCount = 0; }

    // Single byte read/write
    int PopHead( unsigned char& Byte )
    {
        if ( ! IsAllocated( ) || IsEmpty( ) ) return FALSE;

        Byte = Address( )[ m_iHead++ ];

        if ( m_iHead == Size( ) ) m_iHead = 0;

        m_nCount -= 1;

        return TRUE;
    }

    int PopTail( unsigned char& Byte )
    {
        if ( ! IsAllocated( ) || IsEmpty( ) ) return FALSE;

        if ( m_iTail == 0 ) m_iTail = Size( );

        Byte = Address( )[ --m_iTail ];

        m_nCount -= 1;

        return TRUE;
    }

    int PushHead( unsigned char Byte )
    {
        if ( ! IsAllocated( ) || IsFull( ) ) return FALSE;

        if ( m_iHead == 0 ) m_iHead = Size( );

        Address( )[ --m_iHead ] = Byte;

        m_nCount += 1;

        return TRUE;
    }

    int PushTail( unsigned char Byte )
    {
        if ( ! IsAllocated( ) || IsFull( ) ) return FALSE;

        Address( )[ m_iTail++ ] = Byte;

        if ( m_iTail == Size( ) ) m_iTail = 0;

        m_nCount += 1;

        return TRUE;
    }

    int PopHead( unsigned int& Byte ) { unsigned char Cha; int OK = PopHead( Cha ); Byte = (unsigned int) Cha; return OK; }
    int PopTail( unsigned int& Byte ) { unsigned char Cha; int OK = PopTail( Cha ); Byte = (unsigned int) Cha; return OK; }

    int PushHead( unsigned int Byte ) { return PushHead( (unsigned char) Byte ); }
    int PushTail( unsigned int Byte ) { return PushTail( (unsigned char) Byte ); }


    // Multiple byte read/write
    int PopHead( unsigned char *Bytes, int nBytes );
    int PopTail( unsigned char *Bytes, int nBytes );

    int PushHead( const unsigned char *Bytes, int nBytes );
    int PushTail( const unsigned char *Bytes, int nBytes );

private:
    int m_iHead, m_iTail, m_nCount;
};

class IoAFIFO : public IoARingBuffer
{
public:
    IoAFIFO( int Size = 0 ) : IoARingBuffer( Size ) { }

    int Read ( unsigned char& Byte ) { return PopHead ( Byte ); }
    int Write( unsigned char  Byte ) { return PushTail( Byte ); }

    int Read (       unsigned char *Bytes, int nBytes ) { return PopHead ( Bytes, nBytes ); }
    int Write( const unsigned char *Bytes, int nBytes ) { return PushTail( Bytes, nBytes ); }

    int Read ( unsigned char *Bytes ) { return Read ( Bytes, Count( ) ); }
};

class IoAStack : public IoARingBuffer
{
public:
    IoAStack( int Size = 0 ) : IoARingBuffer( Size ) { }

    int Push( unsigned char  Byte ) { return PushTail( Byte ); }
    int Pop ( unsigned char& Byte ) { return PopTail ( Byte ); }

    int Push( const unsigned char *Bytes, int nBytes ) { return PushTail( Bytes, nBytes ); }
    int Pop (       unsigned char *Bytes, int nBytes ) { return PopTail ( Bytes, nBytes ); }

    int Pop ( unsigned char *Bytes ) { return PopTail ( Bytes, Count( ) ); }
};

#endif
