#define TABULA_ENTRY_POINT 1

#include "client/client.h"

#include <GLFW/glfw3.h>
#include <uv.h>

#include <alias/str.h>

// vid
refexport_t re;

cvar_t *vid_ref;
cvar_t *vid_xpos;
cvar_t *vid_ypos;
cvar_t *vid_fullscreen;
cvar_t *vid_gamma;

viddef_t viddef; // global video state; used by other modules

static bool vid__active = false;
static uv_lib_t vid__library;
static bool vid__library_loaded = false;

int ActiveApp;

void AppActivate(bool fActive, bool minimize) {
  Key_ClearStates();

  // we don't want to act like we're active if we're minimized
  if(fActive && !minimize)
    ActiveApp = true;
  else
    ActiveApp = false;

  // minimize/restore mouse-capture on demand
  if(!ActiveApp) {
    IN_Activate(false);
  } else {
    IN_Activate(true);
  }
}

static void vid__Close(void) {
  if(vid__library_loaded) {
    uv_dlclose(&vid__library);
    vid__library_loaded = false;
  }
  vid__active = false;
}

static void vid__API_Printf(int print_level, const char *fmt, ...) {
  va_list ap;
  const char * msg;

  va_start(ap, fmt);
  alias_str_stack_formatv(msg, fmt, ap);
  va_end(ap);

  if(print_level == PRINT_ALL) {
    Com_Printf("%s", msg);
  } else if(print_level == PRINT_DEVELOPER) {
    Com_DPrintf("%s", msg);
  } else if(print_level == PRINT_ALERT) {
    // MessageBox(0, msg, "PRINT_ALERT", MB_ICONWARNING);
    // OutputDebugString(msg);
  }
}

static void vid__API_Error(int err_level, const char *fmt, ...) {
  va_list ap;
  const char * msg;

  va_start(ap, fmt);
  alias_str_stack_formatv(msg, fmt, ap);
  va_end(ap);

  Com_Error(err_level, "%s", msg);
}

typedef struct vidmode_s {
  const char *description;
  int width, height;
  int mode;
} vidmode_t;

static vidmode_t vid_modes[] = {{"Mode 0: 320x240", 320, 240, 0},   {"Mode 1: 400x300", 400, 300, 1},
                                {"Mode 2: 512x384", 512, 384, 2},   {"Mode 3: 640x480", 640, 480, 3},
                                {"Mode 4: 800x600", 800, 600, 4},   {"Mode 5: 960x720", 960, 720, 5},
                                {"Mode 6: 1024x768", 1024, 768, 6}, {"Mode 7: 1152x864", 1152, 864, 7},
                                {"Mode 8: 1280x960", 1280, 960, 8}, {"Mode 9: 1600x1200", 1600, 1200, 9}};

#define VID_NUM_MODES (sizeof(vid_modes) / sizeof(vid_modes[0]))

static bool vid__API_GetModeInfo(int *width, int *height, int mode) {
  if(mode < 0 || mode >= VID_NUM_MODES)
    return false;

  *width = vid_modes[mode].width;
  *height = vid_modes[mode].height;

  return true;
}

static void vid__API_NewWindow(int width, int height) {
  viddef.width = width;
  viddef.height = height;

  cl.force_refdef = true; // can't use a paused refdef
}

static bool vid__OpenFunction(GetRefAPI_t function, const char *name) {
  refimport_t ri;

  Com_Printf("------- Loading %s -------\n", name);

  ri.Cmd_AddCommand = Cmd_AddCommand;
  ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
  ri.Cmd_Argc = Cmd_Argc;
  ri.Cmd_Argv = Cmd_Argv;
  ri.Cmd_ExecuteText = Cbuf_ExecuteText;
  ri.Con_Printf = vid__API_Printf;
  ri.Sys_Error = vid__API_Error;
  ri.FS_LoadFile = FS_LoadFile;
  ri.FS_FreeFile = FS_FreeFile;
  ri.FS_Gamedir = FS_Gamedir;
  ri.Cvar_Get = Cvar_Get;
  ri.Cvar_Set = Cvar_Set;
  ri.Cvar_SetValue = Cvar_SetValue;
  ri.Vid_GetModeInfo = vid__API_GetModeInfo;
  ri.Vid_MenuInit = VID_MenuInit;
  ri.Vid_NewWindow = vid__API_NewWindow;

  re = function(ri);

  if(re.Init(NULL, NULL) != 0) {
    re.Shutdown();
    vid__Close();
    return false;
  }

  Com_Printf("------------------------------------\n");
  vid__active = true;

  return true;
}

