/* NeoStats - IRC Statistical Services
** Copyright (c) 1999-2004 Adam Rutter, Justin Hammond
** http://www.neostats.net/
**
**
**  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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
**  USA
**
** NeoStats CVS Identification
** $Id: chans.c 1620 2004-03-05 21:36:07Z Mark $
*/

#include "stats.h"
#include "ircd.h"
#include "dl.h"
#include "hash.h"
#include "log.h"
#include "users.h"
#include "chans.h"
#include "exclude.h"
#ifdef SQLSRV
#include "sqlsrv/rta.h"
#endif

/** @brief Chanmem structure
 *  
 */
typedef struct Chanmem {
	char nick[MAXNICK];
	time_t jointime;
	long flags;
	void *moddata[NUM_MODULES];
} Chanmem;

hash_t *ch;


/** @brief Process the Channel TS Time 
 * 
 * Addes the channel TS time to the channel struct 
 *
 * @param c Channel Struct of channel who's ts is being changed
 * @param tstime ts time of the channel
 *
 * @returns Nothing
 *
 */
void
SetChanTS (Chans * c, const time_t tstime)
{
	c->tstime = tstime;
}

/** @brief Process a Topic Change
 *
 * Processes a Channel topic Change for particular channel and updates internals
 * Also triggers off a TOPICCHANGE event for modules
 *
 * @param owner Char of who changed the topic. Can't be a userstruct as the user might not be online anymore
 * @param c Channel Struct of channel who's topic is being changed
 * @param time when the topic was changed (might have been in the past 
 * @param topic the new topic
 *
 * @return Nothing
*/

void
ChanTopic (const char* chan, const char *owner, const char* ts, const char *topic)
{
	char **av;
	int ac = 0;
	Chans *c;
	time_t time;

	c = findchan (chan);
	if (!c) {
		nlog (LOG_WARNING, LOG_CORE, "ChanTopic: can't find channel %s", chan);
		return;
	}
	if(ts) {
		time = atoi (ts);
	} else {
		time = me.now;
	}

	if(topic) {
		strlcpy (c->topic, topic, BUFSIZE);
	} else {
		c->topic[0] = 0;
	}
	strlcpy (c->topicowner, owner, MAXHOST);
	c->topictime = time;
	AddStringToList (&av, c->name, &ac);
	AddStringToList (&av, (char*)owner, &ac);
	if(topic) {
		AddStringToList (&av, (char*)topic, &ac);
	} else {
		AddStringToList (&av, "", &ac);
	}
	ModuleEvent (EVENT_TOPICCHANGE, av, ac);
	free (av);
}

/** @brief Check if a mode is set on a Channel
 * 
 * used to check if a mode is set on a channel
 * 
 * @param c channel to check
 * @param mode is the mode to check, as a LONG
 *
 * @returns 1 on match, 0 on no match, -1 on error
 *
*/
int
CheckChanMode (Chans * c, long mode)
{
	ModesParm *m;
	lnode_t *mn;
	if (!c) {
		nlog (LOG_WARNING, LOG_CORE, "CheckChanMode: tied to check modes of empty channel");
		return -1;
	}
	if (c->modes & mode) {
		/* its a match */
		return 1;
	}
	/* if we get here, we have to check the modeparm list first */
	mn = list_first (c->modeparms);
	while (mn) {
		m = lnode_get (mn);
		if (m->mode & mode) {
			/* its a match */
			return 1;
		}
		mn = list_next (c->modeparms, mn);
	}
	return 0;
}


/** @brief Compare channel modes from the channel hash
 *
 * used in ChanModes to compare modes (list_find argument)
 *
 * @param v actually its a ModeParm struct
 * @param mode is the mode as a long
 *
 * @return 0 on match, 1 otherwise.
*/

static int
comparemode (const void *v, const void *mode)
{
	ModesParm *m = (void *) v;
	if (m->mode == (long) mode) {
		return 0;
	} else {
		return 1;
	}
}

/** @brief Process a mode change on a channel
 *
 * process a mode change on a channel adding and deleting modes as required
 *
 * @param origin usually the server that sent the mode change. Not used
 * @param av array of variables to pass
 * @param ac number of variables n av
 *
 * @return 0 on error, number of modes processed on success.
*/

