/**
* This file is a part of the Cairo-Dock project
*
* Copyright : (C) see the 'copyright' file.
* E-mail    : see the 'copyright' file.
*
* 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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <glib/gi18n.h>
#include <cairo-dock.h>

#include "applet-struct.h"
#include "applet-musicplayer.h"
#include "applet-dbus.h"
#include "applet-draw.h"
#include "applet-cover.h"
#include "applet-banshee.h"

/*
service : org.bansheeproject.Banshee

<interface name="org.bansheeproject.Banshee.PlayerEngine">
	<method name="Open">
		<arg name="uri" direction="in" type="s" />
	</method>
	<method name="Close" />
	<method name="Pause" />
	<method name="Play" />
	<method name="TogglePlaying" />
	<signal name="EventChanged">
		<arg name="evnt" direction="out" type="s" />
		<arg name="message" direction="out" type="s" />
		<arg name="bufferingPercent" direction="out" type="d" />
	</signal>
	<signal name="StateChanged">
		<arg name="state" direction="out" type="s" />
	</signal>
	<property name="CurrentTrack" type="a{sv}" access="read" />
	<property name="CurrentUri" type="s" access="read" />
	<property name="CurrentState" type="s" access="read" />
	<property name="LastState" type="s" access="read" />
	<property name="Volume" type="q" access="readwrite" />
	<property name="Position" type="u" access="readwrite" />
	<property name="CanSeek" type="b" access="read" />
	<property name="CanPause" type="b" access="read" />
	<property name="Length" type="u" access="read" />
</interface>

interface : org.bansheeproject.Banshee.PlaybackController
path : /org/bansheeproject/Banshee/PlaybackController
	Next
	Previous
	PlaybackShuffleMode ShuffleMode { get; set; }
    PlaybackRepeatMode RepeatMode { get; set; }
	
interface : org.bansheeproject.Banshee.PlayerEngine
path : /org/bansheeproject/Banshee/PlayerEngine
	event DBusPlayerEventHandler EventChanged;
    event DBusPlayerStateHandler StateChanged;

    void Open (string uri);
    
    void Pause ();
    void Play ();
    void TogglePlaying ();
    
    IDictionary CurrentTrack { get; } artist,name,album,track-number
    string CurrentUri { get; }
    
    string CurrentState { get; }

    uint Position { get; set; }
    uint Length { get; }

	cover cache : "$HOME/.cache/album-art/`GetCurrentTrack["artwork-id"])`.jpg"
*/


static inline gboolean _extract_playing_status (const gchar *cCurrentState)
{
	myData.pPreviousPlayingStatus = myData.iPlayingStatus;
	if (cCurrentState == NULL)
		return FALSE;
	if (strcmp (cCurrentState, "playing") == 0)
		myData.iPlayingStatus = PLAYER_PLAYING;
	else if (strcmp (cCurrentState, "paused") == 0)
		myData.iPlayingStatus = PLAYER_PAUSED;
	else if (strcmp (cCurrentState, "idle") == 0)
	{
		myData.iPlayingStatus = PLAYER_STOPPED;
		return FALSE;  // on ne redessine pas ici, on va attendre la prochaine iteration de read_data. En effet, le lecteur passe par l'etat "idle" a chaque chgt de chanson, alors qu'il n'est manifestement pas a l'arret.
	}
	else  // loading, loaded, etc => on considere que le lecteur va jouer la chanson des qu'il aura fini de la charger, on se place donc direct en mode "playing".
	{
		myData.iPlayingStatus = PLAYER_PLAYING;
		return FALSE;  // inutile de redessiner pour ces etats.
	}
	return TRUE;
}

/* Teste si Audacious joue de la musique ou non
 */
static void _banshee_getPlaying (void)
{
	cd_message ("");
	gchar *cCurrentState = cairo_dock_dbus_get_string (myData.dbus_proxy_player, "GetCurrentState");
	_extract_playing_status (cCurrentState);
	g_free (cCurrentState);
}

/* Renvoie le temps ecoule en secondes..
 */
static void _banshee_get_time_elapsed (void)
{
	cd_message ("");
	myData.iCurrentTime = cairo_dock_dbus_get_uinteger (myData.dbus_proxy_player, "GetPosition") / 1000;
}

