
#include "irc.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(user_c)
#include "struct.h"

#include "ircaux.h"
#include "list.h"
#include "hash.h"
#include "struct.h"
#include "userlist.h"
#include "misc.h"
#include "output.h"
#define MAIN_SOURCE
#include "modval.h"

#define USERHOST_HASHSIZE 501
#define USERCHAN_HASHSIZE 20

#ifdef WANT_USERLIST
HashEntry UserListByHost_Table[USERHOST_HASHSIZE];
HashEntry UserListByChannel_Table[USERCHAN_HASHSIZE];

UserList *user_list = NULL;

static long hash_name(char *str, unsigned int size)
{
	const unsigned char *p = (const unsigned char *)str;
	unsigned long g, hash = 0;
	if (!p) return -1;
	while (*p)
	{
		if (*p == '*' || *p =='?' || *p == ',')
			return -1;
		hash = (hash << 4) + ((*p >= 'A' && *p <= 'Z') ? (*p+32) : *p);
		if ((g = hash & 0xF0000000))
			hash ^= g >> 24;
		hash &= ~g;
		p++;
	}
	return (hash % size);
}

  /* userlist entry can be put into our tier 1 */
static inline void add_userlist_to_tier_1(UserList *ptr, unsigned long hvalue)
{
        ptr->next = (UserList *) UserListByHost_Table[hvalue].list;
        /* assign our new linked list into array spot */
        UserListByHost_Table[hvalue].list = (void *) ptr;
        /* quick tally of userlists in chain in this array spot */
        UserListByHost_Table[hvalue].links++;
}

static inline void add_userlist_to_tier_2(UserList *ptr, unsigned long hvalue)
{
	ptr->next = (UserList *) UserListByChannel_Table[hvalue].list;
	/* assign our new linked list into array spot */
	UserListByChannel_Table[hvalue].list = (void *) ptr;
	/* quick tally of userlists in chain in this array spot */
	UserListByChannel_Table[hvalue].links++;
}

static inline void add_userlist_to_tier_3(UserList *uptr)
{
	add_to_list((List **)&user_list, (List *)uptr);
}

void add_userlist(UserList *uptr)
{
char *ptr;
long hvalue;
	if (!(ptr = strrchr(uptr->host, '@')))
	{
		add_userlist_to_tier_3(uptr);
		return;
	}
	if ((hvalue = hash_name(ptr, USERHOST_HASHSIZE)) != -1)
	{
		add_userlist_to_tier_1(uptr, hvalue);
		return;
	}
#if 0
	if (!strchr(uptr->channels, '*'))
	{
		char *channel, *ptr;
		channel = LOCAL_COPY(uptr->channels);
		while ((ptr = next_in_comma_list(channel, &channel)))
		{
			if (!*ptr)
				return;
			hvalue = hash_name(ptr, USERCHAN_HASHSIZE);
			add_userlist_to_tier_2(uptr, hvalue);
			if (channel && *channel)
			{
				UserList *newptr;
				newptr = new_malloc(sizeof(UserList));
				memcpy(newptr, uptr, sizeof(UserList));
				uptr = newptr;
			}
		}
	}
#else
	if ((hvalue = hash_name(uptr->channels, USERCHAN_HASHSIZE)) != -1)
	{
		add_userlist_to_tier_2(uptr, hvalue);
		return;
	}
#endif
	add_userlist_to_tier_3(uptr);
}

/*
 * move_link_to_top: used by find routine, brings link
 * to the top of the list in the specific array location
 */
static inline void move_link_to_top(UserList *tmp, UserList *prev, HashEntry *location)
{
	if (prev) 
	{
		UserList *old_list;
		old_list = (UserList *) location->list;
		location->list = (void *) tmp;
		prev->next = tmp->next;
		tmp->next = old_list;
	}
}

/*
 * remove_link_from_list: used by find routine, removes link
 * from our chain of hashed entries.
 */
static inline void remove_link_from_list(UserList *tmp, UserList *prev, HashEntry *location)
{
	if (prev) 
	{
		/* remove the link from the middle of the list */
		prev->next = tmp->next;
	}
	else {
		/* unlink the first link, and connect next one up */
		location->list = (void *) tmp->next;
	}
	/* set tmp's next to NULL, as its unlinked now */
	tmp->next = NULL;
}



static inline UserList *find_userlist_tier3(char *host, char *channel, int remove)
{
	register UserList *tmp;
	for (tmp = user_list; tmp; tmp = tmp->next)
	{
		if (wild_match(tmp->host, host) || !my_stricmp(tmp->host, host))
		{
			if (check_channel_match(tmp->channels, channel) || !strcmp(tmp->channels, channel))
			{
				if (remove)
					tmp = (UserList *)remove_from_list((List **)&user_list, tmp->nick);
				return tmp;
			}
		}
	}
	return NULL;
}

