#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <grp.h>
#include <limits.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include "attr-sets.h"
#include "process.h"

/*
 * require_group - ensure a collected attribute set contains a specific GID
 * @set: attribute set generated by read_proc_status()
 * @gid: numeric group identifier expected in the set
 * @label: description for error reporting when the lookup fails
 */
static void require_group(attr_sets_entry_t *set, unsigned int gid,
			  const char *label)
{
	if (!check_int_attr_set(set, (int64_t)gid))
		error(1, 0, "%s group %u not found", label, gid);
}

/*
 * main - validate GID collection helper captures all credential facets
 *
 * Return: 0 when all expected group IDs are reported, or terminate via error()
 * if any lookup or syscall fails during the exercise.
 */
int main(void)
{
	int res, num, i, check_intersect = 0;
	gid_t gid, gids[NGROUPS_MAX];
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL
	};
	attr_sets_entry_t *groups;
	FILE *status;
	char buf[4096];
	int saw_gid_line = 0;
	unsigned int missing_gid;

	if (read_proc_status(getpid(), PROC_STAT_GID, &info) != 0)
		error(1, 0, "Unable to obtain gid set");

	groups = info.groups;
	info.groups = NULL;
	if (!groups)
		error(1, 0, "Unable to obtain gid set");

	status = fopen("/proc/self/status", "rt");
	if (!status)
		error(1, errno, "fopen /proc/self/status");

	while (fgets(buf, sizeof(buf), status)) {
		if (memcmp(buf, "Gid:", 4) == 0) {
			unsigned int real_gid = 0, eff_gid = 0;
			unsigned int saved_gid = 0, fs_gid = 0;
			int fields = sscanf(buf, "Gid: %u %u %u %u",
					    &real_gid, &eff_gid,
					    &saved_gid, &fs_gid);

			if (fields >= 1)
				require_group(groups, real_gid, "Real");
			if (fields >= 2)
				require_group(groups, eff_gid, "Effective");
//			if (fields >= 3)
//				require_group(groups, saved_gid, "Saved");
			if (fields >= 4)
				require_group(groups, fs_gid, "Filesystem");
			saw_gid_line = 1;
			break;
		}
	}

	fclose(status);

	if (!saw_gid_line)
		error(1, 0, "Gid line not found in /proc/self/status");

	gid = getgid();

	num = getgroups(NGROUPS_MAX, gids);
	if (num < 0)
		error(1, 0, "Too many groups");

	for (i = 0; i < num; i++) {
		if (gids[i] == gid)
			check_intersect = 1;
		printf("Checking for %u...", (unsigned int)gids[i]);
		res = check_int_attr_set(groups, (int64_t)gids[i]);
		if (!res)
			error(1, 0, "Group %u not found", (unsigned int)gids[i]);
		printf("found\n");
	}

	missing_gid = 0;
	for (; missing_gid < UINT_MAX; missing_gid++) {
		if (!check_int_attr_set(groups, (int64_t)missing_gid))
			break;
	}

	if (missing_gid == UINT_MAX)
		error(1, 0, "Unable to determine missing group for test");

	res = check_int_attr_set(groups, (int64_t)missing_gid);
	if (res)
		error(1, 0, "Found unexpected group");

	if (check_intersect) {
		printf("Doing Negative AVL intersection\n");
		attr_sets_entry_t *g = init_standalone_set(UNSIGNED);
		append_int_attr_set(g, (int64_t)missing_gid);
		append_int_attr_set(g, (int64_t)(missing_gid + 1));
		res = avl_intersection(&(g->tree), &(groups->tree));
		if (res)
			error(1, 0, "Negative AVL intersection failed");

		printf("Doing Positive AVL intersection\n");
		append_int_attr_set(g, (int64_t)gid);
		res = avl_intersection(&(g->tree), &(groups->tree));
		if (!res)
			error(1, 0, "Positive AVL intersection failed");
		destroy_attr_set(g);
		free(g);
	}

	destroy_attr_set(groups);
	free(groups);

	return 0;
}
