/*
 * libning
 *
 * libning 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 "ning.h"
#include "ning_connection.h"
#include "ning_chat.h"

JsonObject *ning_json_parse(const gchar *data, gssize data_len)
{
	JsonParser *parser;
	JsonNode *root_node;
	JsonObject *root_obj;
	
	parser = json_parser_new();
	json_parser_load_from_data(parser, data, (gssize) data_len, NULL);
	
	root_node = json_parser_get_root(parser);
	root_obj =  json_node_dup_object(root_node);
	
	g_object_unref(parser);
	
	return root_obj;
}

gchar *
build_user_json(NingAccount *na)
{
	gchar *user_json;
	gchar *escaped_name;
	gchar *escaped_icon;
	gchar *escaped_id;
	
	if (na && na->name)
	{
		escaped_name = g_strescape(na->name, "");
	} else {
		escaped_name = g_strdup("");
	}
	if (na && na->icon_url)
	{
		escaped_icon = g_strescape(na->icon_url, "");
	} else {
		escaped_icon = g_strdup("");
	}
	if (na && na->ning_id)
	{
		escaped_id = g_strescape(na->ning_id, "");
	} else {
		escaped_id = g_strdup("");
	}
	
	user_json = g_strdup_printf("{\"name\":\"%s\",\"iconUrl\":\"%s\",\"isAdmin\":\"0\",\"ningId\":\"%s\",\"isNC\":\"0\"}", 
			escaped_name, escaped_icon, escaped_id);
	
	g_free(escaped_name);
	g_free(escaped_icon);
	g_free(escaped_id);
	
	return user_json;
}



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

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


static GList *ning_statuses(PurpleAccount *account)
{
	GList *types = NULL;
	PurpleStatusType *status;
	
	purple_debug_info("ning", "statuses\n");
	
	/* Ning people are either online or 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("ning", "statuses return\n");
	
	return types;
}

void ning_chat_login_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
{
	JsonObject *obj;
	const gchar *result;
	const gchar *roomId;
	
	obj = ning_json_parse(data, data_len);

	purple_debug_info("ning", "chat_login_cb: %s\n", data?data:"(null)");
	
	if (data == NULL || data_len == 0)
		return;
	
	//{"command": "login","result": "ok","roomId": "thoughtleaders.thoughtleaders",
	// "count": 2,"token": "37lfxean70eqh_122d86d5cf7_6f95cb8e_122d49f8e48"}
	
	result = json_node_get_string(json_object_get_member(obj, "result"));
	if (!result || !g_str_equal(result, "ok"))
	{
		purple_connection_error(na->pc, _("Could not log on"));
		return;
	}
	purple_connection_update_progress(na->pc, _("Joining public chat"), 5, 5);
	purple_connection_set_state(na->pc, PURPLE_CONNECTED);
	
	g_free(na->chat_token);
	na->chat_token = g_strdup(json_node_get_string(json_object_get_member(obj, "token")));
	
	roomId = json_node_get_string(json_object_get_member(obj, "roomId"));
	
	ning_join_chat_by_name(na, roomId);
	
	json_object_unref(obj);
}

void ning_chat_redir_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
{
	JsonObject *obj;
	gchar *postdata, *encoded_app, *encoded_id;
	gchar *user_json, *user_encoded;

	purple_debug_info("ning", "ning_chat_redir_cb: %s\n", data?data:"(null)");
	
	//We get a randomly generated chat domain to use
	// eg {"domain": "3841.chat07.ningim.com"}
	obj = ning_json_parse(data, data_len);
	
	g_free(na->chat_domain);
	na->chat_domain = g_strdup(json_node_get_string(json_object_get_member(obj, "domain")));
	
	json_object_unref(obj);
	
	//Use our new domain to log into the chat servers
	purple_connection_update_progress(na->pc, _("Logging into chat"), 4, 5);
	
	encoded_app = g_strdup(purple_url_encode(na->ning_app));
	encoded_id = g_strdup(purple_url_encode(na->ning_id));
	
	user_json = build_user_json(na);
	user_encoded = g_strdup(purple_url_encode(user_json));
	
	postdata = g_strdup_printf("a=%s&t=%s%s&i=%s&user=%s", encoded_app, encoded_app, encoded_id, encoded_id, user_encoded);
	ning_post_or_get(na, NING_METHOD_POST, na->chat_domain,
			"/xn/presence/login", postdata, ning_chat_login_cb, NULL, FALSE);
	
	g_free(postdata);
	g_free(encoded_app);
	g_free(encoded_id);
	g_free(user_encoded);
	g_free(user_json);
}

void ning_login_home_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata)
{
	//We need to look for 
	//<script>window.bzplcm.add({"app":"thoughtleaders","user":"37lfxean70eqh"
	//and
	//xg.token = 'b1a7f3ce1719481334cdcc5fe8eabcaa';
	const gchar *start_string = "\nning = ";
	const gchar *mid_string = "}};\n";
	const gchar *xgtoken_start = "xg.token = '";
	gchar *tmp, *ning_json_string, *xg_token;
	gchar *url;
	JsonObject *obj, *profile;
	
	tmp = g_strstr_len(data, data_len, start_string);
	if (tmp == NULL)
	{
		purple_connection_error(na->pc, _("NingID not found"));
		return;
	}
	tmp += strlen(start_string);
	ning_json_string = g_strndup(tmp, strstr(tmp, mid_string) - tmp + 2);
	purple_debug_info("ning", "ning_json_string: %s\n", ning_json_string);
	
	obj = ning_json_parse(ning_json_string, strlen(ning_json_string));
	profile = json_node_get_object(json_object_get_member(obj, "CurrentProfile"));
	g_free(na->ning_id);
	na->ning_id = g_strdup(json_node_get_string(json_object_get_member(profile, "id")));
	g_free(na->name);
	na->name = g_strdup(json_node_get_string(json_object_get_member(profile, "fullName")));
	g_free(na->icon_url);
	na->icon_url = g_strdup_printf("%s&width=16&height=16", json_node_get_string(json_object_get_member(profile, "photoUrl")));
	
	tmp = g_strstr_len(data, data_len, xgtoken_start);
	if (tmp == NULL)
	{
		purple_connection_error(na->pc, _("xgToken not found"));
		return;
	}
	tmp += strlen(xgtoken_start);
	xg_token = g_strndup(tmp, strchr(tmp, '\'') - tmp);
	g_free(na->xg_token);
	na->xg_token = xg_token;
	
	//Now we should have everything we need to sign into chat
	purple_connection_update_progress(na->pc, _("Fetching chat server"), 3, 5);
	
	url = g_strdup_printf("/xn/redirector/redirect?a=%s", purple_url_encode(na->ning_app));
	ning_post_or_get(na, NING_METHOD_GET, "chat01.ningim.com",
					 url, NULL, ning_chat_redir_cb, NULL, FALSE);
	g_free(url);
}

void ning_scan_cookies_for_id(gchar *key, gchar *value, NingAccount *na)
{
	if (g_str_has_prefix(key, "xn_id_"))
	{
		g_free(na->ning_app);
		na->ning_app = g_strdup(&key[6]);
	}
}

static void ning_login_cb(NingAccount *na, gchar *response, gsize len,
						 gpointer userdata)
{
	purple_connection_update_progress(na->pc, _("Fetching token"), 2, 4);
	
	// ok, we're logged into the host website now
	
	// Pull the host's Ning account id from the cookie
	g_hash_table_foreach(na->cookie_table, (GHFunc)ning_scan_cookies_for_id, na);
	
	//Load the homepage to grab the interesting info
	ning_post_or_get(na, NING_METHOD_GET, purple_account_get_string(na->account, "host", NULL),
					"/", NULL, ning_login_home_cb, NULL, FALSE);
}

static void ning_login(PurpleAccount *account)
{
	NingAccount *na;
	gchar *postdata, *encoded_username, *encoded_password;
	gchar *url;
	const gchar *host;
	
	purple_debug_info("ning", "login\n");
	
	/* Create account and initialize state */
	na = g_new0(NingAccount, 1);
	na->account = account;
	na->pc = purple_account_get_connection(account);
	
	na->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */
	
	na->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal,
											  g_free, g_free);
	na->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
												   g_free, g_free);
	
	g_hash_table_replace(na->cookie_table, g_strdup("xg_cookie_check"),
						 g_strdup("1"));
	
	account->gc->proto_data = na;
	
	purple_connection_set_state(na->pc, PURPLE_CONNECTING);
	purple_connection_update_progress(na->pc, _("Logging in"), 1, 4);
	
	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("xg_token=&emailAddress=%s&password=%s",
							   encoded_username, encoded_password);
	g_free(encoded_username);
	g_free(encoded_password);
	
	host = purple_account_get_string(account, "host", NULL);
	if (host == NULL || host[0] == '\0')
	{
		purple_connection_error(na->pc, _("Host not set"));
		return;
	}
	
	host = purple_url_encode(host);
	url = g_strdup_printf("/main/authorization/doSignIn?target=http%%3A%%2F%%2F%s", purple_url_encode(host));
	
	ning_post_or_get(na, NING_METHOD_POST | NING_METHOD_SSL, host,
					url, postdata, ning_login_cb, NULL, FALSE);
	g_free(postdata);
}

