/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include "hexchat.h"
#include "notify.h"
#include "cfgfiles.h"
#include "fe.h"
#include "server.h"
#include "text.h"
#include "util.h"
#include "hexchatc.h"


GSList *notify_list = 0;
int notify_tag = 0;


static char *
despacify_dup (char *str)
{
	char *p, *res = g_malloc (strlen (str) + 1);

	p = res;
	while (1)
	{
		if (*str != ' ')
		{
			*p = *str;
			if (*p == 0)
				return res;
			p++;
		}
		str++;
	}
}

static int
notify_netcmp (char *str, void *serv)
{
	char *net = despacify_dup (server_get_network (serv, TRUE));

	if (rfc_casecmp (str, net) == 0)
	{
		g_free (net);
		return 0;	/* finish & return FALSE from token_foreach() */
	}

	g_free (net);
	return 1;	/* keep going... */
}

/* monitor this nick on this particular network? */

static gboolean
notify_do_network (struct notify *notify, server *serv)
{
	if (!notify->networks)	/* ALL networks for this nick */
		return TRUE;

	if (token_foreach (notify->networks, ',', notify_netcmp, serv))
		return FALSE;	/* network list doesn't contain this one */

	return TRUE;
}

struct notify_per_server *
notify_find_server_entry (struct notify *notify, struct server *serv)
{
	GSList *list = notify->server_list;
	struct notify_per_server *servnot;

	while (list)
	{
		servnot = (struct notify_per_server *) list->data;
		if (servnot->server == serv)
			return servnot;
		list = list->next;
	}

	/* not found, should we add it, or is this not a network where
      we're monitoring this nick? */
	if (!notify_do_network (notify, serv))
		return NULL;

	servnot = g_new0 (struct notify_per_server, 1);
	servnot->server = serv;
	servnot->notify = notify;
	notify->server_list = g_slist_prepend (notify->server_list, servnot);
	return servnot;
}

void
notify_save (void)
{
	int fh;
	struct notify *notify;
        // while reading the notify.conf file, elements are added by prepending to the
        // list. reverse the list before writing to disk to keep the original
        // order of the list
        GSList *list = g_slist_copy(notify_list);
        list = g_slist_reverse(list);

	fh = hexchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
	if (fh != -1)
	{
		while (list)
		{
			notify = (struct notify *) list->data;
			write (fh, notify->name, strlen (notify->name));
			if (notify->networks)
			{
				write (fh, " ", 1);
				write (fh, notify->networks, strlen (notify->networks));
			}
			write (fh, "\n", 1);
			list = list->next;
		}
		close (fh);
	}
        g_slist_free(list);
}

void
notify_load (void)
{
	int fh;
	char buf[256];
	char *sep;

	fh = hexchat_open_file ("notify.conf", O_RDONLY, 0, 0);
	if (fh != -1)
	{
		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
		{
			if (buf[0] != '#' && buf[0] != 0)
			{
				sep = strchr (buf, ' ');
				if (sep)
				{
					sep[0] = 0;
					notify_adduser (buf, sep + 1);					
				}
				else
					notify_adduser (buf, NULL);
			}
		}
		close (fh);
	}
}

static struct notify_per_server *
notify_find (server *serv, char *nick)
{
	GSList *list = notify_list;
	struct notify_per_server *servnot;
	struct notify *notify;

	while (list)
	{
		notify = (struct notify *) list->data;

		servnot = notify_find_server_entry (notify, serv);
		if (!servnot)
		{
			list = list->next;
			continue;
		}

		if (!serv->p_cmp (notify->name, nick))
			return servnot;

		list = list->next;
	}

	return NULL;
}

static void
notify_announce_offline (server * serv, struct notify_per_server *servnot,
								 char *nick, int quiet, 
								 const message_tags_data *tags_data)
{
	session *sess;

	sess = serv->front_session;

	servnot->ison = FALSE;
	servnot->lastoff = time (0);
	if (!quiet)
		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername,
									  server_get_network (serv, TRUE), NULL, 0,
									  tags_data->timestamp);
	fe_notify_update (nick);
	fe_notify_update (0);
}

