/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2007 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 */

#include "xmms/xmms_xformplugin.h"
#include "xmms/xmms_log.h"

#include <curl/curl.h>

/*
 * Type definitions
 */

typedef struct xmms_lastfmeta_data_St {
	gchar url[120];
	CURL *curl_easy;
	CURLM *curl_multi;
	gint handles;
} xmms_lastfmeta_data_t;


/*
 * Function prototypes
 */

static gboolean xmms_lastfmeta_plugin_setup (xmms_xform_plugin_t *xform_plugin);
static gint xmms_lastfmeta_read (xmms_xform_t *xform, xmms_sample_t *buf,
                                 gint len, xmms_error_t *err);
static void xmms_lastfmeta_destroy (xmms_xform_t *decoder);
static gboolean xmms_lastfmeta_init (xmms_xform_t *decoder);
static size_t xmms_lastfmeta_now_playing_callback (void *ptr, size_t size,
                                                   size_t nmemb, void *stream);
static gboolean xmms_lastfm_control (xmms_xform_t *xform, const gchar *cmd);
static size_t xmms_lastfm_feed_buffer (void *ptr, size_t size,
                                       size_t nmemb, void *udata);
static void xmms_lastfm_config_changed (xmms_object_t * object,
                                        gconstpointer data, gpointer udata);
static gchar *xmms_lastfm_memstr (const gchar *haystack, gint haystack_len,
                                  const gchar *needle);



/*
 * Plugin header
 */

XMMS_XFORM_PLUGIN ("lastfmeta", "lastfmeta", XMMS_VERSION,
                   "last.fm metadata", xmms_lastfmeta_plugin_setup);

static gboolean
xmms_lastfmeta_plugin_setup (xmms_xform_plugin_t *xform_plugin)
{
	xmms_xform_methods_t methods;

	XMMS_XFORM_METHODS_INIT (methods);

	methods.init = xmms_lastfmeta_init;
	methods.destroy = xmms_lastfmeta_destroy;
	methods.read = xmms_lastfmeta_read;

	xmms_xform_plugin_methods_set (xform_plugin, &methods);

	xmms_xform_plugin_config_property_register (xform_plugin, "recordtoprofile",
	                                            "0", NULL, NULL);

	xmms_xform_plugin_indata_add (xform_plugin,
	                              XMMS_STREAM_TYPE_MIMETYPE,
	                              "application/x-sync-padded",
	                              NULL);

	xmms_magic_add ("SYNC padded", "application/x-sync-padded",
	                "0 string SYNC", NULL);

	return TRUE;
}

static gboolean
xmms_lastfmeta_init (xmms_xform_t *xform)
{
	const gchar *np_fmt = "http://ws.audioscrobbler.com/radio/np.php?session=%s&debug=0";
	xmms_lastfmeta_data_t *data;
	xmms_config_property_t *val;
	const gchar *session;
	xmms_error_t err;
	gpointer buf[4];
	gint rtp;

	data = g_new (xmms_lastfmeta_data_t, 1);
	data->handles = 0;

	xmms_xform_private_data_set (xform, data);

	/* seek over the first 4 bytes (SYNC) */
	xmms_xform_read (xform, buf, 4, &err);

	session = xmms_xform_metadata_get_str (xform, "session");
	g_snprintf (data->url, sizeof (data->url), np_fmt, session);

	data->curl_easy = curl_easy_init ();
	curl_easy_setopt (data->curl_easy, CURLOPT_URL, data->url);
	curl_easy_setopt (data->curl_easy, CURLOPT_USERAGENT,
	                  "XMMS2/" XMMS_VERSION);
	curl_easy_setopt (data->curl_easy, CURLOPT_CONNECTTIMEOUT, 15);
	curl_easy_setopt (data->curl_easy, CURLOPT_WRITEDATA, xform);
	curl_easy_setopt (data->curl_easy, CURLOPT_WRITEFUNCTION,
	                  xmms_lastfmeta_now_playing_callback);

	data->curl_multi = curl_multi_init ();
	curl_multi_add_handle (data->curl_multi, data->curl_easy);
	curl_multi_perform (data->curl_multi, &(data->handles));


	val = xmms_xform_config_lookup (xform, "recordtoprofile");
	xmms_config_property_callback_set (val, xmms_lastfm_config_changed, xform);

	rtp = xmms_config_property_get_int (val);
	if (!rtp) {
		xmms_lastfm_control (xform, "nortp");
	}

	xmms_xform_outdata_type_add (xform,
	                             XMMS_STREAM_TYPE_MIMETYPE,
	                             "application/octet-stream",
	                             XMMS_STREAM_TYPE_END);

	return TRUE;
}


static void
xmms_lastfmeta_destroy (xmms_xform_t *xform)
{
	xmms_lastfmeta_data_t *data;

	g_return_if_fail (xform);

	data = xmms_xform_private_data_get (xform);
	g_return_if_fail (data);

	curl_multi_remove_handle (data->curl_multi, data->curl_easy);
	curl_easy_cleanup (data->curl_easy);
	curl_multi_cleanup (data->curl_multi);

	g_free (data);
}