static bool vid__OpenLibrary(const char *name) {
  if(uv_dlopen(name, &vid__library) < 0) {
    return false;
  }

  GetRefAPI_t function;
  if(uv_dlsym(&vid__library, "GetRefAPI", (void **)&function) < 0) {
    return false;
  }

  return vid__OpenFunction(function, name);
}

void VID_CheckChanges(void) {
  char name[100];

  if(vid_ref->modified) {
    cl.force_refdef = true; // can't use a paused refdef
    S_StopAllSounds();
  }

  while(vid_ref->modified) {
    /*
    ** refresh has changed
    */
    vid_ref->modified = false;
    vid_fullscreen->modified = true;
    cl.refresh_prepped = false;
    cls.disable_screen = true;

    if(strcmp(vid_ref->string, "internal") == 0) {
      extern refexport_t GetRefAPI(refimport_t rimp);

      vid__OpenFunction(GetRefAPI, "internal");
    } else {
      Com_sprintf(name, sizeof(name), "ref_%s.dll", vid_ref->string);

      if(!vid__OpenLibrary(name)) {
        Cvar_Set("vid_ref", "internal");

        /*
        ** drop the console if we fail to load a refresh
        */
        if(cls.key_dest != key_console) {
          Con_ToggleConsole_f();
        }
      }
    }

    cls.disable_screen = false;
  }

  /*
  ** update our window position
  */
  if(vid_xpos->modified || vid_ypos->modified) {
    // if(!vid_fullscreen->value)
    //   VID_UpdateWindowPosAndSize(vid_xpos->value, vid_ypos->value);

    vid_xpos->modified = false;
    vid_ypos->modified = false;
  }
}

static void vid__Restart(void) {
}

static void vid__Front(void) {
}

void VID_Init(void) {
  /* Create the video variables so we know how to start the graphics drivers */
  vid_ref = Cvar_Get("vid_ref", "internal", CVAR_ARCHIVE);
  vid_xpos = Cvar_Get("vid_xpos", "3", CVAR_ARCHIVE);
  vid_ypos = Cvar_Get("vid_ypos", "22", CVAR_ARCHIVE);
  vid_fullscreen = Cvar_Get("vid_fullscreen", "0", CVAR_ARCHIVE);
  vid_gamma = Cvar_Get("vid_gamma", "1", CVAR_ARCHIVE);

  /* Add some console commands that we want to handle */
  Cmd_AddCommand("vid_restart", vid__Restart);
  Cmd_AddCommand("vid_front", vid__Front);

  /* Start the graphics mode and load refresh DLL */
  VID_CheckChanges();
}

void VID_Shutdown(void) {
  if(vid__active) {
    re.Shutdown();
    vid__Close();
  }
}

// net
bool NET_StringToSockaddr(const char *s, struct sockaddr *sadr) {
  struct hostent *h;
  char *colon;
  char copy[128];

  memset(sadr, 0, sizeof(*sadr));

  ((struct sockaddr_in *)sadr)->sin_family = AF_INET;

  ((struct sockaddr_in *)sadr)->sin_port = 0;

  strcpy(copy, s);
  // strip off a trailing :port if present
  for(colon = copy; *colon; colon++)
    if(*colon == ':') {
      *colon = 0;
      ((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon + 1));
    }

  if(copy[0] >= '0' && copy[0] <= '9') {
    *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
  } else {
    if(!(h = gethostbyname(copy)))
      return 0;
    *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
  }

  return true;
}

static void SockadrToNetadr(const struct sockaddr *s, netadr_t *a) {
  const struct sockaddr_in * in = (const struct sockaddr_in *)s;

  if(s->sa_family == AF_INET) {
    a->type = NA_IP;
    *(uint32_t *)&a->ip = in->sin_addr.s_addr;
    a->port = in->sin_port;
  }
}

