/****************************************************************************
 *
 *  DIRECTIO.CPP
 *
 *  routines for reading Standard AVI files
 *
 *  Copyright (c) 1992 - 1995 Microsoft Corporation.  All Rights Reserved.
 *
 *
 * implementation of a disk i/o class designed to optimise
 * sequential reading and writing to disk by using overlapped i/o (for read
 * ahead and write behind) and using large buffers written with no buffering.
 *
 ***************************************************************************/
#include <windows.h>
#include <win32.h>
#include "debug.h"

#include "directio.h"

#ifdef USE_DIRECTIO

//
// implementation of a disk i/o class designed to optimise
// sequential reading and writing to disk by using overlapped i/o (for read
// ahead and write behind) and using large buffers written with no buffering.



// -- CFileStream class methods ---------------------------------------


// initialise to known (invalid) state
CFileStream::CFileStream()
{
        m_State = Invalid;
        m_Position = 0;
        m_hFile = INVALID_HANDLE_VALUE;
#ifdef CHICAGO
        ZeroMemory(&m_qio, sizeof(m_qio));
#endif
}


BOOL
CFileStream::Open(LPTSTR file, BOOL bWrite, BOOL bTruncate)
{
    if (m_State != Invalid) {
        return FALSE;
    }


    // remember this for default streaming mode
    m_bWrite = bWrite;

    DWORD dwAccess = GENERIC_READ;
    if (bWrite) {
        dwAccess |= GENERIC_WRITE;
    }


    // open the file. Always get read access. exclusive open if we
    // are writing the file, otherwise deny other write opens.

    // never truncate the file, since the file may be de-fragmented.

   #ifdef CHICAGO
    DWORD dwFlags = FILE_FLAG_NO_BUFFERING;
   #else
    DWORD dwFlags = FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
   #endif

    m_hFile = CreateFile(file,
                dwAccess,
                (bWrite ? 0 : FILE_SHARE_READ),
                NULL,
                OPEN_ALWAYS,
                dwFlags,
                0);

    if (m_hFile == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

   #ifdef CHICAGO
    if ( ! QioInitialize(&m_qio, m_hFile, THREAD_PRIORITY_HIGHEST)) {
        CloseHandle (m_hFile);
        return FALSE;
    }
   #endif

    // find the bytes per sector that we have to round to for this file
    // -requires finding the 'root path' for this file.
    TCHAR ch[MAX_PATH];
    LPTSTR ptmp;    //required arg

    GetFullPathName(file, sizeof(ch)/sizeof(ch[0]), ch, &ptmp);

    // truncate this to the name of the root directory
    if ((ch[0] == TEXT('\\')) && (ch[1] == TEXT('\\'))) {

        // path begins with  \\server\share\path so skip the first
        // three backslashes
        ptmp = &ch[2];
        while (*ptmp && (*ptmp != TEXT('\\'))) {
            ptmp++;
        }
        if (*ptmp) {
            // advance past the third backslash
            ptmp++;
        }
    } else {
        // path must be drv:\path
        ptmp = ch;
    }

    // find next backslash and put a null after it
    while (*ptmp && (*ptmp != TEXT('\\'))) {
        ptmp++;
    }
    // found a backslash ?
    if (*ptmp) {
        // skip it and insert null
        ptmp++;
        *ptmp = TEXT('\0');
    }

    DWORD dwtmp1, dwtmp2, dwtmp3;
    if (!GetDiskFreeSpace(ch,
        	&dwtmp1,
        	&m_SectorSize,
        	&dwtmp2,
        	&dwtmp3))
	m_SectorSize = 2048;

    // sigh. now init the first buffer

    // sets the right buffer count and size for current mode
    m_State = Stopped;
    if (!EnsureBuffersValid()) {
        return FALSE;
    }
    m_Current = 0;
    m_Position = 0;

    // if asked to truncate the file, we will not actually do so, since this
    // could throw away a de-fragged file. We will however, note that the file
    // size is 0 and use this to affect reading and writing past 'eof' - eg
    // if you write 8 bytes to the beginning of a truncated file, we do not
    // need to read in the first sector beforehand.
    if (bTruncate) {
	m_Size = 0;
    } else {
	// get the current file size
	m_Size = GetFileSize(m_hFile, NULL);
    }

    // all set
    return TRUE;
}




BOOL
CFileStream::Seek(DWORD pos)
{
    // we just record this and go away
    //if (pos < m_Position) {
    //    DPF("seek back by 0x%x to 0x%x\n", m_Position - pos, pos);
    //}

    m_Position  = pos;

    return TRUE;
}

DWORD
CFileStream::GetCurrentPosition()
{
    return m_Position;
}

BOOL
CFileStream::Write(LPBYTE pData, DWORD count, DWORD * pbyteswritten)
{
    *pbyteswritten = 0;


    // error if file not opened
    if (m_State == Invalid) {
        return FALSE;
    }

    DWORD nBytes;

    while (count > 0) {


        // is our current buffer ready to write this data ?
        // (we need to tell it eof pos as well since if eof is
        // in middle of buffer but beyond valid data, ok to write.)

	if ((m_Current < 0) ||
	    (!m_Buffers[m_Current].QueryPosition(m_Position, m_Size))) {

            // commit this buffer if we have changed position beyond it
            if (m_Current >= 0) {
		if (!m_Buffers[m_Current].Commit()) {
		    // file error - abort
		    return FALSE;
		}

		// if we are streaming, then advance to next buffer while
		// current one is writing.
		if (m_State != Stopped) {
		    m_Current = NextBuffer(m_Current);
		}
	    } else {
		m_Current = 0;
	    }

            // make sure that previous operations on this buffer have completed
            if (!m_Buffers[m_Current].WaitComplete()) {
                // i/o error
                return FALSE;
            }
        }

        // we either have a buffer that has already pre-read the sector
        // we start writing to, or we have an idle buffer that
        // will do the pre-read for us
        if (!m_Buffers[m_Current].Write(m_Position, pData, count, m_Size, &nBytes)) {
            return FALSE;
        }

        count -= nBytes;
        pData += nBytes;
        m_Position += nBytes;
        *pbyteswritten += nBytes;
    }

    if (m_Position > m_Size) {
        m_Size = m_Position;
    }

    return TRUE;
}



BOOL
CFileStream::Read(LPBYTE pData, DWORD count, DWORD * pbytesread)
{

    *pbytesread = 0;

    // error if file not opened
    if (m_State == Invalid) {
        return FALSE;
    }

    // force the read to be within the file size limits
    if (m_Position >= m_Size) {
        // all done - nothing read
        return TRUE;
    } else {
        count = min(count, (m_Size - m_Position));
    }

    BOOL bDoReadAhead = FALSE;
    DWORD nBytes;

    while (count > 0) {

        // is data within current buffer
        if ((m_Current < 0) ||
	    (!m_Buffers[m_Current].QueryPosition(m_Position, m_Size))) {

	    if (m_Current >= 0) {
		// commit this buffer if we have changed position beyond it
		if (!m_Buffers[m_Current].Commit()) {
		    // file error - abort
		    return FALSE;
		}

		// advance to next buffer (if streaming)
		if (m_State == Writing) {
		    m_Current = NextBuffer(m_Current);
		} else if (m_State == Reading) {

		    // smart read-ahead strategy: try to find in existing
		    // buffers, and only issue a read-ahead if we take the
		    // highest buffer
		    int n = NextBuffer(m_Current);
		    m_Current = -1;
		    for (int i = 0; i < m_NrValid; i++) {
			if (m_Buffers[n].QueryPosition(m_Position, m_Size)) {
			    m_Current = n;
			    break;
			}
			n = NextBuffer(n);
		    }
		    if (m_Current < 0) {
			// read-ahead is messed up because we have made too big
			// a seek for the current buffer size
			// Best thing is to use the lowest buffer (should be the
			// one after the highest, and to restart readaheads with
			// this position).
			m_Current = NextBuffer(m_HighestBuffer);
			m_HighestBuffer = m_Current;
			DPF("using idle %d\n", m_Current);

		    }

		    if (m_Current == m_HighestBuffer) {
			bDoReadAhead = TRUE;
		    }
		}
	    } else {
		m_Current = 0;
		if (m_Current == m_HighestBuffer) {
		    bDoReadAhead = TRUE;
		}
	    }



            // make sure that previous operations on this buffer have completed
            if (!m_Buffers[m_Current].WaitComplete()) {
                // i/o error
                return FALSE;
            }
        }

        // now we have a buffer that either contains the data we want, or
        // is idle and ready to fetch it.
        if (!m_Buffers[m_Current].Read(m_Position, pData, count, m_Size, &nBytes)) {
            return FALSE;
        }

        count -= nBytes;
        pData += nBytes;
        m_Position += nBytes;
        *pbytesread += nBytes;

        // do read ahead now if necessary (the Read() call may have required
        // a seek and read if the data was not in the buffer, so delay the
        // read-ahead until after it has completed).
        if (bDoReadAhead) {

            // remember that this new buffer contains the highest position
            // -- we should issue another readahead when we start using this
            // buffer.

            m_HighestBuffer = NextBuffer(m_Current);

            DWORD p = m_Buffers[m_Current].GetNextPosition();

            m_Buffers[m_HighestBuffer].ReadAhead(p, m_Size);

            bDoReadAhead = FALSE;
        }
    }

    return TRUE;
}


// set the right buffer size and count for current mode
BOOL
CFileStream::EnsureBuffersValid()
{
    if (m_State == Invalid) {
        // file not opened
        return FALSE;
    }

   #ifdef CHICAGO
    if (m_State == Writing) {
        m_NrValid = 4;          // total 256k
    } else if (m_State == Reading) {
        m_NrValid = 4;		// total 256k
    } else {
        m_NrValid = 1;		// total 64k
    }

    int size = (64 * 1024);
   #else
    if (m_State == Writing) {
        m_NrValid = 2;		// total 512k
    } else if (m_State == Reading) {
        m_NrValid = 4;		// total 256k
    } else {
        m_NrValid = 1;		// total 64k
    }

    int size = (64 * 1024);
    if (m_State == Writing)
        size = (256 * 1024);
   #endif

    int i =0;

    Assert(m_NrValid <= NR_OF_BUFFERS);

    // init valid buffers
    for (; i < m_NrValid; i++) {
       #ifdef CHICAGO
        if (!m_Buffers[i].Init(m_SectorSize, size, &m_qio)) {
       #else
        if (!m_Buffers[i].Init(m_SectorSize, size, m_hFile)) {
       #endif
            return FALSE;
        }
    }

    // discard others
    for (; i < NR_OF_BUFFERS; i++) {
        m_Buffers[i].FreeMemory();
    }
    return TRUE;
}

BOOL
CFileStream::StartStreaming()
{
    if (m_bWrite) {
	return StartWriteStreaming();
    } else {
	return StartReadStreaming();
    }
}

BOOL
CFileStream::StartWriteStreaming()
{
    m_State = Writing;

    if (!EnsureBuffersValid()) {
        return FALSE;
    }

    return TRUE;
}

BOOL
CFileStream::StartReadStreaming()
{
    // commit the current buffer
    if (!m_Buffers[m_Current].Commit()) {
        return FALSE;
    }

    m_State = Reading;

    if (!EnsureBuffersValid()) {
        return FALSE;
    }

    // start read-ahead on buffer 0 - read from current position
    // (tell buffer the eof point so it won't bother reading beyond it)
    // remember that this is the highest current buffer - when we start using
    // this buffer it is time to issue the next readahead (this allows for
    // seeks backwards and forwards within the valid buffers without upsetting
    // the read-aheads).

    m_HighestBuffer = 0;
    m_Buffers[0].ReadAhead(m_Position, m_Size);

    // set m_Current invalid: this ensures that we will wait for read-ahead
    // to complete before getting data, and that when we start using it, we
    // will issue the next read-ahead.
    m_Current = -1;

    return TRUE;

}

BOOL
CFileStream::StopStreaming()
{
    // complete all i/o
    if (!CommitAndWait()) {
        return FALSE;
    }

    m_Current = 0;
    m_State = Stopped;

    // recalc buffer size/count for new mode
    if (!EnsureBuffersValid()) {
        return FALSE;
    }

    return TRUE;
}


// wait for all transfers to complete.
BOOL CFileStream::CommitAndWait()
{
    // write current buffer
    //
    if (!m_Buffers[m_Current].Commit())
        return FALSE;

   #ifdef CHICAGO
    // flush all buffers that have been queued
    //
    //QioCommit (&m_qio);
   #endif

    // wait for all buffers to complete
    for (int i = 0; i < m_NrValid; i++) {

        if (!m_Buffers[i].WaitComplete()) {
            return FALSE;
        }
    }
    // no need to reset m_Current
    return TRUE;
}


// destructor will call Commit()
CFileStream::~CFileStream()
{
    if (m_hFile != INVALID_HANDLE_VALUE) {
        CommitAndWait();

       #ifdef CHICAGO
        QioShutdown (&m_qio);
       #endif

        CloseHandle(m_hFile);
    }
}


// --- CFileBuffer methods -----------------------------------------



// initiate to an invalid (no buffer ready) state
CFileBuffer::CFileBuffer()
{
    m_pBuffer = NULL;
    m_pAllocedMem = NULL;
    m_State = Invalid;
#ifdef CHICAGO
    m_pqio = NULL;
#endif

}

// allocate memory and become idle.
BOOL
#ifdef CHICAGO
CFileBuffer::Init(DWORD nBytesPerSector, DWORD buffersize, LPQIO pqio)
#else
CFileBuffer::Init(DWORD nBytesPerSector, DWORD buffersize, HANDLE hfile)
#endif
{
    if (m_State != Invalid) {

        if ((nBytesPerSector == m_BytesPerSector) &&
            (buffersize == RoundSizeToSector(m_TotalSize))) {

                // we're there already
                return TRUE;
        }

        // discard what we have
        FreeMemory();
    }

    Assert(m_State == Invalid);

    // round up RAWIO_SIZE to a multiple of sector size
    m_BytesPerSector = nBytesPerSector;
    m_TotalSize = (DWORD) RoundSizeToSector(buffersize);

    m_DataLength = 0;
    m_State = Idle;
    m_bDirty = FALSE;

   #ifdef CHICAGO

    m_pqio = pqio;
    m_pAllocedMem = (unsigned char *)VirtualAlloc (NULL, m_TotalSize,
                              MEM_RESERVE | MEM_COMMIT,
                              PAGE_READWRITE);
    if (m_pAllocedMem == NULL)
        return FALSE;

   #else

    m_hFile = hfile;
    m_pAllocedMem = new BYTE[m_TotalSize];

    if (m_pAllocedMem == NULL)
        return FALSE;

    m_Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!m_Overlapped.hEvent) {
        delete[] m_pAllocedMem;
        return FALSE;
    }

   #endif

   // this is where my naming scheme falls down. RoundPos rounds down, and
   // RoundSize rounds up. to correctly align the buffer and stay within it,
   // we need to round the start address up and the size down.

   // round start address up to sector size
   m_pBuffer = (LPBYTE) RoundSizeToSector((LONG_PTR) m_pAllocedMem);
   // remove rounding from size - and round it again!
   m_TotalSize = (DWORD) RoundPosToSector(m_TotalSize - (m_pBuffer - m_pAllocedMem));



    return TRUE;
}


// revert to invalid state (eg when streaming stops)
void
CFileBuffer::FreeMemory()
{
    if (m_State == Idle) {
        Commit();
    }
    if (m_State == Busy) {
        WaitComplete();
    }

    if (m_State != Invalid) {

       #ifdef CHICAGO

        VirtualFree (m_pAllocedMem, 0, MEM_RELEASE);
        m_pBuffer = NULL;
	m_pAllocedMem = NULL;

       #else

        CloseHandle(m_Overlapped.hEvent);
        delete[] m_pAllocedMem;

       #endif

        m_State = Invalid;
    }
}

// calls commit if dirty before freeing everything.
CFileBuffer::~CFileBuffer()
{
    FreeMemory();
}


// does this position occur anywhere within the current buffer ?
// needs to know current eof for some cases (writing beyond eof
// if eof is within this buffer is ok to this buffer).
//
// we can use this buffer if:
// 1. if the buffer is empty and the write is past eof (where eof is rounded
//    to a sector boundary).
//
// 2. if the start position is within the current m_DataLength
//
// 3. if eof is within the buffer and the write is past eof
//
// all reads are limited by the caller to be within the file limits, so
// the reading case is covered by 2 above (1 and 3 will not occur).
//
// all other cases will require the read of data that is not in the buffer.
// or the (early) committing of data in the buffer
//
BOOL
CFileBuffer::QueryPosition(DWORD pos, DWORD filesize)
{

    if (m_State == Invalid) {
        return FALSE;
    }

    // round filesize to sector boundary
    filesize = (DWORD) RoundSizeToSector(filesize);

    if (pos >= filesize) {

        // write is past eof. ok if buffer empty or if buffer contains
        // eof (and has space in it)
        if ((m_DataLength == 0) ||
            ((m_Position + m_DataLength == filesize) &&
             (m_DataLength < m_TotalSize))) {
                return TRUE;
        }

        // we have data that needs to be flushed before we can do this
        return FALSE;
    } else {

        if ((pos >= m_Position) &&
            (pos < m_Position + m_DataLength)) {

                // we have this byte
                return TRUE;
        }

        // we don't have this byte of valid data. we have some other.
        //
        // you might think that if the write begins on a sector boundary, and
        // this buffer's data is not dirty you could permit this without a
        // pre-read - but we don't know yet where the write will end, and if
        // it ends mid-sector and not past current eof, we will need to
        // read that sector in.
        return FALSE;
    }
}



// write some data to buffer (must be committed separately)
// filesize parameter is the file size before this write, and is used to
// control what we do with the partial sector at beginning and end
// -if not past current eof, we need to read the current sector before
// writing to it.
BOOL
CFileBuffer::Write(
    DWORD pos,
    LPBYTE pData,
    DWORD count,
    DWORD filesize,
    DWORD * pbytesWritten)
{

    // remember for later (during commit)
    m_FileLength = filesize;

    *pbytesWritten = 0;

    if (m_State != Idle) {
        if (!WaitComplete()) {
            return FALSE;
        }
    }

    if (m_State == Invalid) {
        // naughty boy!
        return FALSE;
    }


    // do we need to commit the current contents or read anything ?

    // if there is data, and the start position is not within the valid data
    // range, then flush this lot. note that we count the region from
    // end of valid data to end of actual buffer as valid data if the eof
    // is within this buffer.
    if ((m_DataLength > 0) &&
        ((pos < m_Position) ||
        (pos >= m_Position + m_TotalSize) ||
        ((pos >= m_Position + m_DataLength) &&
         ((m_Position + m_DataLength) < filesize)))) {

            // we're not ok - need to flush current contents
            if (!Commit() || !WaitComplete()) {
                return FALSE;
            }
            m_DataLength = 0;
    }

    // if empty (or we just flushed it), we can start at the beginning
    if (m_DataLength == 0) {
        m_Position = (DWORD) RoundPosToSector(pos);

        // do we need to read the partial sector?
        if ((pos < RoundSizeToSector(filesize))  &&
            (pos % m_BytesPerSector != 0)) {

            // yes - write starts partway through a valid sector
	    m_DataLength = m_BytesPerSector;
            if (!ReadIntoBuffer(0, m_Position, m_BytesPerSector) ||
                !WaitComplete()) {
                    return FALSE;
            }
        }
    }

    // we can start the data. now what about the end?
    // if it all fits within the buffer, and it ends mid-sector and the
    // final sector is within the file length but not currently in the
    // buffer, we will need to pre-read the final buffer

    if ((pos + count) < (m_Position + m_TotalSize)) {

        if ((pos + count) % m_BytesPerSector) {

            // we have to write a partial sector - is it past eof or within
            // valid region ?
            if ((pos+count > m_Position + m_DataLength) &&
                (pos+count < filesize)) {

                    // yes need to read partial sector
                    DWORD sec = (DWORD) RoundPosToSector(pos+count);

		    // need to temporarily set m_DataLength
		    // to the amount read so that WaitComplete can check
		    // its ok
		    m_DataLength = m_BytesPerSector;

                    if (!ReadIntoBuffer(
                        sec - m_Position,       // index in buffer
                        sec,                    // position in file
                        m_BytesPerSector) ||
                        !WaitComplete()) {
                            return FALSE;
                    }
		    // set size correctly again
                    m_DataLength = (sec - m_Position) + m_BytesPerSector;
            }
        }
    }

    // now we can stuff the data in
    int index = pos - m_Position;
    *pbytesWritten = min(count,  m_TotalSize - index);

    CopyMemory(
        &m_pBuffer[index],
        pData,
        *pbytesWritten);

    // adjust data length
    if ((index + *pbytesWritten) > m_DataLength) {
	m_DataLength = (DWORD) RoundSizeToSector(index + *pbytesWritten);
    }

    m_bDirty = TRUE;

    return TRUE;
}




// read data from buffer (will seek and read if necessary first)
BOOL
CFileBuffer::Read(
    DWORD pos,
    LPBYTE pData,
    DWORD count,
    DWORD filelength,
    DWORD * pBytesRead)
{

    Assert(m_State == Idle);

    // remember this for read completion checking
    m_FileLength = filelength;

    *pBytesRead = 0;

    if ((pos < m_Position) ||
        (pos >= m_Position + m_DataLength)) {

        // not in current buffer - flush current contents if dirty
        if (!Commit() || !WaitComplete()) {
            return FALSE;
        }

        m_Position = (DWORD) RoundPosToSector(pos);

        // remember if we round the start down, we also need to increase
        // the length (as well as rounding it up at the other end)
        // force a minimum read size to avoid lots of single sectors
        m_DataLength = count + (pos - m_Position);
        m_DataLength = max(MIN_READ_SIZE, m_DataLength);

        m_DataLength = (DWORD) RoundSizeToSector(m_DataLength);

        m_DataLength = min(m_DataLength, m_TotalSize);

        if (!ReadIntoBuffer(0, m_Position, m_DataLength) ||
	    !WaitComplete()) {
            return FALSE;
        }
    }

    // we have (at least the start part of) the data in the buffer

    int offset = pos - m_Position;
    count = min(count, m_DataLength - offset);
    CopyMemory(pData, &m_pBuffer[offset], count);

    *pBytesRead = count;

    return TRUE;
}


// what is the first file position after this buffer's valid data
// ---return this even if still busy reading it
DWORD
CFileBuffer::GetNextPosition()
{
    if ((m_State == Invalid) || (m_DataLength == 0)) {
        return 0;
    } else {
        return m_Position + m_DataLength;
    }
}

// initiate a read-ahead
void
CFileBuffer::ReadAhead(DWORD start, DWORD filelength)
{
    if (m_State != Idle) {
        if (!CheckComplete()) {
            return;
        }
    }

    // we may already hold this position
    if (QueryPosition(start, filelength)) {
	return;
    }

    m_FileLength = filelength;

    if (m_bDirty) {

        // current data needs to be flushed to disk.
        // we should initiate this, but we can't wait for
        // it to complete, so we won't do the read-ahead
        Commit();
        return;
    }

    m_Position = (DWORD) RoundPosToSector(start);
    m_DataLength = min((DWORD) RoundSizeToSector(filelength - m_Position),
                        m_TotalSize);

    ReadIntoBuffer(0, m_Position, m_DataLength);
    // no wait - this is an async readahead.

}



// initiate the i/o from the buffer
BOOL
CFileBuffer::Commit()
{
    if ((m_State != Idle) || (!m_bDirty)) {
        return TRUE;
    }

#ifndef CHICAGO
    DWORD nrWritten;
#endif

   #ifdef CHICAGO

    m_State = Busy;

    m_qiobuf.dwOffset = m_Position;
    m_qiobuf.lpv = m_pBuffer;
    m_qiobuf.cb = m_DataLength;
    m_qiobuf.cbDone = 0;
    m_qiobuf.bWrite = TRUE;
    m_qiobuf.dwError = ERROR_IO_PENDING;

    QioAdd (m_pqio, &m_qiobuf);

   #else

    ResetEvent(m_Overlapped.hEvent);

    m_State = Busy;

    //start from m_Position
    m_Overlapped.Offset = m_Position;
    m_Overlapped.OffsetHigh = 0;


    if (WriteFile(m_hFile, m_pBuffer, m_DataLength,
            &nrWritten, &m_Overlapped)) {

	DPF(("instant completion"));

        // if it completed already, then sort out the new position
        if (nrWritten != m_DataLength) {
	    DPF("commit- bad length %d not %d", nrWritten, m_DataLength);
            return FALSE;
        }
        m_bDirty = FALSE;
        m_State = Idle;
    } else {
        // should be pending
        if (GetLastError() != ERROR_IO_PENDING) {

	    // no longer busy
	    m_State = Idle;

	    DPF("commit error %d", GetLastError());

            return FALSE;
        }
    }

   #endif

    // we must do this here, since WaitComplete could complete a
    // partial read that would leave the buffer dirty.
    // we are safe since the buffer will remain Busy until this is
    // actually TRUE. (if we fail to write to the disk then the
    // file state is guaranteed messed up).
    m_bDirty = FALSE;

    return TRUE;


}

// wait for any pending commit or read to complete and check for errors.
BOOL
CFileBuffer::WaitComplete()
{
    if (m_State == ErrorOccurred) {

        // the i/o has completed in error but we haven't been able to
        // report the fact yet
        m_State = Idle;
        return FALSE;
    }

    if (m_State == Busy) {
        DWORD actual;

	// no longer busy
        m_State = Idle;

       #ifdef CHICAGO
        if ( ! QioWait (m_pqio, &m_qiobuf, TRUE))
            return FALSE;
        actual = m_qiobuf.cbDone;
       #else
        if (!GetOverlappedResult(m_hFile, &m_Overlapped, &actual, TRUE)) {
	    DPF("WC: GetOverlapped failed %d", GetLastError());
            return FALSE;
        }
       #endif
        if (actual != m_DataLength) {

	    // rounding to sector size may have taken us past eof
	    if (m_Position + actual != m_FileLength) {
		DPF("WC: actual wrong (%d not %d)", actual, m_DataLength);
		return FALSE;
	    }
        }
    }

    return TRUE;

}

// non-blocking check to see if async io is complete
BOOL
CFileBuffer::CheckComplete()
{
    if (m_State == Idle) {
        return TRUE;
    }

    if (m_State != Busy) {
        return FALSE;   // invalid or error
    }

   #ifdef CHICAGO

    if (QioWait(m_pqio, &m_qiobuf, FALSE))
        return FALSE;

    else if (m_qiobuf.dwError == 0) {
        m_State = Idle;
        return TRUE;
        }

    m_State = ErrorOccurred;
    return FALSE;

   #else

    DWORD actual;

    if (GetOverlappedResult(m_hFile, &m_Overlapped, &actual, FALSE)) {

        if ((actual == m_DataLength) ||
            (actual + m_Position == m_FileLength)) {
                m_State = Idle;
                return TRUE;
        }

    } else if (GetLastError() == ERROR_IO_INCOMPLETE) {
        // still busy
        return FALSE;
    }

    // some error state occurred - this must be reported by WaitComplete()
    m_State = ErrorOccurred;
    DPF("CheckComplete error %d", GetLastError());
    return FALSE;

   #endif
}



// initiates an async read request into the buffer (can be an insertion into
// middle of buffer rather than a complete buffer fill - and so will not
// adjust m_Position or m_DataLength). reads count bytes
// offset bytes from the start of the buffer, pos bytes from the start of the
// file. Assumes necessary rounding of length and position has already happened.
BOOL
CFileBuffer::ReadIntoBuffer(int offset, DWORD pos, DWORD count)
{

    Assert(m_State == Idle);

#ifndef CHICAGO
    DWORD nrRead;
#endif

   #ifdef CHICAGO

    m_State = Busy;

    m_qiobuf.dwOffset = pos;
    m_qiobuf.lpv = (LPVOID)(m_pBuffer + offset);
    m_qiobuf.cb = count;
    m_qiobuf.cbDone = 0;
    m_qiobuf.bWrite = FALSE;
    m_qiobuf.dwError = ERROR_IO_PENDING;

    // if this read is not sector aligned, we cannot do it
    // in async in chicago, so do it right now!
    //
    if ((count & 511) || (pos & 511) || (offset & 511))
    {
        DWORD dwOff;

        m_qiobuf.bPending = FALSE;

	DPF("%s %X bytes (non-aligned) at %08X into %08X\r\n", m_qiobuf.bWrite ? "Writing" : "Reading", m_qiobuf.cb, m_qiobuf.dwOffset, m_qiobuf.lpv);
	
        dwOff = SetFilePointer (m_pqio->hFile, m_qiobuf.dwOffset, NULL, FILE_BEGIN);
        if (dwOff != m_qiobuf.dwOffset)
        {
            m_qiobuf.dwError = GetLastError();
	    DPF("avifile32 non-aligned seek error %d", m_qiobuf.dwError);
            return FALSE;
        }
        else if ( ! ReadFile (m_pqio->hFile, m_qiobuf.lpv, m_qiobuf.cb,
                              &m_qiobuf.cbDone, NULL) ||
                  (m_qiobuf.cbDone != m_qiobuf.cb))
        {
            m_qiobuf.dwError = GetLastError ();
	    DPF("avifile32 non-aligned read error %d", m_qiobuf.dwError);
            return FALSE;
        }
        m_State = Idle;
    }
    else
       return QioAdd (m_pqio, &m_qiobuf);

   #else

    ResetEvent(m_Overlapped.hEvent);

    m_State = Busy;


    //start from pos
    m_Overlapped.Offset = pos;
    m_Overlapped.OffsetHigh = 0;


    if (ReadFile(m_hFile, &m_pBuffer[offset], count,
            &nrRead, &m_Overlapped)) {

        m_State = Idle;

	DPF(("instant completion"));

        // if it completed already, then sort out the new position
        if (nrRead != count) {

	    // rounding to sector size may have taken us past eof -
	    // in this case we must still ask for the full sector, but
	    // we will be told about the actual size
	    if (m_Position + nrRead != m_FileLength) {
		DPF("ReadInto: actual wrong");
		return FALSE;
	    }
        }
    } else {
        // should be pending
        if (GetLastError() != ERROR_IO_PENDING) {
            DPF("read failed %d\n", GetLastError());

	    // no longer busy
	    m_State = Idle;
	    DPF("ReadInto failed %d", GetLastError());
            return FALSE;
        }
    }
   #endif
    return TRUE;
}


#endif // USE_DIRECTIO
