/*
 * names.c: This here is used to maintain a list of all the people currently
 * on your channel.  Seems to work 
 *
 * Originally written by Michael Sandrof and others
 * Mostly rewritten by Jeremy Nelson
 *
 * Copyright 1990 Michael Sandrof
 * Copyright 1997 EPIC Software Labs
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#include "irc.h"
#include "ircaux.h"
#include "alist.h"
#include "names.h"
#include "output.h"
#include "screen.h"
#include "window.h"
#include "server.h"

typedef struct nick_stru
{
	char 	*nick;		/* nickname of person on channel */
	u_32int_t hash;		/* Hash of the nickname */
	char	*userhost;	/* Their userhost, if we know it */
	int	chanop;		/* True if they are a channel operator */
	int	voice;		/* 1 if they are, 0 if theyre not, -1 if uk */
	int	half_assed;	/* 1 if they are, 0 if theyre not, -1 if uk */
}	Nick;

typedef	struct	nick_list_stru
{
	Nick	**list;
	int	max;
	int	max_alloc;
	alist_func func;
	hash_type hash;
}	NickList;

/* ChannelList: structure for the list of channels you are current on */
typedef	struct	channel_stru
{
struct	channel_stru *	next;		/* pointer to next channel */
struct	channel_stru *	prev;		/* pointer to previous channel */
	char *		channel;	/* channel name */
	int		server;		/* last or present server connection */
	int		waiting;	/* just acting as a placeholder... */
	Window	*	window;		/* the window the channel is "on" */
	int		bound;		/* Bound to this window? */
	int		current;	/* Current to this window? */
	NickList	nicks;		/* alist of nicks on channel */

	u_long		mode;		/* Current mode settings for channel */
	u_long		i_mode;		/* channel mode for cached string */
	char *		s_mode;		/* cached string version of modes */
	int		limit;		/* max users for the channel */
	char *		key;		/* key for this channel */
	char		chop;		/* true if you are chop */
	char		voice;		/* true if you are voiced */
	char		half_assed;	/* true if you are a helper */
}	Channel;


/*
 * The variable "mode_str" must correspond in order to the modes defined
 * here, or all heck will break loose.  You have been warned.
 */
static	char	mode_str[] = "aciklmnprstz";

const int	MODE_ANONYMOUS	= 1 << 0;	/* av2.9 */
const int	MODE_C		= 1 << 1;	/* erf/TS4 */
const int 	MODE_INVITE 	= 1 << 2;	/* RFC */
const int 	MODE_KEY    	= 1 << 3;	/* RFC */
const int	MODE_LIMIT	= 1 << 4;	/* RFC */
const int 	MODE_MODERATED	= 1 << 5;	/* RFC */
const int	MODE_MSGS	= 1 << 6;	/* RFC */
const int	MODE_PRIVATE	= 1 << 7;	/* RFC */
const int	MODE_REGISTERED = 1 << 8;	/* Dalnet */
const int	MODE_SECRET	= 1 << 9;	/* RFC */
const int	MODE_TOPIC	= 1 << 10;	/* RFC */
const int	MODE_Z		= 1 << 11;	/* erf/TS4 */
const int	MODE_RESTRICTED = 1 << 12;	/* Dalnet */


static	void	add_to_mode_list (const char *, const char *);
static	void	check_mode_list_join (const char *);

struct modelist
{
	char	*chan;
	char	*mode;
	struct modelist *next;
}	*mode_list = (struct modelist *) 0;

/* channel_list: list of all the channels you are currently on */
static	Channel *channel_list = (Channel *) 0;

/* For new eu2.9 !channels */
static	char	new_channel_format[BIG_BUFFER_SIZE];


/*
 * This isnt strictly neccesary, its more of a cosmetic function.
 */