static void NetadrToSockadr(const netadr_t *a, struct sockaddr *s) {
  alias_memory_clear(s, sizeof(s));
  struct sockaddr_in * in = (struct sockaddr_in *)s;
  in->sin_family = AF_INET;
  in->sin_port = a->port;
  in->sin_addr.s_addr = (a->type == NA_BROADCAST) ? INADDR_BROADCAST : *(uint32_t *)&a->ip;
}

bool NET_StringToAdr(char *s, netadr_t *a) {
  struct sockaddr sadr;

  if(!strcmp(s, "localhost")) {
    memset(a, 0, sizeof(*a));
    a->type = NA_LOOPBACK;
    return true;
  }

  if(!NET_StringToSockaddr(s, &sadr))
    return false;

  SockadrToNetadr(&sadr, a);

  return true;
}

bool NET_IsLocalAddress(netadr_t adr) { return adr.type == NA_LOOPBACK; }

char *NET_AdrToString(netadr_t a) {
  static char s[64];

  if(a.type == NA_LOOPBACK)
    Com_sprintf(s, sizeof(s), "loopback");
  else if(a.type == NA_IP)
    Com_sprintf(s, sizeof(s), "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port));
  // else
  //   Com_sprintf(s, sizeof(s), "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i", a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3],
  //               a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9], ntohs(a.port));

  return s;
}

bool NET_CompareAdr(netadr_t a, netadr_t b) {
  if(a.type != b.type)
    return false;

  if(a.type == NA_LOOPBACK)
    return true;

  if(a.type == NA_IP) {
    if(a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
      return true;
    return false;
  }

  return false;
}

bool NET_CompareBaseAdr(netadr_t a, netadr_t b) {
  if(a.type != b.type)
    return false;

  if(a.type == NA_LOOPBACK)
    return true;

  if(a.type == NA_IP) {
    if(a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3])
      return true;
    return false;
  }

  return false;
}

#define NET__MAILBOX_SIZE__LOG2 4
#define NET__MAILBOX_SIZE (1 << NET__MAILBOX_SIZE__LOG2)

struct NET_MailboxMessage {
  byte data[MAX_MSGLEN];
  uint32_t length;
  netadr_t from;
};

struct NET_Mailbox {
  struct NET_MailboxMessage messages[NET__MAILBOX_SIZE];
  uint32_t get;
  uint32_t send;
};

static bool NET_Mailbox__get(struct NET_Mailbox *mailbox, netadr_t *from, sizebuf_t *message) {
  if(mailbox->send - mailbox->get > NET__MAILBOX_SIZE) {
    mailbox->get = mailbox->send - NET__MAILBOX_SIZE;
  }

  if(mailbox->get >= mailbox->send) {
    return false;
  }

  uint32_t index = mailbox->get & (NET__MAILBOX_SIZE - 1);
  mailbox->get++;

  alias_memory_copy(message->data, message->maxsize, mailbox->messages[index].data, mailbox->messages[index].length);
  message->cursize = mailbox->messages[index].length;
  *from = mailbox->messages[index].from;

  return true;
}

static void NET_Mailbox__send(struct NET_Mailbox *mailbox, const void * data, uint32_t length, netadr_t from) {
  uint32_t index = mailbox->send & (NET__MAILBOX_SIZE - 1);
  mailbox->send++;

  alias_memory_copy(mailbox->messages[index].data, sizeof(mailbox->messages[index].data), data, length);
  mailbox->messages[index].length = length;
  mailbox->messages[index].from = from;
}

static struct NET_Mailbox net__mailboxes[NETSRC_COUNT];
static uv_udp_t net__udp_handles[NETSRC_COUNT];

bool NET_GetPacket(netsrc_t sock, netadr_t *from, sizebuf_t *message) {
  struct NET_Mailbox *mailbox = &net__mailboxes[sock];

  return NET_Mailbox__get(mailbox, from, message);
}

static void NET__udp_send_cb(uv_udp_send_t* req, int status) {
  // TODO

  alias_free(NULL, req, sizeof(*req), alignof(*req));
}

static void net__udp_recv_alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
  buf->base = alias_malloc(NULL, suggested_size, 1);
  buf->len = suggested_size;
}

