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

#include "Database.h"
#include "BinaryData.h"
#include "SubfileData.h"
#include "EasyLogging++/easylogging++.h"


namespace LOTRO_DAT {
    Database::Database() {
        LOG(DEBUG) << "Initialising new Database instance.";
        db_ = nullptr;
    }

    bool Database::CloseDatabase() {
        LOG(DEBUG) << "Closing database.";

        if (db_ != nullptr) {
            ExecSql("COMMIT TRANSACTION");
            sqlite3_finalize(insert_request_);
            sqlite3_finalize(fetch_one_request_);
            sqlite3_finalize(get_rows_number_request_);
            if (sqlite3_close_v2(db_) != SQLITE_OK)
                LOG(ERROR) << "Database error when closing: " << sqlite3_errmsg(db_);
            db_ = nullptr;
        }

        LOG(DEBUG) << "Database successfully closed.";
        return true;
    }

    Database::~Database() {
        CloseDatabase();
    }

    bool Database::InitDatabase(const std::string &filename) {
        LOG(DEBUG) << "Initializing database " << filename;

        CloseDatabase();
        if (sqlite3_open(filename.c_str(), &db_) != SQLITE_OK) {
            sqlite3_close(db_);
            db_ = nullptr;
            LOG(ERROR) << "sqlite3_open returned an error. Unable to open file " << filename;
            return false;
        }

        ExecSql("PRAGMA synchronous = OFF");
        ExecSql("PRAGMA count_changes = OFF");
        ExecSql("PRAGMA journal_mode = MEMORY");
        ExecSql("PRAGMA temp_store = MEMORY");
        ExecSql("PRAGMA encoding = \"UTF-8\";");

        ExecSql(CreateTableCommand_);

        sqlite3_prepare_v2(db_, InsertFileCommand_.c_str(), InsertFileCommand_.length(), &insert_request_, nullptr);
        sqlite3_prepare_v2(db_, FetchOneCommand.c_str(), FetchOneCommand.length(), &fetch_one_request_, nullptr);
        sqlite3_prepare_v2(db_, GetRowsNumberCommand_.c_str(), GetRowsNumberCommand_.length(),
                           &get_rows_number_request_, nullptr);

        ExecSql("BEGIN TRANSACTION");

        LOG(DEBUG) << "Database " << filename << " successfully initialized";
        return true;
    }

	void Database::ExecSql(const std::string &sql) {
        LOG(DEBUG) << "Executing SQL request: " << sql;
        if (db_ == nullptr) {
            LOG(WARNING) << "Trying to execute sql query to db, which hasn't been opened yet.";
            return;
        }
		char *error;
		if (sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &error) != SQLITE_OK) {
			LOG(ERROR) << "SQLite3 error: " << sqlite3_errmsg(db_);
			return;
		}
        LOG(DEBUG) << "SQL request " << sql << " executed successfully";
	}

    bool Database::PushFile(const SubfileData &data) {
        LOG(DEBUG) << "Pushing to database file with file_id " << data.options["fid"].as<long long>();

        if (db_ == nullptr) {
            LOG(WARNING) << "Trying to push file to db, which hasn't been opened yet.";
            return false;
        }

        std::stringstream options_;
        options_ << data.options;

        sqlite3_bind_blob(insert_request_, 1, data.binary_data.data(), data.binary_data.size(), SQLITE_TRANSIENT);
        sqlite3_bind_text16(insert_request_, 2, data.text_data.c_str(), -1, SQLITE_TRANSIENT);
        sqlite3_bind_text(insert_request_, 3, options_.str().c_str(), -1, SQLITE_TRANSIENT);

        if (sqlite3_step(insert_request_) != SQLITE_DONE) {
            LOG(ERROR) << "SQLite3 error: " << sqlite3_errmsg(db_);
            return false;
        }

        sqlite3_reset(insert_request_);

        LOG(DEBUG) << "File with file_id " << data.options["fid"].as<long long>() << " pushed to database successfully.";
        return true;
    }

    SubfileData Database::GetNextFile() {
        LOG(DEBUG) << "Getting next file from database..";

        if (db_ == nullptr) {
            LOG(WARNING) << "Trying to get next file from db, which hasn't been opened yet.";
            return SubfileData();
        }

        SubfileData data;
        int result = sqlite3_step(fetch_one_request_);

        if (result == SQLITE_ROW) {
            data.binary_data = BinaryData((char *) sqlite3_column_blob(fetch_one_request_, 0),
                                          unsigned(sqlite3_column_bytes(fetch_one_request_, 0)));

            data.text_data = std::u16string((char16_t *) sqlite3_column_text16(fetch_one_request_, 1));

            std::string _options = std::string((char *) sqlite3_column_text(fetch_one_request_, 2),
                                               unsigned(sqlite3_column_bytes(fetch_one_request_, 2)));
            data.options = YAML::Load(_options);
            return data;
        }

        if (result == SQLITE_DONE) {
            LOG(DEBUG) << "Next file fetched successfully.";
            return data;
        }

        LOG(ERROR) << "SQLite3 fetch_one request returned " << result << " code. SQLite message is: "<< sqlite3_errmsg(db_);
        return data;
    }

    size_t Database::CountRows() {
        LOG(INFO) << "Counting rows in database...";
        if (db_ == nullptr) {
            LOG(WARNING) << "Trying to execute sql query (Count rows) to db, which hasn't been opened yet.";
            return 0;
        }

        int result = sqlite3_step(get_rows_number_request_);

        if (result == SQLITE_ERROR) {
            LOG(ERROR) << "Error when counting rows " << sqlite3_errmsg(db_);
            return 0;
        }
        long long res = sqlite3_column_int64(get_rows_number_request_, 0);
        sqlite3_reset(get_rows_number_request_);
        LOG(INFO) << "Counted " << size_t(res) << " rows in database.";
        return size_t(res);
    }
}