/******************************************************************************
 * $Id: ogrsqlitevfs.cpp 27741 2014-09-26 19:20:02Z goatbar $
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements SQLite VFS
 * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>

 ******************************************************************************
 * Copyright (c) 2011-2012, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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.
 ****************************************************************************/

#include "cpl_atomic_ops.h"
#include "ogr_sqlite.h"

CPL_CVSID("$Id: ogrsqlitevfs.cpp 27741 2014-09-26 19:20:02Z goatbar $");

//#define DEBUG_IO 1

#ifdef HAVE_SQLITE_VFS

typedef struct
{
    char                       szVFSName[64];
    sqlite3_vfs               *pDefaultVFS;
    pfnNotifyFileOpenedType    pfn;
    void                      *pfnUserData;
    int                        nCounter;
} OGRSQLiteVFSAppDataStruct;

#define GET_UNDERLYING_VFS(pVFS)  ((OGRSQLiteVFSAppDataStruct* )pVFS->pAppData)->pDefaultVFS

typedef struct
{
    const struct sqlite3_io_methods *pMethods;
    VSILFILE                        *fp;
    int                              bDeleteOnClose;
    char                            *pszFilename;
} OGRSQLiteFileStruct;

static int OGRSQLiteIOClose(sqlite3_file* pFile)
{
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteIOClose(%p (%s))", pMyFile->fp, pMyFile->pszFilename);
#endif
    VSIFCloseL(pMyFile->fp);
    if (pMyFile->bDeleteOnClose)
        VSIUnlink(pMyFile->pszFilename);
    CPLFree(pMyFile->pszFilename);
    return SQLITE_OK;
}

static int OGRSQLiteIORead(sqlite3_file* pFile, void* pBuffer, int iAmt, sqlite3_int64 iOfst)
{
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    VSIFSeekL(pMyFile->fp, (vsi_l_offset) iOfst, SEEK_SET);
    int nRead = (int)VSIFReadL(pBuffer, 1, iAmt, pMyFile->fp);
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteIORead(%p, %d, %d) = %d", pMyFile->fp, iAmt, (int)iOfst, nRead);
#endif
    if (nRead < iAmt)
    {
        memset(((char*)pBuffer) + nRead, 0, iAmt - nRead);
        return SQLITE_IOERR_SHORT_READ;
    }
    return SQLITE_OK;
}

static int OGRSQLiteIOWrite(sqlite3_file* pFile, const void* pBuffer, int iAmt, sqlite3_int64 iOfst)
{
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    VSIFSeekL(pMyFile->fp, (vsi_l_offset) iOfst, SEEK_SET);
    int nWritten = (int)VSIFWriteL(pBuffer, 1, iAmt, pMyFile->fp);
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteIOWrite(%p, %d, %d) = %d", pMyFile->fp, iAmt, (int)iOfst, nWritten);
#endif
    if (nWritten < iAmt)
    {
        return SQLITE_IOERR_WRITE;
    }
    return SQLITE_OK;
}

static int OGRSQLiteIOTruncate(sqlite3_file* pFile, sqlite3_int64 size)
{
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteIOTruncate(%p, " CPL_FRMT_GIB ")", pMyFile->fp, size);
#endif
    int nRet = VSIFTruncateL(pMyFile->fp, size);
    return (nRet == 0) ? SQLITE_OK : SQLITE_IOERR_TRUNCATE;
}

static int OGRSQLiteIOSync(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                           sqlite3_file* pFile,
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                           int flags)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOSync(%p, %d)", pMyFile->fp, flags);
#endif
    return SQLITE_OK;
}

static int OGRSQLiteIOFileSize(sqlite3_file* pFile, sqlite3_int64 *pSize)
{
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    vsi_l_offset nCurOffset = VSIFTellL(pMyFile->fp);
    VSIFSeekL(pMyFile->fp, 0, SEEK_END);
    *pSize = VSIFTellL(pMyFile->fp);
    VSIFSeekL(pMyFile->fp, nCurOffset, SEEK_SET);
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteIOFileSize(%p) = " CPL_FRMT_GIB, pMyFile->fp, *pSize);
#endif
    return SQLITE_OK;
}

