/* 
 * s t u f f  r e l a t e d  t o  c h a n n e l  u s e r s
 * 
 */

/* user.c       -functions related to channel users..
 * 
 * Author: Tano Fotang, 1997
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file src/COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 */

#include <forms.h>
#include <errno.h>
#include "aux.h"
#include "nicks.h"
#include "sula.h"
#include "chanwin.h"
#include "global.h"
#include "script.h"
#include "parser.h"
#include "mode.h"
#include "user.h"
#include "servers.h"
#include "setting.h"
#include "ignore.h"

extern Winstruct *Win;/* parser window */
extern FD_serverlist *server_tool;
/* 
* J O I N
* 
*/
static void addto_go(Winstruct *);
static int AddToGlobalChanList(Server * s, const char *chan);
static Channel *AddToWinChanList(Winstruct *, const char *channame);
static gChannel *search_gchanlist(const Server * s, const char *channame, int *found);
void    p_join()
{
	char   *sender = NULL, *joined_nick = NULL, *channel = NULL;
	char   *login = NULL, *host = NULL,*buf=NULL;
	size_t  ll;
	int just_update;
	#if DEBUG>4
	fprintf(stderr,"join start");
	#endif
	just_update=check_msg(JOIN, Msg);
	sender = nextstr(Msg, 1);
	ll = strlen(sender);
	joined_nick = (char *) malloc(sizeof(char) * ll);
	host = (char *) malloc(sizeof(char) * ll);
	login = (char *) malloc(sizeof(char) * ll);
	if (sscanf(sender, "%[^!]!", joined_nick) < 1)
		{free(joined_nick);joined_nick = strdup( "<unknown>");}
	if (sscanf(sender, "%*[^!]!%[^@]@", login) < 1)
		login = strdup("<unknown>");
	if (sscanf(sender, "%*[^@]@%s", host) < 1)
		host = strdup("<unknown>");
	channel = (char *) malloc(sizeof(char) * strlen(Msg));
	if (sscanf(Msg, "%*s JOIN :%s", channel) < 1)
		return;
	/* ircd 2.9.4 */
	channel=unescape(channel);
	if (strcmp(joined_nick, Win->server->nick)) {
		User   *u = (User *) malloc(sizeof(User));

		if (!u) {
			err_ret("%s %d", __FILE__, __LINE__);
			return;
		}
		u->name = strdup(joined_nick);
		u->address = (char *) malloc(sizeof(char) * ll);
		sprintf(u->address, "%s@%s", login, host);
		u->mode = strdup("H");
		u->ircname = NULL;
		new_addto_chanusers(Win, channel, u);
		free(u->name);free(u->address);free(u->mode);free(u);
		load_userlist(Win, channel, 1);
		if (!ignored(sender, IG_MISC) && !just_update) {
			if (gflags & SHOW_RAW_MSG)
				fl_addto_browser(Win->chanwin->brow2, Msg);
			else {
				fmt_string = "%n%l%h%c$";
				buf = format_msg(fmt_other_join, joined_nick, login, host, channel);
				if (buf){
					fl_addto_browser(Win->chanwin->brow2, buf);
					free(buf);
					}
			}
		}
	}
	else {
		if(just_update) goto update;
		if (gflags & SHOW_RAW_MSG)
			fl_addto_browser(Win->chanwin->brow2, Msg);
		else {
			fmt_string = "%f%n%l%h%c$";
			buf = format_msg(fmt_you_join, Win->server->servname, joined_nick,
					 login, host, channel);
			if (buf){
				fl_addto_browser(Win->chanwin->brow2, buf);
				free(buf);
			}
		}
update:		
		free(login);
		free(host);
		free(joined_nick);
		if (X == 1)
			getwinbycmd(Win->server, "JOIN", channel, 1);	/*hasnt been removed yet */
		if (AddToGlobalChanList(Win->server, channel))
			err_sys("%s %d mem..", __FILE__, __LINE__);
		Win->chanCur = AddToWinChanList(Win, channel);
		if (Win->chanCur == NULL)
			err_sys("%s %d mem..", __FILE__, __LINE__);
		Win->lastJoin->name = Win->chanCur->name;	/* just a pointer.. */
		Win->lastJoin->key = Win->chanCur->key;		/* just a pointer.. */
		addto_go(Win);
		if (Win->chanwin->chanusers->u_vdata && Win->chanStart != Win->chanCur)
			fl_addto_choice(((FD_chanusers *)Win->chanwin->chanusers->u_vdata)->channel,
			channel);
		buf = (char *) malloc(sizeof(char) * (strlen(channel) + 16));
		sprintf(buf, "WHO %s\n", channel);
		addto_cmd_sent(Win, "WHO_UPDATE", channel);
		sendto_server(Win, buf);
		sprintf(buf, "MODE %s\n", channel);
		sendto_server(Win, buf);
		free(buf);
		load_userlist(Win, Win->chanCur->name, 1);
		if(!fl_get_button(Win->chanwin->r_channel)){
			/*fl_set_button(Win->chanwin->r_channel)*/
			fl_trigger_object(Win->chanwin->r_channel);
		}
		UpdateStatus(Win);
		#if DEBUG>4
		fprintf(stderr,"join end");
		#endif
	}

}

