#include <kklib.h>
#include <sqlite3.h>

static inline sqlite3 *borrow_db(kk_box_t cref, kk_context_t *_ctx) {
    return (sqlite3 *)kk_cptr_unbox_borrowed(cref, _ctx);
}

kk_string_t kk_sqlite3_errstr(int32_t x, kk_context_t *_ctx) {
    const char *s = sqlite3_errstr((int)x);
    // NOTE(zephyr): kk_string_alloc_from_utf8 calls C stdlib strlen(),
    // which is not guaranteed to handle a NULL pointer.
    if (s == NULL) {
        return kk_string_empty();
    }
    return kk_string_alloc_from_utf8(s, _ctx);
}

int32_t kk_sqlite3_extended_errcode(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3 *db = borrow_db(cref, _ctx);
    return (int32_t)sqlite3_extended_errcode(db);
}

kk_integer_t kk_sqlite3_error_offset(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3 *db = borrow_db(cref, _ctx);
    int r = sqlite3_error_offset(db);
    return kk_integer_from_int(r, _ctx);
}

kk_std_core_types__tuple2 kk_sqlite3_open_v2(kk_string_t filename, int32_t flags, kk_string_t vfs, kk_context_t *_ctx) {
    kk_ssize_t vfs_len = 0;
    const char *name = kk_string_cbuf_borrow(filename, NULL, _ctx);
    const char *v = kk_string_cbuf_borrow(vfs, &vfs_len, _ctx);
    sqlite3 *db = NULL;
    int r = sqlite3_open_v2(name, &db, (int)flags, vfs_len != 0 ? v : NULL);
    return kk_std_core_types__new_Tuple2(kk_box_box(kk_cptr_box(db, _ctx), _ctx), kk_int32_box((int32_t)r, _ctx), _ctx);
}

int32_t kk_sqlite3_close(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3 *db = borrow_db(cref, _ctx);
    return (int32_t)sqlite3_close(db);
}

kk_std_core_types__tuple3 kk_sqlite3_prepare_v3(kk_box_t cref, kk_std_core_sslice__sslice sql, int32_t flags, kk_context_t *_ctx) {
    kk_box_t fst, snd, thd;
    sqlite3 *db = borrow_db(cref, _ctx);

    kk_intx_t sql_start = kk_integer_clamp_borrow(sql.start, _ctx);
    kk_intx_t sql_len = kk_integer_clamp_borrow(sql.len, _ctx);
    // TODO(zephyr): instead of borrowing and then duping to construct left,
    // check whether we can replace the reference
    const char *sql_bytes = kk_string_cbuf_borrow(sql.str, NULL, _ctx) + sql_start;

    sqlite3_stmt *stmt = NULL;
    const char *left_bytes = NULL;
    int r = sqlite3_prepare_v3(db, sql_bytes, (int)sql_len, flags, &stmt, &left_bytes);

    kk_std_core_sslice__sslice left = {
        .str = kk_string_dup(sql.str, _ctx),
        .start = kk_integer_from_int(sql_start + (left_bytes - sql_bytes), _ctx),
        .len = kk_integer_from_int(sql_len - (left_bytes - sql_bytes), _ctx),
    };
    fst = kk_box_box(kk_cptr_box(stmt, _ctx), _ctx);
    snd = kk_std_core_sslice__sslice_box(left, _ctx);
    thd = kk_int32_box((int32_t)r, _ctx);
    return kk_std_core_types__new_Tuple3(fst, snd, thd, _ctx);
}

static inline sqlite3_stmt *borrow_stmt(kk_box_t cref, kk_context_t *_ctx) {
    return (sqlite3_stmt *)kk_cptr_unbox_borrowed(cref, _ctx);
}

int32_t kk_sqlite3_finalize(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return (int32_t)sqlite3_finalize(stmt);
}

int32_t kk_sqlite3_step(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return (int32_t)sqlite3_step(stmt);
}

int32_t kk_sqlite3_reset(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return (int32_t)sqlite3_reset(stmt);
}

kk_integer_t kk_sqlite3_column_count(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    int r = sqlite3_column_count(stmt);
    return kk_integer_from_int(r, _ctx);
}

int32_t kk_sqlite3_column_type(kk_box_t cref, int32_t col, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return (int32_t)sqlite3_column_type(stmt, (int)col);
}

double kk_sqlite3_column_double(kk_box_t cref, int32_t col, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_column_double(stmt, (int)col);
}

kk_integer_t kk_sqlite3_column_integer(kk_box_t cref, int32_t col, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    sqlite3_int64 r = sqlite3_column_int64(stmt, (int)col);
    return kk_integer_from_int64((int64_t)r, _ctx);
}

kk_string_t kk_sqlite3_column_text(kk_box_t cref, int32_t col, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    const unsigned char *text = sqlite3_column_text(stmt, (int)col);
    if (text == NULL) {
        // The column value is NULL, but we can't put that in a Koka string.
        // Since a string was requested, return an empty string instead.
        return kk_string_empty();
    }
    int len = sqlite3_column_bytes(stmt, (int)col);
    return kk_string_alloc_from_qutf8n((kk_ssize_t)len, (const char *)text, _ctx);
}

int32_t kk_sqlite3_bind_double(kk_box_t cref, int32_t param, double value, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_bind_double(stmt, param, value);
}

int32_t kk_sqlite3_bind_int64(kk_box_t cref, int32_t param, int64_t value, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_bind_int64(stmt, param, (sqlite3_int64)value);
}

int32_t kk_sqlite3_bind_null(kk_box_t cref, int32_t param, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_bind_null(stmt, param);
}

int32_t kk_sqlite3_bind_text(kk_box_t cref, int32_t param, kk_string_t value, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    kk_ssize_t len = 0;
    const char *text = kk_string_cbuf_borrow(value, &len, _ctx);
    return sqlite3_bind_text64(stmt, param, text, (sqlite3_uint64)len, SQLITE_TRANSIENT, SQLITE_UTF8);
}

int32_t kk_sqlite3_bind_zeroblob(kk_box_t cref, int32_t param, int64_t length, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_bind_zeroblob64(stmt, param, (sqlite3_uint64)length);
}

int32_t kk_sqlite3_clear_bindings(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return sqlite3_clear_bindings(stmt);
}

int32_t kk_sqlite3_parameter_count(kk_box_t cref, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    return (int32_t)sqlite3_bind_parameter_count(stmt);
}

kk_string_t kk_sqlite3_parameter_name(kk_box_t cref, int32_t param, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    const char *name = sqlite3_bind_parameter_name(stmt, (int)param);
    if (name == NULL) {
        return kk_string_empty();
    }
    return kk_string_alloc_from_utf8(name, _ctx);
}

int32_t kk_sqlite3_parameter_index(kk_box_t cref, kk_string_t name, kk_context_t *_ctx) {
    sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
    const char *s = kk_string_cbuf_borrow(name, NULL, _ctx);
    return (int32_t)sqlite3_bind_parameter_index(stmt, s);
}
