#include "stdafx.h"
#include "FileBuffer.h"

int IoAFileBuffer::Allocate( int BufferSize )
{
    Free( );

    if ( BufferSize <= 0 ) return FALSE;

    const int PageSize = 4096;

    // Extra pages to guarantee: (1) page-aligned buffer start address; (2) padding to extend buffer to end of last page
    void *pBuffer = (void *) new unsigned char[ PageSize + BufferSize + PageSize ];

    if ( pBuffer == NULL ) return FALSE;

    m_pBuffer      = pBuffer;
    m_BufferSize   = BufferSize;
    m_BufferOffset = PageSize - ( (unsigned int) pBuffer % PageSize );  // offset to next page boundary
    m_DataSize     = 0;

    return TRUE;
}

void IoAFileBuffer::Free( )
{
    delete [ ] (unsigned char *) m_pBuffer;

    m_pBuffer      = NULL;
    m_BufferSize   = 0;
    m_BufferOffset = 0;
}

int IoAFileBuffer::SynchronousRead( const char *FilePath, int ByteOffset, int ByteCount )
{
    if ( ByteOffset < 0 || ByteCount < 0 ) return FALSE;

    HANDLE FileHandle = ::CreateFile( FilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

    if ( FileHandle == INVALID_HANDLE_VALUE ) return FALSE;

    DWORD FileSize = ::GetFileSize( FileHandle, NULL );

    if ( FileSize < (DWORD) ByteOffset + ByteCount ) return FALSE;

    DWORD BytesToRead = ByteCount ? (DWORD) ByteCount : FileSize;

    if ( BytesToRead > (DWORD) BufferSize( ) ) return FALSE;

    if ( ByteOffset )
    {
        LONG FilePointer = (LONG) ByteOffset;

        if ( ::SetFilePointer( FileHandle, FilePointer, NULL, FILE_BEGIN ) == 0xffffffff ) return FALSE;
    }

    DWORD BytesRead;

    if ( ! ::ReadFile( FileHandle, Address( ), BytesToRead, &BytesRead, NULL ) ) return FALSE;

    if ( ! ::CloseHandle( FileHandle ) ) return FALSE;;

    if ( BytesRead != BytesToRead ) return FALSE;

    int DataSize = BytesRead;

    if ( ! SetDataSize( DataSize ) ) return FALSE;

    return TRUE;
}

int IoAFileBuffer::SynchronousRead( const char *FilePath )
{
    HANDLE FileHandle = ::CreateFile( FilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

    if ( FileHandle == INVALID_HANDLE_VALUE ) return FALSE;

    DWORD FileSize = ::GetFileSize( FileHandle, NULL );

    if ( (int) FileSize > BufferSize( ) ) return FALSE;

    DWORD BytesRead;

    if ( ! ::ReadFile( FileHandle, Address( ), FileSize, &BytesRead, NULL ) ) return FALSE;

    if ( ! ::CloseHandle( FileHandle ) ) return FALSE;;

    if ( BytesRead != FileSize ) return FALSE;

    if ( ! SetDataSize( FileSize ) ) return FALSE;

    return TRUE;
}

int IoAFileBuffer::SynchronousWrite( const char *FilePath, int ByteOffset, int ByteCount ) const
{
	if ( ByteOffset < 0 || ByteCount < 0 ) return FALSE;

	if ( ByteOffset + ByteCount > DataSize( ) ) return FALSE;

    HANDLE FileHandle = ::CreateFile( FilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );

    if ( FileHandle == INVALID_HANDLE_VALUE ) return FALSE;

	DWORD FileSize = ByteCount;

    DWORD BytesWritten;

    if ( ! ::WriteFile( FileHandle, ByteAddress( ) + ByteOffset, FileSize, &BytesWritten, NULL ) ) return FALSE;

    if ( ! ::CloseHandle( FileHandle ) ) return FALSE;;

    if ( (int) BytesWritten != FileSize ) return FALSE;

    return TRUE;
}

int IoAFileBuffer::SynchronousWrite( const char *FilePath ) const
{
    HANDLE FileHandle = ::CreateFile( FilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );

    if ( FileHandle == INVALID_HANDLE_VALUE ) return FALSE;

    DWORD BytesWritten;

    if ( ! ::WriteFile( FileHandle, Address( ), DataSize( ), &BytesWritten, NULL ) ) return FALSE;

    if ( ! ::CloseHandle( FileHandle ) ) return FALSE;;

    if ( (int) BytesWritten != DataSize( ) ) return FALSE;

    return TRUE;
}

int IoAFileBuffer::AsynchronousWrite( const char *FilePath )
{
    if ( IsAsynchronousIOBusy( ) ) return FALSE;  // IO thread already spoken for

    m_FilePath = CString( FilePath );

    ::SetEvent( m_hAsynchDiskIO );

    return TRUE;
}

int IoAFileBuffer::CreateEvents( )
{
    DestroyEvents( );

    int AllEventsCreated = TRUE;

    // Create auto-reset events
    if ( ( m_hDestroyThread = ::CreateEvent( NULL, FALSE, FALSE, NULL ) ) == NULL ) AllEventsCreated =  FALSE;

    // Create manual-reset events
    if ( ( m_hThreadRunning = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) AllEventsCreated =  FALSE;
    if ( ( m_hAsynchDiskIO  = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) AllEventsCreated =  FALSE;
    if ( ( m_hThreadIdle    = ::CreateEvent( NULL, TRUE, TRUE,  NULL ) ) == NULL ) AllEventsCreated =  FALSE;
    if ( ( m_hThreadBusy    = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) AllEventsCreated =  FALSE;

    // If we haven't created all the necessary events, clean up the mess
    if ( ! AllEventsCreated )
    {
        DestroyEvents( );
    }

    return AllEventsCreated;
}

void IoAFileBuffer::DestroyEvents( )
{
    if ( m_hDestroyThread ) { ::CloseHandle( m_hDestroyThread ); m_hDestroyThread = NULL; }
    if ( m_hThreadRunning ) { ::CloseHandle( m_hThreadRunning ); m_hThreadRunning = NULL; }
    if ( m_hAsynchDiskIO  ) { ::CloseHandle( m_hAsynchDiskIO  ); m_hAsynchDiskIO  = NULL; }
    if ( m_hThreadIdle    ) { ::CloseHandle( m_hThreadIdle    ); m_hThreadIdle    = NULL; }
    if ( m_hThreadBusy    ) { ::CloseHandle( m_hThreadBusy    ); m_hThreadBusy    = NULL; }
}

int IoAFileBuffer::CreateThread( )
{
    // Make sure all events are created
    if ( ! CreateEvents( ) ) return FALSE;

    DestroyThread( );

    // Create DMA control thread
//    m_pIOThread = ::AfxBeginThread( IOThreadProc, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED, NULL );
    m_pIOThread = ::AfxBeginThread( IOThreadProc, this, THREAD_PRIORITY_HIGHEST, 0, CREATE_SUSPENDED, NULL );

    if ( m_pIOThread == NULL ) return FALSE;

    m_pIOThread->m_bAutoDelete = FALSE;

    // Start thread execution
    m_pIOThread->ResumeThread( );

    HANDLE EventHandles[ 2 ];

    EventHandles[ 0 ] = m_pIOThread->m_hThread;  // signalled when thread exits
    EventHandles[ 1 ] = m_hThreadRunning;        // signalled when thread starts up

    // Wait for thread exit or thread start event
    DWORD Event = ::WaitForMultipleObjects( 2, EventHandles, FALSE, INFINITE );

    if ( Event != WAIT_OBJECT_0 + 1 ) return FALSE;

    return TRUE;
}

void IoAFileBuffer::DestroyThread( )
{
	// See if pointer to thread object is defined
    if ( m_pIOThread )
    {
		// Signal DestroyThread thread event to force thread to exit
		if ( m_hDestroyThread ) ::SetEvent( m_hDestroyThread );

		// Wait for thread to exit normally (and terminate thread if timeout interval elapsed)
		DWORD Milliseconds  = 5000;        // timeout interval
		DWORD TerminateCode = 0xffffffff;  // exit code if thread forcibly terminated

        // See if thread handle is not signalled => thread is still running
        if ( ::WaitForSingleObject( m_pIOThread->m_hThread, 0 ) == WAIT_TIMEOUT )  // thread handle not signalled
        {
            // Wait for thread handle to be signalled => thread has exited normally
            if ( ::WaitForSingleObject( m_pIOThread->m_hThread, Milliseconds ) == WAIT_TIMEOUT )
            {
                // Wait has timed out, thread has not exited normally
                ::TerminateThread( m_pIOThread->m_hThread, TerminateCode );  // as an absolute last resort, terminate thread
            }
        }

		// Now manually reset DestroyThread event (in case it is not reset by thread wait)
		if ( m_hDestroyThread ) ::ResetEvent( m_hDestroyThread );

		// Destroy thread object (CWinThread destructor closes thread handle to release thread resources)
		delete m_pIOThread;

		m_pIOThread = NULL;
    }
}

int IoAFileBuffer::IOThread( )
{
    ::SetEvent( m_hThreadRunning );

    HANDLE EventHandles[ 2 ];

    EventHandles[ 0 ] = m_hDestroyThread;
    EventHandles[ 1 ] = m_hAsynchDiskIO;

    // Thread command loop (wait for signal DestroyThread or AsynchIO)
    for ( ; ; )
    {
        DWORD Event = ::WaitForMultipleObjects( 2, EventHandles, FALSE, INFINITE );

        // See if AsynchIO signalled
        if ( Event == WAIT_OBJECT_0 + 1 )
        {
            ::ResetEvent( m_hThreadIdle );
            ::SetEvent  ( m_hThreadBusy );

            HANDLE FileHandle = INVALID_HANDLE_VALUE;

            int Done = FALSE;

            for ( ; ; )
            {
                // Open new file for un-buffered write
                FileHandle = ::CreateFile( m_FilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                                           FILE_FLAG_NO_BUFFERING, NULL );

                if ( FileHandle == INVALID_HANDLE_VALUE )
                {
                    break;
                }

                DWORD SectorSize = 512;
    
                DWORD SectorCount = ( (DWORD) DataSize( ) + ( SectorSize - 1 ) ) / SectorSize;

                DWORD ByteCount = SectorCount * SectorSize;  // round up to next sector boundary

                DWORD BytesWritten;

                // Write contents of file buffer to disk
                if ( ! ::WriteFile( FileHandle, Address( ), ByteCount, &BytesWritten, NULL ) )
                {
                    break;
                }

                if ( BytesWritten != ByteCount )
                {
                    break;
                }

                // Close file handle
                if ( ! ::CloseHandle( FileHandle ) )
                {
                    break;
                }

                FileHandle = INVALID_HANDLE_VALUE;  // file handle is closed

                // See if we need to truncate the file to its proper size
                if ( BytesWritten > (DWORD) DataSize( ) )
                {
                    FileHandle = ::CreateFile( m_FilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
                                               FILE_ATTRIBUTE_NORMAL, NULL );

                    if ( FileHandle == INVALID_HANDLE_VALUE )
                    {
                        break;
                    }

                    if ( ::SetFilePointer( FileHandle, (LONG) DataSize( ), NULL, FILE_BEGIN ) == 0xffffffff )
                    {
                        break;
                    }

                    if ( ! ::SetEndOfFile( FileHandle ) )
                    {
                        break;
                    }

                    if ( ! ::CloseHandle( FileHandle ) )
                    {
                        break;
                    }

                    FileHandle = INVALID_HANDLE_VALUE;  // file handle is closed
                }

                Done = TRUE;

				break;
            }

            if ( ! Done )
            {
                // Try not to abandon any open file handle
                if ( FileHandle != INVALID_HANDLE_VALUE )
                {
                    ::CloseHandle( FileHandle );
                }
            }

            ::ResetEvent( m_hThreadBusy );
            ::SetEvent  ( m_hThreadIdle );
        }

        // If DestroyThread signalled, break out of thread command loop
        else if ( Event == WAIT_OBJECT_0 )
        {
            break;
        }

        // Check for wait error
        else if ( Event == WAIT_FAILED )
        {
            return FALSE;
        }

        ::ResetEvent( m_hAsynchDiskIO );
    }

    ::ResetEvent( m_hThreadRunning );

    return TRUE;
}