static void addto_go(Winstruct * win)
{
	Go     *p;
	int     gone = win->chanwin->status->u_ldata + 1;	/* 1 for #sula */

	/* push one */
	if (win->chanCur == win->chanStart)
		return;		/* shldnt happen */
	p = win->goStart;
	while (p != win->goEnd)
		if (!strcasecmp(p->name, win->chanCur->name)) {		/* raise it */
			Go     *q;

			if (p == win->goStart || p == win->goStart->next)
				return;
			q = win->goStart->next;

			win->goStart->next = (Go *) malloc(sizeof(Go));
			if (win->goStart->next == NULL) {
				err_ret("%s %d", __FILE__, __LINE__);
				return;
			}
			win->goStart->next->name = strdup(win->chanCur->name);
			win->goStart->next->key = strdup2(win->chanCur->key);
			win->goStart->next->next = q;
			q = p->next;
			free(p->name);
			free(p->key);
			if (q == win->goEnd) {
				win->goEnd = p;
			}
			else
				*p = *q;
			free(q);

			go_menu_setup(win);
			return;
		}
		else
			p = p->next;
	if (gone > max_go) {	/* remove last one in the list */
		Go     *q = NULL;

		p = win->goStart->next;		/* skip sula */
		while (p != win->goEnd) {
			q = p;
			p = p->next;
		}
		if (q == NULL)
			return;
		q->next = p;
		free(q->name);
		free(q->key);
		if (p == win->goEnd)
			win->goEnd = q;
		else
			*q = *p;
		free(p);
	}
	p = win->goStart->next;
	win->goStart->next = (Go *) malloc(sizeof(Go));
	if (win->goStart->next == NULL) {
		err_ret("%s %d", __FILE__, __LINE__);
		return;
	}
	win->goStart->next->name = strdup(win->chanCur->name);
	win->goStart->next->key = strdup2(win->chanCur->key);
	win->goStart->next->next = p;
	win->chanwin->status->u_ldata = (long) gone;
	go_menu_setup(win);

}
extern void go_menu_setup(Winstruct * win)
{
	Go     *q = win->goStart;
	int     p;

	if (win->chanwin->go_menu->u_ldata!=-1)
		fl_freepup(win->chanwin->go_menu->u_ldata);
retry:
	p = fl_newpup(FL_ObjWin(win->chanwin->go_menu));
	if(p==-1){
		fprintf(stderr, "%s [%d] pup max increased:%d\n",
		__FILE__, __LINE__, new_pup_max);
		goto retry;
	}
	fl_setpup_shadow(p, 0);
	fl_setpup_cursor(p, XC_right_ptr);
	fl_setpup_bw(p, -1);
	while (q != win->goEnd) {
		fl_addtopup(p, q->name);
		q = q->next;
	}
	fl_set_menu_popup(win->chanwin->go_menu, p);
	win->chanwin->go_menu->u_ldata=p;
}