int	traverse_all_channels (Channel **ptr, int server)
{
	int	real_server;

	if (server < 0)
		real_server = -(server + 1);
	else
		real_server = server;

	if (!*ptr)
		*ptr = channel_list;
	else
		*ptr = (*ptr)->next;

	if (real_server == server)
		while (*ptr && (*ptr)->server != real_server)
			*ptr = (*ptr)->next;
	else
		while (*ptr && (*ptr)->server == real_server)
			*ptr = (*ptr)->next;


	/*
	 * Cheap check to save CPU
	 */
	if (!*ptr)
		return 0;

	/*
	 * Ugh.  Ok.  If we get here, then we either have a channel that
	 * is not attached to a window (how does that happen? [when the
	 * window is killed, you idjit!])  or the window we are attached 
	 * to is not on the same server that we think we should be.  In 
	 * all cases, *our* concept of what server we are on is most 
	 * important.  So we have to go find a window that thinks its on 
	 * the same server we are.
	 */
	if (!(*ptr)->window || (*ptr)->window->server != (*ptr)->server)
	{
		Window *w = NULL;
		(*ptr)->window = NULL;

		while (traverse_all_windows(&w))
		{
			if (w->server == (*ptr)->server)
				(*ptr)->window = w;
		}

		/*
		 * This should probably be an impossible case.  I cannot
		 * imagine any situation where we would have a server open
		 * with channels on it, but without any window attached to
		 * it.  We'll just output this bogus message for now.
		 *
		 * This used to output the message if the channel was on the
		 * list, but that came up when the server was already
		 * connected, which wasnt terribly useful.
		 */
		if ((x_debug & DEBUG_CHANNELS) && !(*ptr)->window)
		{
			/*
			 * Not sure if anyone cares about this message
			 * Probably should be cleaned up after.  Hopefully
			 * someone will call window_check_servers() soon.
			 */
			say("Found orphaned channel [%s] on server [%d]",
				(*ptr)->channel, (*ptr)->server);
		}
	}

	return 1;
}


/*
 *
 * Channel maintainance
 *
 */

static Channel *find_channel (const char *channel, int server)
{
	Channel *ch = NULL;

	if (server == -1)
		server = primary_server;

	/* Automatically grok the ``*'' channel. */
	if (!channel || !*channel || !strcmp(channel, "*"))
		if (!(channel = get_channel_by_refnum(0)))
			return NULL;		/* sb colten */

	while (traverse_all_channels(&ch, server))
		if (!my_stricmp(ch->channel, channel))
			return ch;

	return NULL;
}

/* Channel constructor */
static Channel *create_channel (const char *name, int server)
{
	Channel *new_c = (Channel *)new_malloc(sizeof(Channel));

	new_c->prev = new_c->next = NULL;
	new_c->channel = m_strdup(name);
	new_c->server = server;
	new_c->waiting = 0;
	new_c->window = NULL;
	new_c->bound = 0;
	new_c->current = 0;
	new_c->nicks.max_alloc = new_c->nicks.max = 0;
	new_c->nicks.list = NULL;
	new_c->nicks.func = (alist_func) my_strnicmp;
	new_c->nicks.hash = HASH_INSENSITIVE;

	new_c->mode = 0;
	new_c->i_mode = 0;
	new_c->s_mode = NULL;
	new_c->limit = 0;
	new_c->key = NULL;
	new_c->chop = 0;
	new_c->voice = 0;
	new_c->half_assed = 0;

	new_c->next = channel_list;
	if (channel_list)
		channel_list->prev = new_c;
	channel_list = new_c;
	return new_c;
}

/* Nicklist destructor */
static void 	clear_channel (Channel *chan)
{
	NickList *list = &chan->nicks;
	int	i;

	for (i = 0; i < list->max; i++)
	{
		new_free(&list->list[i]->nick);
		new_free(&list->list[i]->userhost);
		new_free(&list->list[i]);
	}
	new_free((void **)&list->list);
	list->max = list->max_alloc = 0;
}

/* Channel destructor */
static void 	destroy_channel (Channel *chan)
{
	if (chan != channel_list)
	{
		if (!chan->prev)
			panic("chan != channel_list, but chan->prev is NULL");
		chan->prev->next = chan->next;
	}
	else
	{
		if (chan->prev)
			panic("channel_list->prev is not NULL");
		channel_list = chan->next;
	}

	if (chan->next)
		chan->next->prev = chan->prev;

	new_free(&chan->channel);
	chan->server = -1;
	chan->window = NULL;

	if (chan->nicks.max_alloc)
		clear_channel(chan);

	chan->mode = 0;
	chan->i_mode = 0;
	new_free(&chan->s_mode);
	chan->limit = 0;
	new_free(&chan->key); 
	chan->chop = 0;
	chan->voice = 0;
}

/*
 * add_channel: adds the named channel to the channel list.  If the channel
 * is already in the list, nothing happens.   The added channel becomes the
 * current channel as well 
 */
