/*
 notify-ison.c : irssi

    Copyright (C) 1999-2000 Timo Sirainen

    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
*/

#include "module.h"
#include "signals.h"
#include "misc.h"
#include "settings.h"

#include "irc.h"
#include "irc-servers.h"
#include "servers-redirect.h"

#include "notifylist.h"

#define DEFAULT_NOTIFY_CHECK_TIME "1min"
#define DEFAULT_NOTIFY_WHOIS_TIME "5min"

typedef struct {
	char *nick;
	int hostok;
} ISON_REC;

static int notify_tag;
static int notify_whois_time;

NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick)
{
	MODULE_SERVER_REC *mserver;
	NOTIFY_NICK_REC *rec;

	mserver = MODULE_DATA(server);

	rec = g_new0(NOTIFY_NICK_REC, 1);
	rec->nick = g_strdup(nick);

	mserver->notify_users = g_slist_append(mserver->notify_users, rec);
	return rec;
}

void notify_nick_destroy(NOTIFY_NICK_REC *rec)
{
	g_free(rec->nick);
	g_free_not_null(rec->user);
	g_free_not_null(rec->host);
	g_free_not_null(rec->realname);
	g_free_not_null(rec->awaymsg);
	g_free(rec);
}

NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick)
{
	MODULE_SERVER_REC *mserver;
	NOTIFY_NICK_REC *rec;
	GSList *tmp;

	mserver = MODULE_DATA(server);
	for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) {
		rec = tmp->data;

		if (g_strcasecmp(rec->nick, nick) == 0)
			return rec;
	}

	return NULL;
}

static void ison_send(IRC_SERVER_REC *server, GString *cmd)
{
	MODULE_SERVER_REC *mserver;

	mserver = MODULE_DATA(server);
	mserver->ison_count++;

	g_string_truncate(cmd, cmd->len-1);
	g_string_prepend(cmd, "ISON :");

	server_redirect_event(server, "ison", 1, NULL, -1, NULL,
			      "event 303", "notifylist event", NULL);
	irc_send_cmd(server, cmd->str);

	g_string_truncate(cmd, 0);
}

/* timeout function: send /ISON commands to server to check if someone in
   notify list is in IRC */
static void notifylist_timeout_server(IRC_SERVER_REC *server)
{
	MODULE_SERVER_REC *mserver;
	GSList *tmp;
	GString *cmd;
	char *nick, *ptr;
	int len;

	g_return_if_fail(server != NULL);

	if (!IS_IRC_SERVER(server))
		return;

	mserver = MODULE_DATA(server);
	if (mserver->ison_count > 0) {
		/* still not received all replies to previous /ISON commands.. */
		return;
	}

	cmd = g_string_new(NULL);
	for (tmp = notifies; tmp != NULL; tmp = tmp->next) {
		NOTIFYLIST_REC *rec = tmp->data;

		if (!notifylist_ircnets_match(rec, server->connrec->chatnet))
                        continue;

		nick = g_strdup(rec->mask);
		ptr = strchr(nick, '!');
		if (ptr != NULL) *ptr = '\0';

		len = strlen(nick);

		if (cmd->len+len+1 > 510)
                        ison_send(server, cmd);

		g_string_sprintfa(cmd, "%s ", nick);
		g_free(nick);
	}

	if (cmd->len > 0)
		ison_send(server, cmd);
	g_string_free(cmd, TRUE);
}

static int notifylist_timeout_func(void)
{
	g_slist_foreach(servers, (GFunc) notifylist_timeout_server, NULL);
	return 1;
}

static void ison_save_users(MODULE_SERVER_REC *mserver, char *online)
{
	char *ptr;

	while (online != NULL && *online != '\0') {
		ptr = strchr(online, ' ');
		if (ptr != NULL) *ptr++ = '\0';

		mserver->ison_tempusers =
			g_slist_append(mserver->ison_tempusers, g_strdup(online));
		online = ptr;
	}
}

static void whois_send(IRC_SERVER_REC *server, const char *nicks,
		       const char *whois_request)
{
	char *p, *str;

	/* "nick1,nick2" -> "nick1,nick2 nick1 nick2" because
	   End of WHOIS give nick1,nick2 while other whois events give
	   nick1 or nick2 */
        str = g_strconcat(nicks, " ", nicks, NULL);
	for (p = str+strlen(nicks)+1; *p != '\0'; p++)
		if (*p == ',') *p = ' ';

	server_redirect_event(server, "whois", 1, str, TRUE,
                              "notifylist event whois end",
			      "event 318", "notifylist event whois end",
			      "event 311", "notifylist event whois",
			      "event 301", "notifylist event whois away",
			      "event 317", "notifylist event whois idle",
			      "", "event empty", NULL);
	g_free(str);

	irc_send_cmdv(server, "WHOIS %s", whois_request);
}

