/*********************************************************************
 * \file   BigFile.cpp
 *
 * \author Romain BOULLARD
 * \date   October 2025
 *********************************************************************/
#include <Engine/BigFile/BigFile.hpp>

#include <Engine/EngineAssertHandler.hpp>

namespace Bigfoot
{
BigFile::BigFile(const File& p_file)
{
    [[maybe_unused]]
    const int result = sqlite3_open_v2(p_file.ToAbsolute().GetPath().data(), &m_db, SQLITE_OPEN_READWRITE, nullptr);
    CRITICAL_ASSERT(EngineAssertHandler,
                    result == SQLITE_OK,
                    "Failed to open BigFile {} DB: {}",
                    p_file.ToAbsolute().GetPath(),
                    sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::BeginTransaction()
{
    [[maybe_unused]]
    const int result = sqlite3_exec(m_db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to begin transaction: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::CommitTransaction()
{
    [[maybe_unused]]
    const int result = sqlite3_exec(m_db, "COMMIT TRANSACTION;", nullptr, nullptr, nullptr);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to commit: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::RollbackTransaction()
{
    [[maybe_unused]]
    const int result = sqlite3_exec(m_db, "ROLLBACK TRANSACTION;", nullptr, nullptr, nullptr);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to rollback: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

BigFile::~BigFile()
{
    [[maybe_unused]]
    const int result = sqlite3_close_v2(m_db);
    CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to close BigFile DB: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

BigFile::Request::Request(const BigFile& p_bigFile, const eastl::string_view p_request):
    m_db(p_bigFile.m_db)
{
    [[maybe_unused]]
    const int result = sqlite3_prepare_v2(m_db,
                                          p_request.data(),
                                          static_cast<std::uint32_t>(p_request.size()),
                                          &m_statement,
                                          nullptr);
    CRITICAL_ASSERT(EngineAssertHandler,
                    result == SQLITE_OK,
                    "Failed to create statement from BigFile DB: {}",
                    sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const std::int32_t p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_int(m_statement, p_index, p_value);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const std::uint32_t p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_int(m_statement, p_index, p_value);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const std::int64_t p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_int64(m_statement, p_index, p_value);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const std::uint64_t p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_int64(m_statement, p_index, std::bit_cast<std::int64_t>(p_value));
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const float p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_double(m_statement, p_index, p_value);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const double p_value)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_double(m_statement, p_index, p_value);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index, const eastl::string_view p_value, const CopyValue p_copy)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_text(m_statement,
                                         p_index,
                                         p_value.data(),
                                         static_cast<std::uint32_t>(p_value.size()),
                                         p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

void BigFile::Request::Bind(const std::uint32_t p_index,
                            const eastl::span<const std::byte> p_value,
                            const CopyValue p_copy)
{
    ASSERT(EngineAssertHandler,
           (p_index >= 1) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
           "Invalid index for statement");

    [[maybe_unused]]
    const int result = sqlite3_bind_blob(m_statement,
                                         p_index,
                                         p_value.data(),
                                         static_cast<std::uint32_t>(p_value.size()),
                                         p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC);
    ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db));
}

/****************************************************************************************/

bool BigFile::Request::Step()
{
    const int result = sqlite3_step(m_statement);
    ASSERT(EngineAssertHandler,
           (result == SQLITE_DONE) || (result == SQLITE_ROW),
           "Failed to step through the statement: {}",
           sqlite3_errmsg(m_db));

    return result == SQLITE_ROW;
}

/****************************************************************************************/

std::uint32_t BigFile::Request::Execute()
{
    [[maybe_unused]]
    const int result = sqlite3_step(m_statement);
    ASSERT(EngineAssertHandler, (result == SQLITE_DONE), "Failed to execute the statement: {}", sqlite3_errmsg(m_db));

    return sqlite3_changes(m_db);
}

/****************************************************************************************/

BigFile::Request::Column::Column(const Request& p_request, const std::uint32_t p_index):
    m_statement(p_request.m_statement),
    m_index(p_index)
{
}

/****************************************************************************************/

BigFile::Request::Column::operator std::int32_t() const
{
    return sqlite3_column_int(m_statement, m_index);
}

/****************************************************************************************/

BigFile::Request::Column::operator std::uint32_t() const
{
    return sqlite3_column_int(m_statement, m_index);
}

/****************************************************************************************/

BigFile::Request::Column::operator std::int64_t() const
{
    return sqlite3_column_int64(m_statement, m_index);
}

/****************************************************************************************/

BigFile::Request::Column::operator std::uint64_t() const
{
    return std::bit_cast<std::uint64_t>(sqlite3_column_int64(m_statement, m_index));
}

/****************************************************************************************/

BigFile::Request::Column::operator float() const
{
    return static_cast<float>(sqlite3_column_double(m_statement, m_index));
}

/****************************************************************************************/

BigFile::Request::Column::operator double() const
{
    return sqlite3_column_double(m_statement, m_index);
}

/****************************************************************************************/

BigFile::Request::Column::operator eastl::string_view() const
{
    return eastl::string_view {reinterpret_cast<const char*>(sqlite3_column_text(m_statement, m_index)),
                               static_cast<std::size_t>(sqlite3_column_bytes(m_statement, m_index))};
}

/****************************************************************************************/

BigFile::Request::Column::operator eastl::span<const std::byte>() const
{
    return eastl::span<const std::byte> {static_cast<const std::byte*>(sqlite3_column_blob(m_statement, m_index)),
                                         static_cast<std::size_t>(sqlite3_column_bytes(m_statement, m_index))};
}

/****************************************************************************************/

BigFile::Request::Column BigFile::Request::Get(const std::uint32_t p_index) const
{
    ASSERT(EngineAssertHandler,
           p_index < static_cast<std::uint32_t>(sqlite3_column_count(m_statement)),
           "Invalid index for column!");
    return {*this, p_index};
}

/****************************************************************************************/

BigFile::Request::~Request()
{
    [[maybe_unused]]
    const int result = sqlite3_finalize(m_statement);
    CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db));
}
}   // namespace Bigfoot