static inline void _extract_metadata (GHashTable *data_list)
{
	GValue *value;
	g_free (myData.cArtist);
	value = (GValue *) g_hash_table_lookup (data_list, "artist");
	if (value != NULL && G_VALUE_HOLDS_STRING (value))
		myData.cArtist = g_strdup (g_value_get_string(value));
	else
		myData.cArtist = NULL;

	g_free (myData.cAlbum);
	value = (GValue *) g_hash_table_lookup (data_list, "album");
	if (value != NULL && G_VALUE_HOLDS_STRING (value))
		myData.cAlbum = g_strdup (g_value_get_string(value));
	else
		myData.cAlbum = NULL;

	g_free (myData.cTitle);
	value = (GValue *) g_hash_table_lookup (data_list, "name");
	if (value != NULL && G_VALUE_HOLDS_STRING (value))
		myData.cTitle = g_strdup (g_value_get_string (value));
	else
		myData.cTitle = NULL;

	value = (GValue *) g_hash_table_lookup(data_list, "track-number");
	if (value != NULL && G_VALUE_HOLDS_INT(value))
		myData.iTrackNumber = g_value_get_int(value);
	else
		myData.iTrackNumber = 0;
	
	value = (GValue *) g_hash_table_lookup(data_list, "length");
	if (value != NULL && G_VALUE_HOLDS_INT(value))
		myData.iSongLength = g_value_get_int(value);
	else
		myData.iSongLength = cairo_dock_dbus_get_uinteger (myData.dbus_proxy_player, "GetLength") / 1000;
		
	g_free (myData.cPlayingUri);
	myData.cPlayingUri = cairo_dock_dbus_get_string (myData.dbus_proxy_player, "GetCurrentUri");
	
	const gchar *cString = NULL;
	value = (GValue *) g_hash_table_lookup(data_list, "artwork-id");
	if (value != NULL && G_VALUE_HOLDS_STRING(value))
		cString = g_value_get_string(value);
	cd_debug ("MP : got cover path from Banshee : '%s'\n", cString);
	gchar *cCoverPath = (cString ? g_strdup_printf ("%s/.cache/album-art/%s.jpg", g_getenv ("HOME"), cString) : NULL);
	if (cString && ! g_file_test (cCoverPath, G_FILE_TEST_EXISTS)) // path has changed :-/
	{
		g_free (cCoverPath);
		cCoverPath = g_strdup_printf ("%s/.cache/media-art/%s.jpg", g_getenv ("HOME"), cString);
	}
	cd_musicplayer_get_cover_path (cCoverPath, FALSE);
	g_free (cCoverPath);
}

/* Recupere les infos de la chanson courante, y compris le chemin de la couverture (la telecharge au besoin).
 */
static void cd_banshee_getSongInfos (void)
{
	GHashTable *data_list = NULL;
	GValue *value;
	
	if (dbus_g_proxy_call (myData.dbus_proxy_player, "GetCurrentTrack", NULL, G_TYPE_INVALID,
		MP_DBUS_TYPE_SONG_METADATA,
		  &data_list,
		  G_TYPE_INVALID))
	{
		_extract_metadata (data_list);
		
		g_hash_table_destroy (data_list);
	}
	else
	{
		cd_warning ("  can't get song properties");
		g_free (myData.cPlayingUri);
		myData.cPlayingUri = NULL;
		g_free (myData.cTitle);
		myData.cTitle = NULL;
		g_free (myData.cAlbum);
		myData.cAlbum = NULL;
		g_free (myData.cCoverPath);
		myData.cCoverPath = NULL;
	}
	myData.cRawTitle = g_strdup_printf ("%s - %s", myData.cArtist, myData.cTitle);
}

static void cd_banshee_getCoverPath (void)
{
	cd_debug ("MP - %s ()\n", __func__);
	GHashTable *data_list = NULL;
	GValue *value;
	GError *erreur = NULL;
	
	dbus_g_proxy_call (myData.dbus_proxy_player, "GetCurrentTrack", &erreur, G_TYPE_INVALID,
		MP_DBUS_TYPE_SONG_METADATA,
		&data_list,
		G_TYPE_INVALID);
	if (erreur != NULL)
	{
		cd_warning (erreur->message);
		g_error_free (erreur);
		myData.iPlayingStatus = PLAYER_NONE;
	}
	else
	{
		const gchar *cString = NULL;
		value = (GValue *) g_hash_table_lookup(data_list, "artwork-id");
		if (value != NULL && G_VALUE_HOLDS_STRING(value))
			cString = g_value_get_string(value);
		cd_debug ("MP -  => got cover path from Banshee : '%s'\n", cString);
		gchar *cCoverPath = g_strdup_printf ("%s/.cache/album-art/300/%s.jpg", g_getenv ("HOME"), cString);
		if (! g_file_test  (cCoverPath, G_FILE_TEST_EXISTS))
		{
			g_free (cCoverPath);
			cCoverPath = g_strdup_printf ("%s/.cache/album-art/%s.jpg", g_getenv ("HOME"), cString);
			if (! g_file_test (cCoverPath, G_FILE_TEST_EXISTS)) // path has changed :-/
			{
				g_free (cCoverPath);
				cCoverPath = g_strdup_printf ("%s/.cache/media-art/300/%s.jpg", g_getenv ("HOME"), cString);
				if (! g_file_test (cCoverPath, G_FILE_TEST_EXISTS))
				{
					g_free (cCoverPath);
					cCoverPath = g_strdup_printf ("%s/.cache/media-art/%s.jpg", g_getenv ("HOME"), cString);
				}
			}
		}
		cd_musicplayer_get_cover_path (cString, TRUE);  // TRUE <=> on cherche nous-meme si aucune fournie.
		g_free (cCoverPath);
		g_hash_table_destroy (data_list);
	}
}
/* Fonction executée à chaque changement de musique.
 */
