/*
 * libokcupid
 *
 * libokcupid is the property of its developers.  See the COPYRIGHT file
 * for more details.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "okcupid.h"
#include "okc_connection.h"
#include "okc_messages.h"
#include "okc_blist.h"

/******************************************************************************/
/* Utility functions */
/******************************************************************************/

gchar *okc_convert_unicode(const gchar *input)
{
	gunichar unicode_char;
	gchar unicode_char_str[6];
	gint unicode_char_len;
	gchar *next_pos;
	gchar *input_string;
	gchar *output_string;

	if (input == NULL)
		return NULL;

	next_pos = input_string = g_strdup(input);

	while ((next_pos = strstr(next_pos, "\\u")))
	{
		/* grab the unicode */
		sscanf(next_pos, "\\u%4x", &unicode_char);
		/* turn it to a char* */
		unicode_char_len = g_unichar_to_utf8(unicode_char, unicode_char_str);
		/* shove it back into the string */
		g_memmove(next_pos, unicode_char_str, unicode_char_len);
		/* move all the data after the \u0000 along */
		g_stpcpy(next_pos + unicode_char_len, next_pos + 6);
	}

	output_string = g_strcompress(input_string);
	g_free(input_string);

	return output_string;
}

/* Like purple_strdup_withhtml, but escapes htmlentities too */
gchar *okc_strdup_withhtml(const gchar *src)
{
	gulong destsize, i, j;
	gchar *dest;

	g_return_val_if_fail(src != NULL, NULL);

	/* New length is (length of src) + (number of \n's * 3) + (number of &'s * 5) +
		(number of <'s * 4) + (number of >'s *4) + (number of "'s * 6) -
		(number of \r's) + 1 */
	destsize = 1;
	for (i = 0; src[i] != '\0'; i++)
	{
		if (src[i] == '\n' || src[i] == '<' || src[i] == '>')
			destsize += 4;
		else if (src[i] == '&')
			destsize += 5;
		else if (src[i] == '"')
			destsize += 6;
		else if (src[i] != '\r')
			destsize++;
	}

	dest = g_malloc(destsize);

	/* Copy stuff, ignoring \r's, because they are dumb */
	for (i = 0, j = 0; src[i] != '\0'; i++) {
		if (src[i] == '\n') {
			strcpy(&dest[j], "<BR>");
			j += 4;
		} else if (src[i] == '<') {
			strcpy(&dest[j], "&lt;");
			j += 4;
		} else if (src[i] == '>') {
			strcpy(&dest[j], "&gt;");
			j += 4;
		} else if (src[i] == '&') {
			strcpy(&dest[j], "&amp;");
			j += 5;
		} else if (src[i] == '"') {
			strcpy(&dest[j], "&quot;");
			j += 6;
		} else if (src[i] != '\r')
			dest[j++] = src[i];
	}

	dest[destsize-1] = '\0';

	return dest;
}

/******************************************************************************/
/* PRPL functions */
/******************************************************************************/

static const char *okc_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
{
	return "okcupid";
}

static GList *okc_statuses(PurpleAccount *account)
{
	GList *types = NULL;
	PurpleStatusType *status;

	purple_debug_info("okcupid", "statuses\n");

	/* OkCupid people are either online, online with no IM, offline */
	
	status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE);
	types = g_list_append(types, status);

	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
	types = g_list_append(types, status);

	purple_debug_info("okcupid", "statuses return\n");

	return types;
}

static gboolean okc_get_messages_failsafe(OkCupidAccount *oca)
{
	if (oca->last_messages_download_time < (time(NULL) - (60*5))) {
		/* Messages haven't been downloaded in a while-
		 * something is probably wrong */
		purple_debug_warning("okcupid",
				"executing message check failsafe\n");
		okc_get_new_messages(oca);
	}

	return TRUE;
}

static void okc_login_cb(OkCupidAccount *oca, gchar *response, gsize len,
		gpointer userdata)
{
	JsonParser *parser;
	JsonNode *root;
	JsonObject *message;
	gint status;

	purple_connection_update_progress(oca->pc, _("Authenticating"), 2, 3);

	if (!response)
	{
		purple_connection_error(oca->pc, "No login response");
		return;
	}
	parser = json_parser_new();
	if (!json_parser_load_from_data(parser, response, len, NULL))
	{
		purple_connection_error(oca->pc, "Error parsing login response");
		return;
	}
	root = json_parser_get_root(parser);
	message = json_node_get_object(root);
	status = json_node_get_int(json_object_get_member(message, "status"));
	if (status >= 100)
	{
		purple_connection_error(oca->pc, "Bad username or password");
		return;
	}

	/* ok, we're logged in now! */
	purple_connection_set_state(oca->pc, PURPLE_CONNECTED);

	/* This will kick off our long-poll message retrieval loop */
	okc_get_new_messages_now(oca);
	
	oca->perpetual_messages_timer = purple_timeout_add_seconds(15,
			(GSourceFunc)okc_get_messages_failsafe, oca);
}

