#define TB_IMPL
#include "termbox2.h"

// Needs to be included after including termbox2.h
#include "erl_nif.h"

int tui_init = 0;
char *buf;

static ERL_NIF_TERM ex_start(ErlNifEnv *env, int argc,
                             const ERL_NIF_TERM argv[]) {
  buf = enif_alloc(1024 + 1);
  tb_init();
  // tb_set_output_mode(TB_OUTPUT_256);
  tui_init = 1;
  // pthread_create(&event_handler_pt, NULL, event_handler_pthread_routine,
  // NULL);
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM ex_stop(ErlNifEnv *env, int argc,
                            const ERL_NIF_TERM argv[]) {
  tui_init = 0;
  tb_shutdown();
  enif_free(buf);
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM present(ErlNifEnv *env, int argc,
                            const ERL_NIF_TERM argv[]) {
  tb_present();
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM set_cursor(ErlNifEnv *env, int argc,
                               const ERL_NIF_TERM argv[]) {
  int x, y;
  enif_get_int(env, argv[0], &x);
  enif_get_int(env, argv[1], &y);
  tb_set_cursor(x, y);
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM hide_cursor(ErlNifEnv *env, int argc,
                                const ERL_NIF_TERM argv[]) {
  tb_hide_cursor();
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM clear_region(ErlNifEnv *env, int argc,
                                 const ERL_NIF_TERM argv[]) {
  int x, y, width, height;
  enif_get_int(env, argv[0], &x);
  enif_get_int(env, argv[1], &y);
  enif_get_int(env, argv[2], &width);
  enif_get_int(env, argv[3], &height);
  for (int i = x; i < x + width; ++i) {
    for (int j = y; j < y + height; ++j) {
      tb_set_cell(i, j, ' ', TB_DEFAULT, TB_DEFAULT);
    }
  }
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM clear(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
  tb_clear();
  return enif_make_int(env, 0);
}

static ERL_NIF_TERM width(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
  return enif_make_int(env, tb_width());
}

static ERL_NIF_TERM height(ErlNifEnv *env, int argc,
                           const ERL_NIF_TERM argv[]) {
  return enif_make_int(env, tb_height());
}

static ERL_NIF_TERM poll(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
  int timeout;
  struct tb_event event = {0};
  enif_get_int(env, argv[0], &timeout);
  int result = tb_peek_event(&event, timeout);
  if (result == TB_OK) {
    ErlNifEnv *env = enif_alloc_env();
    if (event.type == TB_EVENT_KEY) {
      ERL_NIF_TERM result = enif_make_tuple4(
          env, enif_make_atom(env, "key"), enif_make_uint(env, event.mod),
          enif_make_uint(env, event.key), enif_make_uint(env, event.ch));
      return result;
    } else if (event.type == TB_EVENT_RESIZE) {
      ERL_NIF_TERM result = enif_make_tuple3(env, enif_make_atom(env, "resize"),
                                             enif_make_uint(env, event.w),
                                             enif_make_uint(env, event.h));
      return result;
    } else if (event.type == TB_EVENT_MOUSE) {
      ERL_NIF_TERM result = enif_make_tuple3(env, enif_make_atom(env, "mouse"),
                                             enif_make_uint(env, event.x),
                                             enif_make_uint(env, event.y));
      return result;
    } else {
      return enif_make_atom(env, "none");
    }
  } else {
    return enif_make_atom(env, "none");
  }
}

static ERL_NIF_TERM tb2_puts(ErlNifEnv *env, int argc,
                             const ERL_NIF_TERM argv[]) {
  int x, y, attr, bg_attr;
  enif_get_int(env, argv[0], &x);
  enif_get_int(env, argv[1], &y);
  enif_get_int(env, argv[2], &attr);
  enif_get_int(env, argv[3], &bg_attr);
  ErlNifBinary bin;
  // NOTE: Cody helped here, chatgpt not so much
  enif_inspect_binary(env, argv[4], &bin);
  int len = bin.size;
  if (len > tb_width() - x) {
    len = tb_width() - x;
  }
  memcpy(buf, bin.data, len);
  buf[len] = '\0';
  if (x >= 0 && x < tb_width() && y >= 0 && y < tb_height()) {
    tb_print(x, y, TB_DEFAULT | attr, TB_DEFAULT | bg_attr, buf);
  }
  // tb_printf(x, y, TB_DEFAULT | TB_BOLD, TB_DEFAULT, buf);
  return enif_make_int(env, 0);
}

static ErlNifFunc nif_funcs[] = {{"present", 0, present, 0},
                                 {"set_cursor", 2, set_cursor, 0},
                                 {"hide_cursor", 0, hide_cursor, 0},
                                 {"clear", 0, clear, 0},
                                 {"clear_region", 4, clear_region, 0},
                                 {"width", 0, width, 0},
                                 {"height", 0, height, 0},
                                 {"ex_start", 0, ex_start, 0},
                                 {"ex_stop", 0, ex_stop, 0},
                                 {"poll", 1, poll, 0},
                                 {"puts", 5, tb2_puts, 0}};

ERL_NIF_INIT(Elixir.ElementTui.TermBox2Ex, nif_funcs, NULL, NULL, NULL, NULL)
