#ifndef __QCOMMON_SQL_H__
#define __QCOMMON_SQL_H__

#include <sqlite3.h>
#include <stdbool.h>

#include <alias/cpp.h>

#define SQL_QUERY_BIND_ARGS_column_text(...)
#define SQL_QUERY_BIND_ARGS_column_int64(...)
#define SQL_QUERY_BIND_ARGS_bind_text(index, name) const char *name,
#define SQL_QUERY_BIND_ARGS(X) ALIAS_CPP_CAT(SQL_QUERY_BIND_ARGS_, X)

#define SQL_QUERY_BIND_column_text(...)
#define SQL_QUERY_BIND_column_int64(...)
#define SQL_QUERY_BIND_bind_text(index, name) sqlite3_bind_text(stmt, index + 1, name, -1, SQLITE_STATIC);
#define SQL_QUERY_BIND(X) ALIAS_CPP_CAT(SQL_QUERY_BIND_, X)

#define SQL_QUERY_CALLBACK_ARGS_bind_text(...)
#define SQL_QUERY_CALLBACK_ARGS_column_text(index, name) , const unsigned char *name
#define SQL_QUERY_CALLBACK_ARGS_column_int64(index, name) , int64_t name
#define SQL_QUERY_CALLBACK_ARGS(X) ALIAS_CPP_CAT(SQL_QUERY_CALLBACK_ARGS_, X)

#define SQL_QUERY_EXTRACT_bind_text(...)
#define SQL_QUERY_EXTRACT_column_text(index, name) const unsigned char *name = sqlite3_column_text(stmt, index);
#define SQL_QUERY_EXTRACT_column_int64(index, name) sqlite3_int64 name = sqlite3_column_int64(stmt, index);
#define SQL_QUERY_EXTRACT(X) ALIAS_CPP_CAT(SQL_QUERY_EXTRACT_, X)

#define SQL_QUERY_PASS_bind_text(...)
#define SQL_QUERY_PASS_column_text(index, name) , name
#define SQL_QUERY_PASS_column_int64(index, name) , name
#define SQL_QUERY_PASS(X) ALIAS_CPP_CAT(SQL_QUERY_PASS_, X)

#define SQL_QUERY(NAME, DB, SQL, ...)                                                                                  \
  sqlite3_stmt *NAME##_stmt(void) {                                                                                    \
    static sqlite3 *stmt_db = NULL;                                                                                    \
    static sqlite3_stmt *stmt = NULL;                                                                                  \
    if(DB != stmt_db) {                                                                                                \
      stmt = NULL;                                                                                                     \
    }                                                                                                                  \
    if(DB == NULL) {                                                                                                   \
      return NULL;                                                                                                     \
    }                                                                                                                  \
    if(stmt == NULL) {                                                                                                 \
      stmt_db = DB;                                                                                                    \
      int err;                                                                                                         \
      if((err = sqlite3_prepare_v2(DB, SQL, -1, &stmt, NULL))) {                                                       \
        Com_Error(ERR_FATAL, "SQL statement failed to prepare: %i", err);                                              \
      }                                                                                                                \
    }                                                                                                                  \
    return stmt;                                                                                                       \
  }                                                                                                                    \
  int NAME(ALIAS_CPP_EVAL_2(ALIAS_CPP_MAP(SQL_QUERY_BIND_ARGS, ##__VA_ARGS__)) void (*callback)(                       \
               void *ud ALIAS_CPP_EVAL(ALIAS_CPP_MAP(SQL_QUERY_CALLBACK_ARGS, ##__VA_ARGS__))),                        \
           void *ud) {                                                                                                 \
    sqlite3_stmt *stmt = NAME##_stmt();                                                                                \
    sqlite3_reset(stmt);                                                                                               \
    sqlite3_clear_bindings(stmt);                                                                                      \
    ALIAS_CPP_EVAL(ALIAS_CPP_MAP(SQL_QUERY_BIND, ##__VA_ARGS__))                                                       \
    int rows = 0;                                                                                                      \
    for(;;) {                                                                                                          \
      int code = sqlite3_step(stmt);                                                                                   \
      if(code == SQLITE_DONE) {                                                                                        \
        break;                                                                                                         \
      }                                                                                                                \
      if(code == SQLITE_ROW) {                                                                                         \
        ALIAS_CPP_EVAL(ALIAS_CPP_MAP(SQL_QUERY_EXTRACT, ##__VA_ARGS__))                                                \
        callback(ud ALIAS_CPP_EVAL(ALIAS_CPP_MAP(SQL_QUERY_PASS, ##__VA_ARGS__)));                                     \
        rows++;                                                                                                        \
        continue;                                                                                                      \
      }                                                                                                                \
      Com_Error(ERR_FATAL, "unexpected SQLite step result: %i", code);                                                 \
    }                                                                                                                  \
    return rows;                                                                                                       \
  }

#define SQL_DO(NAME, DB, SQL, ...)                                                                                     \
  sqlite3_stmt *NAME##_stmt(void) {                                                                                    \
    static sqlite3 *stmt_db = NULL;                                                                                    \
    static sqlite3_stmt *stmt = NULL;                                                                                  \
    if(DB != stmt_db) {                                                                                                \
      stmt = NULL;                                                                                                     \
    }                                                                                                                  \
    if(DB == NULL) {                                                                                                   \
      return NULL;                                                                                                     \
    }                                                                                                                  \
    if(stmt == NULL) {                                                                                                 \
      stmt_db = DB;                                                                                                    \
      int err;                                                                                                         \
      if(err = sqlite3_prepare_v2(DB, SQL, -1, &stmt, NULL)) {                                                         \
        Com_Error(ERR_FATAL, "SQL statement failed to prepare: %i", err);                                              \
      }                                                                                                                \
    }                                                                                                                  \
    return stmt;                                                                                                       \
  }                                                                                                                    \
  void NAME(ALIAS_CPP_EVAL_2(ALIAS_CPP_MAP(SQL_QUERY_BIND_ARGS, ##__VA_ARGS__))) {                                     \
    sqlite3_stmt *stmt = NAME##_stmt();                                                                                \
    sqlite3_reset(stmt);                                                                                               \
    sqlite3_clear_bindings(stmt);                                                                                      \
    ALIAS_CPP_EVAL(ALIAS_CPP_MAP(SQL_QUERY_BIND, ##__VA_ARGS__))                                                       \
    for(;;) {                                                                                                          \
      int code = sqlite3_step(stmt);                                                                                   \
      if(code == SQLITE_DONE) {                                                                                        \
        break;                                                                                                         \
      }                                                                                                                \
      if(code == SQLITE_ROW) {                                                                                         \
        continue;                                                                                                      \
      }                                                                                                                \
      Com_Error(ERR_FATAL, "unexpected SQLite step result: %i", code);                                                 \
    }                                                                                                                  \
  }

bool SQLite_exec(sqlite3 *db,                /* The database on which the SQL executes */
                 const char *zSql,           /* The SQL to be executed */
                 sqlite3_callback xCallback, /* Invoke this callback routine */
                 void *pArg                  /* First argument to xCallback() */
);

#endif