/////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2005-2017 Dawson Dean
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/////////////////////////////////////////////////////////////////////////////
//
// Memory Block I/O
//
// This implements a generic block-device interface that reads and
// writes memory buffers.
//
// This always implements IO with synchronous operations.
/////////////////////////////////////////////////////////////////////////////

#include "osIndependantLayer.h"
#include "config.h"
#include "log.h"
#include "debugging.h"
#include "memAlloc.h"
#include "refCount.h"
#include "threads.h"
#include "stringLib.h"
#include "stringParse.h"
#include "queue.h"
#include "jobQueue.h"
#include "rbTree.h"
#include "url.h"
#include "blockIO.h"

FILE_DEBUGGING_GLOBALS(LOG_LEVEL_DEFAULT, 0);

#define MAX_SANE_MEMORY_BLOCK_IO_SIZE   10000000

/////////////////////////////////////////////////////////////////////////////
class CMemoryBlockIO : public CAsyncBlockIO {
public:
    CMemoryBlockIO();
    virtual ~CMemoryBlockIO();
    NEWEX_IMPL()

    // CAsyncBlockIO
    virtual void Close();
    virtual ErrVal Resize(int64 newLength);

    // CDebugObject
    virtual ErrVal CheckState();

protected:
    friend class CMemoryIOSystem;

    // CAsyncBlockIO
    virtual void ReadBlockAsyncImpl(CIOBuffer *pBuffer);
    virtual void WriteBlockAsyncImpl(CIOBuffer *pBuffer);

    bool    m_fBufferAllocatedFromMemory;
    int32   m_BufferPhysicalSize;

    char    *m_pBuffer;
}; // CMemoryBlockIO




/////////////////////////////////////////////////////////////////////////////
class CMemoryIOSystem : public CIOSystem {
public:
    CMemoryIOSystem();
    virtual ~CMemoryIOSystem();
    NEWEX_IMPL()

    // CIOSystem
    virtual ErrVal Shutdown();
    virtual ErrVal OpenBlockIO(
                        CParsedUrl *pUrl,
                        int32 options,
                        CAsyncBlockIOCallback *pCallback);
    virtual int32 GetDefaultBytesPerBlock() { return(1024); }
    virtual int64 GetIOStartPosition(int64 pos) { return(pos); }
    virtual int32 GetBlockBufferAlignment() { return(0); }

    // CDebugObject
    virtual ErrVal CheckState();
}; // CMemoryIOSystem




/////////////////////////////////////////////////////////////////////////////
//
// [InitializeMemoryBlockIO]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
InitializeMemoryBlockIO() {
    g_pMemoryIOSystem = newex CMemoryIOSystem;
    if (NULL == g_pMemoryIOSystem) {
        returnErr(EFail);
    }
    returnErr(ENoErr);
} // InitializeMemoryBlockIO.




/////////////////////////////////////////////////////////////////////////////
//
// [CMemoryBlockIO]
//
/////////////////////////////////////////////////////////////////////////////
CMemoryBlockIO::CMemoryBlockIO() {
    m_MediaType = MEMORY_MEDIA;
    m_fSeekable = true;

    m_pBuffer = NULL;
    m_BufferPhysicalSize = 0;
    m_fBufferAllocatedFromMemory = false;
} // CMemoryBlockIO.




/////////////////////////////////////////////////////////////////////////////
//
// [~CMemoryBlockIO]
//
/////////////////////////////////////////////////////////////////////////////
CMemoryBlockIO::~CMemoryBlockIO() {
    // Do NOT deallocate the memory buffer. That is done as
    // part of an explicit delete call.
    m_pBuffer = NULL;
} // ~CMemoryBlockIO.






/////////////////////////////////////////////////////////////////////////////
//
// [Resize]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CMemoryBlockIO::Resize(int64 newLength) {
    ErrVal err = ENoErr;
    AutoLock(m_pLock);
    RunChecks();

    DEBUG_LOG("CMemoryBlockIO::Resize: new length = %ld, old maxLength = %ld.",
        newLength, m_BufferPhysicalSize);

    if (newLength >= MAX_SANE_MEMORY_BLOCK_IO_SIZE) {
        gotoErr(EFail);
    }

    if (newLength <= m_BufferPhysicalSize) {
        m_MediaSize = newLength;
        err = ENoErr;
        gotoErr(err);
    }

    if ((!(m_BlockIOFlags & CAsyncBlockIO::RESIZEABLE))
        || (!m_fBufferAllocatedFromMemory)) {
        gotoErr(EFail);
    }

    DEBUG_LOG("CMemoryBlockIO::Resize: Allocating new larger buffer.");

    m_pBuffer = (char *) g_MainMem.Realloc(m_pBuffer, (int32) newLength);
    if (NULL == m_pBuffer) {
        gotoErr(EFail);
    }

    m_MediaSize = newLength;
    m_BufferPhysicalSize = (int32) newLength;