int
ChanMode (char *origin, char **av, int ac)
{
	char *modes;
	int add = 0;
	int j = 2;
	int i;
	int modeexists;
	Chans *c;
	ModesParm *m;
	lnode_t *mn;
	char **data;
	int datasize = 0;

	c = findchan (av[0]);
	if (!c) {
		return 0;
	}
	
	AddStringToList(&data, origin, &datasize);
	for (i = 0; i < ac; i++) {
		AddStringToList(&data, av[i], &datasize);	
	}
	ModuleEvent(EVENT_CHANMODE, data, datasize);
	free(data);	

	modes = av[1];
	while (*modes) {
		switch (*modes) {
		case '+':
			add = 1;
			break;
		case '-':
			add = 0;
			break;
		default:
			for (i = 0; i < ircd_cmodecount; i++) {

				if (*modes == chan_modes[i].flag) {
					if (add) {
						if (chan_modes[i].nickparam) {
							ChanUserMode (av[0], av[j], 1, chan_modes[i].mode);
							j++;
						} else {
							if (chan_modes[i].parameters) {
								mn = list_first (c->modeparms);
								modeexists = 0;
								while (mn) {
									m = lnode_get (mn);
									/* mode limit and mode key replace current values */
									if ((m->mode == CMODE_LIMIT) && (chan_modes[i].mode == CMODE_LIMIT)) {
										strlcpy (m->param, av[j], PARAMSIZE);
										j++;
										modeexists = 1;
										break;
									} else if ((m->mode == CMODE_KEY) && (chan_modes[i].mode == CMODE_KEY)) {
										strlcpy (m->param, av[j], PARAMSIZE);
										j++;
										modeexists = 1;
										break;
									} else if (((int *) m->mode == (int *) chan_modes[i].mode) && !ircstrcasecmp (m->param, av[j])) {
										nlog (LOG_INFO, LOG_CORE, "ChanMode: Mode %c (%s) already exists, not adding again", chan_modes[i].flag, av[j]);
										j++;
										modeexists = 1;
										break;
									}
									mn = list_next (c->modeparms, mn);
								}
								if (modeexists != 1) {
									m = smalloc (sizeof (ModesParm));
									m->mode = chan_modes[i].mode;
									strlcpy (m->param, av[j], PARAMSIZE);
									mn = lnode_create (m);
									if (list_isfull (c->modeparms)) {
										nlog (LOG_CRITICAL, LOG_CORE, "ChanMode: modelist is full adding to channel %s", c->name);
										do_exit (NS_EXIT_ERROR, "List full - see log file");
									} else {
										list_append (c->modeparms, mn);
									}
									j++;
								}
							} else {
								c->modes |= chan_modes[i].mode;
							}
						}
					} else {
						if (chan_modes[i].nickparam) {
							ChanUserMode (av[0], av[j], 0, chan_modes[i].mode);
							j++;
						} else {
							if (chan_modes[i].parameters) {
								mn = list_find (c->modeparms, (int *) chan_modes[i].mode, comparemode);
								if (!mn) {
									nlog (LOG_INFO, LOG_CORE, "ChanMode: can't find mode %c for channel %s", *modes, c->name);
								} else {
									list_delete (c->modeparms, mn);
									m = lnode_get (mn);
									lnode_destroy (mn);
									free (m);

									if (!(chan_modes[i].mode == CMODE_LIMIT))
										j++;
								}
							} else {
								c->modes &= ~chan_modes[i].mode;
							}
						}
					}
				}
			}
		}
		modes++;
	}
	return j;

}

/** @brief Process a mode change that affects a user on a channel
 *
 * process a mode change on a channel that affects a user
 *
 * @param c Channel Struct of channel mode being changed
 * @param u User struct of user that mode is affecting
 * @param add 1 means add, 0 means remove mode
 * @param mode is the long int of the mode
 *
 * @return Nothing
*/

