/*
 * names.c: This here is used to maintain a list of all the people currently
 * on your channel.  Seems to work 
 *
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */
#include <stdio.h>
#include "irc.h"
#include "ircaux.h"
#include "names.h"
#include "window.h"
#include "screen.h"
#include "server.h"
#include "lastlog.h"
#include "list.h"
#include "output.h"

static	char	mode_str[] = "pltainsmkv";

/* NickList is declared in window.h */

/* ChannelList - moved to names.h */

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

/* clear_channel: erases all entries in a nick list for the given channel */
static	void	clear_channel(chan)
ChannelList *chan;
{
	NickList *tmp,
		*next;

	for (tmp = chan->nicks; tmp; tmp = next)
	{
		next = tmp->next;
		new_free(&(tmp->nick));
		new_free(&tmp);
	}
	chan->nicks = null(NickList *);
}

extern ChannelList *lookup_channel(channel, server, unlink)
char	*channel;
int	server;
int	unlink;
{
	ChannelList	*chan,
			*last;

	chan = channel_list;
	last = null(ChannelList *);
	if (server == -1)
		server = primary_server;
	while(chan)
	{
		if((chan->server == server) && !stricmp(chan->channel, channel))
		{
			if (unlink)
			{
				if (last)
					last->next = chan->next;
				else
					channel_list = chan->next;
			}
			break;
		}
		last = chan;
		chan = chan->next;
	}
	return(chan);
}

/*
 * 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(channel, server)
char	*channel;
int	server;
{
	ChannelList *new;

	if (new = lookup_channel(channel, server, 0))
	{
		new->mode = 0;
		new->limit = 0;
		new->chop = 0;
		new->server = server;
		new->window = curr_scr_win;
		malloc_strcpy(&(new->channel), channel);
		clear_channel(new);
	}
	else
	{
		new = (ChannelList *) new_malloc(sizeof(ChannelList));
		new->channel = null(char *);
		new->mode = 0;
		new->limit = 0;
		new->chop = 0;
		new->server = server;
		new->window = curr_scr_win;
		malloc_strcpy(&(new->channel), channel);
		new->nicks = null(NickList *);
		add_to_list(&channel_list, new);
	}
	if (!is_current_channel(channel, 0))
	{
		int	flag = 1;
		Window	*tmp;

		while (tmp = traverse_all_windows(&flag))
		{
			if (!tmp->waiting_channel)
				continue;
			if (!stricmp(tmp->waiting_channel, channel))
			{	
				set_channel_by_refnum(tmp->refnum, channel);
				new->window = tmp;
				new_free(&tmp->waiting_channel);
				update_all_windows();
				return;
			}
		}
		set_channel_by_refnum(0, channel);
		new->window = curr_scr_win;
	}
	update_all_windows();
}

/*
 * 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(channel, nick, server)
char	*channel;
char	*nick;
int	server;
{
	NickList *new;
	ChannelList *chan;
	int	ischop = 0;

	if (chan = lookup_channel(channel, server, 0))
	{
		if (*nick == '@')
		{
			nick++;
			if (stricmp(nick, get_server_nickname(server)) == 0)
				chan->chop = 1;
			ischop = 1;
		}

		if (new = (NickList *) remove_from_list(&(chan->nicks), nick))
		{
			new_free(&(new->nick));
			new_free(&new);
		}
		new = (NickList *) new_malloc(sizeof(NickList));
		new->nick = null(char *);
		new->chanop = ischop;
		malloc_strcpy(&(new->nick), nick);
		add_to_list(&(chan->nicks), new);
	}
}


/*
 * recreate_mode: converts the bitmap representation of a channels mode into
 * a string 
 */
static	char	*recreate_mode(chan)
ChannelList *chan;
{
	int	mode_pos = 0,
		str_pos = 0,
		mode;
	static	char	str[20];

	mode = chan->mode;
	while (mode)
	{
		if (mode % 2)
			str[str_pos++] = mode_str[mode_pos];
		mode /= 2;
		mode_pos++;
	}
	str[str_pos] = null(char);
	if (chan->limit)
	{
		sprintf(buffer, "%d", chan->limit);
		strcat(str, " ");
		strcat(str, buffer);
	}
	return (str);
}