static void ning_close(PurpleConnection *pc)
{
	NingAccount *na;
	gchar *postdata;
	gchar *host_encoded;
	gchar *xg_token_encoded;
	PurpleDnsQueryData *dns_query;
	
	purple_debug_info("ning", "disconnecting account\n");
	
	na = pc->proto_data;
	
	host_encoded = g_strdup(purple_url_encode(purple_account_get_string(na->account, "host", "")));
	xg_token_encoded = g_strdup(purple_url_encode(na->xg_token));
	
	postdata = g_strdup_printf("target=%s&xg_token=%s", host_encoded, xg_token_encoded);
	
	ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL),
					 "/main/authorization/signOut", postdata, NULL, NULL, FALSE);
	
	g_free(host_encoded);
	g_free(xg_token_encoded);
	g_free(postdata);
	
	purple_debug_info("ning", "destroying %d incomplete connections\n",
					  g_slist_length(na->conns));
	
	while (na->conns != NULL)
		ning_connection_destroy(na->conns->data);
	
	while (na->dns_queries != NULL) {
		dns_query = na->dns_queries->data;
		purple_debug_info("ning", "canceling dns query for %s\n",
						  purple_dnsquery_get_host(dns_query));
		na->dns_queries = g_slist_remove(na->dns_queries, dns_query);
		purple_dnsquery_destroy(dns_query);
	}
	
	g_hash_table_destroy(na->cookie_table);
	g_hash_table_destroy(na->hostname_ip_cache);
	
	while (na->chats != NULL)
	{	
		NingChat *chat = na->chats->data;
		na->chats = g_list_remove(na->chats, chat);
		purple_timeout_remove(chat->userlist_timer);
		purple_timeout_remove(chat->message_poll_timer);
		purple_conv_chat_left(PURPLE_CONV_CHAT(purple_find_chat(pc, chat->purple_id)));
		g_free(chat->roomId);
		g_free(chat->ning_hash);
		g_free(chat);
	}
	
	g_free(na->ning_id);
	g_free(na->name);
	g_free(na->icon_url);
	g_free(na->xg_token);
	g_free(na->ning_app);
	
	g_free(na);
}

