#include "emscripten.h"
#include "sqlite3.h"
#include <stdlib.h>
#include <stdio.h>

#define GATHER_ERROR_DETAILS(db) \
    int errcode__ = sqlite3_extended_errcode(db); \
    const char* msg__ = sqlite3_errmsg(db);

#define THROW_ERROR() \
    throw_error(msg__, errcode__);

#define THROW_ON_ERROR(db) \
  GATHER_ERROR_DETAILS(db) \
  THROW_ERROR();

typedef struct slice {
  void* ptr;
  unsigned int len;
} slice;

static slice out = {0, 0};

const char* sqlite_code_string(int code);

_Noreturn void throw_error(const char* msg, int code) {
  const char* name = sqlite_code_string(code);
  EM_ASM({ throw sqliteError(UTF8ToString($0), UTF8ToString($1), $2) }, name, msg, code);

  // this loop is not reachable
  while (1) {}
}

/* EXPORTS */

EMSCRIPTEN_KEEPALIVE int sqlite3_open_v2_x(
  char *filename,
  int flags
) {
  sqlite3* out_db = NULL;
  int ret = sqlite3_open_v2(filename, &out_db, flags, "unix-dotfile");


  if (ret == SQLITE_OK) {
    return (int) out_db;
  } else {
    GATHER_ERROR_DETAILS(out_db);
    THROW_ERROR();
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_close_x(int conn) {
  sqlite3* db = (sqlite3*) conn;
  int ret = sqlite3_close(db);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(db);
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_close_v2_x(int conn) {
  sqlite3* db = (sqlite3*) conn;
  int ret = sqlite3_close_v2(db);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(db);
  }
}


EMSCRIPTEN_KEEPALIVE int sqlite3_prepare_v2_x(
  int handle,
  const char *zSql
) {
  sqlite3* db = (sqlite3*) handle;
  sqlite3_stmt *ppStmt = NULL;
  int ret = sqlite3_prepare_v2(db, zSql, -1, &ppStmt, NULL);
  if (ret == SQLITE_OK) {
    return (int) ppStmt;
  } else {
    THROW_ON_ERROR(db);
  }
}

EMSCRIPTEN_KEEPALIVE int sqlite3_bind_parameter_index_x(int handle, const char *zName) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_bind_parameter_index(stmt, zName);
}

EMSCRIPTEN_KEEPALIVE void sqlite3_bind_double_x(int handle, int db_handle, int param, double value) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  int ret = sqlite3_bind_double(stmt, param, value);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
    return;
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_bind_int_x(int handle, int db_handle, int param, int value) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  int ret = sqlite3_bind_int(stmt, param, value);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
    return;
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_bind_text_x(int handle, int db_handle, int param, char* value, int len) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  int ret = sqlite3_bind_text(stmt, param, value, len, SQLITE_TRANSIENT);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
    return;
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_bind_null_x(int handle, int db_handle, int param) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  int ret = sqlite3_bind_null(stmt, param);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
    return;
  }
}

EMSCRIPTEN_KEEPALIVE int sqlite3_step_x(int handle, int db_handle) {
  int ret = sqlite3_step((sqlite3_stmt*) handle);
  if (ret == SQLITE_DONE) {
    return 0;
  } else if (ret == SQLITE_ROW) {
    return 1;
  } else {
    THROW_ON_ERROR(((sqlite3*) db_handle))
    return -1;
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_reset_x(int handle, int db_handle) {
  int ret = sqlite3_reset((sqlite3_stmt*) handle);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
  }
}

EMSCRIPTEN_KEEPALIVE int sqlite3_column_count_x(int handle) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_column_count(stmt);
}

EMSCRIPTEN_KEEPALIVE int sqlite3_column_type_x(int handle, int index) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_column_type(stmt, index);
}

EMSCRIPTEN_KEEPALIVE const char* sqlite3_column_name_x(int handle, int index) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_column_name(stmt, index);
}

EMSCRIPTEN_KEEPALIVE int sqlite3_column_int_x(int handle, int index) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_column_int(stmt, index);
}