abort:
    returnErr(err);
} // Resize.








/////////////////////////////////////////////////////////////////////////////
//
// [Close]
//
/////////////////////////////////////////////////////////////////////////////
void
CMemoryBlockIO::Close() {
    AutoLock(m_pLock);
    RunChecksOnce();

    DEBUG_LOG("CMemoryBlockIO::Close.");

    CAsyncBlockIO::Close();

    if ((m_pBuffer) && (m_fBufferAllocatedFromMemory)) {
        g_MainMem.Free(m_pBuffer);
        m_pBuffer = NULL;
    }
} // Close.






/////////////////////////////////////////////////////////////////////////////
//
// [WriteBlockAsyncImpl]
//
// Writing may expand the buffer if that is allowed.
/////////////////////////////////////////////////////////////////////////////
void
CMemoryBlockIO::WriteBlockAsyncImpl(CIOBuffer *pBuffer) {
    ErrVal err = ENoErr;
    char *destPtr;
    int32 newLength;
    AutoLock(m_pLock);
    RunChecks();

    DEBUG_LOG("CMemoryBlockIO::WriteBlockAsyncImpl.");

    if (NULL == m_pBuffer) {
        return;
    }

    // If the requested IO is too big, then we either
    // expand the buffer or clip the IO.
    if ((pBuffer->m_PosInMedia + pBuffer->m_NumValidBytes) > m_MediaSize) {
        DEBUG_LOG("CMemoryBlockIO::WriteBlockAsyncImpl. Expanding the memory buffer.");
        DEBUG_LOG("CMemoryBlockIO::WriteBlockAsyncImpl. m_MediaSize = %d", m_MediaSize);
        DEBUG_LOG("CMemoryBlockIO::WriteBlockAsyncImpl. pBuffer->m_NumValidBytes = %d", pBuffer->m_NumValidBytes);
        DEBUG_LOG("CMemoryBlockIO::WriteBlockAsyncImpl. pBuffer->m_PosInMedia = %d", (int32) pBuffer->m_PosInMedia);

        if ((pBuffer->m_PosInMedia + pBuffer->m_NumValidBytes) <= m_BufferPhysicalSize) {
            m_MediaSize = pBuffer->m_PosInMedia + pBuffer->m_NumValidBytes;
        } else if (m_BlockIOFlags & RESIZEABLE) {
            newLength = (int32) (pBuffer->m_PosInMedia + pBuffer->m_NumValidBytes);
            if (newLength >= MAX_SANE_MEMORY_BLOCK_IO_SIZE) {
                gotoErr(EFail);
            }

            m_pBuffer = (char *) (g_MainMem.Realloc(m_pBuffer, newLength));
            if (NULL == m_pBuffer) {
                gotoErr(EFail);
            }

            m_BufferPhysicalSize = newLength;
            m_MediaSize = pBuffer->m_PosInMedia + pBuffer->m_NumValidBytes;
        }    // resizing the buffer
        else {
            gotoErr(EFail);
        } // clipping the IO.
    } // handling a too-big IO.

    // Do the actual IO.
    destPtr = m_pBuffer + pBuffer->m_PosInMedia;
    memcpy(
        destPtr,
        pBuffer->m_pLogicalBuffer,
        pBuffer->m_NumValidBytes);

abort:
    // Mark the block as complete.
    FinishIO(pBuffer, err, pBuffer->m_NumValidBytes);
} // WriteBlockAsyncImpl.