static int OGRSQLiteIOLock(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                           sqlite3_file* pFile,
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                           int flags)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOLock(%p)", pMyFile->fp);
#endif
    return SQLITE_OK;
}

static int OGRSQLiteIOUnlock(CPL_UNUSED sqlite3_file* pFile, CPL_UNUSED int flags)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOUnlock(%p)", pMyFile->fp);
#endif
    return SQLITE_OK;
}

static int OGRSQLiteIOCheckReservedLock(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                        sqlite3_file* pFile,
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                        int *pResOut)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOCheckReservedLock(%p)", pMyFile->fp);
#endif
    *pResOut = 0;
    return SQLITE_OK;
}

static int OGRSQLiteIOFileControl(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                  sqlite3_file* pFile,
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                  int op,
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                  void *pArg)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOFileControl(%p, %d)", pMyFile->fp, op);
#endif
    return SQLITE_NOTFOUND;
}

static int OGRSQLiteIOSectorSize(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                 sqlite3_file* pFile)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIOSectorSize(%p)", pMyFile->fp);
#endif
    return 0;
}

static int OGRSQLiteIODeviceCharacteristics(
#ifndef DEBUG_IO
CPL_UNUSED 
#endif
                                            sqlite3_file* pFile)
{
#ifdef DEBUG_IO
    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    CPLDebug("SQLITE", "OGRSQLiteIODeviceCharacteristics(%p)", pMyFile->fp);
#endif
    return 0;
}

static const sqlite3_io_methods OGRSQLiteIOMethods =
{
    1,
    OGRSQLiteIOClose,
    OGRSQLiteIORead,
    OGRSQLiteIOWrite,
    OGRSQLiteIOTruncate,
    OGRSQLiteIOSync,
    OGRSQLiteIOFileSize,
    OGRSQLiteIOLock,
    OGRSQLiteIOUnlock,
    OGRSQLiteIOCheckReservedLock,
    OGRSQLiteIOFileControl,
    OGRSQLiteIOSectorSize,
    OGRSQLiteIODeviceCharacteristics
#if 0
    // TODO: These are in sqlite3.
    , 0, // xShmMap
    0, // xShmLock
    0, // xShmBarrier
    0, // xShmUnmap
    0, // xFetch
    0 // xUnfetch
#endif
};

static int OGRSQLiteVFSOpen(sqlite3_vfs* pVFS,
                            const char *zName,
                            sqlite3_file* pFile,
                            int flags,
                            int *pOutFlags)
{
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteVFSOpen(%s, %d)", zName ? zName : "(null)", flags);
#endif

    OGRSQLiteVFSAppDataStruct* pAppData = (OGRSQLiteVFSAppDataStruct* )pVFS->pAppData;

    if (zName == NULL)
    {
        zName = CPLSPrintf("/vsimem/sqlite/%p_%d",
                           pVFS, CPLAtomicInc(&(pAppData->nCounter)));
    }

    OGRSQLiteFileStruct* pMyFile = (OGRSQLiteFileStruct*) pFile;
    pMyFile->pMethods = NULL;
    pMyFile->bDeleteOnClose = FALSE;
    pMyFile->pszFilename = NULL;
    if ( flags & SQLITE_OPEN_READONLY )
        pMyFile->fp = VSIFOpenL(zName, "rb");
    else if ( flags & SQLITE_OPEN_CREATE )
        pMyFile->fp = VSIFOpenL(zName, "wb+");
    else if ( flags & SQLITE_OPEN_READWRITE )
        pMyFile->fp = VSIFOpenL(zName, "rb+");
    else
        pMyFile->fp = NULL;

    if (pMyFile->fp == NULL)
        return SQLITE_CANTOPEN;

#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteVFSOpen() = %p", pMyFile->fp);
#endif

    pfnNotifyFileOpenedType pfn = pAppData->pfn;
    if (pfn)
    {
        pfn(pAppData->pfnUserData, zName, pMyFile->fp);
    }

    pMyFile->pMethods = &OGRSQLiteIOMethods;
    pMyFile->bDeleteOnClose = ( flags & SQLITE_OPEN_DELETEONCLOSE );
    pMyFile->pszFilename = CPLStrdup(zName);

    if (pOutFlags != NULL)
        *pOutFlags = flags;

    return SQLITE_OK;
}