void
ChanUserMode (const char* chan, const char* nick, int add, long mode)
{
	lnode_t *cmn;
	Chanmem *cm;
	Chans * c;
	User * u;

	u = finduser(nick);
	if (!u) {
		nlog (LOG_WARNING, LOG_CORE, "ChanUserMode: can't find user %s", nick);
		return;
	}
	c = findchan(chan);
	if (!c) {
		nlog (LOG_WARNING, LOG_CORE, "ChanUserMode: can't find channel %s", chan);
		return;
	}
	cmn = list_find (c->chanmembers, u->nick, comparef);
	if (!cmn) {
		if (me.debug_mode) {
			chanalert (s_Services, "ChanUserMode: %s is not a member of channel %s", u->nick, c->name);
			ChanDump (c->name);
			UserDump (u->nick);
		}
		return;
	}
	cm = lnode_get (cmn);
	if (add) {
		nlog (LOG_DEBUG2, LOG_CORE, "ChanUserMode: Adding mode %ld to Channel %s User %s", mode, c->name, u->nick);
		cm->flags |= mode;
	} else {
		nlog (LOG_DEBUG2, LOG_CORE, "ChanUserMode: Deleting Mode %ld to Channel %s User %s", mode, c->name, u->nick);
		cm->flags &= ~mode;
	}
}

/** @brief Create a new channel record
 *
 * And insert it into the hash, mallocing required memory for it and so on
 * also check that the channel hash is not full
 *
 * @param chan name of the channel to create
 *
 * @returns c the newly created channel record
 * @todo Dynamically resizable channel hashes
*/
static Chans *
new_chan (const char *chan)
{
	char **av;
	int ac = 0;
	Chans *c;
	hnode_t *cn;

	SET_SEGV_LOCATION();
	c = calloc (sizeof (Chans), 1);
	strlcpy (c->name, chan, CHANLEN);
	cn = hnode_create (c);
	if (hash_isfull (ch)) {
		nlog (LOG_CRITICAL, LOG_CORE, "new_chan: channel hash is full");
	} else {
		hash_insert (ch, cn, c->name);
	}
	c->chanmembers = list_create (CHAN_MEM_SIZE);
	c->modeparms = list_create (MAXMODES);
	c->cur_users = 0;
	c->topictime = 0;
	c->modes = 0;
	c->tstime = me.now;
	c->flags = 0;
	/* check exclusions */
	ns_do_exclude_chan(c);
	AddStringToList (&av, c->name, &ac);
	ModuleEvent (EVENT_NEWCHAN, av, ac);
	free (av);
	return c;
}

/** @brief Deletes a channel record
 *
 * frees any memory associated with the record and removes it from the channel hash
 *
 * @param c the corrosponding channel structure you wish to delete
 *
 * @returns Nothing
*/

void
del_chan (Chans * c)
{
	hnode_t *cn;
	lnode_t *cm;

	SET_SEGV_LOCATION();
	cn = hash_lookup (ch, c->name);
	if (!cn) {
		nlog (LOG_WARNING, LOG_CORE, "del_chan: channel %s not found.", c->name);
		return;
	} else {
		nlog (LOG_DEBUG2, LOG_CORE, "del_chan: deleting channel %s", c->name);
		cm = list_first (c->modeparms);
		while (cm) {
			free (lnode_get (cm));
			cm = list_next (c->modeparms, cm);
		}
		list_destroy_nodes (c->modeparms);
		list_destroy (c->modeparms);
		list_destroy (c->chanmembers);
		hash_delete (ch, cn);
		hnode_destroy (cn);
		free (c);
	}
}

/** @brief Process a kick from a channel. 
 *
 * In fact, this does nothing special apart from call part_chan, and processing a channel kick
 * @param kickby, the user nick or servername doing the kick
 * @param chan, channel name user being kicked from
 * @param kicked, the user nick getting kicked
 * @param kickreason the reason the user was kicked
 *
 */

