#define _POSIX_C_SOURCE 200809L

#include <lektor/common.h>
#include <lektor/database.h>
#include <lektor/internal/dbmacro.h>

#define sqlite_just_exec(func, query)                                                                                  \
    bool func(lkt_db *db)                                                                                              \
    {                                                                                                                  \
        SQLITE_EXEC(db, "BEGIN TRANSACTION;" query "COMMIT;", error);                                                  \
        return true;                                                                                                   \
    error:                                                                                                             \
        SQLITE_DO_ROLLBACK(db);                                                                                        \
        return false;                                                                                                  \
    }
// clang-format off
sqlite_just_exec(database_queue_toggle_pause, "UPDATE queue_state SET paused = 1 - paused;");
sqlite_just_exec(database_queue_crop, "DELETE FROM queue WHERE queue.kara_id <> (SELECT current FROM queue_state LIMIT 1);");
sqlite_just_exec(database_queue_stop, "UPDATE queue_state SET current = NULL;");
sqlite_just_exec(database_queue_clear, "DELETE FROM queue;"
                                       "DELETE FROM queue_tmp;"
                                       "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
                                       "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';"
                                       "UPDATE queue_state SET current = NULL;");
sqlite_just_exec(database_config_queue_default, "UPDATE queue_state SET volume = 100, paused = 1,"
                                                " random = 0, repeat = 0, single = 0, consume = 0,"
                                                " current = NULL, duration = 0;");
sqlite_just_exec(database_queue_flat, "UPDATE queue SET priority = 1;");
// clang-format on
#undef sqlite_just_exec

PRIVATE_FUNCTION void
__queue_resequence(lkt_db *db)
{
    static const char *SQL = "UPDATE " LKT_PROTECTED_DATABASE
                             ".sqlite_sequence SET seq = (SELECT COUNT(*) FROM queue) WHERE name = 'queue';";
    SQLITE_EXEC(db, SQL, error);
    LOG_INFO("DB", "Re-sequenced the queue");
    return;
error:
    LOG_ERROR("DB", "Failed to update the " LKT_PROTECTED_DATABASE ".sqlite_sequence table...");
    return;
}

bool
database_queue_set_paused(lkt_db *db, bool paused)
{
    const char *SQL = paused ? "UPDATE queue_state SET paused = 1;" : "UPDATE queue_state SET paused = 0;";
    SQLITE_EXEC(db, SQL, error);
    return true;
error:
    return false;
}

bool
database_queue_state(lkt_db *db, struct lkt_queue_state *res)
{
    static const char *SQL_STMT = "SELECT"
                                  " volume, paused, random, repeat, single, current, duration, consume,"
                                  " (SELECT COUNT(*) FROM queue) AS length "
                                  "FROM queue_state;\n";
    sqlite3_stmt *stmt = 0;
    bool ret           = false;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_STEP_ROW(db, stmt, error);

    res->volume = sqlite3_column_int(stmt, 0);
    res->paused = sqlite3_column_int(stmt, 1);
    res->random = sqlite3_column_int(stmt, 2);
    res->repeat = sqlite3_column_int(stmt, 3);
    res->single = sqlite3_column_int(stmt, 4);

    if (sqlite3_column_type(stmt, 5) == SQLITE_NULL)
        res->current = -1;
    else
        res->current = sqlite3_column_int(stmt, 5);

    if (sqlite3_column_type(stmt, 6) == SQLITE_NULL)
        res->duration = 0;
    else
        res->duration = sqlite3_column_int(stmt, 6);

    res->consume = sqlite3_column_int(stmt, 7);
    res->length  = sqlite3_column_int(stmt, 8);
    ret          = true;
error:
    sqlite3_finalize(stmt);
    return ret;
}

void
database_queue_playtime(lkt_db *db, uint64_t *seconds)
{
    static const char *SQL = "SELECT SUM(cached_duration) "
                             "FROM kara "
                             "JOIN queue ON queue.kara_id = kara.id;";
    sqlite3_stmt *stmt = NULL;
    *seconds           = 0;
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_STEP_ROW(db, stmt, error);
    *seconds = sqlite3_column_int(stmt, 0);
error:
    sqlite3_finalize(stmt);
    return;
}

void
database_total_playtime(lkt_db *db, uint64_t *seconds)
{
    static const char *SQL = "SELECT SUM(cached_duration) FROM kara;";
    sqlite3_stmt *stmt     = NULL;
    *seconds               = 0;
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_STEP_ROW(db, stmt, error);
    *seconds = sqlite3_column_int(stmt, 0);
error:
    sqlite3_finalize(stmt);
    return;
}

