#import <ObjXMPP/namespaces.h>
#include <string.h>

#import "JubGtkRosterUI.h"
#import "JubGObjectMap.h"
#import "JubGtkChatUI.h"
#import "JubGtkHelper.h"

static void roster_row_activated(GtkTreeView *tree_view, GtkTreePath *path,
    GtkTreeViewColumn *column, gpointer data)
{
	JubGtkRosterUI *roster = data;
	GtkTreeIter row_iter;
	GtkTreeModel *tree_model;
	gchar *jid_s;
	XMPPJID *jid;
	OFAutoreleasePool *pool;

	tree_model = gtk_tree_view_get_model(tree_view);
	gtk_tree_model_get_iter(tree_model, &row_iter, path);
	gtk_tree_model_get(tree_model, &row_iter, 1, &jid_s, -1);

	// This was a group row
	if (!jid_s) return;

	pool = [OFAutoreleasePool new];
	jid = [XMPPJID JIDWithString: [OFString stringWithUTF8String: jid_s]];

	[roster performSelectorOnMainThread: @selector(chatForJID:)
				 withObject: jid
			      waitUntilDone: NO];
	[pool release];
}

static void presence_changed(GtkComboBox *combo_box, gpointer data)
{
	XMPPPresence *pres;
	XMPPConnection *connection = data;
	OFAutoreleasePool *pool = [OFAutoreleasePool new];

	const char *status = gtk_combo_box_get_active_id(combo_box);

	if (!strcmp(status, "unavailable"))
		pres = [XMPPPresence presenceWithType: @"unavailable"];
	else {
		pres = [XMPPPresence presence];
		if (strcmp(status, "available"))
			[pres setShow: @(status)];
	}

	[connection sendStanza: pres];

	[pool release];
}

static gboolean filter_roster_by_presence(GtkTreeModel *model,
    GtkTreeIter *iter, gpointer data)
{
	char *status;
	gtk_tree_model_get(model, iter, 2, &status, -1);

	// Groups have no status
	if (!status)
		return TRUE;

	if (!strcmp(status, "unavailable"))
		return FALSE;

	return TRUE;
}

