/************************************************************************
 *   Unreal Internet Relay Chat, src/list.c
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Finland
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* -- Jto -- 20 Jun 1990
 * extern void free() fixed as suggested by
 * gruner@informatik.tu-muenchen.de
 */

/* -- Jto -- 03 Jun 1990
 * Added chname initialization...
 */

/* -- Jto -- 24 May 1990
 * Moved is_full() to channel.c
 */

/* -- Jto -- 10 May 1990
 * Added #include <sys.h>
 * Changed memset(xx,0,yy) into bzero(xx,yy)
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "proto.h"
#include "numeric.h"
#ifdef	DBMALLOC
#include "malloc.h"
#endif
#include "mempool.h"
#include <assert.h>
#include <string.h>
void free_link(Link *);
Link *make_link();
extern ircstats IRCstats;

ID_Copyright
    ("(C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen");
ID_Notes("2.24 4/20/94");

#ifdef	DEBUGMODE
static struct liststats {
	int  inuse;
} cloc, crem, users, servs, links, classs, aconfs;

#endif

void outofmemory();

MODVAR int  flinks = 0;
MODVAR int  freelinks = 0;
MODVAR Link *freelink = NULL;
MODVAR Member *freemember = NULL;
MODVAR Membership *freemembership = NULL;
MODVAR MembershipL *freemembershipL = NULL;
MODVAR int  numclients = 0;

/* unless documented otherwise, these are all local-only, except client_list. */
MODVAR struct list_head client_list, lclient_list, server_list, oper_list, unknown_list, global_server_list;

static mp_pool_t *user_pool = NULL;

void initlists(void)
{
#ifdef	DEBUGMODE
	bzero((char *)&cloc, sizeof(cloc));
	bzero((char *)&crem, sizeof(crem));
	bzero((char *)&users, sizeof(users));
	bzero((char *)&servs, sizeof(servs));
	bzero((char *)&links, sizeof(links));
	bzero((char *)&classs, sizeof(classs));
#endif

	INIT_LIST_HEAD(&client_list);
	INIT_LIST_HEAD(&lclient_list);
	INIT_LIST_HEAD(&server_list);
	INIT_LIST_HEAD(&oper_list);
	INIT_LIST_HEAD(&unknown_list);
	INIT_LIST_HEAD(&global_server_list);

	user_pool = mp_pool_new(sizeof(anUser), 512 * 1024);
}

void outofmemory(void)
{
	Debug((DEBUG_FATAL, "Out of memory: restarting server..."));
	restart("Out of Memory");
}


/*
** Create a new aClient structure and set it to initial state.
**
**	from == NULL,	create local client (a client connected
**			to a socket).
**
**	from,	create remote client (behind a socket
**			associated with the client defined by
**			'from'). ('from' is a local client!!).
*/
aClient *make_client(aClient *from, aClient *servr)
{
	aClient *cptr = NULL;

	cptr = MyMallocEx(sizeof(aClient));

#ifdef	DEBUGMODE
	if (!from)
		cloc.inuse++;
	else
		crem.inuse++;
#endif

	/* Note:  structure is zero (calloc) */
	cptr->from = from ? from : cptr;	/* 'from' of local client is self! */
	cptr->user = NULL;
	cptr->serv = NULL;
	cptr->srvptr = servr;
	cptr->status = STAT_UNKNOWN;

	INIT_LIST_HEAD(&cptr->client_node);
	INIT_LIST_HEAD(&cptr->client_hash);
	INIT_LIST_HEAD(&cptr->id_hash);

	(void)strcpy(cptr->username, "unknown");
	if (!from)
	{
		/* Local client */
		
		cptr->local = MyMallocEx(sizeof(aLocalClient));
		
		INIT_LIST_HEAD(&cptr->lclient_node);
		INIT_LIST_HEAD(&cptr->special_node);

		cptr->local->since = cptr->local->lasttime =
		cptr->lastnick = cptr->local->firsttime = TStime();
		cptr->local->class = NULL;
		cptr->local->passwd = NULL;
		cptr->local->sockhost[0] = '\0';
		cptr->local->buffer[0] = '\0';
		cptr->local->authfd = -1;
		cptr->fd = -1;

		dbuf_queue_init(&cptr->local->recvQ);
		dbuf_queue_init(&cptr->local->sendQ);
	} else {
		cptr->fd = -256;
	}
	return (cptr);
}

