//
// Created by Иван_Архипов on 17.11.2017.
//

#include "Database.h"
#include "DatException.h"
#include "BinaryData.h"
#include <cstring>

namespace LOTRO_DAT {

    Database::Database() {
        db_ = nullptr;
        query_size_ = 0;
    }

    Database::Database(const char *filename) {
        query_size_ = 0;
        InitDatabase(filename);
    }

    Database::Database(const std::string &filename) {
        query_size_ = 0;
        InitDatabase(filename.c_str());
    }

    Database::~Database() {
        if (query_size_ != 0)
            ExecSql("COMMIT");

        if (db_ != nullptr) {
            sqlite3_finalize(insert_text_);
            sqlite3_finalize(insert_binary_);

            sqlite3_close(db_);
        }
    }

    void Database::InitDatabase(const char *filename) {
        if (sqlite3_open(filename, &db_) != SQLITE_OK) {
            sqlite3_close(db_);
            throw DatException("Bad Database::InitDatabase() - sqlite3_open returned an error..."
                    , DATABASE_EXCEPTION);
        }

        ExecSql(CreateBinaryTableCommand_);
        ExecSql(CreateTextTableCommand_);
        ExecSql(CreateMetadataTableCommand_);

        ExecSql("PRAGMA synchronous = OFF");
        ExecSql("PRAGMA count_changes = OFF");
        ExecSql("PRAGMA journal_mode = MEMORY");
        ExecSql("PRAGMA temp_store = MEMORY");

        sqlite3_prepare_v2(db_, InsertBinaryCommand_, strlen(InsertBinaryCommand_), &insert_binary_, nullptr);
        sqlite3_prepare_v2(db_, InsertTextCommand_, strlen(InsertTextCommand_), &insert_text_, nullptr);

        //sqlite3_prepare_v2(db_, GetTextCommand, 50ll * 1024ll * 1024ll, &get_text_, nullptr);
        //sqlite3_prepare_v2(db_, GetBinaryCommand, 50ll * 1024ll * 1024ll, &get_binary_, nullptr);

    }

    void Database::ExecSql(const char *sql) {
        char *error;
        if (sqlite3_exec(db_, sql, nullptr, nullptr, &error) != SQLITE_OK) {
            fprintf(stderr, "SQLite3 error: %s\n", sqlite3_errmsg(db_));
            throw DatException((std::string("Bad Database::ExecSql() - unable to perform request")
                                + std::string(sql)).c_str(), DATABASE_EXCEPTION);
        }
    }

    void Database::PushTextFile(long long file_id, long long gossip_id, char *text, char *args, int dat_id) {
        if (query_size_ == 0)
            ExecSql("BEGIN TRANSACTION");

        sqlite3_bind_int(insert_text_, 1, (int)file_id);
        sqlite3_bind_int(insert_text_, 2, (int)gossip_id);
        sqlite3_bind_text(insert_text_, 3, text, sizeof(text), SQLITE_TRANSIENT);
        sqlite3_bind_text(insert_text_, 4, args, sizeof(args), SQLITE_TRANSIENT);

        if (sqlite3_step(insert_text_) != SQLITE_OK) {
            fprintf(stderr, "SQLite3 error: %s\n", sqlite3_errmsg(db_));
            throw DatException((std::string("Bad Database::PushTextFile() - unable to perform push operation")).c_str(),
                               DATABASE_EXCEPTION);
        }

        sqlite3_reset(insert_text_);

        query_size_++;
        if (query_size_ >= QUERY_MAX_SIZE) {
            ExecSql("COMMIT");
            query_size_ = 0;
        }
    }

    //BinaryData &Database::GetTextFile(long long file_id) {}

    void Database::PushBinaryFile(long long file_id, const BinaryData &data) {
        if (query_size_ == 0)
            ExecSql("BEGIN TRANSACTION");

        sqlite3_bind_int(insert_binary_, 1, (int)file_id);
        sqlite3_bind_blob(insert_binary_, 2, data.data(), data.size(), SQLITE_TRANSIENT);

        if (sqlite3_step(insert_binary_) != SQLITE_DONE) {
            fprintf(stderr, "SQLite3 error: %s\n", sqlite3_errmsg(db_));
            throw DatException(std::string("Bad Database::PushTextFile() - unable to perform operation").c_str()
                    , DATABASE_EXCEPTION);
        }
        sqlite3_reset(insert_binary_);

        query_size_++;
        if (query_size_ >= QUERY_MAX_SIZE) {
            ExecSql("COMMIT");
            query_size_ = 0;
        }
    }

    //BinaryData &Database::GetBinaryFile(long long file_id) {
    //
    //}
}