// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef ML_DATABASE_H
#define ML_DATABASE_H

#include "Dimension.h"
#include "ml-private.h"

#include "json/single_include/nlohmann/json.hpp"

namespace ml {

class Statement {
public:
    using RowCallback = std::function<void(sqlite3_stmt *Stmt)>;

public:
    Statement(const char *RawStmt) : RawStmt(RawStmt), ParsedStmt(nullptr) {}

    template<typename ...ArgTypes>
    bool exec(sqlite3 *Conn, RowCallback RowCb, ArgTypes ...Args) {
        if (!prepare(Conn))
            return false;

        switch (bind(1, Args...)) {
        case 0:
            return false;
        case sizeof...(Args):
            break;
        default:
            return resetAndClear(false);
        }

        while (true) {
            switch (int RC = sqlite3_step(ParsedStmt)) {
            case SQLITE_BUSY: case SQLITE_LOCKED:
                usleep(SQLITE_INSERT_DELAY * USEC_PER_MS);
                continue;
            case SQLITE_ROW:
                RowCb(ParsedStmt);
                continue;
            case SQLITE_DONE:
                return resetAndClear(true);
            default:
                error("Stepping through '%s' returned rc=%d", RawStmt, RC);
                return resetAndClear(false);
            }
        }
    }

    ~Statement() {
        if (!ParsedStmt)
            return;

        int RC = sqlite3_finalize(ParsedStmt);
        if (RC != SQLITE_OK)
            error("Could not properly finalize statement (rc=%d)", RC);
    }

private:
    bool prepare(sqlite3 *Conn);

    bool bindValue(size_t Pos, const int Value);
    bool bindValue(size_t Pos, const std::string &Value);

    template<typename ArgType, typename ...ArgTypes>
    size_t bind(size_t Pos, ArgType T) {
        return bindValue(Pos, T);
    }

    template<typename ArgType, typename ...ArgTypes>
    size_t bind(size_t Pos, ArgType T, ArgTypes ...Args) {
        return bindValue(Pos, T) + bind(Pos + 1, Args...);
    }

    bool resetAndClear(bool Ret);

private:
    const char *RawStmt;
    sqlite3_stmt *ParsedStmt;
};

class Database {
private:
    static const char *SQL_CREATE_ANOMALIES_TABLE;
    static const char *SQL_INSERT_ANOMALY;
    static const char *SQL_SELECT_ANOMALY;
    static const char *SQL_SELECT_ANOMALY_EVENTS;

public:
    Database(const std::string &Path);

    ~Database();

    template<typename ...ArgTypes>
    bool insertAnomaly(ArgTypes... Args) {
        Statement::RowCallback RowCb = [](sqlite3_stmt *Stmt) { (void) Stmt; };
        return InsertAnomalyStmt.exec(Conn, RowCb, Args...);
    }

    template<typename ...ArgTypes>
    bool getAnomalyInfo(nlohmann::json &Json, ArgTypes&&... Args) {
        Statement::RowCallback RowCb = [&](sqlite3_stmt *Stmt) {
            const char *Text = static_cast<const char *>(sqlite3_column_blob(Stmt, 0));
            Json = nlohmann::json::parse(Text);
        };
        return GetAnomalyInfoStmt.exec(Conn, RowCb, Args...);
    }

    template<typename ...ArgTypes>
    bool getAnomaliesInRange(std::vector<std::pair<time_t, time_t>> &V, ArgTypes&&... Args) {
        Statement::RowCallback RowCb = [&](sqlite3_stmt *Stmt) {
            V.push_back({
                sqlite3_column_int64(Stmt, 0),
                sqlite3_column_int64(Stmt, 1)
            });
        };
        return GetAnomaliesInRangeStmt.exec(Conn, RowCb, Args...);
    }

private:
    sqlite3 *Conn;

    Statement InsertAnomalyStmt{SQL_INSERT_ANOMALY};
    Statement GetAnomalyInfoStmt{SQL_SELECT_ANOMALY};
    Statement GetAnomaliesInRangeStmt{SQL_SELECT_ANOMALY_EVENTS};
};

}

#endif /* ML_DATABASE_H */