static inline UserList *find_userlist_tier1(char *userhost, char *channel, unsigned long hvalue, int remove)
{
HashEntry *location;
register UserList *tmp, *prev = NULL;
	location = &UserListByHost_Table[hvalue];
	for (tmp = (UserList *)location->list; tmp; prev = tmp, tmp = tmp->next)
	{
		if ((wild_match(tmp->host, userhost) ||!my_stricmp(tmp->host, userhost)) && check_channel_match(tmp->channels, channel))
		{
			if (remove)
			{
				location->links--;
				remove_link_from_list(tmp, prev, location);
			} else
				move_link_to_top(tmp, prev, location);
			location->hits++;
			return tmp;
		}
	}
	return NULL;
}

static inline UserList *find_userlist_tier2(char *userhost,unsigned long hvalue, int remove)
{
HashEntry *location;
register UserList *tmp, *prev = NULL;
	location = &UserListByChannel_Table[hvalue];
	for (tmp = (UserList *)location->list; tmp; prev = tmp, tmp = tmp->next)
	{
		if (wild_match(tmp->host, userhost) || !my_stricmp(tmp->host, userhost))
		{
			if (remove)
			{
				location->links--;
				remove_link_from_list(tmp, prev, location);
			} else
				move_link_to_top(tmp, prev, location);
			location->hits++;
			return tmp;
		}
	}
	return NULL;
}

UserList *find_userlist(char *userhost, char *channel, int remove)
{
char *ptr;
long hvalue;
/*char *host = clear_server_flags(userhost);*/
char *host = userhost;
	if (!(ptr = strrchr(userhost, '@')))
		return find_userlist_tier3(host, channel, remove);
	if ((hvalue = hash_name(ptr, USERHOST_HASHSIZE)) != -1)
	{
		UserList *tmp;
		if ((tmp = find_userlist_tier1(host, channel, hvalue, remove)))		
			return tmp;
	}	
	if ((hvalue = hash_name(channel, USERCHAN_HASHSIZE)) != -1)
	{
		UserList *tmp;
		if ((tmp = find_userlist_tier2(host, hvalue, remove)))
			return tmp;
	}	
	/* if we got here this is shitty */
	return find_userlist_tier3(host, channel, remove);
}

UserList *create_sorted_userlist(void)
{

	UserList *tmp = NULL, *t, *new;
	int i;
	for (i = 0; i < USERHOST_HASHSIZE; i++)
	{
		t = (UserList *)UserListByHost_Table[i].list;
		while (t)
		{
			new = new_malloc(sizeof(UserList));
			memcpy(new, t, sizeof(UserList));
			add_to_list((List **)&tmp, (List *)new);
			t = t->next;
		}
	}
	for (i = 0; i < USERCHAN_HASHSIZE; i++)
	{
		t = (UserList *)UserListByChannel_Table[i].list;
		while (t)
		{
			new = new_malloc(sizeof(UserList));
			memcpy(new, t, sizeof(UserList));
			add_to_list((List **)&tmp, (List *)new);
			t = t->next;
		}
	}
	for (t = user_list; t; t = t->next)
	{
		new = new_malloc(sizeof(UserList));
		memcpy(new, t, sizeof(UserList));
		add_to_list((List **)&tmp, (List *)new);
	}
	return tmp;
}

void destroy_sorted_userlist(UserList **uptr)
{
UserList *t;
	if (!uptr || !*uptr)
		return;
	while (*uptr)
	{
		t = (*uptr)->next;
		new_free(uptr);
		*uptr = t;
	}
}

