#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

typedef struct User User;
struct User
{
	char*	name;
	char*	leader;
	User*	next;
	int	n;
	char*	mem[];
};

enum{
	Ulog=	6,
	Usize=	1<<Ulog,
	Umask=	Usize-1,
};

static struct{
	Lock;
	User*	hash[Usize];
} users;

static void
checkname(char *s)
{
	Rune r;
	static char invalid[] = "#:,()";

	if(*s == 0 || *s == '-' || *s == '+')
		error("illegal name");
	while((r = *s) != 0){
		if(r < Runeself){
			s++;
			if(r < 0x20 || r >= 0x7F && r < 0xA0 || jehanne_strchr(invalid, r) != nil)
				r = Runeerror;
		}else
			s += jehanne_chartorune(&r, s);
		if(r == Runeerror)
			error("invalid character in name");
	}
}

static uint32_t
hashpjw(char *s)
{
	uint32_t h, g;

	h = 0;
	for(; *s != 0; s++){
		h = (h << 4) + (*s&0xFF);
		g = h & 0xf0000000;
		if(g != 0)
			h ^= ((g >> 24) & 0xff) | g;
	}
	return h & 0x7FFFFFFF;
}

static User**
lookuser(char *name)
{
	uint32_t h;
	User **l, *u;

	h = hashpjw(name) & Umask;
	for(l = &users.hash[h]; (u = *l) != nil; l = &u->next)
		if(jehanne_strcmp(u->name, name) == 0)
			break;
	return l;
}

static char*
tack(char **p, char *s)
{
	char *o;

	o = *p;
	jehanne_strcpy(o, s);
	*p += jehanne_strlen(o)+1;
	return o;
}

void
adduser(char *uid, char *leader, int nm, char **mem)
{
	User **l, *u, *v;
	char *o;
	int i, nc;

	if(leader != nil){
		if(*leader == '\0')
			leader = nil;
		else if(jehanne_strcmp(leader, uid) == 0)
			leader = uid;
	}
	checkname(uid);
	nc = jehanne_strlen(uid)+1;
	if(leader != nil && leader != uid){
		checkname(leader);
		nc += jehanne_strlen(leader)+1;
	}
	for(i = 0; i < nm; i++){
		checkname(mem[i]);
		nc += jehanne_strlen(mem[i])+1;
	}
	v = jehanne_mallocz(sizeof(User)+nm*sizeof(v->mem[0])+nc, 1);
	if(v == nil)
		error(Enomem);
	o = (char*)(v+1)+nm*sizeof(v->mem[0]);
	v->name = tack(&o, uid);
	if(leader == nil)
		v->leader = nil;
	else if(jehanne_strcmp(v->name, leader) != 0)
		v->leader = tack(&o, leader);
	else
		v->leader = v->name;
	v->n = nm;
	for(i = 0; i < nm; i++)
		v->mem[i] = tack(&o, mem[i]);
	lock(&users);
	l = lookuser(uid);
	u = *l;
	if(u != nil){
		/* replace */
		v->next = u->next;
		jehanne_free(u);
	}
	*l = v;
	unlock(&users);
}

int
deluser(char *name)
{
	User **l, *u;

	lock(&users);
	l = lookuser(name);
	u = *l;
	if(u == nil){
		unlock(&users);
		return 0;
	}
	*l = u->next;
	unlock(&users);
	jehanne_free(u);
	return 1;
}

static int
ismember(char *s, int n, char **mem)
{
	int i;

	for(i = 0; i < n; i++)
		if(jehanne_strcmp(s, mem[i]) == 0)
			return 1;
	return 0;
}

int
ingroup(char *uid, char *gid)
{
	User *g;

	if(jehanne_strcmp(uid, gid) == 0)
		return 1;
	lock(&users);
	g = *lookuser(gid);
	if(g != nil && ismember(uid, g->n, g->mem)){
		unlock(&users);
		return 1;
	}
	unlock(&users);
	return 0;
}

int
leadsgroup(char *uid, char *gid)
{
	User *g;

	lock(&users);
	g = *lookuser(gid);
	if(g != nil){
		if(g->leader != nil && jehanne_strcmp(uid, g->leader) == 0 ||
		   g->leader == nil && ismember(uid, g->n, g->mem)){
			unlock(&users);
			return 1;
		}
	}
	unlock(&users);
	return g == nil && jehanne_strcmp(uid, gid) == 0;
}

char*
usersread(void)
{
	int i, m;
	User *u;
	Fmt fmt;

	jehanne_fmtstrinit(&fmt);
	for(i = 0; i < nelem(users.hash); i++){
		lock(&users);
		for(u = users.hash[i]; u != nil; u = u->next){
			jehanne_fmtprint(&fmt, "%q", u->name);
			if(u->leader != nil || u->n != 0){
				jehanne_fmtprint(&fmt, " %q", u->leader != nil? u->leader: "");
				for(m = 0; m < u->n; m++)
					jehanne_fmtprint(&fmt, " %q", u->mem[m]);
			}
			jehanne_fmtprint(&fmt, "\n");
		}
		unlock(&users);
	}
	return jehanne_fmtstrflush(&fmt);
}

long
userswrite(void *buf, long n)
{
	int i, nf;
	char *p, *s, *e, *flds[100];

	if(n <= 0)
		return n;
	if(n > 16*1024)
		error(Etoobig);
	p = jehanne_malloc(n+1);
	if(p == nil)
		error(Enomem);
	if(waserror()){
		jehanne_free(p);
		nexterror();
	}
	jehanne_memmove(p, buf, n);
	p[n] = '\0';
	if(p[n-1] != '\n')
		error("incomplete line");
	for(s = p; (e = jehanne_strchr(s, '\n')) != nil; s = e){
		*e++ = '\0';
		if(*s == '#')
			continue;
		nf = jehanne_tokenize(s, flds, nelem(flds));
		if(nf == nelem(flds))
			error("too many group members");
		if(jehanne_strcmp(flds[0], "-") == 0){
			for(i = 1; i < nf; i++)
				deluser(flds[i]);
		}else if(nf > 1)
			adduser(flds[0], flds[1], nf-2, flds+2);
		else if(nf != 0)
			adduser(flds[0], nil, 0, nil);
	}
	poperror();
	jehanne_free(p);
	return n;
}
