#include <stdio.h>
#include <assert.h>

#include "internal.h"
#include "plugin.h"
#include "gtkplugin.h"
#include "gtkblist.h"
#include "gtkutils.h"
#include "connection.h"
#include "conversation.h"
#include "network.h"

#include <gtk/gtkgl.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "crazychat.h"
#include "cc_network.h"
#include "cc_interface.h"
#include "cc_gtk_gl.h"
#include "util.h"

/* --- begin type and global variable definitions --- */

static struct crazychat cc_info;

/* --- begin function declarations --- */

/**
 * Called by gaim plugin to start CrazyChat
 * @param cc	the crazychat struct
 */
static void cc_init(struct crazychat *cc);

/**
 * Called by gaim plugin to destroy CrazyChat
 * @param cc	the crazychat struct
 */
static void cc_destroy(struct crazychat *cc);


/**
 * Buddy menu drawing callback.  Adds a CrazyChat menuitem.
 * @param menu	the buddy menu widget
 * @param b	the buddy whose menu this is
 */
static gboolean cc_buddy_menu(GtkWidget *menu, GaimBuddy *b);

/**
 * Buddy menu callback.  Initiates the CrazyChat session.
 * @param item	the gtk buddy menu item
 * @param b	the buddy whose menu the item was in
 */
static void cc_menu_cb(GtkMenuItem *item, GaimBuddy *b);

/**
 * IM callback.  Handles receiving a CrazyChat session request.
 * @param account	the account we received the IM on
 * @param sender	the buddy who we received the message from
 * @param message	the message we received
 * @param flags		IM flags
 * @param data		user data
 */
static gboolean receive_im_cb(GaimAccount *account, char **sender,
		char **message,	int *flags, void *data);

/**
 * Displaying IM callback.  Drops CrazyChat messages from IM window.
 * @param account	the account we are displaying the IM on
 * @param conv		the conversation we are displaying the IM on
 * @param message	the message we are displaying
 * @param data		user data
 */
static gboolean display_im_cb(GaimAccount *account, const char *who, char **message,
			GaimConnection *conv, GaimMessageFlags flags, void *data);

/**
 * Callback for CrazyChat plugin configuration frame
 * @param plugin	the plugin data
 * @return	the configuration frame
 */
static GtkWidget *get_config_frame(GaimPlugin *plugin);

/**
 * TCP port callback.  Changes the port used to listen for new CC sessions
 * @param spin		the spinner button whose value changed
 * @param data		user data
 */
static void tcp_port_cb(GtkSpinButton *spin, struct crazychat *cc);

/**
 * UDP port callback.  Changes the port used to send/recv CC session frames
 * @param spin		the spinner button whose value changed
 * @param data		user data
 */
static void udp_port_cb(GtkSpinButton *spin, struct crazychat *cc);

/**
 * Features enabling/disabling callback.  Initializes the input processing
 * or shuts it down.
 * @param data		user data
 */
static void features_enable_cb(struct crazychat *cc);

/**
 * User signed on callback.  Now we have a buddy list to connect a signal
 * handler to.
 * @param gc		the gaim connection we are signed on
 * @param plugin	our plugin struct
 */
static gboolean cc_signed_on(GaimConnection *gc, void *plugin);

/**
 * Plugin loading callback.  If a buddy list exists, connect our buddy menu
 * drawing callback to the signal handler, otherwise, connect a signed on
 * signal handler so we know when we get a buddy list.
 * @param plugin	our plugin struct
 */
static gboolean plugin_load(GaimPlugin *plugin);

/**
 * Plugin unloading callback.  Disconnect all handlers and free data.
 * @param plugin	our plugin struct
 */
static gboolean plugin_unload(GaimPlugin *plugin);


/* --- end function declarations --- */


#define CRAZYCHAT_PLUGIN_ID "gtk-crazychat"

static PidginPluginUiInfo ui_info = {
	get_config_frame				/**< get_config_frame */
};

