/*
 * Pidgin-MPRISx plugin.
 *
 * Copyright (c) 2007 Sabin Iacob.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#define DBUS_API_SUBJECT_TO_CHANGE 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>

#include "gtkplugin.h"
#include "util.h"
#include "debug.h"
#include "connection.h"
#include "savedstatuses.h"
#include "version.h"

#define PIDGINMPRIS_PLUGIN_ID	"core-pidgin-mpris"

#define OPT_PIDGINMPRIS 		"/plugins/pidgin_mpris"
#define OPT_PROCESS_STATUS	OPT_PIDGINMPRIS "/process_status"
#define OPT_PROCESS_USERINFO	OPT_PIDGINMPRIS "/process_userinfo"
#define OPT_PLAYER_NAME		OPT_PIDGINMPRIS "/player_name"
#define OPT_FORMAT_STRING	OPT_PIDGINMPRIS "/format_string"

#define DBUS_MPRIS_NAMESPACE		"org.mpris."
#define DBUS_MPRIS_PLAYER			"org.freedesktop.MediaPlayer"
#define DBUS_MPRIS_PLAYER_PATH		"/Player"
#define DBUS_MPRIS_TRACK_SIGNAL		"TrackChange"
#define DBUS_MPRIS_STATUS_SIGNAL	"StatusChange"

#define DBUS_TYPE_G_STRING_VALUE_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))

#define MPRIS_TOKEN		"%now-playing"
#define MPRIS_START		"<span class='mpris'>"
#define MPRIS_END		"</span>"

#define mpris_debug(fmt, ...)	purple_debug(PURPLE_DEBUG_INFO, "Now Playing", \
					fmt, ## __VA_ARGS__);
#define mpris_error(fmt, ...)	purple_debug(PURPLE_DEBUG_ERROR, "Now Playing", \
					fmt, ## __VA_ARGS__);

typedef struct {
	PurplePlugin *plugin;
	DBusGConnection *bus;
	DBusGProxy *player;
	gchar *player_name;
} pidginmpris_t;

pidginmpris_t *pidginmpris;

static void
mpris_process_status(PurpleConnection *gc, gchar *mpris_info)
{
	gchar *new, *tmp1, *tmp2, *begin, *end;
	const gchar *old, *proto;
	PurpleAccount *account;
	PurplePresence *presence;
	PurplePlugin *prpl;
	PurplePluginProtocolInfo *prpl_info;
	PurpleStatus *status;

	account = purple_connection_get_account(gc);
	presence = purple_account_get_presence(account);

	proto = purple_account_get_protocol_id(account);
	prpl = purple_find_prpl(proto);
	g_return_if_fail(prpl != NULL);

	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
	g_return_if_fail(prpl_info != NULL && prpl_info->set_status != NULL);

	status = purple_presence_get_active_status(presence);
	g_return_if_fail(status != NULL);

	old = purple_status_get_attr_string(status, "message");
	g_return_if_fail(old != NULL && strlen(old) != 0);

	g_return_if_fail(strstr(old, MPRIS_TOKEN) || (strstr(old, MPRIS_START) && strstr(old, MPRIS_END)));

	/* construct our new status message */
	tmp1 = purple_strreplace(old, MPRIS_TOKEN, MPRIS_START MPRIS_END);
	g_return_if_fail(tmp1 != NULL);
	begin = strstr(tmp1, MPRIS_START);
	end = strstr(tmp1, MPRIS_END);
	tmp2 = g_strndup(tmp1, strlen(tmp1) - strlen(begin) + strlen(MPRIS_START)); 
	new = g_strconcat(tmp2, mpris_info, end, NULL);
	g_free(tmp1);
	g_free(tmp2);

	/* only set the status message if the text has changed */
	if (g_ascii_strcasecmp(old, new) != 0) {
		purple_status_set_attr_string(status, "message", new);
		prpl_info->set_status(account, status);
	}

	g_free(new);
}