EMSCRIPTEN_KEEPALIVE double sqlite3_column_double_x(int handle, int index) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  return sqlite3_column_double(stmt, index);
}

EMSCRIPTEN_KEEPALIVE void sqlite3_column_text_x(int handle, int index) {
  sqlite3_stmt* stmt = (sqlite3_stmt*) handle;
  const unsigned char* s = sqlite3_column_text(stmt, index);
  int len = sqlite3_column_bytes(stmt, index);
  out.ptr = (void*) s;
  out.len = len;
}

EMSCRIPTEN_KEEPALIVE void sqlite3_finalize_x(int handle, int db_handle) {
  int ret = sqlite3_finalize((sqlite3_stmt*) handle);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(((sqlite3*) db_handle));
  }
}

EMSCRIPTEN_KEEPALIVE void sqlite3_finalize_silent_x(int handle) {
  sqlite3_finalize((sqlite3_stmt*) handle);
}

EMSCRIPTEN_KEEPALIVE void sqlite3_clear_bindings_x(int handle) {
  sqlite3_clear_bindings((sqlite3_stmt*) handle);
}

EMSCRIPTEN_KEEPALIVE int sqlite3_changes_x(int handle) {
  return sqlite3_changes((sqlite3*) handle);
}

EMSCRIPTEN_KEEPALIVE int sqlite3_last_insert_rowid_x(int handle) {
  // TODO int64
  return sqlite3_last_insert_rowid((sqlite3*) handle);
}

EMSCRIPTEN_KEEPALIVE void sqlite3_exec_x(int handle, const char* sql) {
  sqlite3* db = (sqlite3*) handle;
  int ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
  if (ret != SQLITE_OK) {
    THROW_ON_ERROR(db);
  }
}

void logger(void* _a, int errno, const char* msg) {
  EM_ASM({ console.error("[sqlite]", UTF8ToString($0), UTF8ToString($1)) }, sqlite_code_string(errno), msg);
}

int main()
{
  EM_ASM({
    Module.VERSION = UTF8ToString($0);
  }, SQLITE_VERSION);

  EM_ASM({
    Module.OPEN_READONLY = $0;
    Module.OPEN_READWRITE = $1;
    Module.OPEN_CREATE = $2;
    Module.OPEN_FULLMUTEX = $3;
    Module.OPEN_URI = $4;
    Module.OPEN_SHAREDCACHE = $5;
    Module.OPEN_PRIVATECACHE = $6;
    Module.VERSION_NUMBER = $7;
    Module.OK = $8;
    Module.ERROR = $9;
    Module.INTERNAL = $10;
    Module.PERM = $11;
    Module.ABORT = $12;
    Module.BUSY = $13;
    Module.LOCKED = $14;
    Module.NOMEM = $15;
  },
    SQLITE_OPEN_READONLY,
    SQLITE_OPEN_READWRITE,
    SQLITE_OPEN_CREATE,
    SQLITE_OPEN_FULLMUTEX,
    SQLITE_OPEN_URI,
    SQLITE_OPEN_SHAREDCACHE,
    SQLITE_OPEN_PRIVATECACHE,
    SQLITE_VERSION_NUMBER,
    SQLITE_OK,
    SQLITE_ERROR,
    SQLITE_INTERNAL,
    SQLITE_PERM,
    SQLITE_ABORT,
    SQLITE_BUSY,
    SQLITE_LOCKED,
    SQLITE_NOMEM
  );

  EM_ASM({
    Module.READONLY = $0;
    Module.INTERRUPT = $1;
    Module.IOERR = $2;
    Module.CORRUPT = $3;
    Module.NOTFOUND = $4;
    Module.FULL = $5;
    Module.CANTOPEN = $6;
    Module.PROTOCOL = $7;
    Module.EMPTY = $8;
    Module.SCHEMA = $9;
    Module.TOOBIG = $10;
    Module.CONSTRAINT = $11;
    Module.MISMATCH = $12;
    Module.MISUSE = $13;
    Module.NOLFS = $14;
    Module.AUTH = $15;
  },
    SQLITE_READONLY,
    SQLITE_INTERRUPT,
    SQLITE_IOERR,
    SQLITE_CORRUPT,
    SQLITE_NOTFOUND,
    SQLITE_FULL,
    SQLITE_CANTOPEN,
    SQLITE_PROTOCOL,
    SQLITE_EMPTY,
    SQLITE_SCHEMA,
    SQLITE_TOOBIG,
    SQLITE_CONSTRAINT,
    SQLITE_MISMATCH,
    SQLITE_MISUSE,
    SQLITE_NOLFS,
    SQLITE_AUTH
  );

  EM_ASM({
    Module.FORMAT = $0;
    Module.RANGE = $1;
    Module.NOTADB = $2;
    Module.INTEGER = $3;
    Module.FLOAT = $4;
    Module.BLOB = $5;
    Module.NULL = $6;
    Module.TEXT = $7;
  },
    SQLITE_FORMAT,
    SQLITE_RANGE,
    SQLITE_NOTADB,
    SQLITE_INTEGER,
    SQLITE_FLOAT,
    SQLITE_BLOB,
    SQLITE_NULL,
    SQLITE_TEXT
  );

  void** pptr = &out.ptr;
  unsigned int* plen = &out.len;

  EM_ASM({
    Module.stringOutPtr = $0;
    Module.lengthOutPtr = $1;
  }, pptr, plen);

#ifdef SQLITE_DEBUG
  sqlite3_config(SQLITE_CONFIG_LOG, logger, NULL);
#endif

  return 0;
}