static int AddToGlobalChanList(Server * s, const char *channame)
{
/* add a channel to the global list of all channels Each server has an
 * associated list of channels This list is necessary (sp) so  one can return 
 * to channels after losing connection to server */

	gChannel *p, *q;
	int     found;

	assert (s && channame);
	p = search_gchanlist(s, channame, &found);
	if (found)
		return 0;
	q = (gChannel *) malloc(sizeof(gChannel));
	if (q == NULL)
		return errno;
	if (p == chanEnd)
		chanEnd = q;
	else
		*q = *p;
	p->next = q;
	p->name = strdup(channame);	/* todo: dont malloc!!!!!!!!! */
	p->server = strdup(s->servname);	/* todo: dont malloc!!!!!!!!! */
	p->port = s->port;
	return 0;
}
static gChannel *search_gchanlist(const Server * s, const char *channame, int *found)
{
/* search global channel list for a channel "channelname" which is on server
 * s */
	gChannel *p = chanStart;
	char   *name, *server;

	assert (channame && s);
	name = lowercase(channame);
	server = lowercase(s->servname);
	while (p != chanEnd)
		if (
			   !strcmp(lowercase(p->name), name) &&
			   !strcmp(lowercase(p->server), server) &&
			   s->port == p->port
		   ) {
			*found = 1;
			return p;
		}
		else
			p = p->next;
	*found = 0;
	return p;
}

static Channel *AddToWinChanList(Winstruct * win, const char *channame)
{
/* add a channel to the list of channels we're in on the window means weve
 * joined the channel */
	Channel *p;

	assert (win && channame);
	p = (Channel *) malloc(sizeof(Channel));
	if (p == NULL)
		return NULL;
	if (!(p->name = strdup(channame)))
		return NULL;

	p->modes = NULL;
	p->key = NULL;
	p->limit = 0;
	p->topic = NULL;
	p->userStart = p->userEnd = (User *) malloc(sizeof(User));
	if (p->userStart == NULL)
		return NULL;
	p->left = win->chanCur;
	p->right = win->chanCur->right;
	win->chanCur->right->left = p;
	win->chanCur->right = p;
	return p;

}

/****************************************************************/
/* */
/* N I C K                                              */
/* */
/****************************************************************/

void    p_nick(void)
{

	char   *sender, *nick, *newnick, *buf=NULL;
	Channel *current;
	size_t  ll;
	int just_update;

	#if DEBUG>4
	fprintf(stderr,"nick start");
	#endif
	ll = strlen(Msg);
	sender = nextstr(Msg,1);
	nick = (char *) malloc(sizeof(char) * strlen(sender));
	if (sscanf(sender, "%[^!]!%*s", nick) < 1) {
		fprintf(stderr, "\n%s: Strange string\n", sender);
	       free(nick);	return;
	}
	newnick = (char *) malloc(sizeof(char) * ll);
	if (sscanf(Msg, "%*s NICK :%[^\r\n]\r\n", newnick) < 1) {
		free(nick);free(newnick);
		return;
	}
	if (!strcmp(nick, Win->server->nick)) {
		just_update=check_msg(NICK, Msg);
		if(!just_update){
		if (gflags & SHOW_RAW_MSG)
			fl_addto_browser(Win->chanwin->brow2, Msg);
		else {
			fmt_string = "%f%n%p$";
			buf = format_msg(fmt_you_nick, Win->server->servname, nick, newnick);
			if (buf){
				fl_addto_browser(Win->chanwin->brow2, buf);
				free(buf);
				}
		}
		}/*if(!just_update)*/ 
		Win->server->nick = strdup(newnick);
		aNickList[Win->server->iNick].current=
		         add_nick_to_list(Win->server->iNick, newnick);
		if( !aNickList[Win->server->iNick].current) err_sys("error");
		update_server_stuff(Win->server->fd);
		if(server_tool) fl_trigger_object(server_tool->update);
	}
	else if (!ignored(sender, IG_MISC) && check_msg(NICK, Msg)==0) {
		if (gflags & SHOW_RAW_MSG)
			fl_addto_browser(Win->chanwin->brow2, Msg);
		else {
			char   *login, *host;
			size_t  ll = strlen(sender) + 1;
			login = (char *) malloc(sizeof(char) * ll);
			host = (char *) malloc(sizeof(char) * ll);

			if (sscanf(sender, "%*[^!]!%[^@]@%s", login, host) < 2) {
				host = sender;
				strcpy(login, " ");
			}
			fmt_string = "%n%l%h%p$";
			buf = format_msg(fmt_other_nick, nick, login, host, newnick);
			if (buf){
				fl_addto_browser(Win->chanwin->brow2, buf);
				free(buf);
				}
			free(login); free(host);
		}
	}
	current = Win->chanStart->right;
	while (current != Win->chanStart) {
		if (get_user_byuser(current, nick))
			RenameChanUser(Win, current->name, nick, newnick);
		current = current->right;
	}
	free(nick); free(newnick);
	#if DEBUG>4
	fprintf(stderr,"nick end");
	#endif
}

