#ifndef CSQLITEDB_H
#define CSQLITEDB_H

#include "sqlite3.h"
#include "CSQLiteException.h"
#include "GdCPP_Exports.h"

class GDCPP_API CSQLiteDB
{
    friend class CSQLiteStatement;
public:
    /// 构造一个实例，未打开数据库
    CSQLiteDB() noexcept
        : dbErrCode(0)
        , mpSQLite(nullptr)
    {

    }

    /**
     * @brief Open the provided database UTF-8 filename.
     *
     * @param[in] apFilename        UTF-8 path/uri to the database file ("filename" sqlite3 parameter)
     * @param[in] aFlags            flags for sqlite3_open_v2()
     *
     * 尽量使用后面的openRW()或openRO()
     */
    int open(const char* apFilename, const int   aFlags) noexcept;
    int open(const std::string &apFilename, const int   aFlags) noexcept;

    inline int openRW(const char* apFilename){
        return open(apFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
    }

    inline int openRW(const std::string& apFilename) {
        return open(apFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
    }

	inline int openRW(const std::filesystem::path& apFilename) {
		return open(apFilename.string(), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
	}

    inline int openRO(const char* apFilename){
        return open(apFilename, SQLITE_OPEN_READONLY);
    }
    inline int openRO(const std::string& apFilename) {
        return open(apFilename, SQLITE_OPEN_READONLY);
    }
	inline int openRO(const std::filesystem::path& apFilename) {
		return open(apFilename.string(), SQLITE_OPEN_READONLY);
	}

    int openInRam(const char* apFilename);

    /**
     * @brief Close the SQLite database connection.
     *
     * All SQLite statements must have been finalized before,
     * so all Statement objects must have been unregistered.
     *
     * @warning assert in case of error
     */
    virtual int close();


    virtual ~CSQLiteDB()
    {
        close();
    }

    int errcode() {
        return sqlite3_errcode(mpSQLite);
    }

    const char* errmsg() {
		return sqlite3_errmsg(mpSQLite);
	}

    /**
     * @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 writing 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 CSQLiteExceptionin case of error
     */
    void setBusyTimeout(const int aBusyTimeoutMs);

    /**
     * @brief Shortcut to execute one or multiple statements without results.
     *
     *  This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" :
     *  - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE"
     *  - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP"
     *  - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK"
     *
     * @see Statement::exec() to handle precompiled statements (for better performances) without results
     * @see Statement::executeStep() to handle "SELECT" queries with results
     *
     * @param[in] apQueries  one or multiple UTF-8 encoded, semicolon-separate SQL statements
     *
     * @return number of rows modified by the *last* INSERT, UPDATE or DELETE statement (beware of multiple statements)
     * @warning undefined for CREATE or DROP table: returns the value of a previous INSERT, UPDATE or DELETE statement.
     *
     * @throw CSQLiteExceptionin case of error
     */
    int exec(const char* apQueries);

    /**
     * @brief Shortcut to test if a table exists.
     *
     *  Table names are case sensitive.
     *
     * @param[in] apTableName an UTF-8 encoded case sensitive Table name
     *
     * @return true if the table exists.
     *
     * @throw CSQLiteException in case of error
     */
    bool tableExists(const char* apTableName);

    /**
     * @brief Return raw pointer to SQLite Database Connection Handle.
     *
     * This is often needed to mix this wrapper with other libraries or for advance usage not supported by SQLiteCpp.
     */
    inline sqlite3* getHandle() const noexcept // nothrow
    {
        return mpSQLite;
    }

    inline bool isOpened()
    {
        return mpSQLite!=nullptr;
    }

    int dbErrCode;
    std::string dbErrMsg;
private:
    /// @{ Database must be non-copyable
    CSQLiteDB(const CSQLiteDB&);
    CSQLiteDB& operator=(const CSQLiteDB&);
    /// @}

    /**
     * @brief Check if aRet equal SQLITE_OK, else throw a CSQLiteException with the SQLite error message
     */
    inline void check(const int aRet) const
    {
        if (SQLITE_OK != aRet)
        {
            throw CSQLiteException(mpSQLite, aRet);
        }
    }

protected:
    sqlite3*    mpSQLite;   ///< Pointer to SQLite Database Connection Handle
    std::string mFilename;  ///< UTF-8 filename used to open the database

public:
	// 获取数据库文件名
	const std::string& getFilename() const
	{
		return mFilename;
	}

	// 以下与事务相关
    // 标记是否启动了事务。true未启动事务
    bool bInTransaction = false;

    inline bool startTransaction()
    {
        ASSERT(!bInTransaction);
        ASSERT(isOpened());
        try
        {
            exec("BEGIN TRANSACTION");
            bInTransaction = true;
            return true;
        }
        catch (CSQLiteException&)
        {
            return false;
        }
    }

    inline bool Rollback()
    {
        ASSERT(bInTransaction);
        ASSERT(isOpened());
        try
        {
            int ret = exec("ROLLBACK");
            bInTransaction = false;
            return true;
        }
        catch (CSQLiteException&)
        {
            return false;
        }
    }

    inline bool Commit()
    {
        ASSERT(bInTransaction);
        ASSERT(isOpened());
        try
        {
            int ret = exec("COMMIT");
            return true;
        }
        catch (CSQLiteException&)
        {
            return false;
        }
    }
};

#endif // CSQLITEDB_H
