#pragma once

#include "sqlite3.h"
#include <qstring.h>
#include <stdexcept>

class SQLiteException : public std::runtime_error
{
public:
    explicit SQLiteException(const char* msg)
        : std::runtime_error(msg)
    {}
};

class SQLiteConnection;

class SQLitePreparedStatement
{
public:
    SQLitePreparedStatement() = default;
    SQLitePreparedStatement(const SQLitePreparedStatement &) = delete;
    SQLitePreparedStatement(SQLitePreparedStatement&& rhs);
    SQLitePreparedStatement &operator=(SQLitePreparedStatement&& rhs);

    ~SQLitePreparedStatement();

    void Open(SQLiteConnection &db, const char *query);
    void Open(SQLiteConnection &db, const QString &query);

    void Bind(int index, const char *s, int length = 0);
    void Bind(int index, const QString &s);
    void Bind(int index, int v);

    const char* ColumnCharPtr(int col);
    QString ColumnText(int col);
    int ColumnInteger(int col);

    bool Step();

    void Reset()
    {
        sqlite3_reset(pStatement);
    }

private:
    sqlite3_stmt *pStatement = nullptr;

    void Free()
    {
        if (pStatement != nullptr)
        {
            sqlite3_finalize(pStatement);
            pStatement = nullptr;
        }
    }
};


class SQLiteTransaction
{
public:
    explicit SQLiteTransaction(SQLiteConnection &db);
    SQLiteTransaction(const SQLiteTransaction&&) = delete;

    ~SQLiteTransaction();

    void Commit();
    void Rollback();
private:
    SQLiteConnection &db;
    bool inTransaction = false;
    void UncheckedRollback();
};

class SQLiteConnection
{
public:
    SQLiteConnection() = default;
    SQLiteConnection(const SQLiteConnection&) = delete;

    SQLiteConnection(SQLiteConnection&& rhs);

    SQLiteConnection& operator=(const SQLiteConnection &) = delete;

    SQLiteConnection& operator=(SQLiteConnection && rhs);

    ~SQLiteConnection()
    {
        if (pDb)
        {
            sqlite3_close(pDb);
        }
    }

    void Open(const char *filename)
    {
        int result = sqlite3_open(filename, &pDb);
        CheckResult(result);
    }

    void Open(QString filename)
    {
        int result = sqlite3_open16((void*)filename.data(), &pDb);
        CheckResult(result);
    }

    void CheckResult(int result)
    {
        if (result == SQLITE_OK)
            return;

        if (pDb == nullptr)
        {
            const char * msg = sqlite3_errstr(result);
            throw SQLiteException(msg);
        }
        const char * msg = sqlite3_errmsg(pDb);
        throw SQLiteException(msg);
    }

    SQLitePreparedStatement Prepare(const char* query)
    {
        SQLitePreparedStatement stmt;
        stmt.Open(*this, query);
        return stmt;
    }

    SQLitePreparedStatement Prepare(const QString &query)
    {
        SQLitePreparedStatement stmt;
        stmt.Open(*this, query);
        return stmt;
    }

    void Exec(const char* query)
    {
        SQLitePreparedStatement stmt = Prepare(query);
        stmt.Step();
    }

    void Exec(QString query)
    {
        SQLitePreparedStatement stmt = Prepare(query.toUtf8().constData());
        stmt.Step();
    }

    int64_t LastInsertRowId()
    {
        return sqlite3_last_insert_rowid(pDb);
    }

private:
    sqlite3 *pDb = nullptr;

    friend class SQLitePreparedStatement;
};