static void
mpris_process_userinfo(PurpleConnection *gc, gchar *mpris_info)
{
	gchar *new;
	const gchar *old, *proto;
	PurpleAccount *account;
	PurplePlugin *prpl;
	PurplePluginProtocolInfo *prpl_info;

	account = purple_connection_get_account(gc);

	proto = purple_account_get_protocol_id(account);
	prpl = purple_find_prpl(proto);
	g_return_if_fail(prpl != NULL);

	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
	g_return_if_fail(prpl_info != NULL && prpl_info->set_status != NULL);

	/* retrieve the old user info */
	old = purple_account_get_user_info(account);
	g_return_if_fail(old != NULL && strlen(old) != 0);

	g_return_if_fail(strstr(old, MPRIS_TOKEN));

	/* replace the %now-playing with our song info */
	new = purple_strreplace(old, MPRIS_TOKEN, mpris_info);
	g_return_if_fail(new != NULL);

	/* only set the user info if the text has changed */
	if (g_ascii_strcasecmp(old, new) != 0) {
		prpl_info->set_info(gc, new);
	}

	g_free(new);
}

static void
mpris_process(gchar *mpris_info)
{
	GList *l;
	PurpleConnection *gc;

	for (l = purple_connections_get_all(); l != NULL; l = l->next) {
		gc = (PurpleConnection *) l->data;

		/* make sure we're connected */
		if (purple_connection_get_state(gc) != PURPLE_CONNECTED) {
			continue;
		}

		if (purple_prefs_get_bool(OPT_PROCESS_STATUS)) {
			mpris_process_status(gc, mpris_info);
		}

		if (purple_prefs_get_bool(OPT_PROCESS_USERINFO)) {
			mpris_process_userinfo(gc, mpris_info);
		}
	}
}

static void
mpris_track_signal_cb(DBusGProxy *player_proxy, GHashTable *table, gpointer data)
{
	gint i;
	GValue *value;
	gchar *artist = NULL;
	gchar *album = NULL;
	gchar *genre = NULL;
	gchar *title = NULL;
	gchar *format, *mpris_info = NULL;

	/* fetch values from hash table */
	value = (GValue *) g_hash_table_lookup(table, "artist");
	if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
		artist = g_value_dup_string(value);
	}
	value = (GValue *) g_hash_table_lookup(table, "album");
	if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
		album = g_value_dup_string(value);
	}
	value = (GValue *) g_hash_table_lookup(table, "genre");
	if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
		genre = g_value_dup_string(value);
	}
	value = (GValue *) g_hash_table_lookup(table, "title");
	if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
		title = g_value_dup_string(value);
	}
	
	format = g_strdup(purple_prefs_get_string(OPT_FORMAT_STRING));

	/* substitute in values */
	if (artist && strstr(format, "%artist")) {
 		mpris_info = purple_strreplace(format, "%artist",  g_strstrip(artist));
		g_free(format);
		format = mpris_info;
	}
	if (album && strstr(format, "%album")) {
 		mpris_info = purple_strreplace(format, "%album",  g_strstrip(album));
		g_free(format);
		format = mpris_info;
	}
	if (genre && strstr(format, "%genre")) {
 		mpris_info = purple_strreplace(format, "%genre",  g_strstrip(genre));
		g_free(format);
		format = mpris_info;
	}
	if (title && strstr(format, "%title")) {
	 	mpris_info = purple_strreplace(format, "%title",  g_strstrip(title));
		g_free(format);
		format = mpris_info;
	}

	if (strstr(format, "%player")) {
		mpris_info = purple_strreplace(format, "%player", purple_prefs_get_string(OPT_PLAYER_NAME));
		g_free(format);
		format = mpris_info;
	}

	g_return_if_fail(mpris_info != NULL);

	mpris_process(mpris_info);

	g_free(mpris_info);
}

static void
mpris_status_signal_cb(DBusGProxy *player_proxy, gint status, gpointer data)
{
	mpris_debug("StatusChange %d\n", status);

	if (status != 0) {
		mpris_process("");
	}
}