/*
 * 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	int	decifer_mode(mode_str, mode, chop, nicks)
char	*mode_str;
unsigned char	*mode;
unsigned char	*chop;
NickList **nicks;
{
	char	*limit = 0;
	char	*person;
	int	add = 0;
	int	limit_set = 0;
	int	limit_reset = 0;
	char	*rest;
	NickList *ThisNick;
	unsigned char	value = 0;

	if (!(mode_str = next_arg(mode_str, &rest)))
		return -1;
	for (; *mode_str; mode_str++)
	{
		switch (*mode_str)
		{
		case '+':
			add = 1;
			value = 0;
			break;
		case '-':
			add = 0;
			value = 0;
			break;
		case 'p':
			value = MODE_PRIVATE;
			break;
		case 'l':
			value = MODE_LIMIT;
			if (add)
			{
				limit_set = 1;
				if (!(limit = next_arg(rest, &rest)))
					limit = empty_string;
			}
			else
				limit_reset = 1;
			break;
		case 't':
			value = MODE_TOPIC;
			break;
		case 'a':
			value = MODE_ANONYMOUS;
			break;
		case 'i':
			value = MODE_INVITE;
			break;
		case 'n':
			value = MODE_MSGS;
			break;
		case 's':
			value = MODE_SECRET;
			break;
		case 'm':
			value = MODE_MODERATED;
			break;
		case 'o':
			if ((person = next_arg(rest, &rest)) &&
			    !stricmp(person, get_server_nickname(from_server)))
				*chop = add;
			ThisNick = (NickList *) list_lookup(nicks, person,
					!USE_WILDCARDS, !REMOVE_FROM_LIST);
			if (ThisNick)
				ThisNick->chanop=add;
			break;
		}
		if (add)
			*mode |= value;
		else
			*mode &= ~value;
	}
	if (limit_set)
		return (atoi(limit));
	else if (limit_reset)
		return(0);
	else
		return(-1);
}

/* get_channel_mode: returns the current mode string for the given channel */
char	*get_channel_mode(channel, server)
char	*channel;
int	server;
{
	ChannelList *tmp;

	if (tmp = lookup_channel(channel, server, 0))
		return (recreate_mode(tmp));
	return (empty_string);
}

/*
 * set_channel_mode: This will set the mode of the given channel.  It will
 * zap any existing mode and replace it with the mode given 
 */
void	set_channel_mode(channel, mode)
char	*channel,
*mode;
{
	ChannelList *tmp;
	int	limit;

	if (tmp = lookup_channel(channel, from_server, 0))
	{
		tmp->mode = 0;
		if ((limit = decifer_mode(mode, &(tmp->mode), &(tmp->chop),
		    &(tmp->nicks))) != -1)
			tmp->limit = limit;
	}
}

/*
 * update_channel_mode: This will modify the mode for the given channel
 * according the the new mode given.  
 */
void	update_channel_mode(channel, mode)
char	*channel,
*mode;
{
	ChannelList *tmp;
	int	limit;

	if (tmp = lookup_channel(channel, from_server, 0))
	{
		if ((limit = decifer_mode(mode, &(tmp->mode), &(tmp->chop),
				&(tmp->nicks))) != -1)
			tmp->limit = limit;
	}
}

/*
 * is_channel_mode: returns the logical AND of the given mode with the
 * channels mode.  Useful for testing a channels mode 
 */
int	is_channel_mode(channel, mode)
char	*channel;
int	mode;
{
	ChannelList *tmp;

	if (tmp = lookup_channel(channel, from_server, 0))
		return (tmp->mode & mode);
	return (0);
}

/*
 * remove_channel: removes the named channel from the channel_list.  If the
 * channel is not on the channel_list, nothing happens.  If the channel was
 * the current channel, this will select the top of the channel_list to be
 * the current_channel, or 0 if the list is empty. 
 */
void	remove_channel(channel, server)
char	*channel;
int	server;
{
	ChannelList *tmp;

	if (channel)
	{
		if (tmp = lookup_channel(channel, server, 1))
		{
			clear_channel(tmp);
			new_free(&(tmp->channel));
			new_free(&tmp);
		}
		if (is_current_channel(channel, 1))
			switch_channels();
	}
	else
	{
		ChannelList *next;

		for (tmp = channel_list; tmp; tmp = next)
		{
			next = tmp->next;
			clear_channel(tmp);
			new_free(&(tmp->channel));
			new_free(&tmp);
		}
		channel_list = null(ChannelList *);
	}
	update_all_windows();
}