static void
notify_announce_online (server * serv, struct notify_per_server *servnot,
								char *nick, const message_tags_data *tags_data)
{
	session *sess;

	sess = serv->front_session;

	servnot->lastseen = time (0);
	if (servnot->ison)
		return;

	servnot->ison = TRUE;
	servnot->laston = time (0);
	EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYONLINE, sess, nick, serv->servername,
					 server_get_network (serv, TRUE), NULL, 0,
					 tags_data->timestamp);
	fe_notify_update (nick);
	fe_notify_update (0);

	if (prefs.hex_notify_whois_online)
	{

	    /* Let's do whois with idle time (like in /quote WHOIS %s %s) */

	    char *wii_str = g_strdup_printf ("%s %s", nick, nick);
	    serv->p_whois (serv, wii_str);
	    g_free (wii_str);
	}
}

/* handles numeric 601 */

void
notify_set_offline (server * serv, char *nick, int quiet,
						  const message_tags_data *tags_data)
{
	struct notify_per_server *servnot;

	servnot = notify_find (serv, nick);
	if (!servnot)
		return;

	notify_announce_offline (serv, servnot, nick, quiet, tags_data);
}

/* handles numeric 604 and 600 */

void
notify_set_online (server * serv, char *nick,
						 const message_tags_data *tags_data)
{
	struct notify_per_server *servnot;

	servnot = notify_find (serv, nick);
	if (!servnot)
		return;

	notify_announce_online (serv, servnot, nick, tags_data);
}

/* monitor can send lists for numeric 730/731 */

void
notify_set_offline_list (server * serv, char *users, int quiet,
						  const message_tags_data *tags_data)
{
	struct notify_per_server *servnot;
	char nick[NICKLEN];
	char *token, *chr;

	token = strtok (users, ",");
	while (token != NULL)
	{
		chr = strchr (token, '!');
		if (chr != NULL)
			*chr = '\0';

		g_strlcpy (nick, token, sizeof(nick));

		servnot = notify_find (serv, nick);
		if (servnot)
			notify_announce_offline (serv, servnot, nick, quiet, tags_data);

		token = strtok (NULL, ",");
	}
}

void
notify_set_online_list (server * serv, char *users,
						 const message_tags_data *tags_data)
{
	struct notify_per_server *servnot;
	char nick[NICKLEN];
	char *token, *chr;

	token = strtok (users, ",");
	while (token != NULL)
	{
		chr = strchr (token, '!');
		if (chr != NULL)
			*chr = '\0';

		g_strlcpy (nick, token, sizeof(nick));

		servnot = notify_find (serv, nick);
		if (servnot)
			notify_announce_online (serv, servnot, nick, tags_data);

		token = strtok (NULL, ",");
	}
}

static void
notify_watch (server * serv, char *nick, int add)
{
	char tbuf[256];
	char addchar = '+';

	if (!add)
		addchar = '-';

	if (serv->supports_monitor)
		g_snprintf (tbuf, sizeof (tbuf), "MONITOR %c %s", addchar, nick);
	else if (serv->supports_watch)
		g_snprintf (tbuf, sizeof (tbuf), "WATCH %c%s", addchar, nick);
	else
		return;

	serv->p_raw (serv, tbuf);
}

static void
notify_watch_all (struct notify *notify, int add)
{
	server *serv;
	GSList *list = serv_list;
	while (list)
	{
		serv = list->data;
		if (serv->connected && serv->end_of_motd && notify_do_network (notify, serv))
			notify_watch (serv, notify->name, add);
		list = list->next;
	}
}