void 	add_channel (const char *name, int server)
{
	Channel *new_c;

	if ((new_c = find_channel(name, server)))
	{
		destroy_channel(new_c);
		malloc_strcpy(&(new_c->channel), name);
		new_c->server = server;
		new_c->window = current_window;
	}
	else
		new_c = create_channel(name, server);


	if (!is_current_channel(name, 0))
	{
		Window	*tmp = NULL;

		while (traverse_all_windows(&tmp))
		{
                        if (tmp->server != from_server)
                                continue;
                        if (!tmp->waiting_channel && !tmp->bind_channel)
                                 continue;
                        if (tmp->bind_channel && 
			    !my_stricmp(tmp->bind_channel, name) && 
			    tmp->server == from_server)
                        {
                                set_channel_by_refnum(tmp->refnum, name);
                                new_c->window = tmp;
                                update_all_windows();
                                return;
                        }
                        if (tmp->waiting_channel && 
			    !my_stricmp(tmp->waiting_channel, name) && 
			    tmp->server == from_server)
			{
				set_channel_by_refnum(tmp->refnum, name);
				new_c->window = tmp;
				new_free(&tmp->waiting_channel);
				update_all_windows();
				return;
			}
		}
		set_channel_by_refnum(0, name);
		new_c->window = current_window;
	}
	update_all_windows();
}

/*
 * remove_channel: removes the named channel from the channel list.
 * If the channel is not in the channel list, nothing happens.
 * If the channel is the current channel, the next current channel will
 *	be whatever is at channel_list when we're done.  If channel_list
 * 	is empty when we're done, you go into limbo (channel 0).
 * If you already have a pointer to a channel to be deleted, DO NOT CALL
 * 	this function.  Instead, call "destroy_channel" directly.  Do not pass
 * 	the "name" field from a valid channel through the "channel" arg!
 */
void 	remove_channel (const char *channel, int server)
{
	Channel *tmp;
	int	old_from_server = from_server;

	/* Nuke just one channel */
	if (channel)
	{
		if ((tmp = find_channel(channel, server)))
		{
			destroy_channel(tmp);
			new_free((char **)&tmp);
		}

		if (is_current_channel(channel, 1))
			switch_channels(0, NULL);
	}

	/* Nuke all the channels */
	else
	{
		while ((tmp = channel_list))
		{
			destroy_channel(tmp);
			new_free((char **)&tmp);
		}
	}

	from_server = old_from_server;
	update_all_windows();
}


/*
 *
 * Nickname maintainance
 *
 */


static Nick *	find_nick_on_channel(Channel *ch, const char *nick)
{
	int cnt, loc;
	Nick *new_n = (Nick *)find_array_item((array *)&ch->nicks,
						nick, &cnt, &loc);

	if (cnt >= 0 || !new_n)
		return NULL;

	return new_n;
}

static Nick *	find_nick (int server, const char *channel, const char *nick)
{
	Channel *ch;
	if ((ch = find_channel(channel, server)))
		return find_nick_on_channel(ch, nick);

	return NULL;
}

/*
 * check_channel_type: checks if the given channel is a normal #channel
 * or a new !channel from irc2.10.  If the latter, then it reformats it
 * a bit into a more user-friendly form.
 */
char *	check_channel_type (char *channel)
{
	if (*channel != '!' || strlen(channel) < 6)
		return channel;

	sprintf(new_channel_format, "[%.6s] %s", channel, channel + 6);
	return new_channel_format;
}


/*
 * add_to_channel: adds the given nickname to the given channel.  If the
 * nickname is already on the channel, nothing happens.  If the channel is
 * not on the channel list, nothing happens (although perhaps the channel
 * should be addded to the list?  but this should never happen) 
 */
void 	add_to_channel (const char *channel, const char *nick, int server, int oper, int voice, int ha)
{
	Nick 	*new_n, *old;
	Channel *chan;
	int	ischop = oper;
	int	isvoice = voice;
	int	half_assed = ha;

	if ((chan = find_channel(channel, server)))
	{
		/* 
		 * This is defensive just in case someone in the future
		 * decides to do the right thing...
		 */
		for (;;)
		{
			if (*nick == '+')
			{
				nick++;
				if (is_me(server, nick))
					chan->voice = 1;
				isvoice = 1;
			}
			else if (*nick == '@')
			{
				nick++;
				if (is_me(server, nick))
				{
					check_mode_list_join(channel);
					chan->chop = 1;
				}
				else 
				{
					if (isvoice == 0)
						isvoice = -1;
					if (half_assed == 0)
						half_assed = -1;
				}
				ischop = 1;
			}
			else if (*nick == '%')
			{
				nick++;
				if (is_me(server, nick))
					chan->half_assed = 1;
				else
				{
					if (isvoice == 0)
						isvoice = -1;
				}
				half_assed = 1;
			}
			else
				break;
		}

		new_n = (Nick *)new_malloc(sizeof(Nick));
		new_n->nick = m_strdup(nick);
		new_n->userhost = NULL;
		new_n->chanop = ischop;
		new_n->voice = isvoice;
		new_n->half_assed = half_assed;

		if ((old = (Nick *)add_to_array((array *)&chan->nicks, (array_item *)new_n)))
		{
			new_free(&old->nick);
			new_free(&old->userhost);
		}
	}
}