static GaimPluginInfo info = {
	2,						  /**< api_version    */
	GAIM_PLUGIN_STANDARD,				  /**< type           */
	PIDGIN_PLUGIN_TYPE,				  /**< ui_requirement */
	0,						  /**< flags          */
	NULL,						  /**< dependencies   */
	GAIM_PRIORITY_DEFAULT,				  /**< priority       */

	CRAZYCHAT_PLUGIN_ID,				  /**< id             */
	N_("Crazychat"),				  /**< name           */
	VERSION,					  /**< version        */
							  /**  summary        */
	N_("Plugin to establish a Crazychat session."),
							  /**  description    */
	N_("Uses Gaim to obtain buddy ips to connect for a Crazychat session"),
	"\n"
	"William Chan <chanman@stanford.edu>\n"
	"Ian Spiro <ispiro@stanford.edu>\n"
	"Charlie Stockman<stockman@stanford.edu>\n"
	"Steve Yelderman<scy@stanford.edu>",		  /**< author         */
	GAIM_WEBSITE,					  /**< homepage       */

	plugin_load,					  /**< load           */
	plugin_unload,					  /**< unload         */
	NULL,						  /**< destroy        */

	&ui_info,					  /**< ui_info        */
	&cc_info					  /**< extra_info     */
};

/* --- end plugin struct definition --- */

static void cc_init(struct crazychat *cc)
{
	/* initialize main crazychat thread */
	
	assert(cc);
	memset(cc, 0, sizeof(*cc));

	/* initialize network configuration */
	cc->tcp_port = DEFAULT_CC_PORT;
	cc->udp_port = DEFAULT_CC_PORT;

	/* disable input subsystem */
	//cc->features_state = 0;

	/* initialize input subsystem */
	cc->features_state = 1;
	cc->input_data = init_input(cc);
}

static void cc_destroy(struct crazychat *cc)
{
	assert(cc);

	if (cc->features_state) {
		destroy_input(cc->input_data);
	}
	memset(cc, 0, sizeof(*cc));
}

static gboolean cc_buddy_menu(GtkWidget *menu, GaimBuddy *b)
{
	GtkWidget *menuitem;

	menuitem = gtk_menu_item_new_with_mnemonic("CrazyChat");
	g_signal_connect(G_OBJECT(menuitem), "activate",
			G_CALLBACK(cc_menu_cb), b);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	return FALSE;
}

static void cc_menu_cb(GtkMenuItem *item, GaimBuddy *b)
{
	assert(item);
	assert(b);

	/* send the invite */
	cc_net_send_invite(&cc_info, b->name, b->account);
}

static gboolean receive_im_cb(GaimAccount *account, char **sender,
		char **message,	int *flags, void *data)
{
	struct crazychat *cc;

	cc = (struct crazychat*)data;
	assert(cc);
	if (!strncmp(*message, CRAZYCHAT_INVITE_CODE,
				strlen(CRAZYCHAT_INVITE_CODE))) {
		Debug(*message);
		char *split = strchr(*message, '!');
		assert(split);
		*split = 0;
		split++;
		cc_net_recv_invite(account, cc, *sender,
				&(*message)[strlen(CRAZYCHAT_INVITE_CODE)],
				split);
		return TRUE;
	} else if (!strncmp(*message, CRAZYCHAT_ACCEPT_CODE,
				strlen(CRAZYCHAT_ACCEPT_CODE))) {
		cc_net_recv_accept(account, cc, *sender,
				&(*message)[strlen(CRAZYCHAT_ACCEPT_CODE)]);
		return TRUE;
	} else if (!strncmp(*message, CRAZYCHAT_READY_CODE,
				strlen(CRAZYCHAT_READY_CODE))) {
		cc_net_recv_ready(account, cc, *sender);
		return TRUE;
	}
	
	return FALSE;
}