static int OGRSQLiteVFSDelete(CPL_UNUSED sqlite3_vfs* pVFS,
                              const char *zName,
                              CPL_UNUSED int syncDir)
{
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteVFSDelete(%s)", zName);
#endif
    VSIUnlink(zName);
    return SQLITE_OK;
}

static int OGRSQLiteVFSAccess (CPL_UNUSED sqlite3_vfs* pVFS, const char *zName, int flags, int *pResOut)
{
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteVFSAccess(%s, %d)", zName, flags);
#endif
    VSIStatBufL sStatBufL;
    int nRet;
    if (flags == SQLITE_ACCESS_EXISTS)
    {
        /* Do not try to check the presence of a journal on /vsicurl ! */
        if ( strncmp(zName, "/vsicurl/", 9) == 0 &&
             strlen(zName) > strlen("-journal") &&
             strcmp(zName + strlen(zName) - strlen("-journal"), "-journal") == 0 )
            nRet = -1;
        else
            nRet = VSIStatExL(zName, &sStatBufL, VSI_STAT_EXISTS_FLAG);
    }
    else if (flags == SQLITE_ACCESS_READ)
    {
        VSILFILE* fp = VSIFOpenL(zName, "rb");
        nRet = fp ? 0 : -1;
        if (fp)
            VSIFCloseL(fp);
    }
    else if (flags == SQLITE_ACCESS_READWRITE)
    {
        VSILFILE* fp = VSIFOpenL(zName, "rb+");
        nRet = fp ? 0 : -1;
        if (fp)
            VSIFCloseL(fp);
    }
    else
        nRet = -1;
    *pResOut = (nRet == 0);
    return SQLITE_OK;
}

static int OGRSQLiteVFSFullPathname (sqlite3_vfs* pVFS, const char *zName, int nOut, char *zOut)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
#ifdef DEBUG_IO
    CPLDebug("SQLITE", "OGRSQLiteVFSFullPathname(%s)", zName);
#endif
    if (zName[0] == '/')
    {
        strncpy(zOut, zName, nOut);
        zOut[nOut-1] = '\0';
        return SQLITE_OK;
    }
    return pUnderlyingVFS->xFullPathname(pUnderlyingVFS, zName, nOut, zOut);
}

static void* OGRSQLiteVFSDlOpen (sqlite3_vfs* pVFS, const char *zFilename)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSDlOpen(%s)", zFilename);
    return pUnderlyingVFS->xDlOpen(pUnderlyingVFS, zFilename);
}

static void OGRSQLiteVFSDlError (sqlite3_vfs* pVFS, int nByte, char *zErrMsg)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSDlError()");
    pUnderlyingVFS->xDlError(pUnderlyingVFS, nByte, zErrMsg);
}

/* xDlSym member signature changed in sqlite 3.6.7 (http://www.sqlite.org/changes.html) */
/* This was supposed to be done "in a way that is backwards compatible but which might cause compiler warnings" */
/* Perhaps in C, but definitely not in C++ ( #4515 ) */
#if SQLITE_VERSION_NUMBER >= 3006007
static void (*OGRSQLiteVFSDlSym (sqlite3_vfs* pVFS,void* pHandle, const char *zSymbol))(void)
#else
static void (*OGRSQLiteVFSDlSym (sqlite3_vfs* pVFS,void* pHandle, const char *zSymbol))
#endif
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSDlSym(%s)", zSymbol);
    return pUnderlyingVFS->xDlSym(pUnderlyingVFS, pHandle, zSymbol);
}