void 	add_userhost_to_channel (const char *channel, const char *nick, int server, const char *uh)
{
	Nick *new_n;

	if ((new_n = find_nick(server, channel, nick)))
		malloc_strcpy(&new_n->userhost, uh);
}


/*
 * remove_from_channel: removes the given nickname from the given channel. If
 * the nickname is not on the channel or the channel doesn't exist, nothing
 * happens. 
 */
void 	remove_from_channel (const char *channel, const char *nick, int server)
{
	Channel *chan;
	Nick	*tmp;

	for (chan = channel_list; chan; chan = chan->next)
	{
		if (chan->server != server)
			continue;

		/* This is correct, dont change it! */
		if (channel && my_stricmp(channel, chan->channel))
			continue;

		if ((tmp = (Nick *)remove_from_array((array *)&chan->nicks, nick)))
		{
			new_free(&tmp->nick);
			new_free(&tmp->userhost); /* Da5id reported mf here */
			new_free((char **)&tmp);
		}
	}
}

/*
 * rename_nick: in response to a changed nickname, this looks up the given
 * nickname on all you channels and changes it the new_nick.  Now it also
 * restores the userhost (was lost before [oops!])
 */
void 	rename_nick (const char *old_nick, const char *new_nick, int server)
{
	Channel *chan = NULL;
	Nick	*tmp;

	while (traverse_all_channels(&chan, server))
	{
		if ((tmp = (Nick *)remove_from_array((array *)&chan->nicks, old_nick)))
		{
			malloc_strcpy(&tmp->nick, new_nick);
			add_to_array((array *)&chan->nicks, 
				     (array_item *)tmp);
		}
	}
}

int 	im_on_channel (const char *channel, int refnum)
{
	return (find_channel(channel, refnum) ? 1 : 0);
}

int 	is_on_channel (const char *channel, const char *nick)
{
	if (find_nick(from_server, channel, nick))
		return 1;
	else
		return 0;
}

int 	is_chanop (const char *channel, const char *nick)
{
	Nick *n;

	if ((n = find_nick(from_server, channel, nick)))
		return n->chanop;
	else
		return 0;
}

int	is_chanvoice (const char *channel, const char *nick)
{
	Nick *n;

	if ((n = find_nick(from_server, channel, nick)))
		return n->voice;
	else
		return 0;
}

int	number_on_channel (const char *name, int server)
{
	Channel *channel = find_channel(name, server);

	if (channel)
		return channel->nicks.max;
	else
		return 0;
}

char	*create_nick_list (const char *name, int server)
{
	Channel *channel = find_channel(name, server);
	char 	*str = NULL;
	int 	i;

	if (channel)
		for (i = 0; i < channel->nicks.max; i++)
			m_s3cat(&str, space, channel->nicks.list[i]->nick);

	return str;
}

char	*create_chops_list (const char *name, int server)
{
	Channel *channel = find_channel(name, server);
	char 	*str = NULL;
	int 	i;

	if (channel)
		for (i = 0; i < channel->nicks.max; i++)
			if (channel->nicks.list[i]->chanop)
				m_s3cat(&str, space, channel->nicks.list[i]->nick);

	if (!str)
		return m_strdup(empty_string);
	return str;
}

char	*create_nochops_list (const char *name, int server)
{
	Channel *channel = find_channel(name, server);
	char 	*str = NULL;
	int 	i;

	if (channel)
		for (i = 0; i < channel->nicks.max; i++)
			if (!channel->nicks.list[i]->chanop)
				m_s3cat(&str, space, channel->nicks.list[i]->nick);

	if (!str)
		return m_strdup(empty_string);
	return str;
}

/*
 *
 * Channel mode maintainance
 *
 */

