// dvxSql.c -- DVX SQL database implementation
//
// Wraps SQLite3 with integer handle management. Databases and
// cursors are stored in static tables indexed by handle. Handle 0
// is reserved (invalid).

#include "dvxSql.h"
#include "thirdparty/sqlite/examples/sqlite3.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

// ============================================================
// Handle tables
// ============================================================

#define MAX_DBS     16
#define MAX_CURSORS 64

typedef struct {
    sqlite3 *db;
    char     lastError[256];
    int32_t  affectedRows;
} DbEntryT;

typedef struct {
    sqlite3_stmt *stmt;
    int32_t       dbHandle; // which database owns this cursor
    bool          eof;      // true after sqlite3_step returns SQLITE_DONE
    bool          started;  // true after first dvxSqlNext call
} CursorEntryT;

static DbEntryT     sDbs[MAX_DBS];
static CursorEntryT sCursors[MAX_CURSORS];


// ============================================================
// Internal helpers
// ============================================================

static DbEntryT *getDb(int32_t handle) {
    if (handle <= 0 || handle > MAX_DBS) {
        return NULL;
    }

    if (!sDbs[handle - 1].db) {
        return NULL;
    }

    return &sDbs[handle - 1];
}


static CursorEntryT *getCursor(int32_t handle) {
    if (handle <= 0 || handle > MAX_CURSORS) {
        return NULL;
    }

    if (!sCursors[handle - 1].stmt) {
        return NULL;
    }

    return &sCursors[handle - 1];
}


// ============================================================
// dvxSqlOpen
// ============================================================

int32_t dvxSqlOpen(const char *path) {
    if (!path) {
        return 0;
    }

    // Find a free slot
    for (int32_t i = 0; i < MAX_DBS; i++) {
        if (!sDbs[i].db) {
            int rc = sqlite3_open(path, &sDbs[i].db);

            if (rc != SQLITE_OK) {
                if (sDbs[i].db) {
                    snprintf(sDbs[i].lastError, sizeof(sDbs[i].lastError), "%s", sqlite3_errmsg(sDbs[i].db));
                    sqlite3_close(sDbs[i].db);
                    sDbs[i].db = NULL;
                }

                return 0;
            }

            sDbs[i].lastError[0]  = '\0';
            sDbs[i].affectedRows  = 0;
            return i + 1; // 1-based handle
        }
    }

    return 0; // no free slots
}


// ============================================================
// dvxSqlClose
// ============================================================

void dvxSqlClose(int32_t db) {
    DbEntryT *entry = getDb(db);

    if (!entry) {
        return;
    }

    // Close any open cursors belonging to this database
    for (int32_t i = 0; i < MAX_CURSORS; i++) {
        if (sCursors[i].stmt && sCursors[i].dbHandle == db) {
            sqlite3_finalize(sCursors[i].stmt);
            memset(&sCursors[i], 0, sizeof(CursorEntryT));
        }
    }

    sqlite3_close(entry->db);
    memset(entry, 0, sizeof(DbEntryT));
}


// ============================================================
// dvxSqlExec
// ============================================================

bool dvxSqlExec(int32_t db, const char *sql) {
    DbEntryT *entry = getDb(db);

    if (!entry || !sql) {
        return false;
    }

    char *errMsg = NULL;
    int rc = sqlite3_exec(entry->db, sql, NULL, NULL, &errMsg);

    if (rc != SQLITE_OK) {
        if (errMsg) {
            snprintf(entry->lastError, sizeof(entry->lastError), "%s", errMsg);
            sqlite3_free(errMsg);
        } else {
            snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
        }

        return false;
    }

    entry->lastError[0] = '\0';
    entry->affectedRows = sqlite3_changes(entry->db);
    return true;
}


// ============================================================
// dvxSqlError
// ============================================================

const char *dvxSqlError(int32_t db) {
    DbEntryT *entry = getDb(db);

    if (!entry) {
        return "Invalid database handle";
    }

    return entry->lastError;
}


// ============================================================
// dvxSqlAffectedRows
// ============================================================

int32_t dvxSqlAffectedRows(int32_t db) {
    DbEntryT *entry = getDb(db);

    if (!entry) {
        return 0;
    }

    return entry->affectedRows;
}