static void mpris_connect_dbus_signals()
{
	pidginmpris->player = dbus_g_proxy_new_for_name(pidginmpris->bus,
			pidginmpris->player_name, DBUS_MPRIS_PLAYER_PATH, DBUS_MPRIS_PLAYER);

	dbus_g_proxy_add_signal(pidginmpris->player, DBUS_MPRIS_TRACK_SIGNAL,
			DBUS_TYPE_G_STRING_VALUE_HASHTABLE, G_TYPE_INVALID);
	dbus_g_proxy_connect_signal(pidginmpris->player, DBUS_MPRIS_TRACK_SIGNAL,
			G_CALLBACK(mpris_track_signal_cb), NULL, NULL);

	dbus_g_proxy_add_signal(pidginmpris->player, DBUS_MPRIS_STATUS_SIGNAL,
			G_TYPE_INT, G_TYPE_INVALID);
	dbus_g_proxy_connect_signal(pidginmpris->player, DBUS_MPRIS_STATUS_SIGNAL,
			G_CALLBACK(mpris_status_signal_cb), NULL, NULL);
}

static gboolean mpris_app_running()
{
	gchar *player_name = g_strconcat(DBUS_MPRIS_NAMESPACE, purple_prefs_get_string(OPT_PLAYER_NAME), NULL);

	if(g_strcasecmp(pidginmpris->player_name, player_name) != 0) {
		pidginmpris->player_name = g_strdup(player_name);
		g_object_unref(pidginmpris->player);
		mpris_connect_dbus_signals();
	}
	
	DBusGProxy *player = dbus_g_proxy_new_for_name_owner(pidginmpris->bus, 
			player_name, DBUS_MPRIS_PLAYER_PATH, DBUS_MPRIS_PLAYER, NULL);
	
	if(!player)
		return FALSE;
	
	g_object_unref(player);
	g_free(player_name);
	return TRUE;
}

static void
mpris_signed_on_cb(PurpleConnection *gc, void *data)
{
	mpris_status_signal_cb(NULL, -1, NULL);

	if(mpris_app_running()) {
		GHashTable *table = NULL;
	
		if(dbus_g_proxy_call(pidginmpris->player, "GetMetadata", NULL,
				G_TYPE_INVALID, DBUS_TYPE_G_STRING_VALUE_HASHTABLE, &table, 
				G_TYPE_INVALID)) {
			mpris_track_signal_cb(NULL, table, NULL);
		}
		g_hash_table_destroy(table);
	}
}

static void mpris_prefs_cb(const char *name, PurplePrefType type,
                          gconstpointer value, gpointer data)
{
	mpris_debug("settings change detected at %s\n", name);
	mpris_signed_on_cb(NULL, NULL);
}

static gboolean
load_plugin(PurplePlugin *plugin)
{
	pidginmpris = g_new0(pidginmpris_t, 1);
	pidginmpris->plugin = plugin;
	pidginmpris->player_name = g_strconcat(DBUS_MPRIS_NAMESPACE, 
			purple_prefs_get_string(OPT_PLAYER_NAME), NULL);

	/* initialize dbus connection */
	pidginmpris->bus = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
	if (!pidginmpris->bus) {
		mpris_error("failed to connect to the dbus daemon\n");
		return FALSE;
	}

	/* connect mpris's dbus signals */
	mpris_connect_dbus_signals();

	purple_signal_connect(purple_connections_get_handle(), "signed-on",
			plugin, PURPLE_CALLBACK(mpris_signed_on_cb), NULL);
	
	purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
			plugin, PURPLE_CALLBACK(mpris_signed_on_cb), NULL);

	purple_prefs_connect_callback(purple_prefs_get_handle(), OPT_PIDGINMPRIS, 
			mpris_prefs_cb, NULL);

	mpris_status_signal_cb(NULL, -1, NULL);

	return TRUE;
}