extern void RenameChanUser(Winstruct * win, const char *channame, const char *sender,
			   const char *newnick)
{
	Channel *chan;
	User   *p;
	int     found = 0;

	/*if (!sender || !channame || !win)
		return;*/
	chan = get_chan_byname(win, channame);
	if (chan == NULL)	/* there shld be a channel if someone left it */
		return;

	p = search_userlist(chan, sender, &found);
	if (!found)
		return;		/* user not on the list..minor bug somewhere */
	p->name = strdup(newnick);
	load_userlist(win, channame, 1);
}

/* 
 * someone has left a channel: kick, quit, part
 * 
 * the following 3 functions are used further below
 * 
 */

extern void RemoveFromChanUsers(Winstruct * win, const char *channame, const char *sender)
{
/* take someone who has left off the list of channel members */
	Channel *chan;
	User   *p;

	assert (sender && channame && win);
	chan = get_chan_byname(win, channame);
	if (chan == NULL) 
		return;
	p = chan->userStart;
	while (p != chan->userEnd)
		if (!strcasecmp(p->name, sender)) {
			User   *q = p->next;

			free(p->name);
			free(p->address);
			free(p->ircname);
			if (q == chan->userEnd)
				chan->userEnd = p;
			else
				*p = *q;
			free(q);
			load_userlist(win, channame, 1);
			return;
		}
		else
			p = p->next;
}

static int RemoveFromGlobalChanList(Server * s, const char *channame)
{
/* remove a channel from the global list because we're no longer in the
 * channel */
	gChannel *p, *q;
	int     found;

	if (!s || !channame)
		return -1;	/* see  RemoveFromWinChanList() below */
	p = search_gchanlist(s, channame, &found);
	if (!found)
		return 0;	/* a bug-- see  RemoveFromWinChanList() below */
	assert (p != chanEnd);
	free(p->name);
	free(p->server);
	q = p->next;
	if (q == chanEnd)
		chanEnd = p;
	else
		*p = *q;
	free(q);
	return 0;
}

extern int RemoveFromWinChanList(Winstruct * win, const char *channame)
{
/* rm a channel from the list of channels we're in on the window.  means
 * we've been kicked out of channel or left or something.. */
	Channel *p;
	User   *u, *v;

	if (!win)
		return 0;
	p = get_chan_byname(win, channame);
	if (p == NULL) {	/* channel win disaapeared before PART reply from server */
		fprintf(stderr, "Missed %s?", channame);
		return 0;
	}

	assert (p != win->chanStart);
	win->lastPart->name = strdup(p->name);
	win->lastPart->key = strdup2(p->key);
	free(p->name);
	if (p->topic)
		free(p->topic);
	if (p->key)
		free(p->key);
	u = p->userStart;
	while (u != p->userEnd) {
		free(u->name);
		free(u->address);
		free(u->ircname);
		v = u->next;
		if (v == p->userEnd)
			p->userEnd = u;
		else
			*u = *v;
		free(v);
	}
	free(u);
	p->left->right = p->right;
	p->right->left = p->left;
	if (p == win->chanCur)
		win->chanCur = p->left;		/* use right if u are left-handed! */

	if (win->lastJoin->name == p->name)
		if (p->left == win->chanStart) {
			win->lastJoin->name = NULL;
		}
		else {
			win->lastJoin->name = p->left->name;

		}
	free(p);
	if (win->chanCur == win->chanStart)
		win->chanCur = win->chanStart->right;
	return 0;

}