void
kick_chan (const char *kickby, const char *chan, const char *kicked, const char *kickreason)		
{
	char **av;
	int ac = 0;
	Chans *c;
	Chanmem *cm;
	lnode_t *un;
	User *u;

	SET_SEGV_LOCATION();
	u = finduser (kicked);
	if (!u) {
		nlog (LOG_WARNING, LOG_CORE, "kick_chan: user %s not found %s %s", kicked, chan, kickby);
		if (me.debug_mode) {
			chanalert (s_Services, "kick_chan: user %s not found %s %s", kicked, chan, kickby);
			ChanDump (chan);
		}
		return;
	}
	nlog (LOG_DEBUG2, LOG_CORE, "kick_chan: %s kicking %s from %s for %s", kickby, u->nick, chan, kickreason ? kickreason : "no reason");
	c = findchan (chan);
	if (!c) {
		nlog (LOG_WARNING, LOG_CORE, "kick_chan: channel %s not found", chan);
		return;
	} else {
		un = list_find (c->chanmembers, u->nick, comparef);
		if (!un) {
			nlog (LOG_WARNING, LOG_CORE, "kick_chan: %s isn't a member of channel %s", u->nick, chan);
			if (me.debug_mode) {
				chanalert (s_Services, "kick_chan: %s isn't a member of channel %s", u->nick, chan);
				ChanDump (c->name);
				UserDump (u->nick);
			}
		} else {
			cm = lnode_get (un);
			lnode_destroy (list_delete (c->chanmembers, un));
			free (cm);
			AddStringToList (&av, c->name, &ac);
			AddStringToList (&av, u->nick, &ac);
			AddStringToList (&av, (char *)kickby, &ac);
			if (kickreason != NULL) {
				AddStringToList (&av, (char*)kickreason, &ac);
			}
			ModuleEvent (EVENT_KICK, av, ac);
			free (av);
			ac = 0;
			c->cur_users--;
		}
		if (findbot (u->nick)) {
			/* its one of our bots, so add it to the botchan list */
			del_bot_from_chan (u->nick, c->name);
			AddStringToList (&av, c->name, &ac);
			AddStringToList (&av, u->nick, &ac);
			AddStringToList (&av, (char *)kickby, &ac);
			if (kickreason != NULL) {
				AddStringToList (&av, (char*)kickreason, &ac);
			}
			ModuleEvent (EVENT_KICKBOT, av, ac);
			free (av);
			ac = 0;
		}
		un = list_find (u->chans, c->name, comparef);
		if (!un) {
			nlog (LOG_WARNING, LOG_CORE, "kick_chan: %s not found in channel %s", u->nick, chan);
			if (me.debug_mode) {
				chanalert (s_Services, "kick_chan: %s not found in channel %s", u->nick, chan);
				ChanDump (c->name);
				UserDump (u->nick);
			}
		} else {
			lnode_destroy (list_delete (u->chans, un));
		}
		nlog (LOG_DEBUG3, LOG_CORE, "kick_chan: cur users %s %ld (list %d)", c->name, c->cur_users, (int)list_count (c->chanmembers));
		if (c->cur_users <= 0) {
			AddStringToList (&av, c->name, &ac);
			ModuleEvent (EVENT_DELCHAN, av, ac);
			free (av);
			ac = 0;
			del_chan (c);
		}
	}
}

/** @brief Parts a user from a channel
 *
 * Parts a user from a channel and raises events if required
 * Events raised are PARTCHAN and DELCHAN
 * if its one of our bots, also update bot channel lists
 *
 * @param u the User structure corrosponding to the user that left the channel
 * @param chan the channel to part them from
 * @param reason the reason the user parted, if any
 *
 * @returns Nothing
*/