/////////////////////////////////////////////////////////////////////////////
//
// [ReadBlockAsyncImpl]
//
/////////////////////////////////////////////////////////////////////////////
void
CMemoryBlockIO::ReadBlockAsyncImpl(CIOBuffer *pBuffer) {
    ErrVal err = ENoErr;
    char *srcPtr;
    int32 actualIOSize;
    AutoLock(m_pLock);
    RunChecks();

    DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl.");

    if ((NULL == m_pBuffer) || (NULL == m_pIOSystem)) {
        gotoErr(EFail);
    }

    DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl. pBuffer->m_PosInMedia = %d", pBuffer->m_PosInMedia);
    DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl. pBuffer->m_BufferSize = %d", pBuffer->m_BufferSize);
    DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl. m_MediaSize = %d", m_MediaSize);

    // If the requested IO is too big, then we clip the io.
    actualIOSize = pBuffer->m_BufferSize;
    if ((pBuffer->m_PosInMedia + actualIOSize) > m_MediaSize) {
        actualIOSize = (int32) (m_MediaSize - pBuffer->m_PosInMedia);
        DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl. Clipping IO. actualIOSize = %d", actualIOSize);
    }

    // Don't read more than 1 block per IO.
    if (actualIOSize > m_pIOSystem->GetDefaultBytesPerBlock()) {
        actualIOSize = m_pIOSystem->GetDefaultBytesPerBlock();
        DEBUG_LOG("CMemoryBlockIO::ReadBlockAsyncImpl. Growing to blocksize. actualIOSize = %d", actualIOSize);
    }

    if (actualIOSize < 0) {
        actualIOSize = 0;
        gotoErr(EEOF);
    }

    // Do the actual IO.
    srcPtr = m_pBuffer + pBuffer->m_PosInMedia;
    memcpy(pBuffer->m_pLogicalBuffer, srcPtr, actualIOSize);

abort:
    // Mark the block as complete.
    FinishIO(pBuffer, err, actualIOSize);
} // ReadBlockAsyncImpl.







/////////////////////////////////////////////////////////////////////////////
//
// [CheckState]
//
// This does not check checksums, because we do not know
// if the client is currently modifying a buffer (and hence
// the checksum would be invalid). Checksums must be explicitly
// called by the client.
/////////////////////////////////////////////////////////////////////////////
ErrVal
CMemoryBlockIO::CheckState() {
    ErrVal err = ENoErr;
    AutoLock(m_pLock);

    err = CAsyncBlockIO::CheckState();
    if (err) {
        returnErr(EFail);
    }

    if ((MEMORY_MEDIA != m_MediaType)
        || (NULL == m_pIOSystem)
        || (!m_fSeekable)
        || (NULL == m_pBuffer)
        || (m_BufferPhysicalSize < 0)
        || (m_MediaSize > m_BufferPhysicalSize)) {
        gotoErr(EFail);
    }

    // Don't check the active connection list, since we don't hold
    // any lock.

    if (m_fBufferAllocatedFromMemory) {
        int32 realBufSize;

        realBufSize = g_MainMem.GetPtrSize(m_pBuffer);
        if ((realBufSize < 0)
            || (realBufSize < m_MediaSize)
            || (realBufSize != m_BufferPhysicalSize)) {
            gotoErr(EFail);
        }

        err = g_MainMem.CheckPtr(m_pBuffer);
        if (err) {
            gotoErr(err);
        }
    }

abort:
    returnErr(err);
} // CheckState.





/////////////////////////////////////////////////////////////////////////////
//
// [CMemoryIOSystem]
//
/////////////////////////////////////////////////////////////////////////////
CMemoryIOSystem::CMemoryIOSystem() {
} // CMemoryIOSystem.




/////////////////////////////////////////////////////////////////////////////
//
// [~CMemoryIOSystem]
//
/////////////////////////////////////////////////////////////////////////////
CMemoryIOSystem::~CMemoryIOSystem() {
} // ~CMemoryIOSystem.




/////////////////////////////////////////////////////////////////////////////
//
// [Shutdown]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CMemoryIOSystem::Shutdown() {
    ErrVal err = ENoErr;

    // If we never lazily initialized the IO system, then there is nothing
    // to do.
    if (!m_fInitialized) {
        returnErr(ENoErr);
    }

    // Run any standard debugger checks.
    RunChecks();

    err = CIOSystem::Shutdown();
    if (err) {
        gotoErr(err);
    }

abort:
    returnErr(err);
} // Shutdown.