bool
database_queue_current_kara(lkt_db *db, struct kara_metadata *res, int *id)
{
    static const char *SQL_STMT = "SELECT song_name, source_name, category, language, author_name, "
                                  "song_type, song_number, kara_id"
                                  " FROM kara"
                                  " JOIN queue ON kara_id = kara.id"
                                  " JOIN queue_state ON current = position";
    sqlite3_stmt *stmt = NULL;
    int ret            = false;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_STEP_ROW(db, stmt, error);

    /* Here use gotos because of optimisations done by compilators.
       Most of the time it won't be NULL. */
    if (!res)
        goto no_metadata;
    // clang-format off
    strncpy(res->song_name,   sqlite3_column_chars(stmt, 0), LEKTOR_TAG_MAX - 1);
    strncpy(res->source_name, sqlite3_column_chars(stmt, 1), LEKTOR_TAG_MAX - 1);
    strncpy(res->category,    sqlite3_column_chars(stmt, 2), LEKTOR_TAG_MAX - 1);
    strncpy(res->language,    sqlite3_column_chars(stmt, 3), LEKTOR_TAG_MAX - 1);
    strncpy(res->author_name, sqlite3_column_chars(stmt, 4), LEKTOR_TAG_MAX - 1);
    strncpy(res->song_type,   sqlite3_column_chars(stmt, 5), LEKTOR_TAG_MAX - 1);
    // clang-format on
    res->song_number = sqlite3_column_int(stmt, 6);
no_metadata:
    /* Most of the time this will be NULL. */
    if (id && sqlite3_column_type(stmt, 7) != SQLITE_NULL)
        *id = sqlite3_column_int(stmt, 7);

    ret = true;
error:
    sqlite3_finalize(stmt);
    return ret;
}

#define reorder(db, prio, error)                                                                                       \
    if (prio > 1) {                                                                                                    \
        if (__queue_reorder(db)) {                                                                                     \
            LOG_INFO("DB", "Queue has been reordered");                                                                \
        } else {                                                                                                       \
            LOG_INFO("DB", "Failed to reorder");                                                                       \
            goto error;                                                                                                \
        }                                                                                                              \
    }

static bool
__queue_reorder(lkt_db *db)
{
#define CURRENT_POS_OR_0 /* To move karas after the current */                                                         \
    "(SELECT CASE WHEN (SELECT current FROM queue_state) IS NULL THEN 0"                                               \
    " ELSE (SELECT current FROM queue_state) END AS val LIMIT 1)"
    static const char *SQL_REORDER =
        /* Clear the TMP */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
        /* Separate karas that are after the current one */
        "INSERT INTO queue_tmp (kara_id, priority)"
        " SELECT kara_id, priority FROM queue WHERE position > " CURRENT_POS_OR_0
        " ORDER BY priority DESC, position ASC;"
        "DELETE FROM queue WHERE position > " CURRENT_POS_OR_0 ";"
        /* Update the sqlite_sequence table */
        "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence SET seq = " CURRENT_POS_OR_0 " WHERE name = 'queue';"
        /* Insert back */
        "INSERT INTO queue (position, kara_id, priority)"
        " SELECT position + " CURRENT_POS_OR_0 ", kara_id, priority"
        " FROM queue_tmp;"
        /* Clear the TMP */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';";
#undef CURRENT_POS_OR_0
    SQLITE_EXEC(db, SQL_REORDER, error);
    return true;
error:
    return false;
}

static bool
queue_add_with_col_like_str(lkt_db *db, const char *col, const char *val, int priority)
{
    static const char *SQL_STMT =
        /* Add correspondig karas into the queue */
        "INSERT INTO queue (kara_id, priority)"
        " SELECT id, ?"
        " FROM kara"
        " WHERE %s LIKE ? AND available = 1"
        " ORDER BY RANDOM();";

    RETURN_IF(___is_sql_str_invalid(col), "Column name is invalid", false);
    char SQL[LKT_MAX_SQLITE_STATEMENT];
    sqlite3_stmt *stmt = NULL;

    /* Begin, transactions are atomic so we won't be anoyed
       by multi-threading */
    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error);

    /* Insert at the end of the queue */
    safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_STMT, col);
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_INT(db, stmt, 1, priority, error);
    SQLITE_BIND_TEXT(db, stmt, 2, val, error);
    SQLITE_STEP_DONE(db, stmt, error);
    sqlite3_finalize(stmt);

    /* Reorder kara that are after the current one, do this shit only
       if priority > 1... */
    reorder(db, priority, error);

    /* End */
    SQLITE_EXEC(db, "COMMIT;", error);
    LOG_INFO("DB-DEBUG", "Added with col %s like %s with priority %d", col, val, priority);
    return true;