#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
static GHashTable *ning_get_account_text_table(PurpleAccount *account)
{
	GHashTable *table;

	table = g_hash_table_new(g_str_hash, g_str_equal);

	g_hash_table_insert(table, "login_label", (gpointer)_("Email Address..."));

	return table;
}
#endif

void
ning_change_passwd(PurpleConnection *pc, const char *old_pass, const char *new_pass)
{
	NingAccount *na;
	PurpleAccount *account;
	gchar *encoded_username;
	gchar *encoded_password;
	gchar *encoded_token;
	gchar *postdata;
	
	if (pc == NULL)
		return;
	na = pc->proto_data;
	if (na == NULL || na->xg_token == NULL)
		return;
	account = pc->account;
	if (account == NULL)
		return;
	
	encoded_username = g_strdup(purple_url_encode(purple_account_get_username(account)));
	encoded_password = g_strdup(purple_url_encode(new_pass));
	encoded_token = g_strdup(purple_url_encode(na->xg_token));
	
	postdata = g_strdup_printf("emailAddress=%s&password=%s&xg_token=%s",
							   encoded_username, encoded_password, encoded_token);
	
	ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL),
					 "/profiles/settings/updateEmailAddress", postdata, NULL, NULL, FALSE);
	
	g_free(postdata);
	g_free(encoded_token);
	g_free(encoded_password);
	g_free(encoded_username);
}


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

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

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