/*
 * 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(channel, nick, server)
char	*channel;
char	*nick;
int	server;
{
	ChannelList *chan;
	NickList *tmp;

	if (channel)
	{
		if (chan = lookup_channel(channel, server, 0))
		{
			if (tmp = (NickList *) list_lookup(&(chan->nicks),
				nick, !USE_WILDCARDS, REMOVE_FROM_LIST))
			{
				new_free(&(tmp->nick));
				new_free(&tmp);
			}
		}
	}
	else
	{
		for (chan = channel_list; chan; chan = chan->next)
		{
			if (chan->server == server)
			{
				if (tmp = (NickList *)
				    list_lookup(&(chan->nicks),
				    nick, !USE_WILDCARDS, REMOVE_FROM_LIST))
				{
					new_free(&(tmp->nick));
					new_free(&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 
 */
void	rename_nick(old_nick, new_nick, server)
char	*old_nick,
	*new_nick;
int	server;
{
	ChannelList *chan;
	NickList *tmp;

	for (chan = channel_list; chan; chan = chan->next)
	{
		if (chan->server == server)
		{
			if (tmp = (NickList *) list_lookup(&(chan->nicks),
				old_nick, !USE_WILDCARDS, REMOVE_FROM_LIST))
			{
				new_free(&(tmp->nick));
				new_free(&tmp);
				add_to_channel(chan->channel, new_nick, server);
			}
		}
	}
}

/*
 * is_on_channel: returns true if the given nickname is in the given channel,
 * false otherwise.  Also returns false if the given channel is not on the
 * channel list. 
 */
int	is_on_channel(channel, nick)
char	*channel;
char	*nick;
{
	ChannelList *chan;

	if (chan = lookup_channel(channel, from_server, 0))
	{
		if (list_lookup(&(chan->nicks), nick, !USE_WILDCARDS,
				!REMOVE_FROM_LIST))
			return (1);
	}
	return (0);
}

int	is_chanop(channel, nick)
char	*channel;
char	*nick;
{
	ChannelList *chan;
	NickList *Nick;

	if ((chan = lookup_channel(channel, from_server, 0)) &&
			(Nick = (NickList *) list_lookup(&(chan->nicks),
			nick, !USE_WILDCARDS, !REMOVE_FROM_LIST)) &&
			Nick->chanop)
		return (1);
	return (0);
}

void	show_channel(chan)
ChannelList *chan;
{
	NickList *tmp;
	int	buffer_len,
		len;

	*buffer = null(char);
	buffer_len = 0;
	for (tmp = chan->nicks; tmp; tmp = tmp->next)
	{
		len = strlen(tmp->nick);
		if (buffer_len + len >= (BIG_BUFFER_SIZE / 2))
		{
			say("\t%s +%s (%s): %s", chan->channel,
				recreate_mode(chan),
				get_server_name(chan->server), buffer);
			*buffer = null(char);
			buffer_len = 0;
		}
		strmcat(buffer, tmp->nick, BIG_BUFFER_SIZE);
		strmcat(buffer, " ", BIG_BUFFER_SIZE);
		buffer_len += len + 1;
	}
	say("\t%s +%s (%s): %s", chan->channel, recreate_mode(chan),
		get_server_name(chan->server), buffer);
}

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

	if (channel_list)
	{
		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:");
		for (tmp = channel_list; tmp; tmp = tmp->next)
		{
			if (tmp->server == from_server)
				show_channel(tmp);
		}
		if (connected_to_server != 1)
		{
			say("Other servers:");
			for (tmp = channel_list; tmp; tmp = tmp->next)
			{
				if (tmp->server != from_server)
					show_channel(tmp);
			}
		}
	}
	else
		say("You are not on any channels");
}

void	switch_channels()
{
	ChannelList *tmp;

	if (channel_list)
	{
		if (get_channel_by_refnum(0))
		{
			if (tmp = lookup_channel(get_channel_by_refnum(0),
				from_server, 0))
			{
				for (tmp = tmp->next; tmp; tmp = tmp->next)
				{
					if ((tmp->server == from_server) &&
					    !is_current_channel(tmp->channel,
					    0))
					{
						set_channel_by_refnum(0,
							tmp->channel);
						update_all_windows();
						return;
					}
				}
			}
		}
		for (tmp = channel_list; tmp; tmp = tmp->next)
		{
			if ((tmp->server == from_server) &&
			    (!is_current_channel(tmp->channel, 0)))
			{
				set_channel_by_refnum(0, tmp->channel);
				update_all_windows();
				return;
			}
		}
	}
}