error:
    sqlite3_finalize(stmt);
    SQLITE_DO_ROLLBACK(db);
    __queue_resequence(db);
    return false;
}

bool
database_queue_add_plt(lkt_db *db, const char *plt_name, int priority)
{
    static const char *SQL_STMT =
        /* Insert the playlist */
        "INSERT INTO queue (kara_id, priority) "
        "SELECT kara.id, ?"
        " FROM kara"
        " JOIN kara_playlist ON kara_id = kara.id AND kara.available = 1"
        " JOIN playlist ON playlist_id = playlist.id AND playlist.name = ?"
        " ORDER BY RANDOM();";
    sqlite3_stmt *stmt = NULL;

    /* Begin, transactions are atomic so we won't be anoyed
       by multi-threading */
    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error);

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_BIND_INT(db, stmt, 1, priority, error);
    SQLITE_BIND_TEXT(db, stmt, 2, plt_name, error);
    SQLITE_STEP_DONE(db, stmt, error);
    sqlite3_finalize(stmt);

    /* Do the move shit only if the priority > 1 */
    reorder(db, priority, error);

    SQLITE_EXEC(db, "COMMIT;", error);
    LOG_INFO("DB-DEBUG", "Added play list %s with priority %d", plt_name, priority);
    return true;

error:
    sqlite3_finalize(stmt);
    SQLITE_DO_ROLLBACK(db);
    __queue_resequence(db);
    return false;
}

bool
database_queue_add_id(lkt_db *db, int id, int priority)
{
    static const char *SQL_STMT = "INSERT INTO queue (kara_id, priority) VALUES (?,?);";
    sqlite3_stmt *stmt          = NULL;
    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error);
    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_BIND_INT(db, stmt, 1, id, error);
    SQLITE_BIND_INT(db, stmt, 2, priority, error);
    SQLITE_STEP_DONE(db, stmt, error);
    sqlite3_finalize(stmt);

    /* Do the move shit only if the priority > 1 */
    reorder(db, priority, error_no_stmt);

    SQLITE_EXEC(db, "COMMIT;", error);
    return true;
error:
    LOG_ERROR("DB",
              "Error while adding the kara id %d into the queue with the "
              "priority %d",
              id, priority);
    sqlite3_finalize(stmt);
error_no_stmt:
    LOG_ERROR("DB",
              "Need to rollback the queue because of failed insertion of id "
              "%d with priority %d in the queue",
              id, priority);
    SQLITE_DO_ROLLBACK(db);
    __queue_resequence(db);
    return false;
}

bool
database_queue_add_uri(lkt_db *db, struct lkt_uri *uri, int prio)
{
    switch (uri->type) {
    case URI_QUERY:
        return queue_add_with_col_like_str(db, LKT_DB_ALL, uri->value, prio);

    case URI_ID:
        return database_queue_add_id(db, uri->id, prio);

    case URI_TYPE:
        return queue_add_with_col_like_str(db, LKT_DB_TYPE, uri->value, prio);

    case URI_CATEGORY:
        return queue_add_with_col_like_str(db, LKT_DB_CAT, uri->value, prio);

    case URI_LANGUAGE:
        return queue_add_with_col_like_str(db, LKT_DB_LANG, uri->value, prio);

    case URI_AUTHOR:
        return queue_add_with_col_like_str(db, LKT_DB_AUTHOR, uri->value, prio);

    case URI_PLAYLIST:
        return database_queue_add_plt(db, uri->value, prio);

    default:
        LOG_WARN("DB", "Add to queue for uri of type %d is not implemented", uri->type);
        return false;
    }
}

bool
database_queue_del_id(lkt_db *db, int id)
{
    static const char *SQL_TEMPLATE =
        "BEGIN TRANSACTION;"
        /* Clear the TMP */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
        /* Move the current 'pointer' */
        "UPDATE queue_state SET current = (SELECT NULLIF(COUNT(position), 0) FROM queue JOIN queue_state ON position <= current AND kara_id != %d);"
        "DELETE FROM queue WHERE kara_id = %d;" /* Delete any kara with the specified id */
        "INSERT INTO queue_tmp(priority, position, kara_id) SELECT priority, position, kara_id FROM queue ORDER BY position ASC;"
        "DELETE FROM queue;"
        "INSERT INTO queue(priority, position, kara_id) SELECT priority, ROW_NUMBER() OVER(ORDER BY position ASC), kara_id FROM queue_tmp;"
        "UPDATE " LKT_PROTECTED_DATABASE
        ".sqlite_sequence SET seq = (SELECT COUNT(*) FROM queue) WHERE name = 'queue';" /* Update the sqlite_sequence            */
        /* Clear the TMP */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
        "COMMIT;";
    char SQL[LKT_MAX_SQLITE_STATEMENT];
    safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, id, id);
    SQLITE_EXEC(db, SQL, error);
    __queue_resequence(db);
    return true;