void free_client(aClient *cptr)
{
	if (!list_empty(&cptr->client_node))
		list_del(&cptr->client_node);

	if (MyConnect(cptr))
	{
		if (!list_empty(&cptr->lclient_node))
			list_del(&cptr->lclient_node);
		if (!list_empty(&cptr->special_node))
			list_del(&cptr->special_node);

		RunHook(HOOKTYPE_FREE_CLIENT, cptr);
		if (cptr->local)
		{
			safefree(cptr->local->passwd);
			safefree(cptr->local->error_str);
			if (cptr->local->hostp)
				unreal_free_hostent(cptr->local->hostp);
			
			MyFree(cptr->local);
		}
	}
	
	safefree(cptr->ip);

	MyFree(cptr);
}

/*
** 'make_user' add's an User information block to a client
** if it was not previously allocated.
*/
anUser *make_user(aClient *cptr)
{
	anUser *user;

	user = cptr->user;
	if (!user)
	{
		user = mp_pool_get(user_pool);
		memset(user, 0, sizeof(anUser));

#ifdef	DEBUGMODE
		users.inuse++;
#endif
		user->swhois = NULL;
		user->away = NULL;
#ifdef NO_FLOOD_AWAY
		user->flood.away_t = 0;
		user->flood.away_c = 0;
#endif
		user->refcnt = 1;
		user->joined = 0;
		user->channel = NULL;
		user->invited = NULL;
		user->silence = NULL;
		user->server = NULL;
		strlcpy(user->svid, "0", sizeof(user->svid));
		user->lopt = NULL;
		user->whowas = NULL;
		user->snomask = 0;
		*user->realhost = '\0';
		user->virthost = NULL;
		cptr->user = user;		
	}
	return user;
}

aServer *make_server(aClient *cptr)
{

	aServer *serv = cptr->serv;

	if (!serv)
	{
		serv = (aServer *)MyMallocEx(sizeof(aServer));
#ifdef	DEBUGMODE
		servs.inuse++;
#endif
		serv->user = NULL;
		*serv->by = '\0';
		serv->users = 0;
		serv->up = NULL;
		cptr->serv = serv;
	}
	return cptr->serv;
}

/*
** free_user
**	Decrease user reference count by one and realease block,
**	if count reaches 0
*/
void free_user(anUser *user, aClient *cptr)
{
	if (user->refcnt == 0)
		sendto_realops("[BUG] free_user: ref count for '%s' was already 0!?", user->username);
	else
		--user->refcnt;
	if (user->refcnt == 0)
	{
		RunHook2(HOOKTYPE_FREE_USER, user, cptr);
		if (user->away)
			MyFree(user->away);
		if (user->swhois)
		{
			SWhois *s, *s_next;
			for (s = user->swhois; s; s = s_next)
			{
				s_next = s->next;
				MyFree(s);
			}
		}
		if (user->virthost)
			MyFree(user->virthost);
		if (user->operlogin)
			MyFree(user->operlogin);
		mp_pool_release(user);
#ifdef	DEBUGMODE
		users.inuse--;
#endif
	}
}

/*
 * taken the code from ExitOneClient() for this and placed it here.
 * - avalon
 */