static void net__udp_recv_cb(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) {
  struct NET_Mailbox *mailbox = uv_handle_get_data((uv_handle_t *)handle);

  if(flags & UV_UDP_PARTIAL) {
    // TODO error
  }

  if(nread > 0) {
    netadr_t from;
    SockadrToNetadr(addr, &from);
    NET_Mailbox__send(mailbox, buf->base, nread, from);
  }

  if(buf->base != NULL && (nread < 0 || flags & UV_UDP_MMSG_FREE)) {
    alias_free(NULL, (void *)buf->base, buf->len, 1);
  }
}

void NET_SendPacket(netsrc_t sock, int length, void *data, netadr_t to) {
  if(to.type == NA_LOOPBACK) {
    NET_Mailbox__send(&net__mailboxes[!sock], data, length, to);
    return;
  }

  uv_udp_t *handle = &net__udp_handles[sock];

  struct sockaddr address;
  NetadrToSockadr(&to, &address);

  uv_udp_send_t *req = alias_malloc(NULL, sizeof(*req), alignof(*req));
  uv_buf_t buf = uv_buf_init(data, length);
  int err = uv_udp_send(req, handle, &buf, 1, &address, NET__udp_send_cb);
  if(err != 0) {
    // TODO
  }
}

void NET_Init(void) {
}

void NET_Sleep(int msec) {
}

static const char * net__get_ip(void) {
  cvar_t *ip = Cvar_Get("ip", "localhost", CVAR_NOSET);
  return ip->string;
}

#define TO_STRING(X) TO_STRING__0(X) 
#define TO_STRING__0(X) TO_STRING__1(X)
#define TO_STRING__1(X) #X

static int net__get_server_port(void) {
  int port = Cvar_Get("ip_hostport", "0", CVAR_NOSET)->value;
  if(port != 0) {
    return port;
  }
  port = Cvar_Get("hostport", "0", CVAR_NOSET)->value;
  if(port != 0) {
    return port;
  }
  return Cvar_Get("port", TO_STRING(PORT_SERVER), CVAR_NOSET)->value;
}

static int net__get_client_port(void) {
  int port = Cvar_Get("ip_clientport", "0", CVAR_NOSET)->value;
  if(port != 0) {
    return port;
  }
  return Cvar_Get("clientport", TO_STRING(CLIENT_SERVER), CVAR_NOSET)->value;
}

static bool net__start_udp(uv_udp_t *handle, struct NET_Mailbox *mailbox, const char *ip, int port) {
  int err;

  if((err = uv_udp_init(global_uv_loop(), handle)) != 0) {
    return false;
  }

  uv_handle_set_data((uv_handle_t *)handle, mailbox);

  if((err = uv_udp_set_broadcast(handle, 1)) != 0) {
    return false;
  }

  struct sockaddr_in address;
  if(!ip || !ip[0] || !strcmp(ip, "localhost")) {
    address.sin_addr.s_addr = INADDR_ANY;
  } else {
    NET_StringToSockaddr(ip, (struct sockaddr *)&address);
  }
  if(port == PORT_ANY) {
    address.sin_port = 0;
  } else {
    address.sin_port = htons((short)port);
  }
  address.sin_family = AF_INET;

  if((err = uv_udp_bind(handle, (const struct sockaddr *)&address, 0)) != 0) {
    return false;
  }

  if((err = uv_udp_recv_start(handle, net__udp_recv_alloc_cb, net__udp_recv_cb)) != 0) {
    return false;
  }

  return true;
}

static bool net__activate_udp(void) {
  const char * ip = net__get_ip();
  int port = net__get_server_port();

  if(!net__start_udp(&net__udp_handles[NS_SERVER], &net__mailboxes[NS_SERVER], ip, port)) {
    return false;
  }

  if(Cvar_VariableValue("dedicated")) {
    return true;
  }

  port = net__get_client_port();
  if(!net__start_udp(&net__udp_handles[NS_CLIENT], &net__mailboxes[NS_CLIENT], ip, port)) {
    if(!net__start_udp(&net__udp_handles[NS_CLIENT], &net__mailboxes[NS_CLIENT], ip, PORT_ANY)) {
      return false;
    }
  }

  return true;
}

static void net__deactivate_udp(void) {
}