error:
    SQLITE_DO_ROLLBACK(db);
    return false;
}

bool
database_queue_del_pos(lkt_db *db, int pos)
{
    bool sta                     = false;
    sqlite3_stmt *stmt           = NULL;
    struct lkt_queue_state queue = { .current = -1 };
    RETURN_UNLESS(database_queue_state(db, &queue), "Failed to get the queue state", false);

    /* Delete the current kara, can't do that because we need to skip it, and
     * send signals to the window module to do so, so we just fail here... */
    if (queue.current == pos) {
        LOG_ERROR("DB", "Can't delete the currently playing kara at position %d in queue", pos);
        return false;
    }

    static const char *SQL_TEMPLATE =
        /* Create temporary queue */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
        /* -1 to all positions after the deleted kara */
        "DELETE FROM queue WHERE position = %d;"
        "INSERT INTO queue_tmp (kara_id, priority)"
        "  SELECT kara_id, priority"
        "  FROM queue;"
        /* Re-init positions in the queue */
        "DELETE FROM queue;"
        "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';"
        /* Insert back all karas */
        "INSERT INTO queue (kara_id, priority, position)"
        "  SELECT kara_id, priority, position"
        "  FROM queue_tmp;"
        /* Slap correct values here */
        "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence"
        "  SET seq = (SELECT COUNT(position) FROM queue)"
        "  WHERE name = 'queue';"
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';";
    char SQL[LKT_MAX_SQLITE_STATEMENT];
    safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, pos);

    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback);
    SQLITE_EXEC(db, SQL, error_rollback);
    if (pos < queue.current) {
        LOG_DEBUG("DB", "Needs to decrement current index: pos %d < current %d", pos, queue.current);
        SQLITE_EXEC(db, "UPDATE queue_state SET current = current - 1;", error_in_delete_or_update);
    }

    sta = true;
error_in_delete_or_update:
    if (sta) {
        /* Commit transaction, if failed use a magnificient goto... */
        __queue_resequence(db);
        SQLITE_EXEC(db, "END TRANSACTION;", error_rollback);
    }

    else {
    error_rollback:
        sta = false;
        LOG_DEBUG("DB", "An error occured, needs to rollback transaction");
        SQLITE_DO_ROLLBACK(db);
    }

error_no_rollback:
    if (stmt != NULL)
        sqlite3_finalize(stmt);
    return sta;
}

bool
database_queue_next(lkt_db *db, char filepath[PATH_MAX])
{
    static const char *SQL_STMT =
        "SELECT file_path, position, RANDOM()"
        "  FROM kara"
        "  JOIN queue ON kara.id = queue.kara_id"
        "  JOIN queue_state"
        "    ON current IS NULL"
        "    OR (CASE WHEN (SELECT single FROM queue_state LIMIT 1) = 1 THEN position = current"
        "             WHEN (SELECT random FROM queue_state LIMIT 1) = 1 THEN position <> current"
        "             ELSE position > current END)"
        "  ORDER BY CASE WHEN (SELECT random FROM queue_state LIMIT 1) = 1 THEN RANDOM() ELSE 2 END"
        "  LIMIT 1";
    char SQL_UPDATE[LKT_MAX_SQLITE_STATEMENT];
    bool status        = false;
    int code           = SQLITE_OK, id;
    sqlite3_stmt *stmt = NULL;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);

    code = sqlite3_step(stmt);

    if (code == SQLITE_ROW) {
        id = MAX(1, sqlite3_column_int(stmt, 1));
        safe_snprintf(SQL_UPDATE, LKT_MAX_SQLITE_STATEMENT, "UPDATE queue_state SET current = %d;", id);

        if (filepath != NULL)
            strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX - 1);
        else {
            LOG_ERROR("DB", "Failed to get file, id was %d", id);
            goto error;
        }
    }

    else if (code == SQLITE_DONE) {
        LOG_ERROR("DB", "Failed to get next");
        goto error;
    }

    else {
        LOG_ERROR("DB", "Failed to fetch next kara: %s", sqlite3_errmsg((sqlite3 *)db));
        goto error;
    }

    SQLITE_EXEC(db, SQL_UPDATE, error);
    status = true;