void remove_client_from_list(aClient *cptr)
{
	list_del(&cptr->client_node);
	if (IsServer(cptr))
	{
		IRCstats.servers--;
	}
	if (IsClient(cptr))
	{
		if (IsInvisible(cptr))
		{
			IRCstats.invisible--;
		}
		if (IsOper(cptr) && !IsHideOper(cptr))
		{
			IRCstats.operators--;
			VERIFY_OPERCOUNT(cptr, "rmvlist");
		}
		IRCstats.clients--;
		if (cptr->srvptr && cptr->srvptr->serv)
			cptr->srvptr->serv->users--;
	}
	if (IsUnknown(cptr) || IsConnecting(cptr) || IsHandshake(cptr)
		|| IsSSLHandshake(cptr)
	)
		IRCstats.unknown--;

	if (IsPerson(cptr))	/* Only persons can have been added before */
	{
		add_history(cptr, 0);
		off_history(cptr);	/* Remove all pointers to cptr */
	}
	
	if (cptr->user)
		(void)free_user(cptr->user, cptr);
	if (cptr->serv)
	{
		if (cptr->serv->user)
			free_user(cptr->serv->user, cptr);
		safefree(cptr->serv->features.chanmodes[0]);
		safefree(cptr->serv->features.chanmodes[1]);
		safefree(cptr->serv->features.chanmodes[2]);
		safefree(cptr->serv->features.chanmodes[3]);
		MyFree((char *)cptr->serv);
#ifdef	DEBUGMODE
		servs.inuse--;
#endif
	}
#ifdef	DEBUGMODE
	if (cptr->fd == -2)
		cloc.inuse--;
	else
		crem.inuse--;
#endif
	assert(list_empty(&cptr->client_node));
	assert(list_empty(&cptr->client_hash));
	assert(list_empty(&cptr->id_hash));
	(void)free_client(cptr);
	checklist();
	numclients--;
	return;
}

/*
 * although only a small routine, it appears in a number of places
 * as a collection of a few lines...functions like this *should* be
 * in this file, shouldnt they ?  after all, this is list.c, isnt it ?
 * -avalon
 */
void add_client_to_list(aClient *cptr)
{
	list_add(&cptr->client_node, &client_list);
}

/*
 * Look for ptr in the linked listed pointed to by link.
 */
Link *find_user_link(Link *lp, aClient *ptr)
{
	if (ptr)
		while (lp)
		{
			if (lp->value.cptr == ptr)
				return (lp);
			lp = lp->next;
		}
	return NULL;
}

/* Based on find_str_link() from bahamut -- codemastr */
int find_str_match_link(Link *lp, char *charptr)
{
	if (!charptr)
		return 0;
	for (; lp; lp = lp->next) {
		if(!match(lp->value.cp, charptr))
			return 1;
	}
	return 0;
}

void free_str_list(Link *lp)
{
	Link *next;


	while (lp)
	{
		next = lp->next;
		MyFree((char *)lp->value.cp);
		free_link(lp);
		lp = next;
	}

	return;
}


#define	LINKSIZE	(4072/sizeof(Link))

Link *make_link(void)
{
	Link *lp;
	int  i;

	/* "caching" slab-allocator... ie. we're allocating one pages
	   (hopefully - upped to the Linux default, not dbuf.c) worth of 
	   link-structures at time to avoid all the malloc overhead.
	   All links left free from this process or separately freed 
	   by a call to free_link() are moved over to freelink-list.
	   Impact? Let's see... -Donwulff */
	/* Impact is a huge memory leak -Stskeeps
	   hope this implementation works a little bit better */
	if (freelink == NULL)
	{
		for (i = 1; i <= LINKSIZE; i++)
		{
			lp = (Link *)MyMalloc(sizeof(Link));
			lp->next = freelink;
			freelink = lp;
		}
		freelinks = freelinks + LINKSIZE;
		lp = freelink;
		freelink = lp->next;
		freelinks--;
	}
	else
	{
		lp = freelink;
		freelink = freelink->next;
		freelinks--;
	}
#ifdef	DEBUGMODE
	links.inuse++;
#endif
	return lp;
}

void free_link(Link *lp)
{
	lp->next = freelink;
	freelink = lp;
	freelinks++;

#ifdef	DEBUGMODE
	links.inuse--;
#endif
}