@implementation JubGtkRosterUI
- initWithClient: (JubChatClient*)client_
{
	self = [super init];

	@try {
		GtkTreeView *roster_view;
		GtkBuilder *builder;

		groupMap = [[OFMapTable alloc]
		    initWithKeyFunctions: keyFunctions
			  valueFunctions: rowRefFunctions];
		contactMap = [[OFMutableDictionary alloc] init];
		chatMap = [[OFMutableDictionary alloc] init];
		client = [client_ retain];

		[client.contactManager addDelegate: self];

		builder = gtk_builder_new();
		gtk_builder_add_from_file(builder, "data/gtk/roster.ui", NULL);
		gtk_builder_connect_signals(builder, NULL);

		roster_window =
		    GTK_WIDGET(gtk_builder_get_object(builder, "RosterWindow"));

		gtk_widget_show(roster_window);

		roster_model = GTK_TREE_STORE(gtk_builder_get_object(builder,
		    "RosterTreeStore"));

		roster_filter = GTK_TREE_MODEL_FILTER(
		    gtk_builder_get_object(builder, "RosterTreeModelFilter"));

		gtk_tree_model_filter_set_visible_func(roster_filter,
		    filter_roster_by_presence, NULL, NULL);

		roster_view = GTK_TREE_VIEW(gtk_builder_get_object(builder,
			"RosterTreeView"));

		g_signal_connect(roster_view, "row_activated",
		    G_CALLBACK(roster_row_activated), self);

		presence_combo = GTK_COMBO_BOX(gtk_builder_get_object(builder,
			"PresenceComboBox"));

		presence_combo_changed_handler_id =
		    g_signal_connect(presence_combo, "changed",
			G_CALLBACK(presence_changed), client.connection);

		g_object_unref(G_OBJECT(builder));
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[client.contactManager removeDelegate: self];
	[groupMap release];
	[contactMap release];
	[chatMap release];
	[client release];

	gtk_widget_destroy(roster_window);

	[super dealloc];
}

// FIXME: This needs to move somewhere else
- (JubGtkChatUI*)chatForJID: (XMPPJID*)jid
{
	OFAutoreleasePool *pool = [OFAutoreleasePool new];
	JubGtkChatUI *chat =
	    [chatMap objectForKey: [jid bareJID]];
	if (chat == nil) {
		OFString * title = [@"Chat with " stringByAppendingString:
		    [jid bareJID]];

		chat = [[[JubGtkChatUI alloc]
		    initWithTitle: title
		       closeBlock: ^{
				[chatMap removeObjectForKey: [jid bareJID]];
			}
			sendBlock: ^(OFString *text) {
				XMPPMessage *msg =
				    [XMPPMessage messageWithType: @"chat"];
				msg.to = jid;
				msg.body = text;
				[client.connection sendStanza: msg];
			}
		] autorelease];

		[chatMap setObject: chat
			    forKey: [jid bareJID]];
	}

	[pool release];

	return chat;
}

-  (void)contact: (XMPPContact*)contact
  didSendMessage: (XMPPMessage*)message
{
	if (message.body == nil || ![message.type isEqual: @"chat"])
		return;

	JubGtkChatUI *chat = [self chatForJID: message.from];
	[chat addMessage: message.body
		  sender: [message.from bareJID]];
}

/* Roster Delegate methods */
- (void)Jub_addRosterItem: (XMPPRosterItem*)item
		    group: (OFString*)group
{
	g_idle_add_block(^{
		GtkTreeIter group_iter, contact_iter;
		GtkTreeRowReference *group_ref, *contact_ref;
		GtkTreePath *group_path, *contact_path;
		OFString *bareJID = [item.JID bareJID];
		OFMapTable *contactRows;

		if (!(contactRows = [contactMap objectForKey: bareJID])) {
			contactRows = [OFMapTable
			    mapTableWithKeyFunctions: keyFunctions
				      valueFunctions: rowRefFunctions];

			[contactMap setObject: contactRows
				       forKey: bareJID];
		}

		group_ref = [groupMap valueForKey: group];

		if (!group_ref) {
			// Create new group row
			gtk_tree_store_append(roster_model, &group_iter, NULL);
			gtk_tree_store_set(roster_model, &group_iter,
			    0, [group UTF8String], -1);

			group_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
				roster_model), &group_iter);

			group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
				roster_model), group_path);

			[groupMap setValue: group_ref
				    forKey: group];
		} else {
			// Get iter for existing group row
			group_path = gtk_tree_row_reference_get_path(group_ref);

			gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
			    &group_iter, group_path);
		}
		gtk_tree_path_free(group_path);

		// Create new contact row
		gtk_tree_store_append(roster_model, &contact_iter, &group_iter);
		if (item.name)
			gtk_tree_store_set(roster_model, &contact_iter,
			    0, [item.name UTF8String],
			    1, [bareJID UTF8String],
			    2, "unavailable", -1);
		else
			gtk_tree_store_set(roster_model, &contact_iter,
			    0, [item.JID.node UTF8String],
			    1, [bareJID UTF8String],
			    2, "unavailable", -1);

		contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
			roster_model), &contact_iter);

		contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
			roster_model), contact_path);

		gtk_tree_path_free(contact_path);

		[contactRows setValue: contact_ref
			       forKey: group];
	});
}

- (void)Jub_removeRosterItem: (XMPPRosterItem*)item
		       group: (OFString*)group
{
	g_idle_add_block(^{
		GtkTreeIter contact_iter, group_iter;
		GtkTreePath *contact_path, *group_path;
		GtkTreeRowReference *contact_ref, *group_ref;
		OFString *bareJID = [item.JID bareJID];
		OFMapTable *contactRows = [contactMap objectForKey: bareJID];

		contact_ref = [contactRows valueForKey: group];
		contact_path = gtk_tree_row_reference_get_path(contact_ref);
		gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
		    &contact_iter, contact_path);
		gtk_tree_path_free(contact_path);

		gtk_tree_store_remove(roster_model, &contact_iter);

		group_ref = [groupMap valueForKey: group];
		group_path = gtk_tree_row_reference_get_path(group_ref);
		gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
		    &group_iter, group_path);

		if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(roster_model),
		    &group_iter)) {
			gtk_tree_store_remove(roster_model, &group_iter);
			[groupMap removeValueForKey: group];
		}

		gtk_tree_path_free(group_path);

		[contactRows removeValueForKey: group];
		if([contactRows count] == 0)
			[contactMap removeObjectForKey: bareJID];
	});
}