static void okc_login(PurpleAccount *account)
{
	OkCupidAccount *oca;
	gchar *postdata, *encoded_username, *encoded_password;

	purple_debug_info("okcupid", "login\n");

	/* Create account and initialize state */
	oca = g_new0(OkCupidAccount, 1);
	oca->account = account;
	oca->pc = purple_account_get_connection(account);
	
	oca->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */
	oca->server_seqid = purple_account_get_int(oca->account, "server_seqid", 0);
	oca->server_gmt = purple_account_get_int(oca->account, "server_gmt", 0);
	
	oca->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal,
			g_free, g_free);
	oca->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
			g_free, g_free);
	oca->waiting_conns = g_queue_new();

	account->gc->proto_data = oca;

	purple_connection_set_state(oca->pc, PURPLE_CONNECTING);
	purple_connection_update_progress(oca->pc, _("Connecting"), 1, 3);

	encoded_username = g_strdup(purple_url_encode(
			purple_account_get_username(account)));
	encoded_password = g_strdup(purple_url_encode(
			purple_account_get_password(account)));

	postdata = g_strdup_printf("username=%s&password=%s",
			encoded_username, encoded_password);
	g_free(encoded_username);
	g_free(encoded_password);

	okc_post_or_get(oca, OKC_METHOD_POST | OKC_METHOD_SSL, "www.okcupid.com",
			"/login", postdata, okc_login_cb, NULL, FALSE);
	g_free(postdata);
}

static void okc_close(PurpleConnection *pc)
{
	OkCupidAccount *oca;

	purple_debug_info("okcupid", "disconnecting account\n");

	oca = pc->proto_data;

	okc_post_or_get(oca, OKC_METHOD_POST, NULL, "/logout",
			"ajax=1", NULL, NULL, FALSE);
	
	if (oca->new_messages_check_timer)
		purple_timeout_remove(oca->new_messages_check_timer);
	if (oca->buddy_presence_timer)
		purple_timeout_remove(oca->buddy_presence_timer);
	if (oca->perpetual_messages_timer)
		purple_timeout_remove(oca->perpetual_messages_timer);

	purple_debug_info("okcupid", "destroying %d waiting connections\n",
			g_queue_get_length(oca->waiting_conns));

	while (!g_queue_is_empty(oca->waiting_conns))
		okc_connection_destroy(g_queue_pop_tail(oca->waiting_conns));
	g_queue_free(oca->waiting_conns);

	purple_debug_info("okcupid", "destroying %d incomplete connections\n",
			g_slist_length(oca->conns));

	while (oca->conns != NULL)
		okc_connection_destroy(oca->conns->data);

	while (oca->dns_queries != NULL) {
		PurpleDnsQueryData *dns_query = oca->dns_queries->data;
		purple_debug_info("okcupid", "canceling dns query for %s\n",
					purple_dnsquery_get_host(dns_query));
		oca->dns_queries = g_slist_remove(oca->dns_queries, dns_query);
		purple_dnsquery_destroy(dns_query);
	}

	g_hash_table_destroy(oca->cookie_table);
	g_hash_table_destroy(oca->hostname_ip_cache);
	
	//Store server_seqid and server_gmt so that we dont download all the messages on startup
	purple_account_set_int(oca->account, "server_seqid", oca->server_seqid);
	purple_account_set_int(oca->account, "server_gmt", oca->server_gmt);
	
	g_free(oca);
}

static void okc_buddy_free(PurpleBuddy *buddy)
{
	OkCupidBuddy *obuddy = buddy->proto_data;
	if (obuddy != NULL)
	{
		buddy->proto_data = NULL;

		g_free(obuddy->status_cache);
		g_free(obuddy->thumb_url);
		g_free(obuddy);
	}
}

void okc_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group)
{
	/* Do nuffink, so that buddies aren't deleted! */
}

gchar *okc_status_text(PurpleBuddy *buddy)
{
	OkCupidBuddy *obuddy = buddy->proto_data;
	if (obuddy != NULL && obuddy->status_cache != NULL)
	{
		return g_strdup(obuddy->status_cache);
	}
	return NULL;
}

void okc_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
{
	OkCupidBuddy *obuddy = buddy->proto_data;
	if (obuddy != NULL && obuddy->status_cache != NULL)
	{
		purple_notify_user_info_add_pair(user_info, NULL, obuddy->status_cache);
	}
}

/******************************************************************************/
/* Plugin functions */
/******************************************************************************/

static gboolean plugin_load(PurplePlugin *plugin)
{
	return TRUE;
}

static gboolean plugin_unload(PurplePlugin *plugin)
{
	return TRUE;
}