void
part_chan (User * u, const char *chan, const char *reason)
{
	Chans *c;
	lnode_t *un;
	char **av;
	Chanmem *cm;
	int ac = 0;
	SET_SEGV_LOCATION();
	if (!u) {
		nlog (LOG_WARNING, LOG_CORE, "part_chan: trying to part NULL user from %s", chan);
		if (me.debug_mode) {
			chanalert (s_Services, "part_chan: trying to part NULL user from %s", chan);
			ChanDump (chan);
		}
		return;
	}
	nlog (LOG_DEBUG2, LOG_CORE, "part_chan: parting %s from %s", u->nick, chan);
	c = findchan (chan);
	if (!c) {
		nlog (LOG_WARNING, LOG_CORE, "part_chan: channel %s not found", chan);
		return;
	} else {
		un = list_find (c->chanmembers, u->nick, comparef);
		if (!un) {
			nlog (LOG_WARNING, LOG_CORE, "part_chan: user %s isn't a member of channel %s", u->nick, chan);
			if (me.debug_mode) {
				chanalert (s_Services, "part_chan: user %s isn't a member of channel %s", u->nick, chan);
				ChanDump (c->name);
				UserDump (u->nick);
			}
		} else {
			cm = lnode_get (un);
			lnode_destroy (list_delete (c->chanmembers, un));
			free (cm);
			AddStringToList (&av, c->name, &ac);
			AddStringToList (&av, u->nick, &ac);
			if (reason != NULL) {
				AddStringToList (&av, (char*)reason, &ac);
			}
			ModuleEvent (EVENT_PARTCHAN, av, ac);
			free (av);
			ac = 0;
			c->cur_users--;
		}
		if (findbot (u->nick)) {
			/* its one of our bots, so add it to the botchan list */
			del_bot_from_chan (u->nick, c->name);
			AddStringToList (&av, c->name, &ac);
			AddStringToList (&av, u->nick, &ac);
			if (reason != NULL) {
				AddStringToList (&av, (char*)reason, &ac);
			}
			ModuleEvent (EVENT_PARTBOT, av, ac);
			free (av);
			ac = 0;
		}
		un = list_find (u->chans, c->name, comparef);
		if (!un) {
			nlog (LOG_WARNING, LOG_CORE, "part_chan: user %s not found in channel %s", u->nick, chan);
			if (me.debug_mode) {
				chanalert (s_Services, "part_chan: user %s not found in channel %s", u->nick, chan);
				ChanDump (c->name);
				UserDump (u->nick);
			}
			return;
		} else {
			lnode_destroy (list_delete (u->chans, un));
		}
		nlog (LOG_DEBUG3, LOG_CORE, "part_chan: cur users %s %ld (list %d)", c->name, c->cur_users, (int)list_count (c->chanmembers));
		if (c->cur_users <= 0) {
			AddStringToList (&av, c->name, &ac);
			ModuleEvent (EVENT_DELCHAN, av, ac);
			free (av);
			ac = 0;
			del_chan (c);
		}
	}
}

/** @brief Change channel records when a nick change occurs
 *
 * goes through the channel members list, changing users nicks after a nickname change occurs
 *
 * @param c the channel to check (as called by the user functions)
 * @param newnick the New Nickname of the client
 * @param oldnick the old nickname of the client
 *
 * @returns Nothing
 * @todo What happens if one of our bots change their nick?
*/

void
ChanNickChange (Chans * c, const char *newnick, const char *oldnick)
{
	lnode_t *cm;
	Chanmem *cml;
	SET_SEGV_LOCATION();
	cm = list_find (c->chanmembers, oldnick, comparef);
	if (!cm) {
		nlog (LOG_WARNING, LOG_CORE, "ChanNickChange: %s isn't a member of %s", oldnick, c->name);
		if (me.debug_mode) {
			chanalert (s_Services, "ChanNickChange: %s isn't a member of %s", oldnick, c->name);
			ChanDump (c->name);
			UserDump (oldnick);
		}
		return;
	} else {
		nlog (LOG_DEBUG3, LOG_CORE, "ChanNickChange: newnick %s, oldnick %s", newnick, oldnick);
		cml = lnode_get (cm);
		strlcpy (cml->nick, newnick, MAXNICK);
	}
}



/** @brief Process a user joining a channel
 *
 * joins a user to a channel and raises JOINCHAN event and if required NEWCHAN events
 * if the channel is new, a new channel record is requested and defaults are set
 * if its one of our bots, also update the botchanlist
 *
 * @param u The User structure of the user joining the channel
 * @param chan the channel name
 *
 * @returns Nothing
*/


