#include "All.h"
#include "WholeFileIO.h"

namespace APE
{

CWholeFileIO * CreateWholeFileIO(IAPEIO * pSource, int64 nSize)
{
    // the size should be correct
    ASSERT(nSize == pSource->GetSize());

    int nResult = ERROR_SUCCESS;
    CWholeFileIO * pIO = APE_NULL;

    // make sure we're at the head of the file
    pSource->Seek(0, SeekFileBegin);

    // the the size as 32-bit
    const uint32 n32BitSize = static_cast<uint32>(nSize); // we established it will fit above

    if (nSize == APE_FILE_SIZE_UNDEFINED)
    {
        // simply read until we're at the EOF
        // we read in 64MB chunks, and grow by 64MB and after the final read there's a little extra at the end that's just filled with garbage and not used by the CWholeFileIO object
        int64 nBytes = 0;
        
        // keeping this constant was tested by a user and performed the best
        // these were his results encoding a single big file from a pipe:
        // 2x : 45.5s (nGrowBytes *= 2)
        // 1m : 68.2s
        // 16m : 22.6s
        // 64m : 21.8s
        const int64 nGrowBytes = 64 * APE_BYTES_IN_MEGABYTE; 

        // allocate the first buffer then start the read loop
        unsigned char * pBuffer = new unsigned char [static_cast<size_t>(nGrowBytes)];
        unsigned int nBytesRead = 0;
        while ((pBuffer != APE_NULL) && (pSource->Read(&pBuffer[nBytes], static_cast<unsigned int>(nGrowBytes), &nBytesRead) == ERROR_SUCCESS))
        {
            // increment the position
            nBytes += nBytesRead;

            // check for the end of file
            if (nBytesRead < nGrowBytes)
            {
                // we're at the end of the file, so just stop reading
                break;
            }
            else
            {
                // make a new buffer that contains the new data and read again
                unsigned char * pNewBuffer = new unsigned char [static_cast<size_t>(nBytes + nGrowBytes)];
                if (pNewBuffer != APE_NULL)
                {
                    // copy old buffer
                    memcpy(pNewBuffer, pBuffer, static_cast<size_t>(nBytes));

                    // move new buffer into the buffer pointer
                    delete [] pBuffer;
                    pBuffer = pNewBuffer;
                }
                else
                {
                    // just release the buffer and stop reading (since allocation failed)
                    APE_SAFE_ARRAY_DELETE(pBuffer)
                }
            }
        }

        // create IO object
        if (pBuffer != APE_NULL)
            pIO = new CWholeFileIO(pSource, pBuffer, nBytes);

        // don't delete pBuffer since it's now owned by the file I/O object
    }
    else if (nSize == n32BitSize)
    {
        // create a buffer
        CSmartPtr<unsigned char> spWholeFile;
        try
        {
            spWholeFile.AllocateArray(n32BitSize);
            //spWholeFile.Assign(new unsigned char[0x7FFFFFFF], true); // test for allocating a huge size
        }
        catch (...)
        {
        }

        if (spWholeFile != APE_NULL)
        {
            // read
            unsigned int nBytesRead = 0;
            nResult = pSource->Read(spWholeFile, n32BitSize, &nBytesRead);
            if (nBytesRead < n32BitSize)
                nResult = ERROR_IO_READ;

            if (nResult == ERROR_SUCCESS)
            {
                // create IO object
                pIO = new CWholeFileIO(pSource, spWholeFile, nBytesRead);
                spWholeFile.SetDelete(false); // it's now owned by CWholeFileIO
            }
        }
    }

    return pIO;
}

CWholeFileIO::CWholeFileIO(IAPEIO * pSource, unsigned char * pBuffer, int64 nFileBytes)
{
    // store source
    m_spSource.Assign(pSource);

    // create a buffer
    m_spWholeFile.Assign(pBuffer, true);
    m_nWholeFilePointer = 0;
    m_nWholeFileSize = nFileBytes;
}

CWholeFileIO::~CWholeFileIO()
{
    m_spSource->Close();
    m_spSource.Delete();
}

int CWholeFileIO::Open(const str_utfn *, bool)
{
    return ERROR_SUCCESS;
}

int CWholeFileIO::Close()
{
    return m_spSource->Close();
}

int CWholeFileIO::Read(void * pBuffer, unsigned int nBytesToRead, unsigned int * pBytesRead)
{
    bool bRetVal = true;

    *pBytesRead = 0; // reset

    const int64 nBytesLeft = GetSize() - m_nWholeFilePointer;
    nBytesToRead = APE_MIN(static_cast<unsigned int> (nBytesLeft), nBytesToRead);
    memcpy(pBuffer, &m_spWholeFile[m_nWholeFilePointer], nBytesToRead);
    m_nWholeFilePointer += nBytesToRead;
    *pBytesRead = nBytesToRead;

    // succeed if we actually read data then fail next call
    if ((bRetVal == false) && (*pBytesRead > 0))
        bRetVal = true;

    return bRetVal ? ERROR_SUCCESS : ERROR_IO_READ;
}

int CWholeFileIO::Write(const void * , unsigned int , unsigned int *)
{
    return ERROR_IO_WRITE;
}

int CWholeFileIO::Seek(int64 nPosition, SeekMethod nMethod)
{
    if (nMethod == SeekFileBegin)
    {
        m_nWholeFilePointer = nPosition;
    }
    else if (nMethod == SeekFileCurrent)
    {
        m_nWholeFilePointer += nPosition;
    }
    else if (nMethod == SeekFileEnd)
    {
        m_nWholeFilePointer = GetSize() - abs(nPosition);
    }

    return ERROR_SUCCESS;
}

int CWholeFileIO::SetEOF()
{
    // handle memory buffers
    m_nWholeFileSize = m_nWholeFilePointer;

    // seek in the actual file to where we're at
    m_spSource->Seek(m_nWholeFilePointer, SeekFileBegin);

    // set the EOF in the actual file
    return m_spSource->SetEOF();
}

int64 CWholeFileIO::GetPosition()
{
    return m_nWholeFilePointer;
}

int64 CWholeFileIO::GetSize()
{
    return m_nWholeFileSize;
}

int CWholeFileIO::GetName(str_utfn *)
{
    return ERROR_UNDEFINED;
}

int CWholeFileIO::Create(const str_utfn *)
{
    return ERROR_UNDEFINED;
}

int CWholeFileIO::Delete()
{
    return ERROR_UNDEFINED;
}

}