static void onChangeSong(DBusGProxy *player_proxy, const gchar *cEvent, const gchar *cMessage, double fBufferingPercent, gpointer data)
{
	CD_APPLET_ENTER;
	cd_debug ("MP : %s (%s, %s, %.2f)\n", __func__, cEvent, cMessage, fBufferingPercent);
	if (cMessage != NULL)
	{
		myData.bIsRunning = TRUE;
		if (strcmp (cMessage, "startofstream") == 0)
			cd_banshee_getSongInfos ();
		else if (strcmp (cMessage, "trackinfoupdated") == 0)
		{
			cd_debug ("MP -  trackinfoupdated\n");
			if (myData.cCoverPath == NULL)
			{
				cd_debug ("MP -   aucune pochette, on regarde si Banshee a mieux a nous proposer\n");
				cd_banshee_getCoverPath ();
			}
			else if (!myData.cover_exist) // le lecteur nous avait deja refile l'adresse en avance, on regarde si le fichier est desormais present.
			{
				cd_musiplayer_set_cover_if_present (FALSE);
				CD_APPLET_LEAVE ();
				//return ;
			}
		}
		else
			CD_APPLET_LEAVE ();
			//return ;
	}
	else
	{
		cd_debug ("MP - message vide !\n");
		g_free (myData.cPlayingUri);
		myData.cPlayingUri = NULL;
		g_free (myData.cArtist);
		myData.cArtist = NULL;
		g_free (myData.cAlbum);
		myData.cAlbum = NULL;
		g_free (myData.cTitle);
		myData.cTitle = NULL;
		g_free (myData.cCoverPath);
		myData.cCoverPath = NULL;
		myData.iSongLength = 0;
		myData.iTrackNumber = 0;
		myData.cover_exist = FALSE;
		
		cd_musicplayer_dbus_detect_player ();
	}
	cd_musicplayer_update_icon (TRUE);
	CD_APPLET_LEAVE ();
}
static void g_cclosure_marshal_VOID__STRING_STRING_DOUBLE (GClosure *closure,
	GValue *return_value,
	guint n_param_values,
	const GValue *param_values,
	gpointer invocation_hint,
	gpointer marshal_data);
static void g_cclosure_marshal_VOID__STRING_STRING_DOUBLE (GClosure *closure,
	GValue *return_value,
	guint n_param_values,
	const GValue *param_values,
	gpointer invocation_hint,
	gpointer marshal_data)
{
	cd_debug ("MP - %s ()\n", __func__);
	const GValue *value;
	const gchar *cEvent = NULL;
	const gchar *cMessage = NULL;
	double fBufferingPercent=0.;
	
	value = &param_values[0];
	if (value != NULL && G_VALUE_HOLDS_STRING (value))
		cEvent = g_value_get_string (value);
	
	value = &param_values[1];
	if (value != NULL && G_VALUE_HOLDS_STRING (value))
		cMessage = g_value_get_string (value);
	
	value = &param_values[2];
	if (value != NULL && G_VALUE_HOLDS_DOUBLE (value))
		fBufferingPercent = g_value_get_double (value);
	
	onChangeSong (NULL, cEvent, cMessage, fBufferingPercent, marshal_data);
}


/* Fonction executée à chaque changement play/pause
 */
