/*
 * enhanced_hist.c - Enhanced History Plugin for libpurple
 * Copyright (C) 2004-2008 Andrew Pangborn <gaim@andrewpangborn.com>
 * Copyright (C) 2007-2008 John Bailey <rekkanoryo@rekkanoryo.org>
 * Copyright (C) 2007 Ankit Singla
 *
 * 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.
 */

/* SUMMARY: Puts log of past conversations in the user's IM window and
 * allows the user to select the number of conversations to display from
 * purple plugin preferences.
 *
 * Author: 
 *         Andrew Pangborn  - gaim@andrewpangborn.com
 *
 * Contributors:
 *         Ankit Singla
 */

/* If you can't figure out what this line is for, DON'T TOUCH IT. */
#include "../common/pp_internal.h"

/* Purple headers */
#include <conversation.h>
#include <debug.h>
#include <log.h>
#include <plugin.h>
#include <pluginpref.h>
#include <prefs.h>
#include <signals.h>
#include <util.h>

/* Pidgin headers */
#include <gtkconv.h>
#include <gtkimhtml.h>
#include <gtkplugin.h>
#include <gtkprefs.h>
#include <gtkutils.h>
#include <pidgin.h>

/* libc headers */
#include <time.h>

#define ENHANCED_HISTORY_ID "gtk-plugin_pack-enhanced_history"

#define PREF_ROOT_GPPATH	"/plugins/gtk"
#define PREF_ROOT_PPATH		"/plugins/gtk/plugin_pack"
#define PREF_ROOT_PATH		"/plugins/gtk/plugin_pack/enhanced_history"
#define PREF_NUMBER_PATH	"/plugins/gtk/plugin_pack/enhanced_history/number"
#define PREF_BYTES_PATH		"/plugins/gtk/plugin_pack/enhanced_history/bytes"
#define PREF_MINS_PATH		"/plugins/gtk/plugin_pack/enhanced_history/minutes"
#define PREF_HOURS_PATH		"/plugins/gtk/plugin_pack/enhanced_history/hours"
#define PREF_DAYS_PATH		"/plugins/gtk/plugin_pack/enhanced_history/days"
#define PREF_DATES_PATH		"/plugins/gtk/plugin_pack/enhanced_history/dates"
#define PREF_IM_PATH		"/plugins/gtk/plugin_pack/enhanced_history/im"
#define PREF_CHAT_PATH		"/plugins/gtk/plugin_pack/enhanced_history/chat"

#define PREF_NUMBER_VAL		purple_prefs_get_int(PREF_NUMBER_PATH)
#define PREF_BYTES_VAL		purple_prefs_get_int(PREF_BYTES_PATH)
#define PREF_MINS_VAL		purple_prefs_get_int(PREF_MINS_PATH)
#define PREF_HOURS_VAL		purple_prefs_get_int(PREF_HOURS_PATH)
#define PREF_DAYS_VAL		purple_prefs_get_int(PREF_DAYS_PATH)
#define PREF_DATES_VAL		purple_prefs_get_bool(PREF_DATES_PATH)
#define PREF_IM_VAL			purple_prefs_get_bool(PREF_IM_PATH)
#define PREF_CHAT_VAL		purple_prefs_get_bool(PREF_CHAT_PATH)

static gboolean _scroll_imhtml_to_end(gpointer data)
{
	GtkIMHtml *imhtml = data;
	gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE);
	g_object_unref(G_OBJECT(imhtml));
	return FALSE;
}