error:
    sqlite3_finalize(stmt);
    return status;
}

bool
database_queue_skip_current(lkt_db *db, char filepath[PATH_MAX])
{
    if (database_queue_next(db, filepath))
        return true;
    else if (database_queue_prev(db, filepath))
        return true;
    else {
        database_queue_stop(db);
        return false;
    }
}

bool
database_queue_prev(lkt_db *db, char filepath[PATH_MAX])
{
    static const char *SQL_STMT = "SELECT file_path, position "
                                  "  FROM kara"
                                  "  JOIN queue ON kara.id = queue.kara_id AND available = 1"
                                  "  JOIN queue_state ON CASE "
                                  "    WHEN (SELECT single FROM queue_state LIMIT 1) = 1 THEN position = current"
                                  "    ELSE queue.position < queue_state.current"
                                  "  END"
                                  "  ORDER BY position DESC LIMIT 1;";
    char SQL_UPDATE[LKT_MAX_SQLITE_STATEMENT];
    bool status        = false;
    int code           = SQLITE_OK, id;
    sqlite3_stmt *stmt = NULL;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    code = sqlite3_step(stmt);

    if (code == SQLITE_ROW) {
        id = MAX(1, sqlite3_column_int(stmt, 1));
        safe_snprintf(SQL_UPDATE, LKT_MAX_SQLITE_STATEMENT, "UPDATE queue_state SET current = %d;", id);

        if (filepath != NULL)
            strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX - 1);
        else {
            LOG_ERROR("DB", "Failed to get file, position was %d", id);
            goto error;
        }
    }

    else if (code == SQLITE_DONE) {
        LOG_ERROR("DB", "Failed to get previous");
        goto error;
    }

    else {
        LOG_ERROR("DB", "Failed to fetch prev kara: %s", sqlite3_errmsg((sqlite3 *)db));
        goto error;
    }

    SQLITE_EXEC(db, SQL_UPDATE, error);
    status = true;
error:
    sqlite3_finalize(stmt);
    return status;
}