static void
notify_flush_watches (server * serv, GSList *from, GSList *end)
{
	char tbuf[512];
	GSList *list;
	struct notify *notify;

	serv->supports_monitor ? strcpy (tbuf, "MONITOR + ") : strcpy (tbuf, "WATCH");

	list = from;
	while (list != end)
	{
		notify = list->data;
		if (serv->supports_monitor)
			g_strlcat (tbuf, ",", sizeof(tbuf));
		else
			g_strlcat (tbuf, " +", sizeof(tbuf));
		g_strlcat (tbuf, notify->name, sizeof(tbuf));
		list = list->next;
	}
	serv->p_raw (serv, tbuf);
}

/* called when logging in. e.g. when End of motd. */

void
notify_send_watches (server * serv)
{
	struct notify *notify;
	const int format_len = serv->supports_monitor ? 1 : 2; /* just , for monitor or + and space for watch */
	GSList *list;
	GSList *point;
	GSList *send_list = NULL;
	int len = 0;

	/* Only get the list for this network */
	list = notify_list;
	while (list)
	{
		notify = list->data;

		if (notify_do_network (notify, serv))
		{
			send_list = g_slist_append (send_list, notify);
		}

		list = list->next;
	}

	/* Now send that list in batches */
	point = list = send_list;
	while (list)
	{
		notify = list->data;

		len += strlen (notify->name) + format_len;
		if (len > 500)
		{
			/* Too long send existing list */
			notify_flush_watches (serv, point, list);
			len = strlen (notify->name) + format_len;
			point = list; /* We left off here */
		}

		list = g_slist_next (list);
	}

	if (len) /* We had leftovers under 500, send them all */
	{
		notify_flush_watches (serv, point, NULL);
	}

	g_slist_free (send_list);
}

/* called when receiving a ISON 303 - should this func go? */

void
notify_markonline (server *serv, char *word[], const message_tags_data *tags_data)
{
	struct notify *notify;
	struct notify_per_server *servnot;
	GSList *list = notify_list;
	int i, seen;

	while (list)
	{
		notify = (struct notify *) list->data;
		servnot = notify_find_server_entry (notify, serv);
		if (!servnot)
		{
			list = list->next;
			continue;
		}
		i = 4;
		seen = FALSE;
		while (*word[i])
		{
			if (!serv->p_cmp (notify->name, word[i]))
			{
				seen = TRUE;
				notify_announce_online (serv, servnot, notify->name, tags_data);
				break;
			}
			i++;
			/* FIXME: word[] is only a 32 element array, limits notify list to
			   about 27 people */
			if (i > PDIWORDS - 5)
			{
				/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
				break;
			}
		}
		if (!seen && servnot->ison)
		{
			notify_announce_offline (serv, servnot, notify->name, FALSE, tags_data);
		}
		list = list->next;
	}
	fe_notify_update (0);
}

/* yuck! Old routine for ISON notify */

static void
notify_checklist_for_server (server *serv)
{
	char outbuf[512];
	struct notify *notify;
	GSList *list = notify_list;
	int i = 0;

	strcpy (outbuf, "ISON ");
	while (list)
	{
		notify = list->data;
		if (notify_do_network (notify, serv))
		{
			i++;
			strcat (outbuf, notify->name);
			strcat (outbuf, " ");
			if (strlen (outbuf) > 460)
			{
				/* LAME: we can't send more than 512 bytes to the server, but     *
				 * if we split it in two packets, our offline detection wouldn't  *
				 work                                                           */
				/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
				break;
			}
		}
		list = list->next;
	}

	if (i)
		serv->p_raw (serv, outbuf);
}

int
notify_checklist (void)	/* check ISON list */
{
	struct server *serv;
	GSList *list = serv_list;

	while (list)
	{
		serv = list->data;
		if (serv->connected && serv->end_of_motd && !serv->supports_watch && !serv->supports_monitor)
		{
			notify_checklist_for_server (serv);
		}
		list = list->next;
	}
	return 1;
}

