#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <glib.h>

#include "edv_types.h"
#include "edv_id.h"


/* User ID functions */
edv_uid_struct *EDVUIDNew(void);
void EDVUIDDelete(edv_uid_struct *uid);

edv_uid_struct **EDVUIDGetSystem(gint *total);

edv_uid_struct *EDVUIDMatchByUID(
	edv_uid_struct **list, gint total,
	gint user_id, gint *n
);
edv_uid_struct *EDVUIDMatchByGID(
	edv_uid_struct **list, gint total,
	gint group_id, gint *n
);
gint EDVUIDNameToUID(
	edv_uid_struct **list, gint total, const gchar *user_name
);
const gchar *EDVUIDGetNameFromUID(
	edv_uid_struct **list, gint total,
	gint user_id, gint *n
);
const gchar *EDVUIDGetNameFromGID(
	edv_uid_struct **list, gint total,
	gint group_id, gint *n
);

/* Group ID functions */
edv_gid_struct *EDVGIDNew(void);
void EDVGIDDelete(edv_gid_struct *gid);

edv_gid_struct **EDVGIDGetSystem(gint *total);

edv_gid_struct *EDVGIDMatchByGID(
	edv_gid_struct **list, gint total,
	gint group_id, gint *n
);
gint EDVGIDNameToGID(
	edv_gid_struct **list, gint total, const gchar *group_name
);
const gchar *EDVGIDGetNameFromGID(
	edv_gid_struct **list, gint total,
	gint group_id, gint *n
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Creates a new UID.
 */
edv_uid_struct *EDVUIDNew(void)
{
	return(EDV_UID(g_malloc0(sizeof(edv_uid_struct))));
}

/*
 *	Deletes the UID.
 */
void EDVUIDDelete(edv_uid_struct *uid)
{
	if(uid == NULL)
	    return;

	g_free(uid->name);
	g_free(uid->password);

	g_free(uid->government_name);
	g_free(uid->home_directory);
	g_free(uid->shell_program);

	g_free(uid);
}

/*
 *	Gets a list of UIDs from the system.
 *
 *	The calling function must delete the returned list of UIDs.
 */
edv_uid_struct **EDVUIDGetSystem(gint *total)
{
	gint i;
	struct passwd *pwent;
	edv_uid_struct **list = NULL, *uid;

	if(total == NULL)
	    return(NULL);

	*total = 0;

	/* Open the passwords file and get the first entry */
	pwent = getpwent();

	/* Iterate through each entry */
	while(pwent != NULL)
	{
	    /* Allocate more pointers */
	    i = *total;
	    *total = i + 1;
	    list = (edv_uid_struct **)g_realloc(
		list, (*total) * sizeof(edv_uid_struct *)
	    );
	    if(list == NULL)
	    {
		*total = 0;
		break;
	    }

	    /* Create a new UID */
	    list[i] = uid = EDVUIDNew();
	    if(uid != NULL)
	    {
		g_free(uid->name);
		uid->name = STRDUP(pwent->pw_name);

		g_free(uid->password);
		uid->password = STRDUP(pwent->pw_passwd);

		uid->user_id = pwent->pw_uid;
		uid->group_id = pwent->pw_gid;

		g_free(uid->government_name);
		uid->government_name = STRDUP(pwent->pw_gecos);

		g_free(uid->home_directory);
		uid->home_directory = STRDUP(pwent->pw_dir);

		g_free(uid->shell_program);
		uid->shell_program = STRDUP(pwent->pw_shell);
	    }

	    /* Get next entry */
	    pwent = getpwent();
	}

	/* Close the passwords file */
	endpwent();

	return(list);
}


/*
 *	Matches the UID with the specified user ID.
 */
edv_uid_struct *EDVUIDMatchByUID(
	edv_uid_struct **list, gint total,
	gint user_id, gint *n
)
{
	gint i;
	edv_uid_struct *uid;

	if(n != NULL)
	    *n = -1;

	for(i = 0; i < total; i++)
	{
	    uid = list[i];
	    if(uid == NULL)
		continue;

	    if(uid->user_id == user_id)
	    {
		if(n != NULL)
		    *n = i;
		return(uid);
	    }
	}

	return(NULL);
}

/*
 *	Matches the UID with the specified group ID.
 */
edv_uid_struct *EDVUIDMatchByGID(
	edv_uid_struct **list, gint total,
	gint group_id, gint *n
)
{
	gint i;
	edv_uid_struct *uid;

	if(n != NULL)
	    *n = -1;

	for(i = 0; i < total; i++)
	{
	    uid = list[i];
	    if(uid == NULL)
		continue;

	    if(uid->group_id == group_id)
	    {
		if(n != NULL)
		    *n = i;
		return(uid);
	    }
	}

	return(NULL);
}

/*
 *	Matches the UID with the specified user name.
 */
gint EDVUIDNameToUID(
	edv_uid_struct **list, gint total, const gchar *user_name
)
{
	gint i;
	edv_uid_struct *uid;

	if(STRISEMPTY(user_name))
	    return(0);

	/* Match by number? */
	if(isdigit(*user_name))
	{
	    return(ATOI(user_name));
	}
	else
	{
	    for(i = 0; i < total; i++)
	    {
		uid = list[i];
		if(uid == NULL)
		    continue;

		if(STRISEMPTY(uid->name))
		    continue;

		if(!strcmp(uid->name, user_name))
		    return(uid->user_id);
	    }
	}

	return(0);
}


/*
 *	Matches the user name with the specified user ID.
 *
 *	On failed matched a number string describing the user ID is
 *	returned, so this function never returns NULL.
 */
const gchar *EDVUIDGetNameFromUID(
	edv_uid_struct **list, gint total,
	gint user_id, gint *n
)
{
	static gchar num_str[40];
	edv_uid_struct *uid = EDVUIDMatchByUID(list, total, user_id, n);
	if((uid != NULL) ? (uid->name != NULL) : FALSE)
	{
	    return(uid->name);
	}
	else if(user_id < 0)
	{
	    *num_str = '\0';
	    return(num_str);
	}
	else
	{
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i",
		user_id
	    );
	    return(num_str);
	}
}