/*
 * get_cmode: Get the current mode string for the specified server.
 * We only do a refresh if the mode has changed since we generated it.
 */
static char *	get_cmode (Channel *chan)
{
	int	mode_pos = 0,
		str_pos = 0;
	char	local_buffer[BIG_BUFFER_SIZE];

	/* Used the cache value if its still good */
	if ((chan->mode == chan->i_mode) && chan->s_mode)
		return chan->s_mode;

	chan->i_mode = chan->mode;

	local_buffer[0] = 0;
	while (chan->mode >= (1 << mode_pos))
	{
		if (chan->mode & (1 << mode_pos))
			local_buffer[str_pos++] = mode_str[mode_pos];
		mode_pos++;
	}
	local_buffer[str_pos] = 0;

	if (chan->key)
	{
		strcat(local_buffer, " ");
		strcat(local_buffer, chan->key);
	}
	if (chan->limit)
	{
		strcat(local_buffer, " ");
		strcat(local_buffer, ltoa(chan->limit));
	}

	malloc_strcpy(&chan->s_mode, local_buffer);
	return (chan->s_mode);
}

/*
 * decifer_mode: This will figure out the mode string as returned by mode
 * commands and convert that mode string into a one byte bit map of modes 
 */
static void	decifer_mode (const char *modes, Channel *chan)
{
	char		*limit = 0;
	const char	*person;
	int		add = 0;
	char		*rest;
	Nick		*nick;
	int		value = 0;
	char		*mode_str;

	/* Make a copy of it.*/
	mode_str = alloca(strlen(modes) + 1);
	strcpy(mode_str, modes);

	/* Punt if its not all there */
	if (!(mode_str = next_arg(mode_str, &rest)))
		return;

	for (; *mode_str; mode_str++)
	{
		switch (*mode_str)
		{
			case '+':
				add = 1;
				continue;
			case '-':
				add = 0;
				continue;

			case 'i':
				value = MODE_INVITE;
				break;
			case 'm':
				value = MODE_MODERATED;
				break;
			case 'n':
				value = MODE_MSGS;
				break;
			case 'p':
				value = MODE_PRIVATE;
				break;
			case 'r':
				value = MODE_REGISTERED;
				break;
			case 'R':
				value = MODE_RESTRICTED;
				break;
			case 's':
				value = MODE_SECRET;
				break;
			case 't':
				value = MODE_TOPIC;
				break;
			case 'z':		/* Erf/TS4 "zapped" */
				value = MODE_Z;
				break;
			case 'k':
			{
				char *key = next_arg(rest, &rest);

				value = MODE_KEY;

				if (add)
					malloc_strcpy(&chan->key, key);
				else
					new_free(&chan->key);

				chan->i_mode = -1;	/* Revoke old cache */
				break;	
			}
			case 'l':
			{
				value = MODE_LIMIT;
				if (add)
					limit = next_arg(rest, &rest);
				else
					limit = zero;

				chan->limit = my_atol(limit);
				chan->i_mode = -1;	/* Revoke old cache */
				continue;
			}

			case 'o':
			{
				person = next_arg(rest, &rest);
				/* 
				 * Borked av2.9 sends a +o to the channel
				 * when you create it, but doesnt bother to
				 * send your nickname, too. blah.
				 */
				if (!person)
				    person = get_server_nickname(from_server);

				if (is_me(from_server, person))
					chan->chop = add;
				if ((nick = find_nick_on_channel(chan, person)))
					nick->chanop = add;
				continue;
			}
			case 'v':
			{
				person = next_arg(rest, &rest);
				if (is_me(from_server, person))
					chan->voice = add;
				if ((nick = find_nick_on_channel(chan, person)))
					nick->voice = add;
				continue;
			}
			case 'h': /* erfnet's borked 'half-assed oper' mode */
			{
				person = next_arg(rest, &rest);
				if (is_me(from_server, person))
					chan->half_assed = add;
				if ((nick = find_nick_on_channel(chan, person)))
					nick->half_assed = add;
				continue;
			}
			case 'b':
			case 'e':	/* borked erfnet ban exceptions */
			{
				next_arg(rest, &rest);
				continue;
			}
		}

		if (add)
			chan->mode |= value;
		else
			chan->mode &= ~value;
	}

	if (!chan->limit)
		chan->mode &= ~MODE_LIMIT;
	else
		chan->mode |= MODE_LIMIT;
}