// ============================================================
// dvxSqlQuery
// ============================================================

int32_t dvxSqlQuery(int32_t db, const char *sql) {
    DbEntryT *entry = getDb(db);

    if (!entry || !sql) {
        return 0;
    }

    // Find a free cursor slot
    for (int32_t i = 0; i < MAX_CURSORS; i++) {
        if (!sCursors[i].stmt) {
            sqlite3_stmt *stmt = NULL;
            int rc = sqlite3_prepare_v2(entry->db, sql, -1, &stmt, NULL);

            if (rc != SQLITE_OK) {
                snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
                return 0;
            }

            entry->lastError[0]  = '\0';
            sCursors[i].stmt     = stmt;
            sCursors[i].dbHandle = db;
            sCursors[i].eof      = false;
            sCursors[i].started  = false;
            return i + 1; // 1-based handle
        }
    }

    snprintf(entry->lastError, sizeof(entry->lastError), "Too many open cursors");
    return 0;
}


// ============================================================
// dvxSqlNext
// ============================================================

bool dvxSqlNext(int32_t rs) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur || cur->eof) {
        return false;
    }

    int rc = sqlite3_step(cur->stmt);
    cur->started = true;

    if (rc == SQLITE_ROW) {
        return true;
    }

    cur->eof = true;
    return false;
}


// ============================================================
// dvxSqlEof
// ============================================================

bool dvxSqlEof(int32_t rs) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return true;
    }

    return cur->eof;
}


// ============================================================
// dvxSqlFieldCount
// ============================================================

int32_t dvxSqlFieldCount(int32_t rs) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return 0;
    }

    return sqlite3_column_count(cur->stmt);
}


// ============================================================
// dvxSqlFieldName
// ============================================================

const char *dvxSqlFieldName(int32_t rs, int32_t col) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return "";
    }

    const char *name = sqlite3_column_name(cur->stmt, col);
    return name ? name : "";
}


// ============================================================
// dvxSqlFieldText
// ============================================================

const char *dvxSqlFieldText(int32_t rs, int32_t col) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return "";
    }

    const char *text = (const char *)sqlite3_column_text(cur->stmt, col);
    return text ? text : "";
}


// ============================================================
// dvxSqlFieldByName
// ============================================================

const char *dvxSqlFieldByName(int32_t rs, const char *name) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur || !name) {
        return "";
    }

    int32_t colCount = sqlite3_column_count(cur->stmt);

    for (int32_t i = 0; i < colCount; i++) {
        const char *colName = sqlite3_column_name(cur->stmt, i);

        if (colName && strcasecmp(colName, name) == 0) {
            const char *text = (const char *)sqlite3_column_text(cur->stmt, i);
            return text ? text : "";
        }
    }

    return "";
}


// ============================================================
// dvxSqlFieldInt
// ============================================================

int32_t dvxSqlFieldInt(int32_t rs, int32_t col) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return 0;
    }

    return sqlite3_column_int(cur->stmt, col);
}


// ============================================================
// dvxSqlFieldDbl
// ============================================================

double dvxSqlFieldDbl(int32_t rs, int32_t col) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return 0.0;
    }

    return sqlite3_column_double(cur->stmt, col);
}


// ============================================================
// dvxSqlFreeResult
// ============================================================

void dvxSqlFreeResult(int32_t rs) {
    CursorEntryT *cur = getCursor(rs);

    if (!cur) {
        return;
    }

    sqlite3_finalize(cur->stmt);
    memset(cur, 0, sizeof(CursorEntryT));
}


// ============================================================
// dvxSqlEscape
// ============================================================

int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize) {
    if (!src || !dst || dstSize <= 0) {
        return -1;
    }

    int32_t di = 0;

    for (int32_t si = 0; src[si]; si++) {
        if (src[si] == '\'') {
            if (di + 2 >= dstSize) {
                return -1;
            }

            dst[di++] = '\'';
            dst[di++] = '\'';
        } else {
            if (di + 1 >= dstSize) {
                return -1;
            }

            dst[di++] = src[si];
        }
    }

    dst[di] = '\0';
    return di;
}
