#if !defined( _IOAFILEBUFFER_H )
#define _IOAFILEBUFFER_H

class IoAFileBuffer
{
public:
    IoAFileBuffer( ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_BufferOffset( 0 )
	{
		Initialise( );
	}

    IoAFileBuffer( int BufferSize ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_BufferOffset( 0 )
	{
		Initialise( );

		Allocate( BufferSize );
	}

    // Copy constructor
    IoAFileBuffer( const IoAFileBuffer& Other ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_BufferOffset( 0 )
	{
		Initialise( );

		Copy( Other );
	}

	void Initialise( )
	{
		m_hThreadRunning = NULL;
		m_hDestroyThread = NULL;
		m_hAsynchDiskIO  = NULL;
		m_hThreadBusy    = NULL;
		m_hThreadIdle    = NULL;

		m_pIOThread = NULL;
	}

    ~IoAFileBuffer( )
    {
        DestroyThread( );
        DestroyEvents( );
        Free( );
    }

    // Memory allocation
    int Allocate( int BufferSize );

    void Free( );

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

    void *Address( ) const { return (void *) ( (unsigned char  *) m_pBuffer + m_BufferOffset ); }

    unsigned char  *ByteAddress ( ) const { return (unsigned char  *) Address( ); }
    unsigned short *ShortAddress( ) const { return (unsigned short *) Address( ); }
    unsigned long  *LongAddress ( ) const { return (unsigned long  *) Address( ); }

    int BufferSize( ) const { return m_BufferSize; }

    int PageSize( ) const { return 4096; }

    int BufferPages( ) const { return ( BufferSize( ) + PageSize( ) - 1 ) / PageSize( ); }

    int ByteBufferSize ( ) const { return BufferSize( ) / sizeof( unsigned char  ); }
    int ShortBufferSize( ) const { return BufferSize( ) / sizeof( unsigned short ); }
    int LongBufferSize ( ) const { return BufferSize( ) / sizeof( unsigned long  ); }

    void ZeroBuffer( ) { if ( IsAllocated( ) ) ::ZeroMemory( Address( ), BufferSize( ) ); }
    void FillBuffer( int ByteValue )
    {
        if ( IsAllocated( ) ) ::FillMemory( Address( ), BufferSize( ), ByteValue & 0xff );
    }

    int DataSize( ) const { return m_DataSize; }

    int IsValidDataSize( int DataSize )
    {
        return 0 <= DataSize && DataSize <= BufferSize( );
    }

    int SetDataSize( int DataSize )
    {
        if ( ! IsValidDataSize( DataSize ) ) return FALSE;

        m_DataSize = DataSize;

        return TRUE;
    }

    void Reset( ) { m_DataSize = 0; }

    void Empty( ) { m_DataSize = 0; }

    int IsEmpty( ) const { return m_DataSize == 0; }

    int ByteDataSize ( ) const { return DataSize( ) / sizeof( unsigned char  ); }
    int ShortDataSize( ) const { return DataSize( ) / sizeof( unsigned short ); }
    int LongDataSize ( ) const { return DataSize( ) / sizeof( unsigned long  ); }

    int FreeByteCount( ) const { return BufferSize( ) - DataSize( ); }

    int ReduceFreeByteCount( int ByteCount )
    {
        if ( ByteCount < 0 || ByteCount > FreeByteCount( ) ) return FALSE;

        m_DataSize += ByteCount;

        return TRUE;
    }

    int Pointer( ) { return m_BufferPointer; }

    int IsValidPointer( int Pointer )
    {
        return 0 <= Pointer && Pointer < BufferSize( );
    }

    int SetPointer( int Pointer )
    {
        if ( ! IsValidPointer( Pointer ) ) return FALSE;

        m_BufferPointer = Pointer;

        return TRUE;
    }

    int WriteString( const CString& String )
    {
        return CopyData( (LPCTSTR) String, String.GetLength( ) );
    }

    int WriteData( const void *Data, int DataSize )
    {
        if ( Data == NULL ) return FALSE;

        if ( m_BufferPointer + DataSize > BufferSize( ) ) return FALSE;

        ::CopyMemory( ByteAddress( ) + m_BufferPointer, Data, DataSize );

        m_BufferPointer += DataSize;

        return TRUE;
    }

    void ZeroData( )
    {
        if ( IsAllocated( ) )
        {
            ::ZeroMemory( Address( ), DataSize( ) );
        }
    }

    int CopyData( const CString& String, int Append = FALSE )
    {
        return CopyData( (LPCTSTR) String, String.GetLength( ), Append );
    }

    int CopyData( const void *Data, int DataSize, int Append = FALSE )
    {
        if ( Data == NULL ) return FALSE;

        unsigned char *TargetAddress = ByteAddress( );
        int            TargetSize    = DataSize;

        if ( Append )
        {
            TargetAddress += this->DataSize( );
            TargetSize    += this->DataSize( );
        }

        if ( ! SetDataSize( TargetSize ) ) return FALSE;

        ::CopyMemory( (void *) TargetAddress, Data, DataSize );

        return TRUE;
    }

    int CopyData( const IoAFileBuffer& Other, int Append = FALSE )
    {
        if ( ! Other.IsAllocated( ) ) return FALSE;

        unsigned char *TargetAddress = ByteAddress( );
        int            TargetSize    = Other.DataSize( );

        if ( Append )
        {
            TargetAddress += DataSize( );
            TargetSize    += DataSize( );
        }

        if ( ! SetDataSize( TargetSize ) ) return FALSE;

        ::CopyMemory( (void *) TargetAddress, Other.Address( ), Other.DataSize( ) );

        return TRUE;
    }

    int Copy( const IoAFileBuffer& Other )
    {
        if ( ! Other.IsAllocated( ) )
        {
            Free( );
        }
        else
        {
            if ( BufferSize( ) != Other.BufferSize( ) )
            {
                if ( ! Allocate( Other.BufferSize( ) ) ) return FALSE;  // copy buffer size as well as contents
            }

            ::CopyMemory( Address( ), Other.Address( ), Other.DataSize( ) );

            m_DataSize = Other.DataSize( );
        }

        return TRUE;
    }

    int  Read( const CString& FilePath ) { return SynchronousRead( FilePath ); }
    int Write( const CString& FilePath, int Asynchronous = FALSE )
    {
        if ( Asynchronous ) return AsynchronousWrite( FilePath );
        else                return  SynchronousWrite( FilePath );
    }

    int  SynchronousRead( const CString& FilePath ) { return  SynchronousRead( (LPCTSTR) FilePath ); }
    int AsynchronousRead( const CString& FilePath ) { return AsynchronousRead( (LPCTSTR) FilePath ); }

    int  SynchronousWrite( const CString& FilePath ) const { return  SynchronousWrite( (LPCTSTR) FilePath ); }
    int AsynchronousWrite( const CString& FilePath )       { return AsynchronousWrite( (LPCTSTR) FilePath ); }

	int SynchronousRead ( const CString& FilePath, int ByteOffset, int ByteCount )
	{
		return SynchronousRead( (LPCTSTR) FilePath, ByteOffset, ByteCount );
	}

	int SynchronousWrite( const CString& FilePath, int ByteOffset, int ByteCount ) const
	{
		return SynchronousWrite( (LPCTSTR) FilePath, ByteOffset, ByteCount );
	}

    int  Read( const char *FilePath ) { return SynchronousRead( FilePath ); }
    int Write( const char *FilePath, int Asynchronous = FALSE )
    {
        if ( Asynchronous ) return AsynchronousWrite( FilePath );
        else                return  SynchronousWrite( FilePath );
    }

    int  SynchronousRead( const char *FilePath );
    int AsynchronousRead( const char *FilePath );

    int  SynchronousWrite( const char *FilePath ) const;
    int AsynchronousWrite( const char *FilePath );

	int SynchronousRead ( const char *FilePath, int ByteOffset, int ByteCount );
	int SynchronousWrite( const char *FilePath, int ByteOffset, int ByteCount ) const;

//    int AsynchronousSend( const CString& NetworkAddress );

    // Assignment operator
    IoAFileBuffer& operator=( const IoAFileBuffer& Other ) { Copy( Other ); return *this; }

    // Asynchronous thread support
    int CreateEvents( );
    void DestroyEvents( );

    int CreateThread( );
    void DestroyThread( );

    int IsThreadRunning( )      const { return ::WaitForSingleObject( m_hThreadRunning, 0 ) == WAIT_OBJECT_0; }
    int IsAsynchronousIOBusy( ) const { return ::WaitForSingleObject( m_hAsynchDiskIO,  0 ) == WAIT_OBJECT_0; }
    int IsIOThreadBusy( )       const { return ::WaitForSingleObject( m_hThreadBusy,    0 ) == WAIT_OBJECT_0; }
    int IsIOThreadIdle( )       const { return ::WaitForSingleObject( m_hThreadIdle,    0 ) == WAIT_OBJECT_0; }

    int WaitForThreadRunning( )      const { return WaitForEvent( m_hThreadRunning ); }
    int WaitForAsynchronousIOBusy( ) const { return WaitForEvent( m_hAsynchDiskIO  ); }
    int WaitForIOThreadBusy( )       const { return WaitForEvent( m_hThreadBusy    ); }
    int WaitForIOThreadIdle( )       const { return WaitForEvent( m_hThreadIdle    ); }

    int WaitForThreadRunning     ( int MillisecondTimeOut )  const{ return WaitForEvent( m_hThreadRunning, MillisecondTimeOut ); }
    int WaitForAsynchronousIOBusy( int MillisecondTimeOut )  const{ return WaitForEvent( m_hAsynchDiskIO,  MillisecondTimeOut ); }
    int WaitForIOThreadBusy      ( int MillisecondTimeOut )  const{ return WaitForEvent( m_hThreadBusy,    MillisecondTimeOut ); }
    int WaitForIOThreadIdle      ( int MillisecondTimeOut )  const{ return WaitForEvent( m_hThreadIdle,    MillisecondTimeOut ); }

private:
    // Generic wait function
    int WaitForEvent( HANDLE Event, DWORD MillisecondTimeOut = INFINITE ) const
    {
        if ( Event == NULL ) return FALSE;

        DWORD ReturnValue = ::WaitForSingleObject( Event, MillisecondTimeOut );

        return ReturnValue == WAIT_OBJECT_0 || ReturnValue == WAIT_TIMEOUT;
    }

    // Static member function within which IO thread runs
    static UINT IOThreadProc( LPVOID lpParameter )
    {
        if ( lpParameter == NULL ) return FALSE;

        // Convert static member function call into call to regular member function
        IoAFileBuffer *This = (IoAFileBuffer *) (lpParameter);

        return This->IOThread( );
    }

    int IOThread( );

    // Buffer properties
    void *m_pBuffer;
    int   m_BufferSize;
    int   m_BufferOffset;
    int   m_BufferPointer;
    int   m_DataSize;

    // File path
    CString m_FilePath;

    // IO thread
    HANDLE m_hThreadRunning;
    HANDLE m_hDestroyThread;
    HANDLE m_hAsynchDiskIO;
    HANDLE m_hThreadBusy;
    HANDLE m_hThreadIdle;

    CWinThread *m_pIOThread;
};

#endif