static void historize(PurpleConversation *c)
{
	PurpleAccount *account = NULL;
	PurpleConversationType convtype;
	PidginConversation *gtkconv = NULL;
	GtkTextIter start;
	GtkIMHtmlOptions options;
	GList *logs = NULL, *logs_head = NULL;
	struct tm *log_tm = NULL, *local_tm = NULL;
	time_t t, log_time;
	double limit_time = 0.0;
	const char *name = NULL, *alias = NULL, *LOG_MODE = NULL;
	char *header = NULL, *protocol = NULL, *history = NULL;
	int conv_counter = 0;
	int limit_offset = 0;
	int byte_counter = 0;
	int check_time = 0;
	int log_size, overshoot;

	account = purple_conversation_get_account(c);
	name = purple_conversation_get_name(c);
	alias = name;
	LOG_MODE = purple_prefs_get_string("/purple/logging/format");

	/* If logging isn't enabled, don't show any history */
	if(!purple_prefs_get_bool("/purple/logging/log_ims") &&
			!purple_prefs_get_bool("/purple/logging/log_chats"))
		return;
	
	/* If the user wants to show 0 logs, stop now */
	if(PREF_NUMBER_VAL == 0) {
		return;
	}
	
	/* If the logging mode is html, set the output options to include no newline.
	 * Otherwise, it's normal text, so we don't need extra lines */
	if(strcasecmp(LOG_MODE, "html")==0) {
		options = GTK_IMHTML_NO_NEWLINE;
	} else {
		options = GTK_IMHTML_NO_COLOURS;
	}
	
	/* Determine whether this is an IM or a chat. In either case, if the user has that
	 * particular log type disabled, the logs file doesnt not get specified */
	convtype = purple_conversation_get_type(c);
	if (convtype == PURPLE_CONV_TYPE_IM && PREF_IM_VAL) {
		GSList *cur;
		GSList *buddies = NULL;

		/* Find buddies for this conversation. */
		buddies = purple_find_buddies(account, name);

		/* If we found at least one buddy, save the first buddy's alias. */
		if (buddies != NULL)
			alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);

		for(cur = buddies; cur; cur = cur->next) {
			PurpleBlistNode *node = cur->data;

			if(node && (node->prev || node->next)) {
				PurpleBlistNode *node2;

				for(node2 = node->parent->child; node2; node2 = node2->next) {
					logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM,
								purple_buddy_get_name((PurpleBuddy *)node2),
								purple_buddy_get_account((PurpleBuddy *)node2)), logs);
				}

				break;
			}
		}

		g_slist_free(buddies);

		if (logs)
			logs = g_list_sort(logs, purple_log_compare);
		else
			logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
	} else if (convtype == PURPLE_CONV_TYPE_CHAT && PREF_CHAT_VAL) {
		logs = purple_log_get_logs(PURPLE_LOG_CHAT,
			purple_conversation_get_name(c), purple_conversation_get_account(c));
	}

	gtkconv = PIDGIN_CONVERSATION(c);

	/* The logs are non-existant or the user has disabled this type for log displaying. */
	if (!logs) {
		return;
	}

	/* Keep a pointer to the head of the logs for freeing later */
	logs_head = logs;

	/* If all time prefs are not 0, prepare to check times */
	if (!(PREF_MINS_VAL == 0 && PREF_HOURS_VAL == 0 && PREF_DAYS_VAL == 0)) {
		check_time = 1;

		/* Grab current time and normalize it to UTC */
		t = time(NULL);
		local_tm = gmtime(&t);
		t = mktime(local_tm);

		limit_time = (PREF_MINS_VAL * 60.0) + (PREF_HOURS_VAL * 60.0 * 60.0) +
				(PREF_DAYS_VAL * 60.0 * 60.0 * 24.0);
	}

	/* The protocol will need to be adjusted for each log for correct display,
	   so save the current imhtml protocol_name to restore it later */
	protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml)));

	/* Calculate time for the first log */
	log_tm = gmtime(&((PurpleLog*)logs->data)->time);
	log_time = mktime(log_tm);

	/* Continue to add older logs until they run out or the conditions are no
	   longer met */
	while (logs && conv_counter < PREF_NUMBER_VAL
			&& byte_counter < PREF_BYTES_VAL
			&& (!check_time || difftime(t, log_time) < limit_time)) {
		guint flags;

		/* Get the current log's contents as a char* */
		history = purple_log_read((PurpleLog*)logs->data, &flags);
		log_size = strlen(history);

		if (flags & PURPLE_LOG_READ_NO_NEWLINE)
			options |= GTK_IMHTML_NO_NEWLINE;
		else
			options &= ~GTK_IMHTML_NO_NEWLINE;

		/* Update the overall byte count and determine if this log exceeds the limit */
		byte_counter += log_size;
		overshoot = byte_counter - PREF_BYTES_VAL;
		if (overshoot > 0) {
			/* Start looking at the maximum log size for a newline to break at */
			limit_offset = overshoot;
			/* Find the next \n, or stop if the end of the log is reached */
			while (history[limit_offset] && history[limit_offset] != '\n') {
				limit_offset++;
			}
			/* If we're at or very close to the end of the log, forget this log */
			if (!history[limit_offset] || (log_size - limit_offset < 3)) {
				limit_offset = -1;
			}
			else {
				/* Start at the first character after the newline */
				limit_offset++;
			}
		}

		conv_counter++;

		/* If this log won't fit at all, don't display it in the conversation */
		if (limit_offset != -1) {
			/* Set the correct protocol_name for this log */
			gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml),
					purple_account_get_protocol_name(((PurpleLog*)logs->data)->account));

			/* Prepend the contents of the log starting at the calculated offset */
			gtk_text_buffer_get_iter_at_offset(GTK_IMHTML(gtkconv->imhtml)->text_buffer,
					&start, 0);
			gtk_imhtml_insert_html_at_iter(GTK_IMHTML(gtkconv->imhtml),
						history + limit_offset, options, &start);

			/* Prepend the conversation header */
			if (PREF_DATES_VAL) {
				header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
					purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
				gtk_text_buffer_get_iter_at_offset(GTK_IMHTML(gtkconv->imhtml)->text_buffer,
						&start, 0);
				gtk_imhtml_insert_html_at_iter(GTK_IMHTML(gtkconv->imhtml),
						header, options, &start);
				g_free(header);
			}
		}

		g_free(history);

		if (limit_offset > 0) {
			/* This log had to be chopped to fit, so stop after this one */
			break;
		}

		logs = logs->next;

		/* Recalculate log time if we haven't run out of logs */
		if (logs) {
			log_tm = gmtime(&((PurpleLog*)logs->data)->time);
			log_time = mktime(log_tm);
		}
	}

	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<hr>", options);

	/* Restore the original protocol_name */
	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol);
	g_free(protocol);
	
	g_object_ref(G_OBJECT(gtkconv->imhtml));
	g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml);
	
	/* Clear the allocated memory that the logs are using */
	g_list_foreach(logs_head, (GFunc)purple_log_free, NULL);
	g_list_free(logs_head);
	
}