void
join_chan (const char* nick, const char *chan)
{
	User* u;
	Chans *c;
	lnode_t *un, *cn;
	Chanmem *cm;
	char **av;
	int ac = 0;
	
	SET_SEGV_LOCATION();
	u = finduser (nick);
	if (!u) {
		nlog (LOG_WARNING, LOG_CORE, "join_chan: tried to join unknown user %s to channel %s", nick, chan);
		return;
	}
	if (!ircstrcasecmp ("0", chan)) {
		/* join 0 is actually part all chans */
		nlog (LOG_DEBUG2, LOG_CORE, "join_chan: parting %s from all channels", u->nick);
		list_process (u->chans, u, UserPart);
		return;
	}
	c = findchan (chan);
	if (!c) {
		/* its a new Channel */
		nlog (LOG_DEBUG2, LOG_CORE, "join_chan: new channel %s", chan);
		c = new_chan (chan);
	}
	/* add this users details to the channel members hash */
	cm = smalloc (sizeof (Chanmem));
	strlcpy (cm->nick, u->nick, MAXNICK);
	cm->jointime = me.now;
	cm->flags = 0;
	cn = lnode_create (cm);
	nlog (LOG_DEBUG2, LOG_CORE, "join_chan: adding usernode %s to channel %s", u->nick, chan);
	if (list_find (c->chanmembers, u->nick, comparef)) {
		nlog (LOG_WARNING, LOG_CORE, "join_chan: tried to add %s to channel %s but they are already a member", u->nick, chan);
		if (me.debug_mode) {
			chanalert (s_Services, "join_chan: tried to add %s to channel %s but they are already a member", u->nick, chan);
			ChanDump (c->name);
			UserDump (u->nick);
		}
		return;
	}
	if (list_isfull (c->chanmembers)) {
		nlog (LOG_CRITICAL, LOG_CORE, "join_chan: channel %s member list is full", c->name);
		lnode_destroy (cn);
		free (cm);
		return;
	} else {
		list_append (c->chanmembers, cn);
	}
	c->cur_users++;
	un = lnode_create (c->name);
	if (list_isfull (u->chans)) {
		nlog (LOG_CRITICAL, LOG_CORE, "join_chan: user %s member list is full", u->nick);
		lnode_destroy (un);
	} else {
		list_append (u->chans, un);
	}
	AddStringToList (&av, c->name, &ac);
	AddStringToList (&av, u->nick, &ac);
	ModuleEvent (EVENT_JOINCHAN, av, ac);
	free (av);
	nlog (LOG_DEBUG3, LOG_CORE, "join_chan: cur users %s %ld (list %d)", c->name, c->cur_users, (int)list_count (c->chanmembers));
	if (findbot (u->nick)) {
		nlog(LOG_DEBUG3, LOG_CORE, "join_chan: joining bot %s to channel %s", u->nick, c->name);
		add_bot_to_chan (u->nick, c->name);
	}
}

/** @brief Dump Channel information
 *
 * dump either the entire channel list, or a single channel detail. Used for debugging
 * sends the output to the services channel
 *
 * @param chan the channel name to dump, or NULL for all channels
 *
 * @returns Nothing
*/

static void 
dumpchan (Chans* c)
{
	lnode_t *cmn;
 	Chanmem *cm;
	char mode[10];
	int i;
	int j = 0;
	ModesParm *m;

	bzero (mode, 10);
	mode[0] = '+';
	for (i = 0; i < ircd_cmodecount; i++) {
		if (c->modes & chan_modes[i].mode) {
			mode[++j] = chan_modes[i].flag;
		}
	}
	debugtochannel("Channel:    %s", c->name);
	debugtochannel("Mode:       %s tstime %ld", mode, (long)c->tstime);
	debugtochannel("TopicOwner: %s TopicTime: %ld Topic: %s", c->topicowner, (long)c->topictime, c->topic);
	debugtochannel("PubChan?:   %d", is_pub_chan (c));
	debugtochannel("Flags:      %lx", c->flags);
	cmn = list_first (c->modeparms);
	while (cmn) {
		m = lnode_get (cmn);
		for (i = 0; i < ircd_cmodecount; i++) {
			if (m->mode & chan_modes[i].mode) {
				debugtochannel("Modes:      %c Parms %s", chan_modes[i].flag, m->param);
			}
		}
		cmn = list_next (c->modeparms, cmn);
	}
	debugtochannel("Members:    %ld (List %d)", c->cur_users, (int)list_count (c->chanmembers));
	cmn = list_first (c->chanmembers);
	while (cmn) {
		cm = lnode_get (cmn);
		bzero (mode, 10);
		j = 0;
		mode[0] = '+';
		for (i = 0; i < ircd_cmodecount; i++) {
			if (cm->flags & chan_modes[i].mode) {
				mode[++j] = chan_modes[i].flag;
			}
		}
		debugtochannel("            %s Modes %s Joined: %ld", cm->nick, mode, (long)cm->jointime);
		cmn = list_next (c->chanmembers, cmn);
	}
	debugtochannel("========================================");
}