void 	update_channel_mode (const char *channel, const char *mode)
{
	Channel *tmp = find_channel(channel, from_server);

	if (tmp)
		decifer_mode(mode, tmp);
}

const char 	*get_channel_key (const char *channel, int server)
{
	Channel *tmp = find_channel(channel, server);

	if (tmp && tmp->key)
		return tmp->key;
	else
		return empty_string;
}


char	*get_channel_mode (const char *channel, int server)
{
	Channel *tmp = find_channel(channel, server);

	if (tmp)
		return get_cmode(tmp);
	else
		return (empty_string);
}


/*
 * is_channel_mode: returns the logical AND of the given mode with the
 * channels mode.  Useful for testing a channels mode 
 */
int 	is_channel_private (const char *channel, int server_index)
{
	Channel *tmp = find_channel(channel, server_index);

	if (tmp)
		return (tmp->mode & (MODE_PRIVATE | MODE_SECRET));
	else
		return 0;
}

int	is_channel_nomsgs (const char *channel, int server_index)
{
	Channel *tmp = find_channel(channel, server_index);

	if (tmp)
		return (tmp->mode & (MODE_MSGS));
	else
		return 0;
}



/*
 * 
 * ----- misc stuff -----
 *
 */
static void 	show_channel (Channel *chan)
{
	NickList 	*tmp = &chan->nicks;
	char		local_buf[BIG_BUFFER_SIZE * 10 + 1];
	char		*ptr;
	int		nick_len;
	int		len;
	int		i;

	ptr = local_buf;
	*ptr = 0;
	nick_len = 0;

	for (i = 0; i < tmp->max; i++)
	{
		strcpy(ptr, tmp->list[i]->nick);
		if (tmp->list[i]->userhost)
		{
			strcat(ptr, "!");
			strcat(ptr, tmp->list[i]->userhost);
		}
		strcat(ptr, space);

		len = strlen(ptr);
		nick_len += len;
		ptr += len;
	}

	say("\t%s +%s (%s) (Win: %d): %s", 
		chan->channel, 
		get_cmode(chan),
		get_server_name(chan->server), 
		chan->window ? chan->window->refnum : -1,
		local_buf);
}

char	*scan_channel (char *cname)
{
	Channel 	*wc = find_channel(cname, from_server);
	NickList 	*nicks;
	char		buffer[NICKNAME_LEN + 5];
	char		*retval = NULL;
	int		i;

	if (!wc)
		return m_strdup(empty_string);

	nicks = &wc->nicks;
	for (i = 0; i < nicks->max; i++)
	{
		if (nicks->list[i]->chanop)
			buffer[0] = '@';
		else
			buffer[0] = '.';

		if (nicks->list[i]->voice == 1)
			buffer[1] = '+';
		else if (nicks->list[i]->voice == -1)
			buffer[1] = '?';
		else
			buffer[1] = '.';

		strcpy(buffer + 2, nicks->list[i]->nick);
		m_s3cat(&retval, space, buffer);
	}

	return retval;
}


/* list_channels: displays your current channel and your channel list */
void 	list_channels (void)
{
	Channel *tmp = NULL;

	if (!channel_list)
	{
		say("You are not on any channels");
		return;
	}

	if (get_channel_by_refnum(0))
		say("Current channel %s", get_channel_by_refnum(0));
	else
		say("No current channel for this window");

	say("You are on the following channels:");
	while (traverse_all_channels(&tmp, from_server))
		show_channel(tmp);

	if (connected_to_server != 1)
	{
		say("Other servers:");
		tmp = NULL;
		while (traverse_all_channels(&tmp, (-from_server) - 1))
			show_channel(tmp);
	}
}


/* This is a keybinding */
void 	switch_channels (char dumb, char *dumber)
{
	Channel *tmp = NULL;
	Channel *start = NULL;
	char	*nc = get_channel_by_refnum(0);

	if (!channel_list)
		return;		/* Dont bother */

	if (nc)
		if ((start = find_channel(nc, from_server)))
			tmp = start->next;

	if (!tmp)
		tmp = channel_list;

	/*
	 * This attempts to make an entire pass through the channel_list
	 * starting at the "previous" channel.
	 */
	while (tmp != start)
	{
		if (tmp->server != from_server || 
			is_current_channel(tmp->channel, 0))
		{
			tmp = tmp->next;
			if (!tmp && start)
				tmp = channel_list;
			continue;
		}

		set_channel_by_refnum(0, tmp->channel);
		update_all_windows();
		break;
	}
}