#if 0
UserHist *next_userlist(UserList *uptr, int *size)
{
	long hvalue = -1;
	static HashEntry *location = NULL;
	
	if (!uptr)
	{
		location = &UserListByHost_Table[0];
		*size = USERHOST_HASHSIZE;
		hvalue = 0;
		while(((UserList *)location[hvalue].list) == NULL)
		{
			hvalue++;
			if (hvalue == USERHOST_HASHSIZE && *size == USERHOST_HASHSIZE)
			{
				location = &UserListByChannel_Table[0];
				hvalue = 0;
				*size = USERCHAN_HASHSIZE;
			}
			else if (hvalue == USERCHAN_HASHSIZE && *size == USERCHAN_HASHSIZE)
			{
				hvalue = -1;
				break;
			}
		}		
		if (hvalue != -1 && location[hvalue].list != NULL)
			return ((UserHist *)location[hvalue].list);
		location = NULL;		
		*size = -1;
		return (user_list);
	}
	if (uptr->next)
		return uptr->next;
	else if (location && !uptr->next)
	{
		if (*size > -1 && location)
		{
			if (*size == USERHOST_HASHSIZE)
				hvalue = hash_name(strrchr(uptr->host, '@'), *size);
			else if (*size == USERCHAN_HASHSIZE)
				hvalue = hash_name(uptr->channels, *size);
			if (hvalue == -1)
			{
				location = NULL;
				*size = -1;
				return user_list;
			}
			hvalue++;
			if (*size == USERHOST_HASHSIZE && hvalue >= USERHOST_HASHSIZE)
			{
				/* end of current list */
				location = &UserListByChannel_Table[0];
				*size = USERCHAN_HASHSIZE;
			}
			while (((UserList *)location[hvalue].list) == NULL)
			{
				hvalue++;
				if (hvalue >= USERHOST_HASHSIZE)
				{
					location = &UserListByChannel_Table[0];
					hvalue = 0;
					*size = USERCHAN_HASHSIZE;
				}
				else if (*size == USERCHAN_HASHSIZE && hvalue == USERCHAN_HASHSIZE)
					break;
			}
			if (location[hvalue].list != NULL)
				return ((UserList *)location[hvalue].list);
			location = NULL;		
			*size = -1;
			return user_list;
		}
	}	
	return NULL;
}
#endif

static inline int check_best_passwd(char *passwd, char *test)
{
	if (passwd && test)
		return !checkpass(test, passwd) ? 1 : 0;
/*		return !strcmp(passwd, test) ? 1 : 0;*/
	return 0;
}

UserList *find_bestmatch(char *nick, char *userhost, char *channel, char *passwd)
{
UserList *best = NULL;
UserList *best_passwd = NULL;
register UserList *tmp;
long hvalue = 0;
int	best_user_match = 0,
	best_chan_match = 0;
int	chan_match,
	user_match,
	passwd_match = 0;
char	*check;

	/*check = clear_server_flags(userhost);*/
	check = userhost;
	if ((hvalue = hash_name(strrchr(check, '@'), USERHOST_HASHSIZE)) != -1)
	{
		for (tmp = (UserList *)UserListByHost_Table[hvalue].list; tmp; tmp = tmp->next)
		{
			user_match = wild_match(tmp->host, check);
			chan_match = check_channel_match(tmp->channels, channel);
			if (user_match > best_user_match && chan_match > best_chan_match)
			{
				best_chan_match = chan_match;
				best_user_match = user_match;
				best = tmp;
				if ((passwd_match = check_best_passwd(tmp->password, passwd)))
					best_passwd = tmp;
			} 
			else if (best_user_match && user_match == best_user_match)
			{
				if (chan_match > best_chan_match)
				{
					best_chan_match = chan_match;
					best_user_match = user_match;
					best = tmp;
					if ((passwd_match = check_best_passwd(tmp->password, passwd)))
						best_passwd = tmp;
				}
			}
		}
	}
	if ((hvalue = hash_name(channel, USERCHAN_HASHSIZE)) != -1)
	{
		for (tmp = (UserList *)UserListByChannel_Table[hvalue].list; tmp; tmp = tmp->next)
		{
			user_match = wild_match(tmp->host, check);
			if (user_match > best_user_match)
			{
				best_user_match = user_match;
				best = tmp;
				if ((passwd_match = check_best_passwd(tmp->password, passwd)))
					best_passwd = tmp;
			}
		}
	}
	else if (hvalue == -1)
	{
		for (hvalue = 0; hvalue < USERCHAN_HASHSIZE; hvalue++)
		{
			for (tmp = (UserList *)UserListByChannel_Table[hvalue].list; tmp; tmp = tmp->next)
			{
				user_match = wild_match(tmp->host, check);
				chan_match = check_channel_match(tmp->channels, channel);
				if (user_match > best_user_match && chan_match > best_chan_match)
				{
					best_user_match = user_match;
					best_chan_match = chan_match;
					best = tmp;
					if ((passwd_match = check_best_passwd(tmp->password, passwd)))
						best_passwd = tmp;
				}
				else if (best_user_match && user_match == best_user_match)
				{
					if (chan_match > best_chan_match) 
					{
						best_chan_match = chan_match;
						best_user_match = user_match;
						best = tmp;
						if ((passwd_match = check_best_passwd(tmp->password, passwd)))
							best_passwd = tmp;
					}
				}
			}
		}
	}
	for (tmp = user_list; tmp; tmp = tmp->next) 
	{
		if ((chan_match = check_channel_match(tmp->channels, channel)) > 0) 
		{
			user_match = wild_match(tmp->host, check);
			if (user_match > best_user_match && chan_match > best_chan_match)
			{
				best_chan_match = chan_match;
				best_user_match = user_match;
				best = tmp;
				if ((passwd_match = check_best_passwd(tmp->password, passwd)))
					best_passwd = tmp;
			}
			else if (best_user_match && user_match == best_user_match)
			{
				if (chan_match > best_chan_match) 
				{
					best_chan_match = chan_match;
					best_user_match = user_match;
					best = tmp;
					if ((passwd_match = check_best_passwd(tmp->password, passwd)))
						best_passwd = tmp;
				}
			}
		}
	}
	if (passwd)
	{
		if (!best_passwd)
			return NULL;
		else if (best_passwd)
			return best_passwd;
	}
	return best;
}