/*
 *	Matches the user name with the specified group ID.
 *
 *	On failed matched a number string describing the user ID is
 *	returned, so this function never returns NULL.
 */
const gchar *EDVUIDGetNameFromGID(
	edv_uid_struct **list, gint total,
	gint group_id, gint *n
)
{
	static gchar num_str[40];
	edv_uid_struct *uid = EDVUIDMatchByGID(list, total, group_id, n);
	if((uid != NULL) ? (uid->name != NULL) : FALSE)
	{
	    return(uid->name);
	}
	else if(group_id < 0)
	{
	    *num_str = '\0';
	    return(num_str);
	}
	else
	{
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i",
		group_id
	    );
	    return(num_str);
	}
}


/*
 *	Creates a new GID.
 */
edv_gid_struct *EDVGIDNew(void)
{
	return(EDV_GID(g_malloc0(sizeof(edv_gid_struct))));
}

/*
 *	Deletes the GID.
 */
void EDVGIDDelete(edv_gid_struct *gid)
{
	gint i;

	if(gid == NULL)
	    return;

	g_free(gid->name);
	g_free(gid->password);

	for(i = 0; i < gid->total_group_members; i++)
	    g_free(gid->group_member[i]);
	g_free(gid->group_member);
	gid->group_member = NULL;
	gid->total_group_members = 0;

	g_free(gid);
}

/*
 *	Gets a list of GIDs from the system.
 *
 *	The calling function must delete the returned list of GIDs.
 */
edv_gid_struct **EDVGIDGetSystem(gint *total)
{
	gint i;
	struct group *grent;
	edv_gid_struct **list = NULL, *gid;

	if(total == NULL)
	    return(NULL);

	*total = 0;

	/* Open groups file and get the first entry */
	grent = getgrent();

	/* Iterate through each entry */
	while(grent != NULL)
	{
	    /* Allocate more pointers */
	    i = *total;
	    *total = i + 1;
	    list = (edv_gid_struct **)g_realloc(
		list, (*total) * sizeof(edv_gid_struct *)
	    );
	    if(list == NULL)
	    {
		*total = 0;
		break;
	    }

	    /* Create a new GID */
	    list[i] = gid = EDVGIDNew();
	    if(gid != NULL)
	    {
		g_free(gid->name);
		gid->name = STRDUP(grent->gr_name);

		g_free(gid->password);
		gid->password = STRDUP(grent->gr_passwd);

		gid->group_id = grent->gr_gid;

		/* Any group members to add? */
		if(grent->gr_mem != NULL)
		{
		    gint j = 0, m;
		    for(j = 0; grent->gr_mem[j] != NULL; j++)
		    {
			m = gid->total_group_members;
			gid->total_group_members = m + 1;
			gid->group_member = (gchar **)g_realloc(
			    gid->group_member,
			    gid->total_group_members * sizeof(gchar *)
			);
			if(gid->group_member == NULL)
			{
			    gid->total_group_members = 0;
			    break;
			}
			gid->group_member[m] = STRDUP(grent->gr_mem[j]);
		    }
		}
	    }

	    /* Get next entry */
	    grent = getgrent();
	}

	/* Close the groups file */
	endgrent();

	return(list);
}

/*
 *	Matches the GID with the specified group ID.
 */
edv_gid_struct *EDVGIDMatchByGID(
	edv_gid_struct **list, gint total,
	gint group_id, gint *n
)
{
	gint i;
	edv_gid_struct *gid;

	if(n != NULL)
	    *n = -1;

	for(i = 0; i < total; i++)
	{
	    gid = list[i];
	    if(gid == NULL)
		continue;

	    if(gid->group_id == group_id)
	    {
		if(n != NULL)
		    *n = i;
		return(gid);
	    }
	}

	return(NULL);
}

/*
 *	Matches the GID with the specified group name.
 */
gint EDVGIDNameToGID(
	edv_gid_struct **list, gint total, const gchar *group_name
)
{
	gint i;
	edv_gid_struct *gid;

	if(STRISEMPTY(group_name))
	    return(0);

	/* Match by number? */
	if(isdigit(*group_name))
	{
	    return(ATOI(group_name));
	}
	else
	{
	    for(i = 0; i < total; i++)
	    {
		gid = list[i];
		if(gid == NULL)
		    continue;

		if(STRISEMPTY(gid->name))
		    continue;

		if(!strcmp(gid->name, group_name))
		    return(gid->group_id);
	    }
	}

	return(0);
}


/*
 *	Matches the group name from the specified group ID.
 *
 *	On failed matched, a number string containing the group ID is
 *      returned, so this function never returns NULL.
 */
const gchar *EDVGIDGetNameFromGID(
	edv_gid_struct **list, gint total,
	gint group_id, gint *n
)
{
	static gchar num_str[40];
	edv_gid_struct *gid = EDVGIDMatchByGID(list, total, group_id, n);
	if((gid != NULL) ? (gid->name != NULL) : FALSE)
	{
	    return(gid->name);
	}
	else if(group_id < 0)
	{
	    *num_str = '\0';
	    return(num_str);
	}
	else
	{
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i",
		group_id
	    );
	    return(num_str);
	}
}