static void plugin_init(PurplePlugin *plugin)
{
	PurpleAccountOption *option;
	PurplePluginInfo *info = plugin->info;
	PurplePluginProtocolInfo *prpl_info = info->extra_info;
	
	option = purple_account_option_string_new("Host", "host", "api.okcupid.com");
	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
	
	option = purple_account_option_bool_new("Show messages sent from website", "show_sent_messages", FALSE);
	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);

	option = purple_account_option_bool_new("Show people who visit your profile", "show_stalkers", TRUE);
	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);

	option = purple_account_option_bool_new("Always use HTTPS", "force_https", TRUE);
	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
}

static PurplePluginProtocolInfo prpl_info = {
	/* options */
	OPT_PROTO_MAIL_CHECK,

	NULL,                   /* user_splits */
	NULL,                   /* protocol_options */
	NO_BUDDY_ICONS    /* icon_spec */
	/*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */
	okc_list_icon,          /* list_icon */
	NULL,                   /* list_emblems */
	okc_status_text,        /* status_text */
	okc_tooltip_text,       /* tooltip_text */
	okc_statuses,           /* status_types */
	okc_blist_node_menu,    /* blist_node_menu */
	NULL,                   /* chat_info */
	NULL,                   /* chat_info_defaults */
	okc_login,              /* login */
	okc_close,              /* close */
	okc_send_im,            /* send_im */
	NULL,                   /* set_info */
	NULL,                   /* send_typing */
	okc_get_info,           /* get_info */
	NULL,                   /* set_status */
	NULL,                   /* set_idle */
	NULL,                   /* change_passwd */
	okc_add_buddy,          /* add_buddy */
	NULL,                   /* add_buddies */
	okc_remove_buddy,       /* remove_buddy */
	NULL,                   /* remove_buddies */
	NULL,                   /* add_permit */
	okc_block_buddy,        /* add_deny */
	NULL,                   /* rem_permit */
	NULL,                   /* rem_deny */
	NULL,                   /* set_permit_deny */
	NULL,                   /* join_chat */
	NULL,                   /* reject chat invite */
	NULL,                   /* get_chat_name */
	NULL,                   /* chat_invite */
	NULL,                   /* chat_leave */
	NULL,                   /* chat_whisper */
	NULL,                   /* chat_send */
	NULL,                   /* keepalive */
	NULL,                   /* register_user */
	NULL,                   /* get_cb_info */
	NULL,                   /* get_cb_away */
	NULL,                   /* alias_buddy */
	okc_fake_group_buddy,   /* group_buddy */
	NULL,                   /* rename_group */
	okc_buddy_free,         /* buddy_free */
	NULL,                   /* convo_closed */
	purple_normalize_nocase,/* normalize */
	NULL,                   /* set_buddy_icon */
	NULL,                   /* remove_group */
	NULL,                   /* get_cb_real_name */
	NULL,                   /* set_chat_topic */
	NULL,                   /* find_blist_chat */
	NULL,                   /* roomlist_get_list */
	NULL,                   /* roomlist_cancel */
	NULL,                   /* roomlist_expand_category */
	NULL,                   /* can_receive_file */
	NULL,                   /* send_file */
	NULL,                   /* new_xfer */
	NULL,                   /* offline_message */
	NULL,                   /* whiteboard_prpl_ops */
	NULL,                   /* send_raw */
	NULL,                   /* roomlist_room_serialize */
	NULL,                   /* unregister_user */
	NULL,                   /* send_attention */
	NULL,                   /* attention_types */
	sizeof(PurplePluginProtocolInfo), /* struct_size */
	NULL,                   /* get_account_text_table */
};

static PurplePluginInfo info = {
	PURPLE_PLUGIN_MAGIC,
	2,                                 /* major_version  */
	3,                                 /* minor version  */
	PURPLE_PLUGIN_PROTOCOL,            /* type           */
	NULL,                              /* ui_requirement */
	0,                                 /* flags          */
	NULL,                              /* dependencies   */
	PURPLE_PRIORITY_DEFAULT,           /* priority       */
	"prpl-bigbrownchunx-okcupid",      /* id             */
	"OkCupid",                         /* name           */
	PP_VERSION,                        /* version        */
	N_("OkCupid Protocol Plugin"),     /* summary        */
	N_("OkCupid Protocol Plugin"),     /* description    */
	"Eion Robb <eionrobb@gmail.com>",  /* author         */
	PP_WEBSITE,                        /* homepage       */
	plugin_load,                       /* load           */
	plugin_unload,                     /* unload         */
	NULL,                              /* destroy        */
	NULL,                              /* ui_info        */
	&prpl_info,                        /* extra_info     */
	NULL,                              /* prefs_info     */
	NULL,                              /* actions        */

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

PURPLE_INIT_PLUGIN(okcupid, plugin_init, info);
