/*
 * -------------------------------------------------------
 * Copyright (C) 2003-2007 Tommi Saviranta <wnd@iki.fi>
 * -------------------------------------------------------
 * 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* ifdef HAVE_CONFIG_H */

#ifdef QUICKLOG

#include "qlog.h"
#include "client.h"
#include "irc.h"
#include "list.h"
#include "llist.h"
#include "miau.h"
#include "tools.h"
#include "commands.h"
#include "error.h"
#include "common.h"
#include "messages.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>



static list_type *qlog = NULL;



#ifdef QLOGSTAMP
static const char *qlog_add_timestamp(qlogentry *entry, char *buf,
		size_t size);
#endif /* QLOGSTAMP */

static channel_type *qlog_get_channel(const char *msg);



/*
 * Walk thru qlog to see if any of active channels has some log to be replayed.
 * Basically this could be done while writing qlog, but this would be waste of
 * CPU-time as qlog can be checked just as well before replaying.
 */
void
qlog_check(int age)
{
	llist_node *iterl;
	list_type *iter;
	time_t oldest;

	if (age == 0) {
		oldest = 0;
	} else {
		oldest = time(NULL) - age;
	}

	/* First set each channel not to have qlog. */
	for (iterl = active_channels.head; iterl != NULL; iterl = iterl->next) {
		channel_type *chan;
		chan = (channel_type *) iterl->data;
		chan->hasqlog = 0;
	}
	for (iterl = old_channels.head; iterl != NULL; iterl = iterl->next) {
		channel_type *chan;
		chan = (channel_type *) iterl->data;
		chan->hasqlog = 0;
	}

	for (iter = qlog; iter != NULL; iter = iter->next) {
		qlogentry *entry;
		entry = (qlogentry *) iter->data;
		if (entry->timestamp < oldest) {
			continue;
		}
		/* Don't waste time looking for channel if line's privmsg. */
#ifdef INBOX
		if (entry->privmsg == 0)
#endif /* ifdef else INBOX */
		{
			channel_type *chan;
			chan = qlog_get_channel(entry->text);
			if (chan != NULL) {
				chan->hasqlog = 1;
			}
		}
	}
} /* void qlog_check(int age) */



void
qlog_replay_header(connection_type *client)
{
	channel_type *chan;
	llist_node *iter;

	for (iter = active_channels.head; iter != NULL; iter = iter->next) {
		chan = (channel_type *) iter->data;
		if (chan->hasqlog) {
			irc_write(client, ":%s NOTICE %s :%s",
					status.nickname,
					chan->name,
					CLNT_QLOGSTART);
		}
	}
} /* void qlog_replay_header(connection_type *client) */



void
qlog_replay_footer(connection_type *client)
{
	channel_type *chan;
	llist_node *iter;

	for (iter = active_channels.head; iter != NULL; iter = iter->next) {
		chan = (channel_type *) iter->data;
		if (chan->hasqlog) {
			irc_write(client, ":%s NOTICE %s :%s",
					status.nickname,
					chan->name,
					CLNT_QLOGEND);
		}
	}
} /* void qlog_replay_footer(connection_type *client) */



static int
is_my_quit(const char *msg)
{
	const char *quit, *end;
	int len;
#define TMP_LEN 64
	char tmp[TMP_LEN + 1];
	tmp[TMP_LEN] = '\0';

	/* the cheapest test first... */

	if (msg == NULL) {
		return 0;
	}

	/* line must begin with a colon */
	if (*msg != ':') {
		return 0;
	}

	/* and it must be about me */
	len = snprintf(tmp, TMP_LEN, "%s!", status.nickname);
	if (strncmp(msg + 1, tmp, len) != 0) {
		return 0;
	}

	/* and it must have "QUIT :" in it */
	quit = strstr(msg, "QUIT :");
	if (quit == NULL) {
		return 0;
	}

	/* and "QUIT :" must come before the second colon in the message */
	end = strchr(msg + 1, (int) ':');
	if (end == NULL) {
		return 0;
	}
	if (quit > end) {
		return 0;
	}

	/*
	 * We're fairly sure it's about us now, but we still cannot be 100%
	 * sure. We could check username and hostname, but some servers have
	 * tendency to mutilate them beyond recognition.
	 */

	return 1;
} /* static int is_my_quit(const char *msg) */