/* real_channel: returns your "real" channel (your non-multiple channel) */
char	*real_channel()
{
	ChannelList *tmp;

	if (channel_list)
	{
		for (tmp = channel_list; tmp; tmp = tmp->next)
		{
			if ((tmp->server == from_server) &&
					(*(tmp->channel) != '#'))
				return (tmp->channel);
		}
	}
	return (null(char *));
}

int	get_channel_server(channel)
char	*channel;
{
	ChannelList *chan;

	if (chan = (ChannelList *) list_lookup(&channel_list, channel,
			!USE_WILDCARDS, !REMOVE_FROM_LIST))
		return(chan->server);
	return(-1);
}

void	change_server_channels(old, new)
int	old,
	new;
{
	ChannelList *tmp;

	for(tmp = channel_list; tmp ;tmp = tmp->next)
	{
		if (tmp->server == old)
			tmp->server = new;
	}
}

void	clear_channel_list(server)
int	server;
{
	ChannelList *tmp;

	tmp = channel_list;
	while(tmp)
	{
		if (tmp->server == server)
		{
			remove_channel(tmp->channel, server);
			tmp = channel_list;
		}
		else
			tmp = tmp->next;
	}
}

/*
 * 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()
{
	ChannelList *tmp;
	int	version;
	char	*command;

	version = (get_server_version(from_server) > Server2_5) ? 1 : 0;
	command = version ? "JOIN " : "CHANNEL ";
	for (tmp = channel_list; tmp; tmp = tmp->next)
	{
		if (tmp->server == from_server)
		{
			malloc_strcpy(&tmp->window->waiting_channel,
					tmp->window->current_channel);
#ifdef PHONE
			yell("window -%d- gets channel -%s-",
				tmp->window->refnum,
				tmp->window->current_channel);
#endif
			clear_channel(tmp);
			send_to_server("%s %s%s%s", command, tmp->channel,
				version ? " " : "",
				version ? recreate_mode(tmp) : "");
		}
	}
	clear_channel_list(from_server);
	message_from(null(char *), LOG_CRAP);
}

char	*what_channel(nick)
char	*nick;
{
	ChannelList *tmp;

	if (curr_scr_win->current_channel &&
	    is_on_channel(curr_scr_win->current_channel, nick))
		return curr_scr_win->current_channel;
	for (tmp = channel_list; tmp; tmp = tmp->next)
	{
		if ((tmp->server == from_server) && (list_lookup(&(tmp->nicks),
		    nick, !USE_WILDCARDS, !REMOVE_FROM_LIST)))
			return tmp->channel;
	}
	return NULL;
}

char	*walk_channels(nick, init)
int	init;
char	*nick;
{
	static	ChannelList *tmp = null(ChannelList *);

	if (init)
		tmp = channel_list;
	else if (tmp)
		tmp = tmp->next;
	for (;tmp ; tmp = tmp->next)
		if ((tmp->server == from_server) && (list_lookup(&(tmp->nicks),
				nick, !USE_WILDCARDS, !REMOVE_FROM_LIST)))
			return (tmp->channel);
	return null(char *);
}

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

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

extern	void	set_channel_window(window, channel)
Window	*window;
char	*channel;
{
	ChannelList	*tmp;

	if (!channel)
		return;
	for (tmp = channel_list; tmp; tmp = tmp->next)
		if (!stricmp(channel, tmp->channel) &&
				from_server == tmp->server)
		{
			tmp->window = window;
			return;
		}
}

extern	char	*create_channel_list(window)
Window	*window;
{
	ChannelList	*tmp;
	char	*value = null(char *);

	*buffer = '\0';
	for (tmp = channel_list; tmp; tmp = tmp->next)
		if (tmp->server == window->server)
		{
			strcat(buffer, tmp->channel);
			strcat(buffer, " ");
		}
	malloc_strcpy(&value, buffer);

	return value;
}

extern	void	channel_server_delete(i)
int	i;
{
	ChannelList	*tmp;

	for (tmp = channel_list ; tmp; tmp = tmp->next)
		if (tmp->server >= i)
			tmp->server--;
}