static gboolean display_im_cb(GaimAccount *account, GaimConversation *conv,
		char **message, void *data)
{
	struct crazychat *cc;

	cc = (struct crazychat*)data;
	assert(cc);
	if (!strncmp(*message, CRAZYCHAT_INVITE_CODE,
				strlen(CRAZYCHAT_INVITE_CODE))) {
		return TRUE;
	} else if (!strncmp(*message, CRAZYCHAT_ACCEPT_CODE,
				strlen(CRAZYCHAT_ACCEPT_CODE))) {
		return TRUE;
	} else if (!strncmp(*message, CRAZYCHAT_READY_CODE,
				strlen(CRAZYCHAT_READY_CODE))) {
		return TRUE;
	}

	return FALSE;
}

static GtkWidget *get_config_frame(GaimPlugin *plugin)
{
	GtkWidget *ret;
	GtkWidget *frame;
	GtkWidget *vbox, *hbox;
	GtkWidget *drawing_area;
	GtkWidget *label;
	GtkAdjustment *adj;
	GtkWidget *spinner;
	GtkWidget *button, *button1, *button2;
	GSList *group;
	struct draw_info *info;
	struct crazychat *cc;

	cc = (struct crazychat*)plugin->info->extra_info;
	assert(cc);

	/* create widgets */

	/* creating the config frame */
	ret = gtk_vbox_new(FALSE, 18);
	gtk_container_set_border_width(GTK_CONTAINER(ret), 12);

	/* make the network configuration frame */
	frame = pidgin_make_frame(ret, _("Network Configuration"));
	gtk_widget_show(frame);

	/* add boxes for packing purposes */
	vbox = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(frame), vbox, TRUE, TRUE, 0);
	gtk_widget_show(vbox);

	/* add widgets to row 1 */
	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);
	label = gtk_label_new(_("TCP port"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 15);
	gtk_widget_show(label);
	adj = (GtkAdjustment*)gtk_adjustment_new(DEFAULT_CC_PORT, 1,
			G_MAXUSHORT, 1, 1000, 0);
	spinner = gtk_spin_button_new(adj, 1, 0);
	g_signal_connect(G_OBJECT(spinner), "value_changed",
			G_CALLBACK(tcp_port_cb), cc);
	gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
	gtk_widget_show(spinner);
	label = gtk_label_new(_("UDP port"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 15);
	gtk_widget_show(label);
	adj = (GtkAdjustment*)gtk_adjustment_new(DEFAULT_CC_PORT, 1,
			G_MAXUSHORT, 1, 1000, 0);
	spinner = gtk_spin_button_new(adj, 1, 0);
	g_signal_connect(G_OBJECT(spinner), "value_changed",
			G_CALLBACK(udp_port_cb), cc);
	gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
	gtk_widget_show(spinner);

	/* make the feature configuration frame */
	frame = pidgin_make_frame(ret, _("Feature Calibration"));
	gtk_widget_show(frame);

	/* add hbox for packing purposes */
	hbox = gtk_hbox_new(TRUE, 40);
	gtk_box_pack_start(GTK_BOX(frame), hbox, TRUE, TRUE, 0);
	gtk_widget_show(hbox);

	/* add feature calibration options */

	/* add vbox for packing purposes */
	vbox = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
	gtk_widget_show(vbox);

	/* add enabled / disabled */
	button1 = gtk_radio_button_new_with_label(NULL, _("Enabled"));
	gtk_box_pack_start(GTK_BOX(vbox), button1, TRUE, TRUE, 0);
	gtk_widget_show(button1);
	
	group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button1));
	button2 = gtk_radio_button_new_with_label(group, _("Disabled"));
	gtk_box_pack_start(GTK_BOX(vbox), button2, TRUE, TRUE, 0);
	gtk_widget_show(button2);

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button1),
			cc->features_state);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button2),
			!cc->features_state);
	g_signal_connect_swapped(G_OBJECT(button1), "toggled",
			G_CALLBACK(features_enable_cb), cc);

	/* add vbox for packing purposes */
	vbox = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_end(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
	gtk_widget_show(vbox);

	/* add calibrate button */
	button = gtk_button_new_with_label("Calibrate");
	gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, FALSE, 0);
	gtk_widget_show(button);
	
	gtk_widget_show(ret);
	
	return ret;
}