void NET_Config(bool active) {
  static bool is_active = false;

  if(active == is_active) {
    return;
  }

  if(active) {
    net__activate_udp();
  } else {
    net__deactivate_udp();
  }
}

// sys
unsigned int sys_frame_time;
unsigned int sys_msg_time;


void Sys_Error(char *error, ...) {
  va_list argptr;

  CL_Shutdown();
  Qcommon_Shutdown();

  va_start(argptr, error);
  vfprintf(stderr, error, argptr);
  va_end(argptr);

  exit(1);
}

void Sys_SendKeyEvents(void) {
  extern GLFWwindow *glfw_window;

  if(glfw_window != NULL && glfwWindowShouldClose(glfw_window)) {
    Com_Quit();
  }

  glfwPollEvents();
  sys_frame_time = Sys_Milliseconds();
}

static void windows_uv_idle_cb(uv_idle_t *handle) {
  Sys_SendKeyEvents();
}

void Sys_Init(void) {
  static uv_idle_t windows_uv_idle;
  uv_idle_init(global_uv_loop(), &windows_uv_idle);
  uv_idle_start(&windows_uv_idle, windows_uv_idle_cb);
}

void Sys_Quit(void) {
  CL_Shutdown();
  Qcommon_Shutdown();
  exit(0);
}

char *Sys_ConsoleInput(void) {
  return NULL;
}

void Sys_ConsoleOutput(char *string) {
}

char *Sys_GetClipboardData(void) {
  return NULL;
}

void Sys_AppActivate(void) {
}

// --------------------------------------------------------------------------------------------------------------------
static struct {
  char base[MAX_OSPATH];
  char path[MAX_OSPATH + 2];
  char pattern[MAX_OSPATH];
  uv_dir_t *dir;
} find;

static int glob_match(const char *pattern, const char *text);

/* Like glob_match, but match PATTERN against any final segment of TEXT.  */
static int glob_match_after_star(const char *pattern, const char *text) {
  const char *p = pattern;
  const char *t = text;
  char c, c1;

  while ((c = *p++) == '?' || c == '*')
    if (c == '?' && *t++ == '\0')
      return 0;

  if (c == '\0')
    return 1;

  if (c == '\\')
    c1 = *p;
  else
    c1 = c;

  while (1) {
    if ((c == '[' || *t == c1) && glob_match(p - 1, t))
      return 1;
    if (*t++ == '\0')
      return 0;
  }
}

/* Return nonzero if PATTERN has any special globbing chars in it.  */
static int glob_pattern_p(const char *pattern)
{
  const char *p = pattern;
  char c;
  int open = 0;

  while ((c = *p++) != '\0')
    switch (c) {
    case '?':
    case '*':
      return 1;

    case '[':    /* Only accept an open brace if there is a close */
      open++;    /* brace to match it.  Bracket expressions must be */
      continue;  /* complete, according to Posix.2 */
    case ']':
      if (open)
        return 1;
      continue;

    case '\\':
      if (*p++ == '\0')
        return 0;
    }

  return 0;
}

static int glob_match(const char *pattern, const char *text) {
  const char *p = pattern;
  const char *t = text;
  char c;

  while ((c = *p++) != '\0')
    switch (c) {
    case '?':
      if (*t == '\0')
        return 0;
      else
        ++t;
      break;

    case '\\':
      if (*p++ != *t++)
        return 0;
      break;

    case '*':
      return glob_match_after_star(p, t);

    case '[':
      {
        char c1 = *t++;
        int invert;

        if (!c1)
          return (0);

        invert = ((*p == '!') || (*p == '^'));
        if (invert)
          p++;

        c = *p++;
        while (1) {
          register char cstart = c, cend = c;

          if (c == '\\') {
            cstart = *p++;
            cend = cstart;
          }
          if (c == '\0')
            return 0;

          c = *p++;
          if (c == '-' && *p != ']') {
            cend = *p++;
            if (cend == '\\')
              cend = *p++;
            if (cend == '\0')
              return 0;
            c = *p++;
          }
          if (c1 >= cstart && c1 <= cend)
            goto match;
          if (c == ']')
            break;
        }
        if (!invert)
          return 0;
        break;

        match:
        /* Skip the rest of the [...] construct that already matched.  */
        while (c != ']') {
          if (c == '\0')
            return 0;
          c = *p++;
          if (c == '\0')
            return 0;
          else if (c == '\\')
            ++p;
        }
        if (invert)
          return 0;
        break;
      }

    default:
      if (c != *t++)
        return 0;
    }

  return *t == '\0';
}