- (void)contactManager: (XMPPContactManager*)manager
	 didAddContact: (XMPPContact*)contact
{
	XMPPRosterItem *rosterItem = contact.rosterItem;
	OFArray *groups = rosterItem.groups;;

	if (groups == nil)
		groups = @[ @"General" ];

	for (OFString *group in groups)
		[self Jub_addRosterItem: rosterItem
				  group: group];
}

- (void)contactManager: (XMPPContactManager*)manager
      didRemoveContact: (XMPPContact*)contact
{
	XMPPRosterItem *rosterItem = contact.rosterItem;
	OFArray *groups = rosterItem.groups;

	if (groups == nil)
		groups = @[ @"General" ];

	for (OFString *group in groups)
		[self Jub_removeRosterItem: rosterItem
				     group: group];
}

-	     (void)contact: (XMPPContact*)contact
  willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem;
{
	// Remove contact from old set of groups
	XMPPRosterItem *oldItem = contact.rosterItem;
	OFArray *groups = oldItem.groups;

	if (groups == nil)
		groups = @[ @"General" ];

	for (OFString *group in groups)
		[self Jub_removeRosterItem: oldItem
				     group: group];

	// Add contact to new set of groups
	groups = rosterItem.groups;

	if (groups == nil)
		groups = @[ @"General" ];

	for (OFString *group in groups)
		[self Jub_addRosterItem: rosterItem
				  group: group];
}

-   (void)contact: (XMPPContact*)contact
  didSendPresence: (XMPPPresence*)presence
{
	g_idle_add_block(^{
		GtkTreeIter iter;
		GtkTreePath *path;
		GtkTreeRowReference *ref;
		OFString *bareJID = [contact.rosterItem.JID bareJID];
		OFMapTable *contactRows = [contactMap objectForKey: bareJID];

		for (OFString *group in contact.rosterItem.groups) {
			ref = [contactRows valueForKey: group];
			path = gtk_tree_row_reference_get_path(ref);
			gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
			    &iter, path);
			gtk_tree_path_free(path);

			if ([presence.type isEqual: @"available"])
				if (presence.show != nil)
					gtk_tree_store_set(roster_model, &iter,
					    2, [presence.show UTF8String], -1);
				else
					gtk_tree_store_set(roster_model, &iter,
					    2, "available", -1);
			else if ([presence.type isEqual: @"unavailable"])
				gtk_tree_store_set(roster_model, &iter,
				    2, "unavailable", -1);

			gtk_tree_store_set(roster_model, &iter,
			    3, [presence.status UTF8String], -1);
		}

		gtk_tree_model_filter_refilter(roster_filter);
	});
}

-      (void)client: (JubChatClient*)client_
  didChangePresence: (XMPPPresence*)presence
{
	OFString *tooltip = @"";
	OFString *show =
	    [[presence elementForName: @"show"
			    namespace: XMPP_NS_CLIENT] stringValue];
	OFString *status =
	    [[presence elementForName: @"status"
			    namespace: XMPP_NS_CLIENT] stringValue];

	if (status != nil)
		tooltip = [@"<b>Status:</b> " stringByAppendingString: status];

	g_idle_add_block(^{
		// Block the PresenceComboBox's changed handler, so it doesn't
		// fire and resend presence
		g_signal_handler_block(presence_combo,
		    presence_combo_changed_handler_id);

		if ([presence.type isEqual: @"unavailable"])
			gtk_combo_box_set_active_id(presence_combo,
			    "unavailable");
		else if (show == nil)
			gtk_combo_box_set_active_id(presence_combo,
			    "available");
		else
			gtk_combo_box_set_active_id(presence_combo,
			    [show UTF8String]);

		// Unblock the changed handler
		g_signal_handler_unblock(presence_combo,
		    presence_combo_changed_handler_id);

		gtk_widget_set_tooltip_markup(GTK_WIDGET(presence_combo),
		    [tooltip UTF8String]);
	});
}
@end