static gboolean
plugin_load(PurplePlugin *plugin)
{
	purple_signal_connect(purple_conversations_get_handle(),
			"conversation-created", plugin, PURPLE_CALLBACK(historize), NULL);

	return TRUE;
}

static GtkWidget *
eh_prefs_get_frame(PurplePlugin *plugin)
{
	GtkSizeGroup *sg = NULL;
	GtkWidget *vbox = NULL, *frame = NULL;

	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
	vbox = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE);

	/* heading for the more general options */
	frame = pidgin_make_frame(vbox, _("Display Options"));

	/* the integer pref for the number of logs to display */
	pidgin_prefs_labeled_spin_button(frame, _("Maximum number of conversations:"),
			PREF_NUMBER_PATH, 0, 255, NULL);

	/* the integer pref for maximum number of bytes to read back */
	pidgin_prefs_labeled_spin_button(frame, _("Maximum number of bytes:"),
			PREF_BYTES_PATH, 0, 1024*1024, NULL);

	/* the boolean preferences */
	pidgin_prefs_checkbox(_("Show dates with text"), PREF_DATES_PATH, frame);
	pidgin_prefs_checkbox(_("Show logs for IMs"), PREF_IM_PATH, frame);
	pidgin_prefs_checkbox(_("Show logs for chats"), PREF_CHAT_PATH, frame);

	/* heading for the age limit options */
	frame = pidgin_make_frame(vbox, _("Age Limit for Logs (0 to disable):"));

	/* the integer preferences for time limiting */
	pidgin_prefs_labeled_spin_button(frame, "Days:", PREF_DAYS_PATH, 0, 255, sg);
	pidgin_prefs_labeled_spin_button(frame, "Hours:", PREF_HOURS_PATH, 0, 255, sg);
	pidgin_prefs_labeled_spin_button(frame, "Minutes:", PREF_MINS_PATH, 0, 255, sg);

	gtk_widget_show_all(vbox);

	return vbox;
}