static void tcp_port_cb(GtkSpinButton *spin, struct crazychat *cc)
{
	assert(spin);
	assert(cc);
	cc->tcp_port = gtk_spin_button_get_value_as_int(spin);
	Debug("New tcp port: %d\n", cc->tcp_port);
}

static void udp_port_cb(GtkSpinButton *spin, struct crazychat *cc)
{
	assert(spin);
	assert(cc);
	cc->udp_port = gtk_spin_button_get_value_as_int(spin);
	Debug("New udp port: %d\n", cc->udp_port);
}

static void features_enable_cb(struct crazychat *cc)
{
	Debug("Changing features state\n");
	cc->features_state = !cc->features_state;
	if (cc->features_state) {
		cc->input_data = init_input(cc);
	} else {
		if (cc->input_data) {
			gtk_widget_destroy(cc->input_data->widget);
		}
	}
}

static gboolean cc_signed_on(GaimConnection *gc, void *plugin)
{
	struct crazychat *extra;
	void *conv_handle;

	assert(plugin);
	extra = (struct crazychat*)((GaimPlugin*)plugin)->info->extra_info;
	gaim_signal_disconnect
	    (gaim_connections_get_handle(), "signed-on",
	     plugin, GAIM_CALLBACK(cc_signed_on));
	gaim_signal_connect(PIDGIN_BLIST
			    (gaim_get_blist()),
			    "drawing-menu", plugin,
			    GAIM_CALLBACK(cc_buddy_menu), NULL);
	conv_handle = gaim_conversations_get_handle();
	gaim_signal_connect(conv_handle, "received-im-msg", plugin,
		GAIM_CALLBACK(receive_im_cb), extra);
	gaim_signal_connect(conv_handle, "displaying-im-msg", plugin,
		GAIM_CALLBACK(display_im_cb), extra);
	return FALSE;
}

static gboolean plugin_load(GaimPlugin *plugin)
{
	GaimBuddyList *buddy_list;
	void *conv_handle;

	if (cc_init_gtk_gl())
		return FALSE;

	cc_init(&cc_info);
	buddy_list = gaim_get_blist();
	if (buddy_list) {
		gaim_signal_connect(PIDGIN_BLIST
				    (buddy_list),
				    "drawing-menu", plugin,
				    GAIM_CALLBACK(cc_buddy_menu), NULL);
		conv_handle = gaim_conversations_get_handle();
		gaim_signal_connect(conv_handle, "received-im-msg", plugin,
			GAIM_CALLBACK(receive_im_cb), &cc_info);
		gaim_signal_connect(conv_handle, "displaying-im-msg", plugin,
			GAIM_CALLBACK(display_im_cb), &cc_info);
	} else {
		gaim_signal_connect
		    (gaim_connections_get_handle(), "signed-on",
		     plugin, GAIM_CALLBACK(cc_signed_on), plugin);
	}

	Debug("CrazyChat plugin loaded.\n");
	
	return TRUE;
}

static gboolean plugin_unload(GaimPlugin *plugin)
{
	void *conv_handle;
	struct crazychat *extra;
	assert(plugin);
	extra = (struct crazychat*) plugin->info->extra_info;
	cc_destroy(extra);
	conv_handle = gaim_conversations_get_handle();
	gaim_signal_disconnect(PIDGIN_BLIST
			       (gaim_get_blist()),
			       "drawing-menu", plugin,
			       GAIM_CALLBACK(cc_buddy_menu));
	gaim_signal_disconnect(conv_handle, "received-im", plugin,
			       GAIM_CALLBACK(receive_im_cb));
	gaim_signal_disconnect(conv_handle, "displaying-im-msg", plugin,
			       GAIM_CALLBACK(display_im_cb));
	Debug("CrazyChat plugin unloaded.\n");
	return TRUE;
}

static void init_plugin(GaimPlugin *plugin)
{
	gtk_gl_init(NULL, NULL);
	memset(&cc_info, 0, sizeof(cc_info));
	Debug("CrazyChat plugin initialized\n");
}

GAIM_INIT_PLUGIN(crazychat, init_plugin, info)