bool
database_queue_swap(lkt_db *db, int from, int to)
{
    static const char *SQL_POP_ROW_1 = "SELECT position, priority, kara_id FROM queue WHERE position = ?;";
    static const char *SQL_POP_ROW_2 = "DELETE FROM queue WHERE position = ?;";
    static const char *SQL_SLAP_MAX  = "UPDATE " LKT_PROTECTED_DATABASE ".sqlite_sequence"
                                      "  SET seq = (SELECT COUNT(position) FROM queue)"
                                      "  WHERE name = 'queue';";
    static const char *SQL_PUSH_ROW = "INSERT INTO queue (position, priority, kara_id) VALUES (?, ?, ?);";

    struct {
        int position;
        int priority;
        int kara_id;
    } kara_from, kara_to;
    sqlite3_stmt *stmt = NULL;
    bool sta           = false;
    int tmp_kara_id;

    /* Get one kara from the 'queue' and delete it */
#define POP_QUEUE(name)                                                                                                \
    SQLITE_PREPARE(db, stmt, SQL_POP_ROW_1, error);                                                                    \
    SQLITE_BIND_INT(db, stmt, 1, name, error);                                                                         \
    SQLITE_STEP_ROW(db, stmt, error);                                                                                  \
    kara_##name.position = sqlite3_column_int(stmt, 0);                                                                \
    kara_##name.priority = sqlite3_column_int(stmt, 1);                                                                \
    kara_##name.kara_id  = sqlite3_column_int(stmt, 2);                                                                \
    sqlite3_finalize(stmt);                                                                                            \
    SQLITE_PREPARE(db, stmt, SQL_POP_ROW_2, error);                                                                    \
    SQLITE_BIND_INT(db, stmt, 1, name, error);                                                                         \
    SQLITE_STEP_DONE(db, stmt, error);                                                                                 \
    sqlite3_finalize(stmt);
    /* Push one kara on the 'queue' */
#define PUSH_QUEUE(name)                                                                                               \
    SQLITE_PREPARE(db, stmt, SQL_PUSH_ROW, error);                                                                     \
    SQLITE_BIND_INT(db, stmt, 1, kara_##name.position, error);                                                         \
    SQLITE_BIND_INT(db, stmt, 2, kara_##name.priority, error);                                                         \
    SQLITE_BIND_INT(db, stmt, 3, kara_##name.kara_id, error);                                                          \
    SQLITE_STEP_DONE(db, stmt, error);                                                                                 \
    sqlite3_finalize(stmt);

    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback);
    POP_QUEUE(from);
    POP_QUEUE(to);
    tmp_kara_id       = kara_from.kara_id;
    kara_from.kara_id = kara_to.kara_id;
    kara_to.kara_id   = tmp_kara_id;
    PUSH_QUEUE(from);
    PUSH_QUEUE(to);
    SQLITE_EXEC(db, SQL_SLAP_MAX, error_no_stmt);
    SQLITE_EXEC(db, "END TRANSACTION;", error_no_stmt);

#undef PUSH_QUEUE
#undef POP_QUEUE

    sta = true;
    goto error_no_stmt;
error:
    sqlite3_finalize(stmt);
error_no_stmt:
    if (!sta)
        SQLITE_DO_ROLLBACK(db);
error_no_rollback:
    return sta;
}

bool
database_queue_move(lkt_db *db, int from, int to)
{
    static const char *SQL_TEMPLATE_PART_1 =
        /* Clean queue_tmp */
        "DELETE FROM queue_tmp;"
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
        /* Construct queue */
        "INSERT INTO queue_tmp(kara_id,priority)"
        "WITH TEMP_TABLE AS (SELECT"
        "  position, kara_id, priority"
        "  FROM queue"
        "  ORDER BY position ASC, priority DESC)"
        "SELECT kara_id, priority"
        "  FROM TEMP_TABLE WHERE POSITION < %d AND POSITION != %d ORDER BY POSITION;"
        /* Construct queue */
        "INSERT INTO queue_tmp(kara_id,priority)"
        "  VALUES(("
        "    WITH TEMP_TABLE AS ("
        "      SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION,"
        "        kara_id, priority FROM queue)"
        "      SELECT kara_id FROM TEMP_TABLE WHERE POSITION = %d"
        "    ),("
        "      WITH TEMP_TABLE AS ("
        "      SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION,"
        "        kara_id, priority FROM queue)"
        "      SELECT priority FROM TEMP_TABLE WHERE POSITION = "
        "      CASE WHEN (SELECT COUNT(*) FROM TEMP_TABLE)>=%d THEN %d ELSE %d-1 END"
        "  ));";
    static const char *SQL_TEMPLATE_PART_2 =
        /* Construct queue */
        "INSERT INTO queue_tmp(kara_id,priority)"
        "WITH TEMP_TABLE AS ("
        "  SELECT ROW_NUMBER() OVER(ORDER BY position ASC, priority DESC) AS POSITION,"
        "    kara_id, priority FROM queue)"
        "SELECT kara_id, priority"
        "  FROM TEMP_TABLE WHERE POSITION >= %d AND POSITION != %d ORDER BY POSITION;"
        /* Clean queue and insert back */
        "UPDATE queue_state"
        "  SET current = CASE"
        "    WHEN current NOT NULL "
        "    THEN (SELECT position FROM queue_tmp "
        "       WHERE kara_id=(SELECT kara_id FROM queue WHERE position = current))"
        "    ELSE NULL END;"
        "DELETE FROM queue;"
        "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';"
        "INSERT INTO queue(position, kara_id, priority)"
        "  SELECT position, kara_id, priority FROM queue_tmp ORDER BY priority, position;"
        /* Clean queue_tmp */
        "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"

        "DELETE FROM queue_tmp;";

    char SQL[LKT_MAX_SQLITE_STATEMENT];

    SQLITE_EXEC(db, "BEGIN TRANSACTION;", error_no_rollback);
    safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE_PART_1, to, from, from, to, to, to);
    SQLITE_EXEC(db, SQL, error);

    safe_snprintf(SQL, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE_PART_2, to, from);
    SQLITE_EXEC(db, SQL, error);
    SQLITE_EXEC(db, "COMMIT;", error);
    return true;
error:
    SQLITE_DO_ROLLBACK(db);
error_no_rollback:
    return false;
}

bool
database_queue_play(lkt_db *db, int pos)
{
    static const char *SQL_STMT = "UPDATE queue_state SET current = ?, paused = 0"
                                  "  WHERE current <= 0 OR current IS NULL";
    int code           = SQLITE_OK;
    bool ret           = false;
    sqlite3_stmt *stmt = NULL;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_BIND_INT(db, stmt, 1, pos, error);

    code = sqlite3_step(stmt);

    if (code != SQLITE_OK && code != SQLITE_DONE) {
        LOG_ERROR("DB", "Failed to update queue_state: %s", sqlite3_errmsg((sqlite3 *)db));
        goto error;
    }

    ret = true;
error:
    sqlite3_finalize(stmt);
    return ret;
}

bool
database_queue_set_current_index(lkt_db *db, int idx)
{
    static const char *SQL_GET_TEMPLATE = "UPDATE queue_state SET current = %d;";
    char SQL_GET[LKT_MAX_SQLITE_STATEMENT];

    if (idx <= 0) {
        LOG_ERROR("DB", "An idx of %d is invalid, must be >= 0", idx);
        return false;
    }

    if (safe_snprintf(SQL_GET, LKT_MAX_SQLITE_STATEMENT, SQL_GET_TEMPLATE, idx) < 0)
        return false;

    SQLITE_EXEC(db, SQL_GET, error);
    return true;

error:
    return false;
}

bool
database_queue_get_current_file(lkt_db *db, char filepath[PATH_MAX])
{
    static const char *SQL_STMT = "SELECT file_path"
                                  "  FROM kara"
                                  "  JOIN queue ON kara.id = queue.kara_id"
                                  "  JOIN queue_state ON queue.position = queue_state.current";
    bool status        = false;
    int code           = SQLITE_OK;
    sqlite3_stmt *stmt = NULL;

    if (filepath == NULL)
        goto error;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    code = sqlite3_step(stmt);

    if (code == SQLITE_ROW) {
        strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX);
        filepath[PATH_MAX - 1] = '\0';
    }

    else {
        LOG_ERROR("DB", "Failed to fetch prev kara: %s", sqlite3_errmsg((sqlite3 *)db));
        goto error;
    }

    status = true;

error:
    sqlite3_finalize(stmt);
    return status;
}