static PidginPluginUiInfo ui_info = {
	eh_prefs_get_frame, /* get prefs frame */
	0, /* page number - reserved */

	/* reserved pointers */
	NULL,
	NULL,
	NULL,
	NULL
};


static PurplePluginInfo info =
{
	PURPLE_PLUGIN_MAGIC,
	PURPLE_MAJOR_VERSION,
	PURPLE_MINOR_VERSION,
	PURPLE_PLUGIN_STANDARD,
	PIDGIN_PLUGIN_TYPE,
	0,
	NULL,
	PURPLE_PRIORITY_DEFAULT,

	ENHANCED_HISTORY_ID,
	NULL,
	PP_VERSION,
	NULL,
	NULL,

	"Andrew Pangborn <gaim@andrewpangborn.com>",
	PP_WEBSITE,

	plugin_load,
	NULL,
	NULL, 

	&ui_info,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};

static void
init_plugin(PurplePlugin *plugin)
{
	gboolean dates = FALSE, ims = FALSE, chats = FALSE;

	purple_prefs_add_none(PREF_ROOT_GPPATH);
	purple_prefs_add_none(PREF_ROOT_PPATH);
	purple_prefs_add_none(PREF_ROOT_PATH);

	if(purple_prefs_exists("/plugins/core/enhanced_history/int")) {
		if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_date"), "no"))
			dates = TRUE;
		if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_im"), "no"))
			ims = TRUE;
		if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_chat"), "no"))
			chats = TRUE;

		purple_prefs_add_int(PREF_NUMBER_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/int"));
		purple_prefs_add_int(PREF_BYTES_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/bytes"));
		purple_prefs_add_int(PREF_MINS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/mins"));
		purple_prefs_add_int(PREF_HOURS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/hours"));
		purple_prefs_add_int(PREF_DAYS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/days"));
		purple_prefs_add_bool(PREF_DATES_PATH, dates);
		purple_prefs_add_bool(PREF_IM_PATH, ims);
		purple_prefs_add_bool(PREF_CHAT_PATH, chats);

		purple_prefs_remove("/plugins/core/enhanced_history/int");
		purple_prefs_remove("/plugins/core/enhanced_history/bytes");
		purple_prefs_remove("/plugins/core/enhanced_history/mins");
		purple_prefs_remove("/plugins/core/enhanced_history/hours");
		purple_prefs_remove("/plugins/core/enhanced_history/days");
		purple_prefs_remove("/plugins/core/enhanced_history/string_date");
		purple_prefs_remove("/plugins/core/enhanced_history/string_im");
		purple_prefs_remove("/plugins/core/enhanced_history/string_chat");
		purple_prefs_remove("/plugins/core/enhanced_history");
	} else {
		/* Create these prefs with sensible defaults */
		purple_prefs_add_int(PREF_NUMBER_PATH, 10);
		purple_prefs_add_int(PREF_BYTES_PATH, 4096);
		purple_prefs_add_int(PREF_MINS_PATH, 0);
		purple_prefs_add_int(PREF_HOURS_PATH, 0);
		purple_prefs_add_int(PREF_DAYS_PATH, 0);
		purple_prefs_add_bool(PREF_DATES_PATH, TRUE);
		purple_prefs_add_bool(PREF_IM_PATH, TRUE);
		purple_prefs_add_bool(PREF_CHAT_PATH, FALSE);
	}

	info.name = _("Enhanced History");
	info.summary = _("An enhanced version of the history plugin.");
	info.description = _("An enhanced versoin of the history plugin. Grants ability to "
			"select the number of previous conversations to show instead of just one.");
}

PURPLE_INIT_PLUGIN(enhanced_history, init_plugin, info)