void 	change_server_channels (int old_s, int new_s)
{
	Channel *tmp = NULL;

	while (traverse_all_channels(&tmp, old_s))
		tmp->server = new_s;
}

void 	destroy_server_channels (int server)
{
	Channel	*tmp = NULL;

	while (traverse_all_channels(&tmp, server))
	{
		destroy_channel(tmp);
		new_free((char **)&tmp);
	}

	/* Try to figure out what channel to use... >;-) */
	if (!dead)
		switch_channels(0, 0);
}


/*
 * reconnect_all_channels: used after you get disconnected from a server, 
 * clear each channel nickname list and re-JOINs each channel in the 
 * channel_list ..  
 */
void 	reconnect_all_channels (void)
{
	Channel *tmp = NULL;
	char	*s;
	char 	*t;
	char	*channels = NULL;
	char	*keys = NULL;

	while (traverse_all_channels(&tmp, from_server))
	{
		if ((s = get_cmode(tmp)))
			add_to_mode_list(tmp->channel, s);

		if (tmp->window)
		{
			t = tmp->window->current_channel;
			if (t && *t && !is_bound(t, tmp->window->server))
				malloc_strcpy(&tmp->window->waiting_channel, t);
		}

		m_s3cat(&channels, ",", tmp->channel);
		/* Ugh.  Hybrid made me do it. */
		m_s3cat(&keys, ",", tmp->key ? tmp->key : empty_string);

		clear_channel(tmp);
	}

	/*
	 * Interestingly enough, black magic on the server's part makes
	 * this work.  I sure hope they dont "break" this in the future...
	 */
	if (channels)
		send_to_server("JOIN %s %s", channels, 
						keys ? keys : empty_string);

	new_free(&channels);
	new_free(&keys);
	destroy_server_channels(from_server);
}


const char 	*what_channel (const char *nick)
{
	Channel *tmp = NULL;

	if (current_window->current_channel &&
	    is_on_channel(current_window->current_channel, nick))
		return current_window->current_channel;

	while (traverse_all_channels(&tmp, from_server))
	{
		if (find_nick_on_channel(tmp, nick))
			return tmp->channel;
	}

	return NULL;
}

const char 	*walk_channels (int init, const char *nick)
{
	static	Channel *tmp = (Channel *) 0;

	if (init)
		tmp = NULL;

	while (traverse_all_channels(&tmp, from_server))
	{
		if (find_nick_on_channel(tmp, nick))
			return (tmp->channel);
	}

	return NULL;
}

const char *	fetch_userhost (int server, const char *nick)
{
	Channel *tmp = NULL;
	Nick *user = NULL;

	while (traverse_all_channels(&tmp, server))
	{
		if ((user = find_nick_on_channel(tmp, nick)))
			return user->userhost;
	}

	return NULL;
}

int 	get_channel_oper (const char *channel, int server)
{
	Channel *chan;

	if ((chan = find_channel(channel, server)))
		return chan->chop;
	else
		return 0;
}

int 	get_channel_voice (const char *channel, int server)
{
	Channel *chan;

	if ((chan = find_channel(channel, server)))
		return chan->voice;
	else
		return 0;
}

Window *	get_channel_window (const char *channel, int server)
{
	Channel *tmp = find_channel(channel, server);

	if (tmp)
		return tmp->window;

	return NULL;
}

/*
 * When you are about to swap out a window, this re-assigns all of the 
 * channels bound to that window to the newly visible window.  This was
 * supplied by Colten.
 */
void 	swap_channel_win_ptr (Window *v_window, Window *window)
{
	Channel *chan = NULL;

	while (traverse_all_channels(&chan, v_window->server))
	{
		if (chan->window == v_window)
			chan->window = window;
		else if (chan->window == window)
			chan->window = v_window;
	}

	if (window->server == v_window->server)
		return;

	chan = NULL;
	while (traverse_all_channels(&chan, v_window->server))
	{
		if (chan->window == window)
			chan->window = v_window;
	}
}


void 	set_channel_window (Window *window, const char *channel)
{
	Channel *tmp = find_channel(channel, window->server);

	if (tmp)
		tmp->window = window;
}

/*
 * This finds the channel that is the current channel of the specified
 * window, and sets the channel to some new window that wants to steal it.
 */