static gboolean
unload_plugin(PurplePlugin *plugin)
{

	if (pidginmpris->player) {
		g_object_unref(pidginmpris->player);
	}

	if (pidginmpris->bus) {
		dbus_g_connection_unref(pidginmpris->bus);
	}

	g_free(pidginmpris);

	return TRUE;
}

static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin *plugin)
{
	PurplePluginPref *pref;
	PurplePluginPrefFrame *frame = purple_plugin_pref_frame_new();

	/* create gtk elements for the plugin preferences */
	pref = purple_plugin_pref_new_with_label("Now Playing Plugin Settings");
	purple_plugin_pref_frame_add(frame, pref);
	
	pref = purple_plugin_pref_new_with_name_and_label(OPT_PLAYER_NAME,
			"Player to observe");
	purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);
	purple_plugin_pref_add_choice(pref, "Aeon", "aeon");
	purple_plugin_pref_add_choice(pref, "Amarok", "amarok");
	purple_plugin_pref_add_choice(pref, "Audacious", "audacious");
	purple_plugin_pref_add_choice(pref, "BMPx", "bmp");
	purple_plugin_pref_add_choice(pref, "VLC", "vlc");
	purple_plugin_pref_add_choice(pref, "XMMS2", "xmms2");
	purple_plugin_pref_frame_add(frame, pref);

	pref = purple_plugin_pref_new_with_name_and_label(OPT_PROCESS_STATUS,
			"Expand " MPRIS_TOKEN " to song info in the status message");
	purple_plugin_pref_frame_add(frame, pref);
	
	pref = purple_plugin_pref_new_with_name_and_label(OPT_PROCESS_USERINFO,
			"Expand " MPRIS_TOKEN " to song info in the user info");
	purple_plugin_pref_frame_add(frame, pref);

	pref = purple_plugin_pref_new_with_name_and_label(OPT_FORMAT_STRING,
			"Song format string; known tokens are \n%artist, %album, %genre, %title");
	purple_plugin_pref_frame_add(frame, pref);

	return frame;
}

static PurplePluginUiInfo pref_info =
{
	get_plugin_pref_frame
};

static PurplePluginInfo info =
{
	PURPLE_PLUGIN_MAGIC,
	PURPLE_MAJOR_VERSION,
	PURPLE_MINOR_VERSION,
	PURPLE_PLUGIN_STANDARD,				/**< type	*/
	PIDGIN_PLUGIN_TYPE,				/**< ui_req	*/
	0,						/**< flags	*/
	NULL,						/**< deps	*/
	PURPLE_PRIORITY_DEFAULT,			/**< priority	*/
	PIDGINMPRIS_PLUGIN_ID,				/**< id		*/
	"Now Playing",				/**< name	*/
	VERSION,					/**< version	*/
							/**  summary	*/
	"Automatically updates your Pidgin status info with the currently "
			"playing media in a MPRIS compatible player",
							/**  desc	*/
	"Automatically updates your Pidgin status info with the currently "
			"playing media in a MPRIS compatible player.",
	"Sabin Iacob (m0n5t3r) <iacobs@m0n5t3r.info>",		/**< author	*/
							/**< homepage	*/
	"http://m0n5t3r.info/work/pidgin-mpris/",
	load_plugin,					/**< load	*/
	unload_plugin,					/**< unload	*/
	NULL,						/**< destroy	*/
	NULL,						/**< ui_info	*/
	NULL,						/**< extra_info	*/
	&pref_info,					/**< pref info	*/
	NULL
};

static void
init_plugin(PurplePlugin *plugin)
{
	g_type_init();

	/* add plugin preferences */
	purple_prefs_add_none(OPT_PIDGINMPRIS);
	purple_prefs_add_bool(OPT_PROCESS_STATUS, TRUE);
	purple_prefs_add_bool(OPT_PROCESS_USERINFO, TRUE);
	purple_prefs_add_string(OPT_PLAYER_NAME, "bmp");
	purple_prefs_add_string(OPT_FORMAT_STRING, "Playing '%title' by %artist");
}

PURPLE_INIT_PLUGIN(pidgin_mpris, init_plugin, info)