static void onChangePlaying(DBusGProxy *player_proxy, const gchar *cCurrentStatus, gpointer data)
{
	CD_APPLET_ENTER;
	cd_debug ("MP : %s (%s)\n", __func__, cCurrentStatus);
	myData.bIsRunning = TRUE;
	gboolean bStateChanged = _extract_playing_status (cCurrentStatus);
	if (! bStateChanged)
		CD_APPLET_LEAVE ();
		//return ;
	
	if (myData.iPlayingStatus == PLAYER_PLAYING)
		cd_musicplayer_relaunch_handler ();
	if(! myData.cover_exist && myData.cPlayingUri != NULL)
	{
		cd_musicplayer_set_surface (myData.iPlayingStatus);
	}
	else
	{
		CD_APPLET_REDRAW_MY_ICON;
	}
	CD_APPLET_LEAVE ();
}



////////////////////////////
// Definition du backend. //
////////////////////////////

/* Fonction de connexion au bus de audacious.
 */
static gboolean _cd_banshee_dbus_connect_to_bus (void)
{
	if (cairo_dock_dbus_is_enabled ())
	{
		myData.dbus_enable = cd_musicplayer_dbus_connect_to_bus (); // cree le proxy.
		
		myData.dbus_enable_shell = musicplayer_dbus_connect_to_bus_Shell ();  // cree le proxy pour la 2eme interface car AU en a 2.
		
		dbus_g_proxy_add_signal(myData.dbus_proxy_player, "StateChanged",
			G_TYPE_STRING,
			G_TYPE_INVALID);
		// Enregistrement d'un marshaller specifique au signal (sinon impossible de le récupérer ni de le voir
		dbus_g_object_register_marshaller(g_cclosure_marshal_VOID__STRING_STRING_DOUBLE,
			G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE ,G_TYPE_INVALID);	
		dbus_g_proxy_add_signal(myData.dbus_proxy_player, "EventChanged",
			G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE,  // MP_DBUS_TYPE_SONG_METADATA
			G_TYPE_INVALID);
		
		dbus_g_proxy_connect_signal(myData.dbus_proxy_player, "StateChanged",
			G_CALLBACK(onChangePlaying), NULL, NULL);
			
		dbus_g_proxy_connect_signal(myData.dbus_proxy_player, "EventChanged",
			G_CALLBACK(onChangeSong), NULL, NULL);

		return TRUE;
	}
	return FALSE;
}

/* Permet de libérer la mémoire prise par le backend.
 */
static void cd_banshee_free_data (void)
{
	if (myData.dbus_proxy_player != NULL)
	{
		dbus_g_proxy_disconnect_signal(myData.dbus_proxy_player, "StateChanged",
			G_CALLBACK(onChangePlaying), NULL);
		
		dbus_g_proxy_disconnect_signal(myData.dbus_proxy_player, "EventChanged",
			G_CALLBACK(onChangeSong), NULL);
	}
	
	musicplayer_dbus_disconnect_from_bus();
	musicplayer_dbus_disconnect_from_bus_Shell();
}

/* Controle du lecteur.
 */
static void cd_banshee_control (MyPlayerControl pControl, const char *file)
{
	gchar *cCommand = NULL;
	
	switch (pControl)
	{
		case PLAYER_PREVIOUS :
			dbus_g_proxy_call_no_reply (myData.dbus_proxy_shell, "Previous", 
				G_TYPE_BOOLEAN, FALSE,  // FALSE <=> dont play it now
				G_TYPE_INVALID,
				G_TYPE_INVALID);
		break;
		
		case PLAYER_PLAY_PAUSE :
			cairo_dock_dbus_call (myData.dbus_proxy_player, "TogglePlaying");
		break;

		case PLAYER_NEXT :
			dbus_g_proxy_call_no_reply (myData.dbus_proxy_shell, "Next", 
				G_TYPE_BOOLEAN, FALSE,  // FALSE <=> dont play it now
				G_TYPE_INVALID,
				G_TYPE_INVALID);
		break;
		
		case PLAYER_SHUFFLE :
		{
			gboolean bShuffle = cairo_dock_dbus_get_integer (myData.dbus_proxy_shell, "GetShuffleMode");
			cd_debug ("MP - bShuffle : %d\n", bShuffle);
			dbus_g_proxy_call_no_reply (myData.dbus_proxy_shell, "SetShuffleMode", 
				G_TYPE_INT, ! bShuffle,
				G_TYPE_INVALID,
				G_TYPE_INVALID);
		}
		break;
		
		case PLAYER_REPEAT :
		{
			int iRepeat = cairo_dock_dbus_get_integer (myData.dbus_proxy_shell, "GetRepeatMode");
			cd_debug ("MP - iRepeat : %d\n", iRepeat);
			dbus_g_proxy_call_no_reply (myData.dbus_proxy_shell, "SetRepeatMode", 
				G_TYPE_INT, (iRepeat+1)%3,
				G_TYPE_INVALID,
				G_TYPE_INVALID);
		}
		break;
		
		default :
			return;
	}
}