/////////////////////////////////////////////////////////////////////////////
//
// [OpenBlockIO]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CMemoryIOSystem::OpenBlockIO(
                        CParsedUrl *pUrl,
                        int32 options,
                        CAsyncBlockIOCallback *pCallback) {
    ErrVal err = ENoErr;
    char *ptr = NULL;
    CMemoryBlockIO *pBlockIO = NULL;
    char cSaveChar;
    char *pFilePtr;
    char *pEndFilePtr;
    char *pBuffer;
    int32 bufferLength;
    int32 numFieldsRead;
    AutoLock(m_pLock);
    RunChecks();

    DEBUG_LOG("CMemoryIOSystem::OpenBlockIO");

    if ((NULL == pUrl)
        || (CParsedUrl::URL_SCHEME_MEMORY != pUrl->m_Scheme)
        || (NULL == pUrl->m_pPath)
        || (0 == pUrl->m_PathSize)
        || (NULL == pCallback)) {
        gotoErr(EFail);
    }


    // Lazily initialize the IO system when we open the first blockIO.
    if (!m_fInitialized) {
        err = CIOSystem::InitIOSystem();
        if (err) {
            gotoErr(err);
        }
    }

    pBlockIO = newex CMemoryBlockIO;
    if (NULL == pBlockIO) {
        gotoErr(EFail);
    }

    // Parse the URL.
    pFilePtr = pUrl->m_pPath;
    pEndFilePtr = pFilePtr + pUrl->m_PathSize;
    cSaveChar = *pEndFilePtr;
    *pEndFilePtr = 0;

    DEBUG_LOG("CMemoryIOSystem::OpenBlockIO: Url Path = %s", pUrl->m_pPath);
    numFieldsRead = sscanf(pFilePtr, "%p/%d/", &pBuffer, &bufferLength);
    *pEndFilePtr = cSaveChar;
    if (2 != numFieldsRead) {
        gotoErr(EFail);
    }

    if ((options & CAsyncBlockIO::CREATE_NEW_STORE) || (NULL == pBuffer)) {
        DEBUG_LOG("CMemoryIOSystem::OpenBlockIO: Creating a new store");

        ptr = (char *) memAlloc(bufferLength);
        if (NULL == ptr) {
            gotoErr(EFail);
        }

        // These are part of the memory block IO subclass.
        pBlockIO->m_pBuffer = ptr;
        pBlockIO->m_BufferPhysicalSize = bufferLength;
        pBlockIO->m_fBufferAllocatedFromMemory = true;
        ptr = NULL;
    } else
    {
        DEBUG_LOG("CMemoryIOSystem::OpenBlockIO: Opening an existing buffer");

        pBlockIO->m_pBuffer = pBuffer;
        pBlockIO->m_BufferPhysicalSize = bufferLength;
        pBlockIO->m_fBufferAllocatedFromMemory = false;
    }

    // These are part of the base class.
    pBlockIO->m_MediaType = CAsyncBlockIO::MEMORY_MEDIA;
    pBlockIO->m_fSeekable = true;
    pBlockIO->m_pIOSystem = this;

    pBlockIO->m_BlockIOFlags = 0;
    pBlockIO->m_MediaSize = bufferLength;
    pBlockIO->m_ActiveBlockIOs.ResetQueue();

    pBlockIO->ChangeBlockIOCallback(pCallback);
    pBlockIO->m_ActiveBlockIOs.ResetQueue();

    pBlockIO->m_pUrl = pUrl;
    ADDREF_OBJECT(pUrl);

    pBlockIO->m_pLock = CRefLock::Alloc();
    if (NULL == pBlockIO->m_pLock) {
        gotoErr(EFail);
    }

    // Add this connection to the list of active connections.
    m_ActiveBlockIOs.InsertHead(&(pBlockIO->m_ActiveBlockIOs));
    ADDREF_OBJECT(pBlockIO);

    pBlockIO->m_BlockIOFlags |= CAsyncBlockIO::BLOCKIO_IS_OPEN;

    //<>
    pBlockIO->m_fSynchronousDevice = true;
    //<>

    pCallback->OnBlockIOOpen(ENoErr, pBlockIO);

abort:
    // The callback owns pBlockIO now.
    RELEASE_OBJECT(pBlockIO);
    if (ptr) {
        memFree(ptr);
    }

    returnErr(err);
} // OpenBlockIO.





/////////////////////////////////////////////////////////////////////////////
//
// [CheckState]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CMemoryIOSystem::CheckState() {
    AutoLock(m_pLock);
    returnErr(ENoErr);
} // CheckState.