static void
xmms_lastfm_config_changed (xmms_object_t * object, gconstpointer data,
                            gpointer udata)
{
	xmms_config_property_t *val;
	xmms_xform_t *xform;
	const gchar *name;

	xform = (xmms_xform_t *) udata;
	val = (xmms_config_property_t *) object;
	name = xmms_config_property_get_name (val);

	if (g_ascii_strcasecmp (name, "recordtoprofile")) {
		gint rtp = xmms_config_property_get_int (val);
		xmms_lastfm_control (xform, (rtp ? "rtp" : "nortp"));
	}
}

static gint
xmms_lastfmeta_read (xmms_xform_t *xform, xmms_sample_t *buf, gint len,
                     xmms_error_t *err)
{
	xmms_lastfmeta_data_t *data;
	gint ret;

	g_return_val_if_fail (xform, 0);
	data = xmms_xform_private_data_get (xform);
	g_return_val_if_fail (data, 0);

	if (data->handles > 0) {
		curl_multi_perform (data->curl_multi, &(data->handles));
	}

	ret = xmms_xform_read (xform, buf, len, err);
	if (xmms_lastfm_memstr (buf, ret, "SYNC") != NULL) {
		curl_multi_add_handle (data->curl_multi, data->curl_easy);
		curl_multi_perform (data->curl_multi, &(data->handles));
	}

	return ret;
}


static size_t
xmms_lastfmeta_now_playing_callback (void *ptr, size_t size,
                                     size_t nmemb, void *stream)
{
	xmms_xform_t *xform = (xmms_xform_t *) stream;
	gchar **split;
	gint i;

	size_t ret = size * nmemb;

	split = g_strsplit (ptr, "\n", 0);
	for (i = 0; split && split[i]; i++) {
		if (g_str_has_prefix (split[i], "error=")) {
			ret = 0;
			break;
		} else if (g_str_has_prefix (split[i], "artist=")) {
			xmms_xform_metadata_set_str (xform,
			                             XMMS_MEDIALIB_ENTRY_PROPERTY_ARTIST,
			                             split[i] + 7);
		} else if (g_str_has_prefix (split[i], "track=")) {
			xmms_xform_metadata_set_str (xform,
			                             XMMS_MEDIALIB_ENTRY_PROPERTY_TITLE,
			                             split[i] + 6);
		} else if (g_str_has_prefix (split[i], "album=")) {
			xmms_xform_metadata_set_str (xform,
			                             XMMS_MEDIALIB_ENTRY_PROPERTY_ALBUM,
			                             split[i] + 6);
		} else if (g_str_has_prefix (split[i], "station=")) {
			xmms_xform_metadata_set_str (xform,
			                             XMMS_MEDIALIB_ENTRY_PROPERTY_CHANNEL,
			                             split[i] + 8);
		}
	}
	g_strfreev (split);

	return ret;
}


static gboolean
xmms_lastfm_control (xmms_xform_t *xform, const gchar *cmd)
{
	const gchar *url = "http://ws.audioscrobbler.com/radio/control.php";
	const gchar *request_fmt = "session=%s&command=%s";
	const gchar *session;
	xmms_lastfmeta_data_t *data;
	GString *buffer;
	gchar *tmp;
	CURL *curl;

	gboolean ret = FALSE;

	g_return_val_if_fail (xform, 0);
	data = xmms_xform_private_data_get (xform);
	g_return_val_if_fail (data, 0);

	session = xmms_xform_metadata_get_str (xform, "session");

	buffer = g_string_new (NULL);

	curl = curl_easy_init ();
	curl_easy_setopt (curl, CURLOPT_USERAGENT, "XMMS2/" XMMS_VERSION);
	curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15);
	curl_easy_setopt (curl, CURLOPT_WRITEDATA, buffer);
	curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION,
	                  xmms_lastfm_feed_buffer);
	curl_easy_setopt (curl, CURLOPT_URL, url);
	curl_easy_setopt (curl, CURLOPT_POST, 1);

	tmp = g_strdup_printf (request_fmt, session, cmd);
	curl_easy_setopt (curl, CURLOPT_POSTFIELDS, tmp);

	if (curl_easy_perform (curl) == CURLE_OK) {
		if (g_str_has_prefix (buffer->str, "response=OK")) {
			ret = TRUE;
		}
	}

	g_free (tmp);
	g_string_free (buffer, TRUE);

	curl_easy_cleanup (curl);

	return ret;
}


static size_t
xmms_lastfm_feed_buffer (void *ptr, size_t size, size_t nmemb, void *udata)
{
	GString *data = (GString *) udata;

	g_string_append_len (data, ptr, nmemb);

	return size * nmemb;
}


/**
 * This is a modified version of g_strstr_len that disregards \0 chars.
 */
static gchar *
xmms_lastfm_memstr (const gchar *haystack, gint haystack_len,
                    const gchar *needle)
{
	const gchar *p, *end;
	gint needle_len, i;

	needle_len = strlen (needle);

	if (needle_len == 0) {
		return (gchar *) haystack;
	}

	if (haystack_len < needle_len) {
		return NULL;
	}

	end = haystack + haystack_len - needle_len;
	p = haystack;

	while (p <= end) {
		for (i = 0; i < needle_len; i++)
			if (p[i] != needle[i])
				goto next;

		return (gchar *) p;
next:
		p++;
	}

	return NULL;
}
