/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2022 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/s.h"
#include "third_party/lua/lauxlib.h"

/**
 * @fileoverview redbean unix path manipulation module
 */

// path.basename(str)
//     └─→ str
static int LuaPathBasename(lua_State *L) {
  size_t i, n;
  const char *p;
  if ((p = luaL_optlstring(L, 1, 0, &n)) && n) {
    while (n > 1 && p[n - 1] == '/') --n;
    i = n - 1;
    while (i && p[i - 1] != '/') --i;
    lua_pushlstring(L, p + i, n - i);
  } else {
    lua_pushlstring(L, ".", 1);
  }
  return 1;
}

// path.dirname(str)
//     └─→ str
static int LuaPathDirname(lua_State *L) {
  size_t n;
  const char *p;
  if ((p = luaL_optlstring(L, 1, 0, &n)) && n--) {
    for (; p[n] == '/'; n--) {
      if (!n) goto ReturnSlash;
    }
    for (; p[n] != '/'; n--) {
      if (!n) goto ReturnDot;
    }
    for (; p[n] == '/'; n--) {
      if (!n) goto ReturnSlash;
    }
    lua_pushlstring(L, p, n + 1);
    return 1;
  }
ReturnDot:
  lua_pushlstring(L, ".", 1);
  return 1;
ReturnSlash:
  lua_pushlstring(L, "/", 1);
  return 1;
}

// path.join(str, ...)
//     └─→ str
static int LuaPathJoin(lua_State *L) {
  int i, n;
  size_t z;
  bool gotstr;
  const char *c;
  luaL_Buffer b;
  bool needslash;
  if ((n = lua_gettop(L))) {
    luaL_buffinit(L, &b);
    gotstr = false;
    needslash = false;
    for (i = 1; i <= n; ++i) {
      if (lua_isnoneornil(L, i)) continue;
      gotstr = true;
      c = luaL_checklstring(L, i, &z);
      if (z) {
        if (c[0] == '/') {
          luaL_buffsub(&b, luaL_bufflen(&b));
        } else if (needslash) {
          luaL_addchar(&b, '/');
        }
        luaL_addlstring(&b, c, z);
        needslash = c[z - 1] != '/';
      } else if (needslash) {
        luaL_addchar(&b, '/');
        needslash = false;
      }
    }
    if (gotstr) {
      luaL_pushresult(&b);
    } else {
      lua_pushnil(L);
    }
    return 1;
  } else {
    luaL_error(L, "missing argument");
    unreachable;
  }
}

static int CheckPath(lua_State *L, int type, int flags) {
  int olderr;
  struct stat st;
  const char *path;
  path = luaL_checkstring(L, 1);
  olderr = errno;
  if (fstatat(AT_FDCWD, path, &st, flags) != -1) {
    lua_pushboolean(L, !type || (st.st_mode & S_IFMT) == type);
  } else {
    errno = olderr;
    lua_pushboolean(L, false);
  }
  return 1;
}

// path.exists(str)
//     └─→ bool
static int LuaPathExists(lua_State *L) {
  return CheckPath(L, 0, 0);
}

// path.isfile(str)
//     └─→ bool
static int LuaPathIsfile(lua_State *L) {
  return CheckPath(L, S_IFREG, AT_SYMLINK_NOFOLLOW);
}

// path.islink(str)
//     └─→ bool
static int LuaPathIslink(lua_State *L) {
  return CheckPath(L, S_IFLNK, AT_SYMLINK_NOFOLLOW);
}

// path.isdir(str)
//     └─→ bool
static int LuaPathIsdir(lua_State *L) {
  return CheckPath(L, S_IFDIR, AT_SYMLINK_NOFOLLOW);
}

static const luaL_Reg kLuaPath[] = {
    {"basename", LuaPathBasename},  //
    {"dirname", LuaPathDirname},    //
    {"exists", LuaPathExists},      //
    {"isdir", LuaPathIsdir},        //
    {"isfile", LuaPathIsfile},      //
    {"islink", LuaPathIslink},      //
    {"join", LuaPathJoin},          //
    {0},                            //
};

int LuaPath(lua_State *L) {
  luaL_newlib(L, kLuaPath);
  return 1;
}