void
ChanDump (const char *chan)
{
	hnode_t *cn;
	hscan_t sc;
	Chans *c;

	SET_SEGV_LOCATION();
	debugtochannel("================CHANDUMP================");
	if (!chan) {
		debugtochannel("Channels %d", (int)hash_count (ch));
		hash_scan_begin (&sc, ch);
		while ((cn = hash_scan_next (&sc)) != NULL) {
			c = hnode_get (cn);
			dumpchan(c);
		}
	} else {
		c = findchan (chan);
		if (c) {
			dumpchan(c);
		} else {
			debugtochannel("ChanDump: can't find channel %s", chan);
		}
	}
}


/** @brief Find a channel
 *
 * Finds the channel structure for the channel named chan, or NULL if it can't be found
 *
 * @param chan the channel name to find
 *
 * @returns The Channel structure for chan, or NULL if it can't be found.
*/


Chans *
findchan (const char *chan)
{
	Chans *c;
	hnode_t *cn;

	cn = hash_lookup (ch, chan);
	if (cn) {
		c = hnode_get (cn);
		return c;
	}
	nlog (LOG_DEBUG3, LOG_CORE, "findchan: %s not found", chan);
	return NULL;
}

/** @brief Returns if the nick is a member of the channel
 *
 * Returns 1 if nick is part of the channel, 0 if they are not
 *
 * @param chan the channel to check
 * @param u the user to check 
 *
 * @returns 1 if they are, 0 if they are not 
*/

int 
IsChanMember (Chans *c, User *u) 
{
	if (!u || !c) {
		return 0;
	}
	if (list_find (c->chanmembers, u->nick, comparef)) {
		return 1;
	}
	return 0;
}

/** @brief Returns if the nick has a particular channel status e.g. ChanOp
 *
 * @param chan the channel to check
 * @param nick the nick to check 
 *
 * @returns 1 if they are, 0 if they are not 
*/

int test_chan_user_mode(char* chan, char* nick, int flag)
{
	User* u;
	Chans* c;
	lnode_t *cmn;
 	Chanmem *cm;

	u = finduser(nick);
	c = findchan(chan);
	if (!u || !c) {
		return 0;
	}
	cmn = list_find (c->chanmembers, nick, comparef);
	if (!cmn) {
		return 0;
	}
	cm = lnode_get (cmn);
	if (cm->flags & flag) {
			return 1;
	}	
	return 0;
}

#ifdef SQLSRV

/* display the channel modes */
/* BUFSIZE is probably too small.. oh well */
static char chanmodes[BUFSIZE];

void *display_chanmodes (void *tbl, char *col, char *sql, void *row) 
{
	Chans *c = row;
	lnode_t *cmn;
	char tmp[BUFSIZE];
	int i;
	int j = 1;
	ModesParm *m;
	
	chanmodes[0] = '+';
	for (i = 0; i < ircd_cmodecount; i++) {
		if (c->modes & chan_modes[i].mode) {
			chanmodes[j++] = chan_modes[i].flag;
		}
	}
	chanmodes[j++] = '\0';

	cmn = list_first (c->modeparms);
	while (cmn) {
		m = lnode_get (cmn);
		for (i = 0; i < ircd_cmodecount; i++) {
			if (m->mode & chan_modes[i].mode) {
				ircsnprintf(tmp, BUFSIZE, " +%c %s", chan_modes[i].flag, m->param);
				strlcat(chanmodes, tmp, BUFSIZE);
			}
		}
		cmn = list_next (c->modeparms, cmn);
	}
	return chanmodes;
}
/* its huge, because we can have a *LOT* of users in a channel */