static void whois_send_server(IRC_SERVER_REC *server, char *nick)
{
	char *str;

	str = g_strdup_printf("%s %s", nick, nick);
	whois_send(server, nick, str);
	g_free(str);
}

/* try to send as many nicks in one WHOIS as possible */
static void whois_list_send(IRC_SERVER_REC *server, GSList *nicks)
{
	GSList *tmp;
	GString *str;
	char *nick;
        int count;

	str = g_string_new(NULL);
	count = 0;

	for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
		nick = tmp->data;

		count++;
		g_string_sprintfa(str, "%s,", nick);

		if (count >= server->max_whois_in_cmd) {
			g_string_truncate(str, str->len-1);
			whois_send(server, str->str, str->str);
			g_string_truncate(str, 0);
                        count = 0;
		}
	}

	if (str->len > 0) {
		g_string_truncate(str, str->len-1);
		whois_send(server, str->str, str->str);
	}

	g_string_free(str, TRUE);
}

static void ison_check_joins(IRC_SERVER_REC *server)
{
	MODULE_SERVER_REC *mserver;
	NOTIFYLIST_REC *notify;
	NOTIFY_NICK_REC *rec;
	GSList *tmp, *newnicks;
	int send_whois;
	time_t now;

	mserver = MODULE_DATA(server);

	now = time(NULL);
	newnicks = NULL;
	for (tmp = mserver->ison_tempusers; tmp != NULL; tmp = tmp->next) {
		char *nick = tmp->data;

		notify = notifylist_find(nick, server->connrec->chatnet);
		send_whois = notify != NULL &&
			(notify->away_check || notify->idle_check_time > 0);

		rec = notify_nick_find(server, nick);
		if (rec != NULL) {
			/* check if we want to send WHOIS yet.. */
			if (now-rec->last_whois < notify_whois_time)
				continue;
		} else {
			rec = notify_nick_create(server, nick);
			if (!send_whois) newnicks = g_slist_append(newnicks, nick);
		}

		if (send_whois) {
			/* we need away message or idle time -
			   send the WHOIS reply to the nick's server */
                        rec->last_whois = now;
			whois_send_server(server, nick);
		}
	}

	whois_list_send(server, newnicks);
	g_slist_free(newnicks);
}

static void ison_check_parts(IRC_SERVER_REC *server)
{
	MODULE_SERVER_REC *mserver;
	GSList *tmp, *next;

	mserver = MODULE_DATA(server);
	for (tmp = mserver->notify_users; tmp != NULL; tmp = next) {
		NOTIFY_NICK_REC *rec = tmp->data;
		next = tmp->next;

		if (gslist_find_icase_string(mserver->ison_tempusers, rec->nick) != NULL)
			continue;

                notifylist_left(server, rec);
	}
}

static void event_ison(IRC_SERVER_REC *server, const char *data)
{
	MODULE_SERVER_REC *mserver;
	char *params, *online;

	g_return_if_fail(data != NULL);
	g_return_if_fail(server != NULL);

	params = event_get_params(data, 2, NULL, &online);

	mserver = MODULE_DATA(server);
	ison_save_users(mserver, online);

	if (--mserver->ison_count > 0) {
		/* wait for the rest of the /ISON replies */
		g_free(params);
                return;
	}

        ison_check_joins(server);
        ison_check_parts(server);

	/* free memory used by temp list */
	g_slist_foreach(mserver->ison_tempusers, (GFunc) g_free, NULL);
	g_slist_free(mserver->ison_tempusers);
	mserver->ison_tempusers = NULL;

	g_free(params);
}

static void read_settings(void)
{
	if (notify_tag != -1) g_source_remove(notify_tag);
	notify_tag = g_timeout_add(settings_get_time("notify_check_time"),
				   (GSourceFunc) notifylist_timeout_func, NULL);

	notify_whois_time = settings_get_time("notify_whois_time")/1000;
}

void notifylist_ison_init(void)
{
	settings_add_time("misc", "notify_check_time", DEFAULT_NOTIFY_CHECK_TIME);
	settings_add_time("misc", "notify_whois_time", DEFAULT_NOTIFY_WHOIS_TIME);

	notify_tag = -1;
	read_settings();

	signal_add("notifylist event", (SIGNAL_FUNC) event_ison);
	signal_add("setup changed", (SIGNAL_FUNC) read_settings);
}

void notifylist_ison_deinit(void)
{
	g_source_remove(notify_tag);

	signal_remove("notifylist event", (SIGNAL_FUNC) event_ison);
	signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
}