static void plugin_init(PurplePlugin *plugin)
{
#ifdef ENABLE_NLS
	bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif

	PurpleAccountOption *option;
	PurplePluginInfo *info = plugin->info;
	PurplePluginProtocolInfo *prpl_info = info->extra_info;
	
	option = purple_account_option_string_new("Host", "host", "");
	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
	
}

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

	NULL,                   /* user_splits */
	NULL,                   /* protocol_options */
	NO_BUDDY_ICONS          /* icon_spec */
	/*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */
	ning_list_icon,         /* list_icon */
	NULL,                   /* list_emblems */
	NULL,                   /* status_text */
	NULL,                   /* tooltip_text */
	ning_statuses,          /* status_types */
	NULL,                   /* blist_node_menu */
	NULL,                   /* chat_info */
	NULL,                   /* chat_info_defaults */
	ning_login,             /* login */
	ning_close,             /* close */
	ning_send_im,           /* send_im */
	NULL,                   /* set_info */
	NULL,                   /* send_typing */
	NULL,                   /* get_info */
	NULL,                   /* set_status */
	NULL,                   /* set_idle */
	ning_change_passwd,     /* change_passwd */
	NULL,                   /* add_buddy */
	NULL,                   /* add_buddies */
	NULL,                   /* remove_buddy */
	NULL,                   /* remove_buddies */
	NULL,                   /* add_permit */
	NULL,                   /* add_deny */
	NULL,                   /* rem_permit */
	NULL,                   /* rem_deny */
	NULL,                   /* set_permit_deny */
	ning_join_chat,         /* join_chat */
	NULL,                   /* reject chat invite */
	NULL,                   /* get_chat_name */
	NULL,                   /* chat_invite */
	NULL,                   /* chat_leave */
	ning_chat_whisper,      /* chat_whisper */
	ning_chat_send,         /* chat_send */
	NULL,                   /* keepalive */
	NULL,                   /* register_user */
	NULL,                   /* get_cb_info */
	NULL,                   /* get_cb_away */
	NULL,                   /* alias_buddy */
	NULL,                   /* group_buddy */
	NULL,                   /* rename_group */
	NULL,                   /* 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 */
#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
	sizeof(PurplePluginProtocolInfo), /* struct_size */
	ning_get_account_text_table, /* get_account_text_table */
#else
   (gpointer) sizeof(PurplePluginProtocolInfo)
#endif
};

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-ning",          /* id             */
	"Ning",                             /* name           */
	PP_VERSION,                         /* version        */
	N_("Ning Protocol Plugin"),         /* summary        */
	N_("Ning 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(ning, plugin_init, info);
