#include "main.h"
#include "../testing/testing.h"
#include <luajit-2.1/lauxlib.h>
#include <luajit-2.1/lua.h>
#include <luajit-2.1/lualib.h>
#include <stdlib.h>
#include <string.h>

lua_State *L;

#define lua_unreachable(...) \
  ({ \
    luaL_error(L, __VA_ARGS__); \
    nullptr; \
  })

static table_t new_table(size_t len) {
  return (table_t){
    malloc(len * sizeof(char *)) ?: lua_unreachable("allocation failure"),
    len,
  };
}

char const *basename(char const *path) {
  char const *last = strrchr(path, '/');
  return last ? last + 1 : path;
}

test_table(
  basename_test, basename, (char const *, char const *),
  {
    {  "main.c",                    "src/main.c"},
    {"init.lua", "$(HOME)/.config/nvim/init.lua"},
}
)

static bool ismatch(char const *target, char const *pattern) {
  for (; *target && *pattern; target++) pattern += *target == *pattern;

  return *pattern == '\0';
}

test_table(
  match_test, ismatch, (bool, char const *, char const *),
  {
    { true,     "aabbccdd",  "abcd"},
    { true, "src/main.zig",  "s/mz"},
    {false,        "hello", "world"},
    {false,     "init.lua",   "iin"},
}
)

static table_t fuzfilter(table_t tbl, char const *pat) {
  table_t ret = new_table(tbl.len);
  int id = 0;
  bool isfullpath = strchr(pat, '/');
  for (int i = 0; i < tbl.len; i++) {
    char const *target = isfullpath ? tbl.buf[i] : basename(tbl.buf[i]);
    if (ismatch(target, pat)) ret.buf[id++] = tbl.buf[i];
  }

  ret.len = id;
  return ret;
}

static int fuzpath(lua_State *arg) {
  L = arg;
  luaL_checktype(L, 1, LUA_TTABLE);
  luaL_checktype(L, 2, LUA_TSTRING);

  size_t tbl_len = lua_objlen(L, 1);

  table_t table droptbl = new_table(tbl_len);

  for (size_t i = 1; i <= tbl_len; i++) {
    lua_rawgeti(L, 1, i);
    table.buf[i - 1] = luaL_checkstring(L, -1);
    lua_pop(L, 1);
  }

  char const *pattern = luaL_checkstring(L, 2);

  table_t result droptbl = fuzfilter(table, pattern);

  lua_newtable(L);
  for (int i = 0; i < result.len; i++) {
    lua_pushstring(L, result.buf[i]);
    lua_rawseti(L, -2, i + 1);
  }
  return 1;
}

int luaopen_fuzpath(lua_State *L) {
  static luaL_Reg const funcs[] = {
    {"fuzpath", fuzpath},
    {  nullptr, nullptr}
  };

  luaL_newlib(L, funcs);
  return 1;
}