/*
 * Replay quicklog data.
 */
void
qlog_replay(connection_type *client, time_t oldest)
{
	list_type *iter;
#ifdef QLOGSTAMP
	char qlogbuf[IRC_MSGLEN];
#endif /* QLOGSTAMP */

	if (client == NULL) {
#ifdef ENDUSERDEBUG
		enduserdebug("qlog_replay(NULL, oldest)");
#endif /* ifdef ENDUSERDEBUG */
		return;
	}

	if (oldest != 0) {
		oldest = time(NULL) - oldest;
	}

	/* Walk through quicklog. */
	for (iter = qlog; iter != NULL; iter = iter->next) {
		qlogentry *entry;
		entry = (qlogentry *) iter->data;

		/* also skip too old entries */
		if (entry->timestamp < oldest) {
			continue;
		}

		if (cfg.qlog_no_my_quit == 1) {
			if (is_my_quit(entry->text) == 1) {
				continue;
			}
		}

#ifdef QLOGSTAMP
		if (cfg.timestamp != TS_NONE) {
			const char *out;
			out = qlog_add_timestamp(entry, qlogbuf, IRC_MSGLEN);
			irc_write(client, "%s", out);
		} else {
			irc_write(client, "%s", entry->text);
		}
#else /* ifdef QLOGSTAMP */
		irc_write(client, "%s", entry->text);
#endif /* ifdef else QLOGSTAMP */
	}
} /* void qlog_replay(connection_type *client, time_t oldest) */



#ifdef QLOGSTAMP
/*
 * Add timestamp in qlogentry.
 */
static const char *
qlog_add_timestamp(qlogentry *entry, char *buf, size_t size)
{
	int cmd, i;
	char *p;
/* TSLEN: "[HH:MM:SS]\0" == 11 */
#define TSLEN 11
	char stamp[TSLEN];
	
	/* attach tag only if we know what we're doing */
	p = nextword(entry->text);
	if (p == NULL) {
		return entry->text;
	}
	/* Next find out what command it was. */
	i = pos(p, ' ');
	if (i != -1 && i < TSLEN) {
		char tmp[18];
		strncpy(tmp, p, i);
		tmp[i] = '\0';
		cmd = command_find(tmp);
	} else {
		return entry->text;
	}

	/* Is this something we can handle? */
	if (cmd != CMD_PRIVMSG && cmd != CMD_NOTICE && cmd != CMD_QUIT &&
			cmd != CMD_PART && cmd != CMD_KICK && cmd != CMD_KILL) {
		return entry->text;
	}

	switch (cfg.timestamp) {
		case TS_BEGINNING:
		{
			char rep;

			p = strchr(entry->text + 1, (int) ':');
			if (p != NULL) {
				if (p[1] == '\0') {
					p = NULL;
				} else if (p[1] == '\1') {
					p = strchr(p, (int) ' ');
				}
			}
			/*
			 * Confused? Don't break already broken things any
			 * further.
			 */
			if (p == NULL) {
				return entry->text;
			}
			rep = *p;
			*p = '\0'; /* ugly, but makes life easier */

			strftime(stamp, TSLEN, "[%H:%M:%S]",
					localtime(&entry->timestamp));

			snprintf(buf, size, "%s%c%s %s",
					entry->text, rep, stamp, p + 1);
			buf[size - 1] = '\0';
			*p = rep;
			
			return buf;
		}

		case TS_END:
		{
			int add_one;
			int len;

			len = strlen(entry->text);
			if (entry->text[len - 1] == '\1') {
				entry->text[len - 1] = '\0';
				add_one = 1;
			} else {
				add_one = 0;
			}

			strftime(stamp, TSLEN, "[%H:%M:%S]",
					localtime(&entry->timestamp));

			snprintf(buf, size, "%s %s%s", entry->text,
					stamp, add_one == 1 ? "\1" : "");
			buf[size - 1] = '\0';
			return buf;
		}

		default:
			return entry->text;
	}
} /* static const char *qlog_add_timestamp(qlogentry *entry, char *buf,
		size_t size) */