/* Recupere le temps ecoule chaque seconde (pas de signal pour ca).
 */
static void cd_banshee_read_data (void)
{
	if (myData.dbus_enable)
	{
		if (myData.bIsRunning)
		{
			if (myData.iPlayingStatus == PLAYER_PLAYING)
			{
				_banshee_get_time_elapsed();
				if (myData.iCurrentTime < 0)
					myData.iPlayingStatus = PLAYER_STOPPED;
			}
			else if (myData.iPlayingStatus != PLAYER_PAUSED)  // en pause le temps reste constant.
			{
				myData.iCurrentTime = 0;
				if (myData.iPlayingStatus == PLAYER_STOPPED && myData.pPreviousPlayingStatus != PLAYER_STOPPED)  /// utile ?...
				{
					cd_debug ("MP - LECTEUR STOPPE\n");
					myData.pPreviousPlayingStatus = PLAYER_STOPPED;
					//cd_musicplayer_dbus_detect_player ();  // ca fait redemarrer le lecteur ?!
					//if (! myData.bIsRunning)
					{
						cd_musicplayer_set_surface (PLAYER_NONE);
						g_free (myData.cCoverPath);
						myData.cCoverPath = NULL;
					}
				}
			}
		}
		else 
		{
			myData.iCurrentTime = 0;
		}
		cd_message (" myData.iCurrentTime <- %d", __func__, myData.iCurrentTime);
	}
}

/* Initialise le backend de BA.
 */
void cd_banshee_configure (void)
{
	myData.DBus_commands.service = "org.bansheeproject.Banshee";
	myData.DBus_commands.path2 = "/org/bansheeproject/Banshee/PlaybackController";
	myData.DBus_commands.interface2 = "org.bansheeproject.Banshee.PlaybackController";
	myData.DBus_commands.path = "/org/bansheeproject/Banshee/PlayerEngine";
	myData.DBus_commands.interface = "org.bansheeproject.Banshee.PlayerEngine";
	
	myData.dbus_enable = _cd_banshee_dbus_connect_to_bus ();  // se connecte au bus et aux signaux de AU.
	if (myData.dbus_enable)
	{
		cd_musicplayer_dbus_detect_player ();  // on teste la presence de BA sur le bus <=> s'il est ouvert ou pas.
		if(myData.bIsRunning)  // player en cours d'execution, on recupere son etat.
		{
			cd_message ("MP : BA is running\n");
			_banshee_getPlaying();
			cd_banshee_getSongInfos ();
			cd_musicplayer_update_icon (TRUE);
		}
		else  // player eteint.
		{
			cd_musicplayer_set_surface (PLAYER_NONE);
		}
	}
	else  // sinon on signale par l'icone appropriee que le bus n'est pas accessible.
	{
		cd_musicplayer_set_surface (PLAYER_BROKEN);
	}
}

void cd_musicplayer_register_banshee_handler (void)
{
	MusicPlayerHandeler *pBanshee = g_new0 (MusicPlayerHandeler, 1);
	pBanshee->read_data = cd_banshee_read_data;  // recupere le temps ecoule car on n'a pas de signal pour ca.
	pBanshee->free_data = cd_banshee_free_data;
	pBanshee->configure = cd_banshee_configure;  // renseigne les proprietes DBus et se connecte au bus.
	pBanshee->control = cd_banshee_control;
	pBanshee->get_cover = NULL;
	pBanshee->cCoverDir = g_strdup_printf ("%s/.cache/media-art", g_getenv ("HOME"));
	
	pBanshee->appclass = "banshee";  // pffff
	pBanshee->launch = "banshee";
	pBanshee->name = "Banshee";
	pBanshee->cMprisService = "org.bansheeproject.Banshee";
	pBanshee->iPlayerControls = PLAYER_PREVIOUS | PLAYER_PLAY_PAUSE | PLAYER_NEXT | PLAYER_SHUFFLE | PLAYER_REPEAT | PLAYER_ENQUEUE;
	pBanshee->iPlayer = MP_BANSHEE;
	pBanshee->bSeparateAcquisition = FALSE;
	pBanshee->iLevel = PLAYER_GOOD;  // n'a besoin d'une boucle que pour afficher le temps ecoule.
	cd_musicplayer_register_my_handler (pBanshee,"Banshee");
}