void	unset_window_current_channel (Window *old_w, Window *new_w)
{
	Channel *tmp = NULL;

	while (traverse_all_channels(&tmp, old_w->server))
	{
		if (tmp->window == old_w && !my_stricmp(tmp->channel, old_w->current_channel))
		{
			new_free(&old_w->current_channel);
			tmp->window = new_w;
			return;
		}
	}
}

/*
 * This attempts to find another current channel for a window when the 
 * current channel for a window is 'stolen' by another window.
 */
void	reset_window_current_channel (Window *w)
{
	Channel *tmp = NULL;

	while (traverse_all_channels(&tmp, w->server))
	{
		if (tmp->window == w)
		{
			malloc_strcpy(&w->current_channel, tmp->channel);
			return;
		}
	}
}

/*
 * For any given window, re-assign all of the channels that are connected
 * to that window.
 */
void	move_window_channels (Window *window)
{
	Channel *tmp = NULL;
	Window *w = NULL;

	while (traverse_all_channels(&tmp, window->server))
	{
		if (tmp->window != window)
			continue;

		tmp->window = NULL;
		while (traverse_all_windows(&w))
		{
			if (w->server == window->server && w != window)
			{
				tmp->window = w;
				break;
			}
		}
	}
}

char *	create_channel_list (int server)
{
	Channel	*tmp = NULL;
	char	local_buffer[BIG_BUFFER_SIZE + 1];

	*local_buffer = 0;
	while (traverse_all_channels(&tmp, server))
	{
		strmcat(local_buffer, tmp->channel, BIG_BUFFER_SIZE);
		strmcat(local_buffer, " ", BIG_BUFFER_SIZE);
	}

	return m_strdup(local_buffer);
}

void 	channel_server_delete (int i)
{
	Channel	*tmp = NULL;

	while (traverse_all_channels(&tmp, i))
		if (tmp->server >= i)
			tmp->server--;
}

void 	set_waiting_channel (int i)
{
	Window	*tmp = NULL;

	while (traverse_all_windows(&tmp))
	{
		if (tmp->server != i)
			continue;

		if (!tmp->current_channel || !tmp->bind_channel)
			continue;

		tmp->waiting_channel = tmp->current_channel;
		tmp->current_channel = (char *) 0;
	}
}

/*
 * When you join a channel, we have to ask the server for the current mode
 * on the channel.  These functions here handle that.
 */
static void 	add_to_mode_list (const char *channel, const char *mode)
{
	struct modelist	*mptr;

	if (!channel || !*channel || !mode || !*mode)
		return;

	mptr = (struct modelist *) new_malloc(sizeof(struct modelist));
	mptr->chan = (char *) 0;
	mptr->mode = (char *) 0;
	mptr->next = mode_list;
	mode_list = mptr;
	malloc_strcpy(&mptr->chan, channel);
	malloc_strcpy(&mptr->mode, mode);
}

static void 	check_mode_list_join (const char *channel)
{
	struct modelist *mptr = mode_list;

	for (; mptr; mptr = mptr->next)
	{
		if (!my_stricmp(mptr->chan, channel))
		{
			send_to_server("MODE %s %s", mptr->chan, mptr->mode);
			return;
		}
	}
	remove_from_mode_list(channel);
}

void 	remove_from_mode_list (const char *channel)
{
	struct modelist *curr, *prev = (struct modelist *) 0, *next;

	for (next = mode_list; next; )
	{
		curr = next;
		next = curr->next;
		if (!my_stricmp(curr->chan, channel))
		{
			if (curr == mode_list)
				mode_list = curr->next;
			else
				prev->next = curr->next;
			prev = curr;
			new_free(&curr->chan);
			new_free(&curr->mode);
			new_free((char **)&curr);
		}
		else
			prev = curr;
	}
}


/* 
 * Im sure this doesnt belong here, but im not sure where it does belong.
 */
int 	auto_rejoin_callback (void *d)
{
	char *data     = (char *) d;
	char *channel  = next_arg(data, &data);
	int server     = my_atol(next_arg(data, &data));
	Window *window = get_window_by_refnum(my_atol(next_arg(data, &data)));
	char *key      = next_arg(data, &data);
	int old_from_server = from_server;

	from_server = server;
	if (key && *key)
		send_to_server("JOIN %s %s", channel, key);
	else
		send_to_server("JOIN %s", channel);
	from_server = old_from_server;

	if (window)
		malloc_strcpy(&window->waiting_channel, channel);
	new_free((char **)&d);

	return 0;
}