#endif /* ifdef QLOGSTAMP */



/*
 * Remove old lines from quicklog.
 */
void
qlog_flush(time_t oldest, int move_to_inbox)
{
	if (qlog == NULL) {
		return;
	}

	while (qlog != NULL) { /* secondary exit condition */
		qlogentry *entry;
		entry = (qlogentry *) qlog->data;

		/* primary exit condition */
		if (entry->timestamp > oldest) {
			break;
		}

#ifdef INBOX
		if (entry->privmsg == 1 && move_to_inbox == 1) {
			char *message;

			/* Get sender (split it) and beginning of payload. */
			message = strstr(entry->text + 1, " :");

			if (message == NULL) {
#ifdef ENDUSERDEBUG
				enduserdebug("converting invalid qlog-line?");
				enduserdebug("%s", entry->text);
#endif /* ifdef ENDUSERDEBUG */
				goto drop_free;
			}
			strtok(entry->text, " ");

			if (entry->text[0] == '\0' || message[0] == '\0') {
#ifdef ENDUSERDEBUG
				enduserdebug("invalid stuff in qlog");
#endif /* ifdef else ENDUSERDEBUG */
				goto drop_free;
			}

			/* termination and validity guaranteed */
			if (inbox != NULL) {
				fprintf(inbox, "%s <%s> %s\n",
						get_timestamp(&entry->timestamp,
							TIMESTAMP_SHORT),
						entry->text + 1, message + 2);
				fflush(inbox);
			}
		}
#endif /* INBOX */

#ifdef INBOX
drop_free:
#endif /* ifdef INBOX */
		xfree(entry->text);
		xfree(entry);
		qlog = list_delete(qlog, qlog);
	}
} /* void qlog_drop_old(time_t oldest, int move_to_inbox) */



/*
 * Write lines to quick log.
 */
void
qlog_write(const int privmsg, char *format, ...)
{
	qlogentry *line;
	va_list va;
	char buf[BUFFERSIZE];

	/* First remove possible outdated lines. */
	qlog_flush(time(NULL) - cfg.qloglength * 60, 1);

	va_start(va, format);
	vsnprintf(buf, IRC_MSGLEN - 2, format, va);
	va_end(va);
	buf[IRC_MSGLEN - 3] = '\0';

	/* Create new line of quicklog. */
	line = (qlogentry *) xmalloc(sizeof(qlogentry));
	time(&line->timestamp);
	line->text = xstrdup(buf);
#ifdef INBOX
	line->privmsg = privmsg;
#endif /* ifdef INBOX */
	qlog = list_add_tail(qlog, line);
} /* void qlog_write(const int privmsg, char *format, ...) */



static channel_type *
qlog_get_channel(const char *msg)
{
	channel_type *chan;
	char *b, *t;
	int l;

	b = strchr(msg, (int) ' ');
	if (b == NULL) {
		return NULL;
	}

	b = strchr(b + 1, (int) ' ');
	if (b == NULL) {
		return NULL;
	}

	l = pos(b + 1, ' ');
	if (l == -1) {
		return NULL;
	}
	
	t = (char *) xmalloc(l + 1);
	memcpy(t, b + 1, l);
	t[l] = '\0';
	/* Check active/old_channels. */
	chan = channel_find(t, LIST_ACTIVE);
	if (chan == NULL) {
		chan = channel_find(t, LIST_OLD);
	}
	xfree(t);

	return chan;
} /* static channel_type *qlog_get_channel(const char *msg) */



#endif /* ifdef QUICKLOG */


syntax highlighted by Code2HTML, v. 0.9.1