static bool CompareAttributes(const char *path, const char *name, unsigned musthave, unsigned canthave) {
  // . and .. never match
  //  if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
  //      return false;
  alias_str fn;
  alias_str_stack_format(fn, "%s/%s", path, name);

  uv_fs_t req; 
  uv_fs_stat(global_uv_loop(), &req, fn, NULL);
  uv_fs_req_cleanup(&req);

  if(req.result < 0) {
    return false; // shouldn't happen
  }

  if((req.statbuf.st_mode & S_IFDIR) && (canthave & SFF_SUBDIR)) {
    return false;
  }

  if((musthave & SFF_SUBDIR) && !(req.statbuf.st_mode & S_IFDIR)) {
    return false;
  }

  return true;
}

char *Sys_FindNext(unsigned musthave, unsigned canhave) {
  if(find.dir == NULL)
    return NULL;

  uv_dirent_t dirent;
  find.dir->nentries = 1;
  find.dir->dirents = &dirent;

  for(;;) {
    uv_fs_t req;
    int result = uv_fs_readdir(global_uv_loop(), &req, find.dir, NULL);
    uv_fs_req_cleanup(&req);

    if(result <= 0) {
      Sys_FindClose();
      return NULL;
    }

    if(dirent.type != UV_DIRENT_FILE || dirent.name == NULL) {
      continue;
    }

    if(!*find.pattern || glob_match(find.pattern, dirent.name)) {
      //if(*findpattern) {
      //  printf("%s matched %sndpattern, d->d_name);
      //}
      if(CompareAttributes(find.base, dirent.name, musthave, canhave)) {
        sprintf(find.path, sizeof(find.path), "%s/%s", find.base, dirent.name);
        return find.path;
      }
    }
  }
}

char *Sys_FindFirst(const char *path, unsigned musthave, unsigned canhave) {
  char *p;

  if(find.dir != NULL) {
    Sys_Error("Sys_BeginFind without close");
  }

  //  COM_FilePath (path, findbase);
  strcpy(find.base, path);
  if((p = strrchr(find.base, '/')) != NULL) {
    *p = 0;
    strcpy(find.pattern, p + 1);
  } else {
    strcpy(find.pattern, "*");
  }

  if(strcmp(find.pattern, "*.*") == 0) {
    strcpy(find.pattern, "*");
  }

  uv_fs_t req;
  if(uv_fs_opendir(global_uv_loop(), &req, find.base, NULL) < 0) {
    uv_fs_req_cleanup(&req);
    return NULL;
  }
  find.dir = (uv_dir_t *)req.ptr;
  uv_fs_req_cleanup(&req);

  return Sys_FindNext(musthave, canhave);
}

void Sys_FindClose(void) {
  if(find.dir != NULL) {
    uv_fs_t req;
    uv_fs_closedir(global_uv_loop(), &req, find.dir, NULL);
    uv_fs_req_cleanup(&req);
  }
  find.dir = NULL;
}
// --------------------------------------------------------------------------------------------------------------------

void Sys_UnloadGame(void) {}

void *Sys_GetGameAPI(void *parms) {
  extern void *GetGameAPI(void *);
  return GetGameAPI(parms);
}

unsigned int curtime;
unsigned int sys_milliseconds_base;

static void sys_milliseconds_init(void) { sys_milliseconds_base = (unsigned int)(uv_hrtime() / (uint64_t)1000000); }

unsigned int Sys_Milliseconds(void) {
  static uv_once_t init = UV_ONCE_INIT;
  uv_once(&init, sys_milliseconds_init);
  curtime = (unsigned int)(uv_hrtime() / (uint64_t)1000000) - sys_milliseconds_base;
  return curtime;
}

void Sys_Mkdir(char *path) {
  uv_fs_t req;
  uv_fs_mkdir(global_uv_loop(), &req, path, 0777, NULL);
}

int tabula_entry_point(int argc, char **argv) {
  Qcommon_Init(argc, argv);

  return Qcommon_RunFrames();  
}