bool
database_get_kara_path(lkt_db *db, int id, char filepath[PATH_MAX])
{
    bool ret_code          = false;
    sqlite3_stmt *stmt     = NULL;
    static const char *SQL = "SELECT file_path FROM kara_cache WHERE kara_id = ?;";
    RETURN_UNLESS(id && db, "Invalid argument", false);
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_INT(db, stmt, 1, id, error);
    SQLITE_STEP_ROW(db, stmt, error);
    if (filepath)
        safe_strncpy(filepath, sqlite3_column_chars(stmt, 0), PATH_MAX);
    ret_code = true;
error:
    sqlite3_finalize(stmt);
    return ret_code;
}

bool
database_get_kara_id(lkt_db *db, char filepath[PATH_MAX], int *id)
{
    bool ret_code          = false;
    sqlite3_stmt *stmt     = NULL;
    static const char *SQL = "SELECT id FROM kara WHERE file_path = ? AND available = 1;";
    RETURN_UNLESS(id && db, "Invalid argument", false);
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_TEXT(db, stmt, 1, filepath, error);
    SQLITE_STEP_ROW(db, stmt, error);
    if (id)
        *id = sqlite3_column_int(stmt, 0);
    ret_code = true;
error:
    sqlite3_finalize(stmt);
    return ret_code;
}

bool
database_get_kara_position(lkt_db *db, int id, int *pos)
{
    bool ret_code          = false;
    sqlite3_stmt *stmt     = NULL;
    static const char *SQL = "WITH content AS ("
                             " SELECT kara.id AS id, position"
                             "  FROM queue"
                             "  JOIN kara ON kara_id = kara.id"
                             "  GROUP BY position ORDER BY position ASC, priority DESC)"
                             "SELECT id, position FROM content WHERE id = ?;";
    RETURN_UNLESS(db && pos, "Invalid argument", false);
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_INT(db, stmt, 1, id, error);
    SQLITE_STEP_ROW(db, stmt, error);
    *pos     = sqlite3_column_int(stmt, 0);
    ret_code = true;
error:
    sqlite3_finalize(stmt);
    return ret_code;
}

int
database_queue_probe_id(lkt_db *db, int id)
{
    int position           = 0; /* The queue begins with the position 1 */
    sqlite3_stmt *stmt     = NULL;
    static const char *SQL = "SELECT position FROM queue WHERE kara_id = ?;";
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_INT(db, stmt, 1, id, error);
    SQLITE_STEP_ROW(db, stmt, error);
    position = sqlite3_column_int(stmt, 0);
error:
    sqlite3_finalize(stmt);
    return position;
}