static void recreate_chanusers_channels(Winstruct * win)
{
/* recreate choice items */
	Channel *chan;

	if (!win->chanwin->chanusers->u_vdata)
		return;
	fl_clear_choice(((FD_chanusers *)win->chanwin->chanusers->u_vdata)->channel);
	chan = win->chanStart->left;
	while (chan != win->chanStart) {
		fl_addto_choice(((FD_chanusers *)win->chanwin->chanusers->u_vdata)->channel,
		chan->name);
		chan = chan->left;
	}
	return;
}

/****************************************************************/
/* 								*/
/* K I C K                                              	*/
/*								*/
/****************************************************************/

void    p_kick(void)
{
	char   *sender, *kicker=NULL, *kicked=NULL, 
	*channame=NULL, *reason=NULL;
	char   *login=NULL, *host=NULL, *buf=NULL;
	int     ll;

	#if DEBUG>4
	fprintf(stderr,"p_kick start");
	#endif
	sender=nextstr(Msg, 1);
	kicker = (char *) malloc(sizeof(char) * strlen(sender));
	if (sscanf(sender, "%[^!]!%*s", kicker) < 1) {
	      fprintf(stderr, "\n%s: Strange string\n", sender);
	       free(kicker);	return;
	}
	ll = strlen(Msg);
	reason = (char *) malloc(sizeof(char) * (ll));
	kicked = (char *) malloc(sizeof(char) * ll);
	channame = (char *) malloc(sizeof(char) * ll);
	ll = sscanf(Msg, "%*s KICK %s %s :%[^\r\n]\r\n", channame, kicked, reason);
	if (ll < 2) 
		goto done;
	if (ll < 3)
		reason = NULL;
	ll = strlen(sender) + 1;
	login = (char *) malloc(sizeof(char) * ll);
	host = (char *) malloc(sizeof(char) * ll);
	if (sscanf(sender, "%*[^!]!%[^@]@%s", login, host) < 2) {
		strcpy(host,sender);
		login =strdup( "<unknown>");
	}

	if (!strcmp(Win->server->nick, kicked)) {
		if (!ignored(sender, IG_MISC) && !check_msg(KICK, Msg)) {
			if (gflags & SHOW_RAW_MSG)
				fl_addto_browser(Win->chanwin->brow2, Msg);
			else {
				fmt_string = "%f%n%l%h%c%p$";
				buf = format_msg(fmt_you_kick, Win->server->servname, kicker,
					     login, host, channame, reason);
				if (buf){
					fl_addto_browser(Win->chanwin->brow2, buf);
					free(buf);
				}
			}
		}
		buf = NULL;
		if (gflags & AUTO_REJOIN) {
			Channel *p = get_chan_byname(Win, channame);
			buf = (char *) malloc(sizeof(char) * (strlen(channame) + 128));

			sprintf(buf, "JOIN %s %s", channame, p && p->key ? p->key : "");

		}
		ll=RemoveFromGlobalChanList(Win->server, channame);
		assert(ll==0);
		ll=RemoveFromWinChanList(Win, channame);
		assert(ll==0);
		recreate_chanusers_channels(Win);
		load_userlist(Win, NULL, 0);
		UpdateStatus(Win);
		if (buf){
			process_cmd(buf, Win);
			free(buf);
		}
	}
	else {
		if (!ignored(sender, IG_MISC) && !check_msg(KICK, Msg)) {
			if (gflags & SHOW_RAW_MSG)
				fl_addto_browser(Win->chanwin->brow2, Msg);
			else {
				fmt_string = "%f%n%l%h%c%p$";
				buf = format_msg(fmt_other_kick, kicked, kicker,
					     login, host, channame, reason);
				if (buf){
					fl_addto_browser(Win->chanwin->brow2, buf);
					free(buf);
				}
			}
		}
		RemoveFromChanUsers(Win, channame, kicked);
	}
	done:
	free(login);
		free(host);
		free(reason);
		free(kicker);
		free(kicked);
	#if DEBUG>4
	fprintf(stderr,"p_kick end");
	#endif
}