void
notify_showlist (struct session *sess, const message_tags_data *tags_data)
{
	char outbuf[256];
	struct notify *notify;
	GSList *list = notify_list;
	struct notify_per_server *servnot;
	int i = 0;

	EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0,
								  tags_data->timestamp);
	while (list)
	{
		i++;
		notify = (struct notify *) list->data;
		servnot = notify_find_server_entry (notify, sess->server);
		if (servnot && servnot->ison)
			g_snprintf (outbuf, sizeof (outbuf), _("  %-20s online\n"), notify->name);
		else
			g_snprintf (outbuf, sizeof (outbuf), _("  %-20s offline\n"), notify->name);
		PrintTextTimeStamp (sess, outbuf, tags_data->timestamp);
		list = list->next;
	}
	if (i)
	{
		sprintf (outbuf, "%d", i);
		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL,
									  0, tags_data->timestamp);
	} else
		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0,
									  tags_data->timestamp);
}

int
notify_deluser (char *name)
{
	struct notify *notify;
	struct notify_per_server *servnot;
	GSList *list = notify_list;

	while (list)
	{
		notify = (struct notify *) list->data;
		if (!rfc_casecmp (notify->name, name))
		{
			fe_notify_update (notify->name);
			/* Remove the records for each server */
			while (notify->server_list)
			{
				servnot = (struct notify_per_server *) notify->server_list->data;
				notify->server_list =
					g_slist_remove (notify->server_list, servnot);
				g_free (servnot);
			}
			notify_list = g_slist_remove (notify_list, notify);
			notify_watch_all (notify, FALSE);
			g_free (notify->networks);
			g_free (notify->name);
			g_free (notify);
			fe_notify_update (0);
			return 1;
		}
		list = list->next;
	}
	return 0;
}

void
notify_adduser (char *name, char *networks)
{
	struct notify *notify = g_new0 (struct notify, 1);

	notify->name = g_strndup (name, NICKLEN - 1);

	if (networks != NULL)
		notify->networks = despacify_dup (networks);
	notify->server_list = 0;
	notify_list = g_slist_prepend (notify_list, notify);
	notify_checklist ();
	fe_notify_update (notify->name);
	fe_notify_update (0);
	notify_watch_all (notify, TRUE);
}

gboolean
notify_is_in_list (server *serv, char *name)
{
	struct notify *notify;
	GSList *list = notify_list;

	while (list)
	{
		notify = (struct notify *) list->data;
		if (!serv->p_cmp (notify->name, name))
			return TRUE;
		list = list->next;
	}

	return FALSE;
}

int
notify_isnotify (struct session *sess, char *name)
{
	struct notify *notify;
	struct notify_per_server *servnot;
	GSList *list = notify_list;

	while (list)
	{
		notify = (struct notify *) list->data;
		if (!sess->server->p_cmp (notify->name, name))
		{
			servnot = notify_find_server_entry (notify, sess->server);
			if (servnot && servnot->ison)
				return TRUE;
		}
		list = list->next;
	}

	return FALSE;
}

void
notify_cleanup ()
{
	GSList *list = notify_list;
	GSList *nslist, *srvlist;
	struct notify *notify;
	struct notify_per_server *servnot;
	struct server *serv;
	int valid;

	while (list)
	{
		/* Traverse the list of notify structures */
		notify = (struct notify *) list->data;
		nslist = notify->server_list;
		while (nslist)
		{
			/* Look at each per-server structure */
			servnot = (struct notify_per_server *) nslist->data;

			/* Check the server is valid */
			valid = FALSE;
			srvlist = serv_list;
			while (srvlist)
			{
				serv = (struct server *) srvlist->data;
				if (servnot->server == serv)
				{
					valid = serv->connected;	/* Only valid if server is too */
					break;
				}
				srvlist = srvlist->next;
			}
			if (!valid)
			{
				notify->server_list =
					g_slist_remove (notify->server_list, servnot);
				g_free (servnot);
				nslist = notify->server_list;
			} else
			{
				nslist = nslist->next;
			}
		}
		list = list->next;
	}
	fe_notify_update (0);
}