bool
database_queue_shuffle(lkt_db *db)
{
    const char *SQL = "BEGIN TRANSACTION;"
                      /* Create temporary queue */
                      "DELETE FROM queue_tmp;"
                      "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
                      /* When current is NULL, that thing is also NULL, so no insertion is done */
                      "INSERT INTO queue_tmp (kara_id, priority)"
                      "  SELECT kara_id, 5"
                      "  FROM queue"
                      "  JOIN queue_state ON queue.position = queue_state.current;"
                      /* Insert the others karas */
                      "INSERT INTO queue_tmp (kara_id, priority)"
                      "  SELECT kara_id, priority"
                      "  FROM queue"
                      "  JOIN queue_state ON"
                      "    queue.position <> CASE"
                      "      WHEN queue_state.current IS NOT NULL THEN queue_state.current"
                      "      ELSE 0 END"
                      "  ORDER BY RANDOM();"
                      /* Do the insertion in the other way */
                      "DELETE FROM queue;"
                      "DELETE FROM " LKT_PROTECTED_DATABASE ".sqlite_sequence WHERE name = 'queue';"
                      "INSERT INTO queue (position, kara_id, priority)"
                      "  SELECT position, kara_id, priority"
                      "  FROM queue_tmp"
                      "  ORDER BY priority, position;"
                      /* Set the current to the right kara when needed */
                      "UPDATE queue_state"
                      "  SET current = CASE"
                      "    WHEN current NOT NULL THEN 1"
                      "    ELSE NULL END;"
                      /* Clear the TMP */
                      "DELETE FROM queue_tmp;"
                      "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';"
                      "COMMIT;";
    database_queue_flat(db);
    SQLITE_EXEC(db, SQL, error);
    return true;
error:
    SQLITE_DO_ROLLBACK(db);
    return false;
}

bool
database_queue_list(lkt_db *db, size_t from, size_t to, struct lkt_callback *callback)
{
    const char *SQL_STMT =
        "WITH content AS ("
        " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len, position, LENGTH(CAST(position AS TEXT)) AS pos_len"
        "  FROM queue"
        "  JOIN kara ON kara_id = kara.id"
        "  WHERE position >= ? AND position <= ?"
        "  GROUP BY position ORDER BY position ASC, priority DESC)"
        "SELECT id, string, (SELECT MAX(len) FROM content), position, (SELECT MAX(pos_len) FROM content) FROM content;";
    int code, id, id_len, pos, pos_len;
    const char *row;
    bool ret = false;
    sqlite3_stmt *stmt;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_BIND_INT(db, stmt, 1, (int)from, error);
    SQLITE_BIND_INT(db, stmt, 2, (int)to, error);

    for (;;) {
        code = sqlite3_step(stmt);

        if (code == SQLITE_ROW) {
            id      = sqlite3_column_int(stmt, 0);
            row     = sqlite3_column_chars(stmt, 1);
            id_len  = sqlite3_column_int(stmt, 2);
            pos     = sqlite3_column_int(stmt, 3);
            pos_len = sqlite3_column_int(stmt, 4);
            ++(callback->iterations);
            if (callback->call(callback, pos_len, pos, id, id_len, row))
                continue;
            else
                break;
        }

        else if (code == SQLITE_OK || code == SQLITE_DONE)
            goto done;

        else {
            LOG_ERROR("DB", "Failed: %s", sqlite3_errmsg((sqlite3 *)db));
            break;
        }
    }

done:
    ret = true;
error:
    sqlite3_finalize(stmt);
    return ret;
}

bool
database_queue_dump(lkt_db *db, const char *plt_name)
{
    static const char *SQL = "WITH plt_id AS (SELECT playlist.id AS id FROM playlist"
                             " WHERE name COLLATE nocase = ?) "
                             "INSERT OR IGNORE INTO kara_playlist (kara_id, playlist_id)"
                             " SELECT DISTINCT kara_id, plt_id.id"
                             " FROM queue, plt_id"
                             " ORDER BY RANDOM();";
    bool ret_code      = false;
    sqlite3_stmt *stmt = NULL;
    SQLITE_PREPARE(db, stmt, SQL, error);
    SQLITE_BIND_TEXT(db, stmt, 1, plt_name, error);
    SQLITE_STEP_DONE(db, stmt, error);
    ret_code = true;
error:
    sqlite3_finalize(stmt);
    return ret_code;
}

bool
database_queue_seekid(lkt_db *db, int id, int *out_pos)
{
    static const char *SQL_STMT = "SELECT position FROM queue WHERE kara_id = ? LIMIT 1";
    int ret                     = 0;
    sqlite3_stmt *stmt;

    SQLITE_PREPARE(db, stmt, SQL_STMT, error);
    SQLITE_BIND_INT(db, stmt, 1, id, error);
    SQLITE_STEP_ROW(db, stmt, error);
    *out_pos = sqlite3_column_int(stmt, 0);
    ret      = true;
error:
    sqlite3_finalize(stmt);
    return ret;
}