#include <bits/errno-return.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <luna/Vector.h>
#include <stdio.h>
#include <stdlib.h>

static struct group grp;
static FILE* f { nullptr };
static char buf[4096];

static Vector<char*> g_members;

extern char* _strtok_once(char* str, const char* delim);

extern "C"
{
    static Result<struct group*> try_getgrent()
    {
        if (!f)
        {
            f = fopen("/etc/group", "r");
            if (!f) return nullptr;
            fcntl(fileno(f), F_SETFD, FD_CLOEXEC);
        }

        while (true)
        {
            char* rc = fgets(buf, sizeof(buf), f);
            if (!rc) return nullptr;

            char* name = _strtok_once(rc, ":\n");
            if (!name) continue;

            char* passwd = _strtok_once(nullptr, ":\n");
            if (!passwd) continue;

            char* gid = _strtok_once(nullptr, ":\n");
            if (!gid) continue;

            char* members = _strtok_once(nullptr, ":\n");
            if (!members) continue;

            g_members.clear();

            char* member = strtok(members, ",");
            TRY(g_members.try_append(member));

            if (member)
            {
                while (true)
                {
                    member = strtok(nullptr, ",");
                    TRY(g_members.try_append(member));
                    if (!member) break;
                }
            }

            grp.gr_name = name;
            grp.gr_passwd = passwd;
            grp.gr_gid = (gid_t)strtoul(gid, NULL, 10);
            grp.gr_mem = g_members.data();

            return &grp;
        }
    }

    struct group* getgrent()
    {
        return TRY_OR_SET_ERRNO(try_getgrent(), group*, nullptr);
    }

    struct group* getgrnam(const char* name)
    {
        setgrent();

        struct group* entry;

        while ((entry = getgrent()))
        {
            if (!strcmp(entry->gr_name, name)) { return entry; }
        }

        return entry;
    }

    struct group* getgrgid(gid_t gid)
    {
        setgrent();

        struct group* entry;

        while ((entry = getgrent()))
        {
            if (entry->gr_gid == gid) { return entry; }
        }

        return entry;
    }

    void setgrent()
    {
        if (f) rewind(f);
    }

    void endgrent()
    {
        if (f)
        {
            fclose(f);
            f = nullptr;
        }
    }
}