BUILT_IN_COMMAND(debug_user)
{
HashEntry *location;
UserList *user;
int i;
int tier1 = 0, tier2 = 0, tier3 = 0;
	for (i = 0; i < USERHOST_HASHSIZE; i++)
	{
		user = (UserList *)UserListByHost_Table[i].list;
		location = &UserListByHost_Table[i];
		while (user)
		{
			tier1++;
			put_it("Tier1[%d] %d %d %s %s", i, location->links, location->hits, user->host, user->channels);
			user = user->next;
		}
	}
	for (i = 0; i < USERCHAN_HASHSIZE; i++)
	{
		user = (UserList *)UserListByChannel_Table[i].list;
		location = &UserListByChannel_Table[i];
		while (user)
		{
			tier2++;
			put_it("Tier2[%d] %d %d %s %s", i, location->links, location->hits, user->host, user->channels);
			user = user->next;
		}
	}
	for (tier3 = 0, user = user_list; user; user = user->next, tier3++)
		put_it("Tier3[%d] %s %s", tier3, user->host, user->channels);
	put_it("Tier1 = [%d] Tier2 = [%d] Tier3 = [%d]", tier1, tier2, tier3); 

}

UserList *next_userlist(UserList *uptr, int *size, void **location)
{
unsigned long hvalue = -1;
	/* we start iterating the list will a null pointer */
	if (!uptr)
	{
		*location = &UserListByHost_Table[0];
		*size = USERHOST_HASHSIZE;
		hvalue = 0;
		while(((UserList *)((HashEntry*)*location)[hvalue].list) == NULL)
		{
			hvalue++;
			if (hvalue == USERHOST_HASHSIZE && *size == USERHOST_HASHSIZE)
			{
				*location = &UserListByChannel_Table[0];
				hvalue = 0;
				*size = USERCHAN_HASHSIZE;
			}
			else if (hvalue == USERCHAN_HASHSIZE && *size == USERCHAN_HASHSIZE)
			{
				hvalue = -1;
				break;
			}
		}		
		if (hvalue != -1 && ((HashEntry *)*location)[hvalue].list != NULL)
			return ((UserList *)((HashEntry *)*location)[hvalue].list);
		*location = NULL;		
		*size = -1;
		return (user_list);
	}
	else if (uptr->next)
		return uptr->next;
	else if (*location)
	{
		if (*size > -1)
		{
			if (*size == USERHOST_HASHSIZE)
				hvalue = hash_name(strrchr(uptr->host, '@'), *size);
			else if (*size == USERCHAN_HASHSIZE)
				hvalue = hash_name(uptr->channels, *size);
			if (hvalue == -1)
			{
				*location = NULL;
				*size = -1;
				return user_list;
			}
			hvalue++;
			if (*size == USERHOST_HASHSIZE && hvalue >= USERHOST_HASHSIZE)
			{
				/* end of current list */
				*location = &UserListByChannel_Table[0];
				*size = USERCHAN_HASHSIZE;
			}
			while (((UserList *)((HashEntry *)*location)[hvalue].list) == NULL)
			{
				hvalue++;
				if (hvalue >= USERHOST_HASHSIZE)
				{
					*location = &UserListByChannel_Table[0];
					hvalue = 0;
					*size = USERCHAN_HASHSIZE;
				}
				else if (*size == USERCHAN_HASHSIZE && hvalue >= USERCHAN_HASHSIZE)
				{
					hvalue = -1;
					break;
				}
			}
			if (hvalue != -1 && ((HashEntry *)*location)[hvalue].list != NULL)
				return ((UserList *)((HashEntry *)*location)[hvalue].list);
			*location = NULL;		
			*size = -1;
			return user_list;
		}
	}
	return NULL;
}
#endif