/****************************************************************/
/* */
/* P A R T                                              */
/* */
/****************************************************************/

void    p_part(void)
{
	char   *sender=NULL, *parted_nick=NULL, *login=NULL, 
	*host=NULL, *chan=NULL;
	size_t  ll;

	
	sender=nextstr(Msg, 1);
	ll = strlen(sender);
	parted_nick = (char *) malloc(sizeof(char) * ll);
	login = (char *) malloc(sizeof(char) * ll);
	host = (char *) malloc(sizeof(char) * ll);
	
	if (sscanf(sender, "%[^!]!%[^@]@%s", parted_nick, login, host) < 3)
		if (!(*parted_nick) && strcmp(parted_nick, Win->server->nick))
			goto fini;
	chan = (char *) malloc(sizeof(char) * strlen(Msg));
	if (sscanf(Msg, "%*s PART %s", chan) < 1)
		goto fini;
	if (strcmp(parted_nick, Win->server->nick)) {
		if (!ignored(sender, IG_MISC) && !check_msg(PART, Msg)) {
			if (gflags & SHOW_RAW_MSG)
				fl_addto_browser(Win->chanwin->brow2, Msg);
			else {
				char   *buf;
				fmt_string = "%n%l%h%c$";
				buf = format_msg(fmt_other_part, parted_nick, login, host, chan);
				if (buf){
					fl_addto_browser(Win->chanwin->brow2, buf);
					free(buf);
					}
			}
		}
		RemoveFromChanUsers(Win, chan, parted_nick);
	}
	else {
		if (gflags & SHOW_RAW_MSG)
			fl_addto_browser(Win->chanwin->brow2, Msg);
		else {
			char   *buf;

			fmt_string = "%f%c$";
			buf = format_msg(fmt_you_part, Win->server->servname, chan);
			if (buf){
				fl_addto_browser(Win->chanwin->brow2, buf);
				free(buf);
				}
		}
		ll=RemoveFromGlobalChanList(Win->server, chan);
		assert(ll==0);
		ll=RemoveFromWinChanList(Win, chan);
		assert(ll==0);
		recreate_chanusers_channels(Win);
		load_userlist(Win, NULL, 0);
		UpdateStatus(Win);
		if(Win->chanStart->right==Win->chanStart)
			fl_set_object_label(Win->chanwin->WinStatusLine,
			"-- just left last channel --");
	}
   fini:
	free(login);
	free(host);
	free(chan);
	free(parted_nick);

}
/****************************************************************/
/* */
/* Q U I T                                              */
/* */
/****************************************************************/

void    p_quit(void)
{
	char   *sender, *nick;
	char   *login, *host;
	Channel *current;
	size_t  ll;

	#if DEBUG >4
	fputs("quit start", stderr);
	#endif
	sender=nextstr(Msg, 1);
	ll = strlen(sender);
	nick = (char *) malloc(sizeof(char) * ll);
	login = (char *) malloc(sizeof(char) * ll);
	host = (char *) malloc(sizeof(char) * ll);

	if (sscanf(sender, "%[^!]!%[^@]@%s", nick, login, host) < 3)
	       goto fini;
	if (!ignored(sender, IG_MISC)) {
		if ((gflags & SHOW_RAW_MSG) && !(check_msg(QUIT, Msg)))
			fl_addto_browser(Win->chanwin->brow2, Msg);
		else {
			char   *buf;
			char *reason = (char *) malloc(sizeof(char) * (strlen(Msg)));
			if (sscanf(Msg, "%*s QUIT :%[^\r\n]\r\n", reason) < 1)
				reason = NULL;
			fmt_string = "%n%l%h%p$";
			buf = format_msg(fmt_signoff, nick, login, host, reason);
			if (buf){
				fl_addto_browser(Win->chanwin->brow2, buf);
				free(buf);
				}
			free(reason);
		}
	}
	
	current = Win->chanStart->right;
	while (current != Win->chanStart) {
		if (get_user_byuser(current, nick))
			RemoveFromChanUsers(Win, current->name, nick);
		current = current->right;
	}
	fini:
	free(nick);free(login); free(host);
	#if DEBUG >4
	fputs("quit end", stderr);
	#endif
}
