#include <BAN/StringView.h>
#include <BAN/Vector.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>

static struct stat s_group_st;
static char* s_group_mmap = nullptr;
static int s_group_fd = -1;

static struct group s_group;

id_t parse_id(BAN::StringView string)
{
	id_t id = 0;
	for (char c : string)
	{
		if (!isdigit(c))
			return -1;
		id = (id * 10) + (c - '0');
	}
	return id;
}

static bool open_group_file()
{
	if (s_group_fd == -1)
		s_group_fd = open("/etc/group", O_RDONLY);
	if (s_group_fd == -1)
		return false;

	if (fstat(s_group_fd, &s_group_st) == -1)
		return false;

	if (s_group_mmap == nullptr || s_group_mmap == MAP_FAILED)
		s_group_mmap = (char*)mmap(nullptr, s_group_st.st_size, PROT_READ, MAP_PRIVATE, s_group_fd, 0);
	if (s_group_mmap == MAP_FAILED)
		return false;

	s_group.gr_name = nullptr;
	s_group.gr_mem = nullptr;

	return true;
}

struct group* fill_group(const BAN::Vector<BAN::StringView>& parts)
{
	if (parts.size() != 4)
		return nullptr;

	if (s_group.gr_name)
	{
		free(s_group.gr_name);
		s_group.gr_name = nullptr;
	}

	if (s_group.gr_mem)
	{
		for (size_t i = 0; s_group.gr_mem && s_group.gr_mem[i]; i++)
			free(s_group.gr_mem[i]);
		free(s_group.gr_mem);
		s_group.gr_mem = nullptr;
	}

	auto groups_or_error = parts[3].split(',');
	if (groups_or_error.is_error())
		return nullptr;
	auto groups = groups_or_error.release_value();

	s_group.gr_gid = parse_id(parts[2]);
	if (s_group.gr_gid == -1)
		return nullptr;

	s_group.gr_name = (char*)malloc(parts[0].size() + 1);
	if (s_group.gr_name == nullptr)
		return nullptr;
	memcpy(s_group.gr_name, parts[0].data(), parts[0].size());
	s_group.gr_name[parts[0].size()] = '\0';

	s_group.gr_mem = (char**)malloc((groups.size() + 1) * sizeof(char*));
	if (s_group.gr_mem == nullptr)
		return nullptr;

	for (size_t i = 0; i < groups.size(); i++)
	{
		s_group.gr_mem[i] = (char*)malloc(groups[i].size() + 1);
		if (s_group.gr_mem[i] == nullptr)
		{
			for (size_t j = 0; j < i; j++)
				free(s_group.gr_mem[j]);
			free(s_group.gr_mem);
			s_group.gr_mem = nullptr;
			return nullptr;
		}
		memcpy(s_group.gr_mem[i], groups[i].data(), groups[i].size());
		s_group.gr_mem[i][groups[i].size()] = '\0';
	}
	s_group.gr_mem[groups.size()] = nullptr;

	return &s_group;
}

struct group* getgrnam(const char* name)
{
	if (s_group_mmap == nullptr || s_group_mmap == MAP_FAILED)
		if (!open_group_file())
			return nullptr;

	off_t start = 0;
	off_t end = 0;
	while (start < s_group_st.st_size)
	{
		while (end < s_group_st.st_size && s_group_mmap[end] != '\n')
			end++;

		BAN::StringView line(s_group_mmap + start, end - start);
		start = ++end;

		auto parts_or_error = line.split(':', true);
		if (parts_or_error.is_error())
			return nullptr;

		auto parts = parts_or_error.release_value();
		if (parts.size() == 4 && parts[0] == name)
			return fill_group(parts);
	}

	return nullptr;
}

struct group* getgrgid(gid_t gid)
{
	if (s_group_mmap == nullptr || s_group_mmap == MAP_FAILED)
		if (!open_group_file())
			return nullptr;

	off_t start = 0;
	off_t end = 0;
	while (start < s_group_st.st_size)
	{
		while (end < s_group_st.st_size && s_group_mmap[end] != '\n')
			end++;

		BAN::StringView line(s_group_mmap + start, end - start);
		start = ++end;

		auto parts_or_error = line.split(':', true);
		if (parts_or_error.is_error())
			return nullptr;

		auto parts = parts_or_error.release_value();
		if (parts.size() == 4 && parse_id(parts[2]) == gid)
			return fill_group(parts);
	}

	return nullptr;
}
