#include <string>
#include <sstream>
#include <sqlite3.h>
#include <sqlite3ext.h>
#include <curl/curl.h>
#include "picojson.h"

#ifdef _WIN32
# define EXPORT __declspec(dllexport)
#else
# define EXPORT
#endif

SQLITE_EXTENSION_INIT1;

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  if (mf) {
    mf->data = NULL;
    mf->size = 0;
  }
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf) return block; // through
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf;
  if (mf->size == 0) return NULL;
  buf = (char*) malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

static int
my_connect(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **c) {
  std::stringstream ss;
  ss << "CREATE TABLE " << argv[0]
    << "(id int, full_name text, description text, html_url text)";
  int rc = sqlite3_declare_vtab(db, ss.str().c_str());
  *ppVTab = (sqlite3_vtab *) sqlite3_malloc(sizeof(sqlite3_vtab));
  memset(*ppVTab, 0, sizeof(sqlite3_vtab));
  return rc;
}

static int
my_create(sqlite3 *db, void *pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char **c) {
  return my_connect(db, pAux, argc, argv, ppVTab, c);
}

static int my_disconnect(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

static int
my_destroy(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

typedef struct {
  sqlite3_vtab_cursor base;
  int index;
  picojson::value* rows;
} cursor;

static int
my_open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
  MEMFILE* mf;
  CURL* curl;
  char* json;
  CURLcode res = CURLE_OK;
  char error[CURL_ERROR_SIZE] = {0};
  char* cert_file = getenv("SSL_CERT_FILE");

  mf = memfopen();
  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
  curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.29.0");
  curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repositories");
  if (cert_file)
    curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);
  if (res != CURLE_OK) {
    std::cerr << error << std::endl;
    return SQLITE_FAIL;
  }

  picojson::value* v = new picojson::value;
  std::string err;
  picojson::parse(*v, mf->data, mf->data + mf->size, &err);
  memfclose(mf);

  if (!err.empty()) {
    delete v;
    std::cerr << err << std::endl;
    return SQLITE_FAIL;
  }

  cursor *c = (cursor *)sqlite3_malloc(sizeof(cursor));
  c->rows = v;
  c->index = 0;
  *ppCursor = &c->base;
  return SQLITE_OK;
}

static int
my_close(cursor *c) {
  delete c->rows;
  sqlite3_free(c);
  return SQLITE_OK;
}

static int
my_filter(cursor *c, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
  c->index = 0;
  return SQLITE_OK;
}

static int
my_next(cursor *c) {
  c->index++;
  return SQLITE_OK;
}

static int
my_eof(cursor *c) {
  return c->index >= c->rows->get<picojson::array>().size() ? 1 : 0;
}

static int
my_column(cursor *c, sqlite3_context *ctxt, int i) {
  picojson::value v = c->rows->get<picojson::array>()[c->index];
  picojson::object row = v.get<picojson::object>();
  const char* p = NULL;
  switch (i) {
  case 0:
    p = row["id"].to_str().c_str();
    break;
  case 1:
    p = row["full_name"].to_str().c_str();
    break;
  case 2:
    p = row["description"].to_str().c_str();
    break;
  case 3:
    p = row["html_url"].to_str().c_str();
    break;
  }
  sqlite3_result_text(ctxt, strdup(p), strlen(p), free);
  return SQLITE_OK;
}

static int
my_rowid(cursor *c, sqlite3_int64 *pRowid) {
  *pRowid = c->index;
  return SQLITE_OK;
}

static int
my_bestindex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
  return SQLITE_OK;
}

static const sqlite3_module module = {
  0,
  my_create,
  my_connect,
  my_bestindex,
  my_disconnect,
  my_destroy,
  my_open,
  (int (*)(sqlite3_vtab_cursor *)) my_close,
  (int (*)(sqlite3_vtab_cursor *, int, char const *, int, sqlite3_value **)) my_filter,
  (int (*)(sqlite3_vtab_cursor *)) my_next,
  (int (*)(sqlite3_vtab_cursor *)) my_eof,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_context *, int)) my_column,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_int64 *)) my_rowid,
  NULL, // my_update
  NULL, // my_begin
  NULL, // my_sync
  NULL, // my_commit
  NULL, // my_rollback
  NULL, // my_findfunction
  NULL, // my_rename
};

static void
destructor(void *arg) {
  return;
}


extern "C" {

EXPORT int
sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
  SQLITE_EXTENSION_INIT2(api);
  sqlite3_create_module_v2(db, "github", &module, NULL, destructor);
  return 0;
}

}