Ban *make_ban(void)
{
	Ban *lp;

	lp = (Ban *) MyMalloc(sizeof(Ban));
#ifdef	DEBUGMODE
	links.inuse++;
#endif
	return lp;
}

void free_ban(Ban *lp)
{
	MyFree((char *)lp);
#ifdef	DEBUGMODE
	links.inuse--;
#endif
}

aClass *make_class(void)
{
	aClass *tmp;

	tmp = (aClass *)MyMalloc(sizeof(aClass));
#ifdef	DEBUGMODE
	classs.inuse++;
#endif
	return tmp;
}

void free_class(aClass *tmp)
{
	MyFree((char *)tmp);
#ifdef	DEBUGMODE
	classs.inuse--;
#endif
}

#ifdef	DEBUGMODE
void send_listinfo(aClient *cptr, char *name)
{
	int  inuse = 0, mem = 0, tmp = 0;

	sendto_one(cptr, ":%s %d %s :Local: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name, inuse += cloc.inuse,
	    tmp = cloc.inuse * CLIENT_LOCAL_SIZE);
	mem += tmp;
	sendto_one(cptr, ":%s %d %s :Remote: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name,
	    crem.inuse, tmp = crem.inuse * CLIENT_REMOTE_SIZE);
	mem += tmp;
	inuse += crem.inuse;
	sendto_one(cptr, ":%s %d %s :Users: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name, users.inuse,
	    tmp = users.inuse * sizeof(anUser));
	mem += tmp;
	inuse += users.inuse,
	    sendto_one(cptr, ":%s %d %s :Servs: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name, servs.inuse,
	    tmp = servs.inuse * sizeof(aServer));
	mem += tmp;
	inuse += servs.inuse,
	    sendto_one(cptr, ":%s %d %s :Links: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name, links.inuse,
	    tmp = links.inuse * sizeof(Link));
	mem += tmp;
	inuse += links.inuse,
	    sendto_one(cptr, ":%s %d %s :Classes: inuse: %d(%d)",
	    me.name, RPL_STATSDEBUG, name, classs.inuse,
	    tmp = classs.inuse * sizeof(aClass));
	mem += tmp;
	inuse += aconfs.inuse,
	    sendto_one(cptr, ":%s %d %s :Totals: inuse %d %d",
	    me.name, RPL_STATSDEBUG, name, inuse, mem);
}

#endif

void add_ListItem(ListStruct *item, ListStruct **list) {
	item->next = *list;
	item->prev = NULL;
	if (*list)
		(*list)->prev = item;
	*list = item;
}

ListStruct *del_ListItem(ListStruct *item, ListStruct **list) {
	ListStruct *l, *ret;

	for (l = *list; l; l = l->next) {
		if (l == item) {
			ret = item->next;
			if (l->prev)
				l->prev->next = l->next;
			else
				*list = l->next;
			if (l->next)
				l->next->prev = l->prev;
			return ret;
		}
	}
	return NULL;
}

/** Add item to list with a 'priority'.
 * If there are multiple items with the same priority then it will be
 * added as the last item within.
 */
void add_ListItemPrio(ListStructPrio *new, ListStructPrio **list, int priority)
{
	ListStructPrio *x, *last = NULL;
	
	if (!*list)
	{
		/* We are the only item. Easy. */
		*list = new;
		return;
	}
	
	for (x = *list; x; x = x->next)
	{
		last = x;
		if (x->priority >= priority)
			break;
	}

	if (x)
	{
		if (x->prev)
		{
			/* We will insert ourselves just before this item */
			new->prev = x->prev;
			new->next = x;
			x->prev->next = new;
			x->prev = new;
		} else {
			/* We are the new head */
			*list = new;
			new->next = x;
			x->prev = new;
		}
	} else
	{
		/* We are the last item */
		last->next = new;
		new->prev = last;
	}
}

