/**
 * @file    Database.cpp
 * @ingroup SQLiteCpp
 * @brief   Management of a SQLite Database Connection.
 *
 * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
 *
 * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
 * or copy at http://opensource.org/licenses/MIT)
 */
#include <SQLiteCpp/Database.h>

#include <SQLiteCpp/Exception.h>
#include <SQLiteCpp/Statement.h>

#include <sqlite3.h>
#include <fstream>
#include <cstring>
#include <cassert>

#ifndef SQLITE_DETERMINISTIC
#define SQLITE_DETERMINISTIC 0x800
#endif // SQLITE_DETERMINISTIC


namespace SQLite
{

const int   OPEN_READONLY   = SQLITE_OPEN_READONLY;
const int   OPEN_READWRITE  = SQLITE_OPEN_READWRITE;
const int   OPEN_CREATE     = SQLITE_OPEN_CREATE;
const int   OPEN_URI        = SQLITE_OPEN_URI;
const int   OPEN_FULLMUTEX  = SQLITE_OPEN_FULLMUTEX;

const int   OK              = SQLITE_OK;

const char* VERSION         = SQLITE_VERSION;
const int   VERSION_NUMBER  = SQLITE_VERSION_NUMBER;

// Return SQLite version string using runtime call to the compiled library
const char* getLibVersion() noexcept
{
    return sqlite3_libversion();
}

// Return SQLite version number using runtime call to the compiled library
int getLibVersionNumber() noexcept
{
    return sqlite3_libversion_number();
}


// Open the provided database UTF-8 filename with SQLite::OPEN_xxx provided flags.
Database::Database(const char* apFilename,
                   const int   aFlags         /* = SQLite::OPEN_READONLY*/,
                   const int   aBusyTimeoutMs /* = 0 */,
                   const char* apVfs          /* = nullptr*/) :
    mFilename(apFilename)
{
    sqlite3* handle;
    const int ret = sqlite3_open_v2(apFilename, &handle, aFlags, apVfs);
    mSQLitePtr.reset(handle);
    if (SQLITE_OK != ret)
    {
        throw SQLite::Exception(handle, ret);
    }
    if (aBusyTimeoutMs > 0)
    {
        setBusyTimeout(aBusyTimeoutMs);
    }
}

// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
void Database::Deleter::operator()(sqlite3* apSQLite)
{
    const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.

    // Avoid unreferenced variable warning when build in release mode
    (void) ret;

    // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
    // Never throw an exception in a destructor :
    assert(SQLITE_OK == ret && "database is locked");  // See SQLITECPP_ENABLE_ASSERT_HANDLER
}

/**
 * @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
 *
 *  This is useful in multithreaded program to handle case where a table is locked for writting by a thread.
 *  Any other thread cannot access the table and will receive a SQLITE_BUSY error:
 *  setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error.
 *  Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;".
 *  Default busy timeout is 0ms.
 *
 * @param[in] aBusyTimeoutMs    Amount of milliseconds to wait before returning SQLITE_BUSY
 *
 * @throw SQLite::Exception in case of error
 */
void Database::setBusyTimeout(const int aBusyTimeoutMs)
{
    const int ret = sqlite3_busy_timeout(getHandle(), aBusyTimeoutMs);
    check(ret);
}

// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...).
int Database::exec(const char* apQueries)
{
    const int ret = sqlite3_exec(getHandle(), apQueries, nullptr, nullptr, nullptr);
    check(ret);

    // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only)
    return sqlite3_changes(getHandle());
}

// Shortcut to execute a one step query and fetch the first column of the result.
// WARNING: Be very careful with this dangerous method: you have to
// make a COPY OF THE result, else it will be destroy before the next line
// (when the underlying temporary Statement and Column objects are destroyed)
// this is an issue only for pointer type result (ie. char* and blob)
// (use the Column copy-constructor)
Column Database::execAndGet(const char* apQuery)
{
    Statement query(*this, apQuery);
    (void)query.executeStep(); // Can return false if no result, which will throw next line in getColumn()
    return query.getColumn(0);
}

// Shortcut to test if a table exists.
bool Database::tableExists(const char* apTableName)
{
    Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?");
    query.bind(1, apTableName);
    (void)query.executeStep(); // Cannot return false, as the above query always return a result
    return (1 == query.getColumn(0).getInt());
}

// Get the rowid of the most recent successful INSERT into the database from the current connection.
long long Database::getLastInsertRowid() const noexcept
{
    return sqlite3_last_insert_rowid(getHandle());
}

// Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection.
int Database::getTotalChanges() const noexcept
{
    return sqlite3_total_changes(getHandle());
}

// Return the numeric result code for the most recent failed API call (if any).
int Database::getErrorCode() const noexcept
{
    return sqlite3_errcode(getHandle());
}

// Return the extended numeric result code for the most recent failed API call (if any).
int Database::getExtendedErrorCode() const noexcept
{
    return sqlite3_extended_errcode(getHandle());
}

// Return UTF-8 encoded English language explanation of the most recent failed API call (if any).
const char* Database::getErrorMsg() const noexcept
{
    return sqlite3_errmsg(getHandle());
}

// Parse header data from a database.
Header Database::getHeaderInfo(const std::string& aFilename)
{
    Header h;
    unsigned char buf[100];
    char* pBuf = reinterpret_cast<char*>(&buf[0]);
    char* pHeaderStr = reinterpret_cast<char*>(&h.headerStr[0]);

    if (aFilename.empty())
    {
        throw SQLite::Exception("Filename parameter is empty");
    }

    {
        std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
        if (fileBuffer.is_open())
        {
            fileBuffer.seekg(0, std::ios::beg);
            fileBuffer.read(pBuf, 100);
            fileBuffer.close();
            if (fileBuffer.gcount() < 100)
            {
                throw SQLite::Exception("File " + aFilename + " is too short");
            }
        }
        else
        {
            throw SQLite::Exception("Error opening file " + aFilename);
        }
    }

    // If the "magic string" can't be found then header is invalid, corrupt or unreadable
    strncpy(pHeaderStr, pBuf, 16);
    if (strncmp(pHeaderStr, "SQLite format 3", 15) != 0)
    {
        throw SQLite::Exception("Invalid or encrypted SQLite header in file " + aFilename);
    }

    h.pageSizeBytes = (buf[16] << 8) | buf[17];
    h.fileFormatWriteVersion = buf[18];
    h.fileFormatReadVersion = buf[19];
    h.reservedSpaceBytes = buf[20];
    h.maxEmbeddedPayloadFrac = buf[21];
    h.minEmbeddedPayloadFrac = buf[22];
    h.leafPayloadFrac = buf[23];

    h.fileChangeCounter =
        (buf[24] << 24) |
        (buf[25] << 16) |
        (buf[26] << 8)  |
        (buf[27] << 0);

    h.databaseSizePages =
        (buf[28] << 24) |
        (buf[29] << 16) |
        (buf[30] << 8)  |
        (buf[31] << 0);

    h.firstFreelistTrunkPage =
        (buf[32] << 24) |
        (buf[33] << 16) |
        (buf[34] << 8)  |
        (buf[35] << 0);

    h.totalFreelistPages =
        (buf[36] << 24) |
        (buf[37] << 16) |
        (buf[38] << 8)  |
        (buf[39] << 0);

    h.schemaCookie =
        (buf[40] << 24) |
        (buf[41] << 16) |
        (buf[42] << 8)  |
        (buf[43] << 0);

    h.schemaFormatNumber =
        (buf[44] << 24) |
        (buf[45] << 16) |
        (buf[46] << 8)  |
        (buf[47] << 0);

    h.defaultPageCacheSizeBytes =
        (buf[48] << 24) |
        (buf[49] << 16) |
        (buf[50] << 8)  |
        (buf[51] << 0);

    h.largestBTreePageNumber =
        (buf[52] << 24) |
        (buf[53] << 16) |
        (buf[54] << 8)  |
        (buf[55] << 0);

    h.databaseTextEncoding =
        (buf[56] << 24) |
        (buf[57] << 16) |
        (buf[58] << 8)  |
        (buf[59] << 0);

    h.userVersion =
        (buf[60] << 24) |
        (buf[61] << 16) |
        (buf[62] << 8)  |
        (buf[63] << 0);

    h.incrementalVaccumMode =
        (buf[64] << 24) |
        (buf[65] << 16) |
        (buf[66] << 8)  |
        (buf[67] << 0);

    h.applicationId =
        (buf[68] << 24) |
        (buf[69] << 16) |
        (buf[70] << 8)  |
        (buf[71] << 0);

    h.versionValidFor =
        (buf[92] << 24) |
        (buf[93] << 16) |
        (buf[94] << 8)  |
        (buf[95] << 0);

    h.sqliteVersion =
        (buf[96] << 24) |
        (buf[97] << 16) |
        (buf[98] << 8)  |
        (buf[99] << 0);

    return h;
}
}  // namespace SQLite