static char chanusers[BUFSIZE*10];
void *display_chanusers (void *tbl, char *col, char *sql, void *row) {
        Chans *c = row;
	lnode_t *cmn;
	char sjoin[BUFSIZE];
	char mode[BUFSIZE];
	char final[BUFSIZE*2];
	int i;
	int j = 0;
	int k = 0;
 	Chanmem *cm;
	
	chanusers[0] = '\0';
	cmn = list_first (c->chanmembers);
	while (cmn) {
		cm = lnode_get (cmn);
		j = 0;
		k = 1;
		mode[0] = '+';
		for (i = 0; i < ircd_cmodecount; i++) {
			if (cm->flags & chan_modes[i].mode) {
				if (chan_modes[i].sjoin != 0) 
					sjoin[j++] = chan_modes[i].sjoin;
				else 
					mode[k++] = chan_modes[i].flag;
			}
		}
		mode[k++] = '\0';
		sjoin[j++] = '\0';
		if (k > 2) 
			ircsnprintf(final, BUFSIZE*2, "%s %s%s,", mode, sjoin, cm->nick);
		else 
			ircsnprintf(final, BUFSIZE*2, "%s%s,", sjoin, cm->nick);
		strlcat(chanusers, final, BUFSIZE*10);
		cmn = list_next (c->chanmembers, cmn);
	}



	return chanusers;
}


COLDEF neo_chanscols[] = {
	{
		"chans",
		"name",
		RTA_STR,
		CHANLEN,
		offsetof(struct Chans, name),
		RTA_READONLY,
		NULL,
		NULL,
		"The name of the channel"
	},
	{
		"chans",
		"nomems",
		RTA_INT,
		sizeof(int),
		offsetof(struct Chans, cur_users),
		RTA_READONLY,
		NULL, 
		NULL,
		"The no of users in the channel"
	},
	{
		"chans",
		"modes",
		RTA_STR,
		BUFSIZE,
		offsetof(struct Chans, modes),
		RTA_READONLY,
		display_chanmodes,
		NULL,
		"The modes of the channel"
	},
	{
		"chans",
		"users",
		RTA_STR,
		BUFSIZE*10,
		offsetof(struct Chans, chanmembers),
		RTA_READONLY,
		display_chanusers,
		NULL,
		"The users of the channel"
	},
	{
		"chans",
		"topic",
		RTA_STR,
		BUFSIZE,
		offsetof(struct Chans, topic),
		RTA_READONLY,
		NULL,
		NULL,
		"The topic of the channel"
	},
	{	
		"chans",
		"topicowner",
		RTA_STR,
		MAXHOST,
		offsetof(struct Chans, topicowner),
		RTA_READONLY,
		NULL,
		NULL,
		"Who set the topic"
	},
	{	
		"chans",
		"topictime",
		RTA_INT,
		sizeof(int),
		offsetof(struct Chans, topictime),
		RTA_READONLY,
		NULL,
		NULL,
		"When the topic was set"
	},
	{	
		"chans",
		"created",
		RTA_INT,
		sizeof(int),
		offsetof(struct Chans, tstime),
		RTA_READONLY,
		NULL,
		NULL,
		"when the channel was created"
	},
	{	
		"chans",
		"flags",
		RTA_INT,
		sizeof(int),
		offsetof(struct Chans, flags),
		RTA_READONLY,
		NULL,
		NULL,
		"Flags for this channel"
	},

};

TBLDEF neo_chans = {
	"chans",
	NULL, 	/* for now */
	sizeof(struct Chans),
	0,
	TBL_HASH,
	neo_chanscols,
	sizeof(neo_chanscols) / sizeof(COLDEF),
	"",
	"The list of Channels on the IRC network"
};
#endif /* SQLSRV */



/** @brief initialize the channel data
 *
 * initializes the channel data and channel hash ch.
 *
 * @return Nothing
*/

int 
init_chan_hash ()
{
	ch = hash_create (C_TABLE_SIZE, 0, 0);
	if(!ch)	
		return NS_FAILURE;
#ifdef SQLSRV
	/* add the server hash to the sql library */
	neo_chans.address = ch;
	rta_add_table(&neo_chans);
#endif

	return NS_SUCCESS;
}