static void OGRSQLiteVFSDlClose (sqlite3_vfs* pVFS, void* pHandle)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSDlClose(%p)", pHandle);
    pUnderlyingVFS->xDlClose(pUnderlyingVFS, pHandle);
}

static int OGRSQLiteVFSRandomness (sqlite3_vfs* pVFS, int nByte, char *zOut)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSRandomness()");
    return pUnderlyingVFS->xRandomness(pUnderlyingVFS, nByte, zOut);
}

static int OGRSQLiteVFSSleep (sqlite3_vfs* pVFS, int microseconds)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSSleep()");
    return pUnderlyingVFS->xSleep(pUnderlyingVFS, microseconds);
}

static int OGRSQLiteVFSCurrentTime (sqlite3_vfs* pVFS, double* p1)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSCurrentTime()");
    return pUnderlyingVFS->xCurrentTime(pUnderlyingVFS, p1);
}

static int OGRSQLiteVFSGetLastError (sqlite3_vfs* pVFS, int p1, char *p2)
{
    sqlite3_vfs* pUnderlyingVFS = GET_UNDERLYING_VFS(pVFS);
    //CPLDebug("SQLITE", "OGRSQLiteVFSGetLastError()");
    return pUnderlyingVFS->xGetLastError(pUnderlyingVFS, p1, p2);
}

sqlite3_vfs* OGRSQLiteCreateVFS(pfnNotifyFileOpenedType pfn, void* pfnUserData)
{
    sqlite3_vfs* pDefaultVFS = sqlite3_vfs_find(NULL);
    sqlite3_vfs* pMyVFS = (sqlite3_vfs*) CPLCalloc(1, sizeof(sqlite3_vfs));

    OGRSQLiteVFSAppDataStruct* pVFSAppData =
        (OGRSQLiteVFSAppDataStruct*) CPLCalloc(1, sizeof(OGRSQLiteVFSAppDataStruct));
    sprintf(pVFSAppData->szVFSName, "OGRSQLITEVFS_%p", pVFSAppData);
    pVFSAppData->pDefaultVFS = pDefaultVFS;
    pVFSAppData->pfn = pfn;
    pVFSAppData->pfnUserData = pfnUserData;
    pVFSAppData->nCounter = 0;

    pMyVFS->iVersion = 1;
    pMyVFS->szOsFile = sizeof(OGRSQLiteFileStruct);
    pMyVFS->mxPathname = pDefaultVFS->mxPathname;
    pMyVFS->zName = pVFSAppData->szVFSName;
    pMyVFS->pAppData = pVFSAppData;
    pMyVFS->xOpen = OGRSQLiteVFSOpen;
    pMyVFS->xDelete = OGRSQLiteVFSDelete;
    pMyVFS->xAccess = OGRSQLiteVFSAccess;
    pMyVFS->xFullPathname = OGRSQLiteVFSFullPathname;
    pMyVFS->xDlOpen = OGRSQLiteVFSDlOpen;
    pMyVFS->xDlError = OGRSQLiteVFSDlError;
    pMyVFS->xDlSym = OGRSQLiteVFSDlSym;
    pMyVFS->xDlClose = OGRSQLiteVFSDlClose;
    pMyVFS->xRandomness = OGRSQLiteVFSRandomness;
    pMyVFS->xSleep = OGRSQLiteVFSSleep;
    pMyVFS->xCurrentTime = OGRSQLiteVFSCurrentTime;
    pMyVFS->xGetLastError = OGRSQLiteVFSGetLastError;
    return pMyVFS;
}

#endif // HAVE_SQLITE_VFS