// copy & paste from native bindings
// https://github.com/mapbox/node-sqlite3/blob/dd3ef522088bb5cafede25b9fe661f892b6f10ba/src/node_sqlite3.cc#L72-L102
const char* sqlite_code_string(int code) {
    switch (code) {
        case SQLITE_OK:         return "SQLITE_OK";
        case SQLITE_ERROR:      return "SQLITE_ERROR";
        case SQLITE_INTERNAL:   return "SQLITE_INTERNAL";
        case SQLITE_PERM:       return "SQLITE_PERM";
        case SQLITE_ABORT:      return "SQLITE_ABORT";
        case SQLITE_BUSY:       return "SQLITE_BUSY";
        case SQLITE_LOCKED:     return "SQLITE_LOCKED";
        case SQLITE_NOMEM:      return "SQLITE_NOMEM";
        case SQLITE_READONLY:   return "SQLITE_READONLY";
        case SQLITE_INTERRUPT:  return "SQLITE_INTERRUPT";
        case SQLITE_IOERR:      return "SQLITE_IOERR";
        case SQLITE_CORRUPT:    return "SQLITE_CORRUPT";
        case SQLITE_NOTFOUND:   return "SQLITE_NOTFOUND";
        case SQLITE_FULL:       return "SQLITE_FULL";
        case SQLITE_CANTOPEN:   return "SQLITE_CANTOPEN";
        case SQLITE_PROTOCOL:   return "SQLITE_PROTOCOL";
        case SQLITE_EMPTY:      return "SQLITE_EMPTY";
        case SQLITE_SCHEMA:     return "SQLITE_SCHEMA";
        case SQLITE_TOOBIG:     return "SQLITE_TOOBIG";
        case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
        case SQLITE_MISMATCH:   return "SQLITE_MISMATCH";
        case SQLITE_MISUSE:     return "SQLITE_MISUSE";
        case SQLITE_NOLFS:      return "SQLITE_NOLFS";
        case SQLITE_AUTH:       return "SQLITE_AUTH";
        case SQLITE_FORMAT:     return "SQLITE_FORMAT";
        case SQLITE_RANGE:      return "SQLITE_RANGE";
        case SQLITE_NOTADB:     return "SQLITE_NOTADB";
        case SQLITE_ROW:        return "SQLITE_ROW";
        case SQLITE_DONE:       return "SQLITE_DONE";
        default:                return "UNKNOWN";
    }
}
