/*
 * unity-webapps-music_player-context.c
 * Copyright (C) Canonical LTD 2011
 *
 * Author: Robert Carr <racarr@canonical.com>
 *
 unity-webapps 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 3 of the License, or
 * (at your option) any later version.
 *
 * unity-webapps 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.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
 */

#include "unity-webapps-music-player-context.h"

#include "unity-webapps-dbus-defs.h"
#include "unity-webapps-interest-manager.h"
#include "unity-webapps-interest-tracker.h"
#include "unity-webapps-resource-cache.h"
#include "unity-webapps-debug.h"

#include "config.h"

static void emit_music_player_signal (GDBusConnection *connection, const gchar *signal_name);

static void player_raise_callback (UnityMusicPlayer *player, gpointer user_data);
static void player_next_callback (UnityMusicPlayer *player, gpointer user_data);
static void player_previous_callback (UnityMusicPlayer *player, gpointer user_data);
static void player_play_pause_callback (UnityMusicPlayer *player, gpointer user_data);

typedef struct {
  UnityTrackMetadata *metadata;
  gboolean can_go_next, can_go_previous, can_play, can_pause;
  gint playback_state;
} PlayerState;

static PlayerState*
player_state_new ()
{
  PlayerState *res = g_new0 (PlayerState, 1);

  res->metadata = unity_track_metadata_new ();

  return res;
}

static void
player_state_free (PlayerState *state)
{
  g_object_unref (state->metadata);
  g_free (state);
}

static void
emit_playlist_activated_signal (UnityWebappsMusicPlayerContext *indicator_context,
				const gchar *playlist)
{
  GError *error;

  UNITY_WEBAPPS_NOTE (INDICATOR, "Emitting PlaylistActivated signal (%s)", playlist);

  error = NULL;
  g_dbus_connection_emit_signal (indicator_context->connection,
				 NULL,
				 UNITY_WEBAPPS_MUSIC_PLAYER_PATH,
				 UNITY_WEBAPPS_MUSIC_PLAYER_IFACE,
				 "PlaylistActivated",
				 g_variant_new ("(s)", (gchar *)playlist, NULL),
				 &error);
  if (error != NULL)
    {
      g_warning ("Error emitting PlaylistActivated signal (from sound menu) in music player context: %s", error->message);

      g_error_free (error);
    }
}

static void
unity_webapps_music_player_context_playlist_activated (UnityMusicPlayer *player,
						       gchar *playlist_id,
						       gpointer user_data)
{
  UnityWebappsMusicPlayerContext *context;
  const gchar *playlist_name;

  context = (UnityWebappsMusicPlayerContext *)user_data;
  playlist_name = (const gchar *)g_hash_table_lookup (context->playlist_names_by_id, playlist_id);
  if (playlist_name == NULL)
    {
      UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Got playlist activation for playlist we don't know about");
    }

  emit_playlist_activated_signal (context, playlist_name);
}




static UnityMusicPlayer *
get_unity_music_player (UnityWebappsMusicPlayerContext *context)
{
  UnityMusicPlayer *player;

  if (context->music_player == NULL)
    {
      UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Initializing UnityMusicPlayer");
      player = unity_music_player_new (context->desktop_name);

      g_signal_connect (player, "activate-playlist",
			G_CALLBACK (unity_webapps_music_player_context_playlist_activated),
			context);

      unity_music_player_set_is_blacklisted (player, FALSE);

      g_signal_connect (player, "raise", G_CALLBACK (player_raise_callback), context);
      g_signal_connect (player, "play_pause", G_CALLBACK (player_play_pause_callback), context);
      g_signal_connect (player, "next", G_CALLBACK (player_next_callback), context);
      g_signal_connect (player, "previous", G_CALLBACK (player_previous_callback), context);
#ifdef HAVE_UNITY_MUSIC_PLAYER_EXPORT
      unity_music_player_export (player);
#endif
      unity_music_player_set_title (player, context->label);

      context->music_player = player;
    }

  return context->music_player;
}


static void
real_set_track (UnityWebappsMusicPlayerContext *music_player_context,
		gint interest_id,
		const gchar *artist,
		const gchar *album,
		const gchar *title,
		const gchar *art_location)
{
  PlayerState *state;
  state = g_hash_table_lookup (music_player_context->state_by_interest_id, GINT_TO_POINTER (interest_id));
  if (!state)
    {
      state = player_state_new ();
      g_hash_table_replace (music_player_context->state_by_interest_id, GINT_TO_POINTER (interest_id), state);
    }

  unity_track_metadata_set_artist (state->metadata, artist);
  unity_track_metadata_set_album (state->metadata, album);
  unity_track_metadata_set_title (state->metadata, title);

  if (art_location != NULL)
    {
      GFile *artwork_file = NULL;
      artwork_file = g_file_new_for_path (art_location);
      unity_track_metadata_set_art_location (state->metadata, artwork_file);
      g_object_unref (G_OBJECT (artwork_file));
    }

  if (unity_webapps_interest_tracker_get_most_recent_interest (music_player_context->interest_tracker) == interest_id)
    unity_music_player_set_current_track (get_unity_music_player (music_player_context), state->metadata);

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Set track: %s", title);
}

static void
player_raise_callback (UnityMusicPlayer *player, gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Got Raise callback");

  music_player_context = (UnityWebappsMusicPlayerContext *)user_data;

  unity_webapps_context_daemon_emit_raise (music_player_context->connection, -1, NULL);

}

static void
emit_music_player_signal (GDBusConnection *connection, const gchar *signal_name)
{
  GError *error;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Emitting signal %s", signal_name);

  error = NULL;

  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_MUSIC_PLAYER_PATH,
				 UNITY_WEBAPPS_MUSIC_PLAYER_IFACE,
				 signal_name,
				 NULL /* Params */,
				 &error);

  if (error != NULL)
    {
      g_warning ("Error emitting %s signal in music player context", signal_name);
      g_error_free (error);

      return;
    }

}

static void
player_play_pause_callback (UnityMusicPlayer *player, gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Got PlayPause callback");

  music_player_context = (UnityWebappsMusicPlayerContext *)user_data;

  emit_music_player_signal (music_player_context->connection, "PlayPause");
}

static void
player_previous_callback (UnityMusicPlayer *player, gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Got Previous callback");

  music_player_context = (UnityWebappsMusicPlayerContext *)user_data;

  emit_music_player_signal (music_player_context->connection, "Previous");
}

static void
player_next_callback (UnityMusicPlayer *player, gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Got Next callback");

  music_player_context = (UnityWebappsMusicPlayerContext *)user_data;

  emit_music_player_signal (music_player_context->connection, "Next");
}

static void
unity_webapps_music_player_context_clear_playlists (UnityWebappsMusicPlayerContext *context)
{
  UnityMusicPlayer *player;
  UnityPlaylist **playlists, *p;
  gint num_playlists, i;

  player = get_unity_music_player (context);

  playlists = unity_music_player_get_playlists (player, &num_playlists);

  for (i = 0; i < num_playlists; i++)
    {
      p = playlists[i];
      unity_music_player_remove_playlist (player, p);
    }

  g_hash_table_remove_all (context->playlist_names_by_id);

  g_free (playlists);
}

static void
unity_webapps_music_player_context_set_playlists (UnityWebappsMusicPlayerContext *context,
						  const gchar *const *playlists)
{
  GIcon *icon;
  UnityMusicPlayer *player;
  gint i, len;
  GError *error = NULL;

  player = get_unity_music_player (context);
  len = g_strv_length ((gchar **)playlists);
  icon = g_icon_new_for_string ("playlist-symbolic", &error);

  if (error != NULL)
    {
      UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Failed to load playlist icon");
      icon = NULL;
    }

  for (i = 0; i < len; i++)
    {
      UnityPlaylist *p;
      const gchar *playlist_name;
      gchar *id;

      playlist_name = playlists[i];
      id = g_strdup_printf("/Playlist%d", i);
      p = unity_playlist_new(id);

      g_hash_table_insert (context->playlist_names_by_id, g_strdup(id),
			   g_strdup (playlist_name));

      g_free (id);

      unity_playlist_set_icon (p, icon);
      unity_playlist_set_name (p, playlist_name);

      unity_music_player_add_playlist (player, p);
    }

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Added %d playlists", len);
}



static gboolean
on_handle_set_playlists (UnityWebappsGenMusicPlayer *music_player,
			 GDBusMethodInvocation *invocation,
			 const gchar *const *playlists,
			 gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context;
  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Handling SetPlaylists call");

  music_player_context = (UnityWebappsMusicPlayerContext *)user_data;

  unity_webapps_music_player_context_clear_playlists (music_player_context);
  unity_webapps_music_player_context_set_playlists (music_player_context, playlists);

  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

static gboolean
on_handle_set_track (UnityWebappsGenMusicPlayer *indicator,
		     GDBusMethodInvocation *invocation,
		     const gchar *artist,
		     const gchar *album,
		     const gchar *title,
		     const gchar *icon_url,
		     gint interest_id,
		     gpointer user_data)
{
  UnityWebappsMusicPlayerContext *context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Handling set track: %s by %s from %s", title, artist, album);

  context = (UnityWebappsMusicPlayerContext *)user_data;

  if (g_strcmp0 (icon_url, "") == 0)
    {
      // TODO: DEFAULT?
      real_set_track (context, interest_id, artist, album, title, NULL);
    }
  else
    {
      gchar *tmp_file;

      tmp_file = unity_webapps_resource_cache_lookup_uri (icon_url);

      real_set_track (context, interest_id, artist, album, title, tmp_file);

      g_free (tmp_file);
    }

  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

#define ADD_HANDLER(field, field_type, field_variant_string_arg)	\
  static gboolean							\
  on_handle_set_##field (UnityWebappsGenMusicPlayer *indicator,		\
			 GDBusMethodInvocation *invocation,		\
			 gint interest_id,				\
			 field_type field,				\
			 gpointer user_data)				\
  {									\
    PlayerState *state;							\
    UnityWebappsMusicPlayerContext *context;				\
    context = (UnityWebappsMusicPlayerContext *)user_data;		\
    state = g_hash_table_lookup (context->state_by_interest_id, GINT_TO_POINTER (interest_id));	\
    if (!state)								\
      {									\
	state = player_state_new ();					\
	g_hash_table_replace (context->state_by_interest_id, GINT_TO_POINTER (interest_id), state); \
      }									\
    state->field = field;						\
    if (context->music_player && unity_webapps_interest_tracker_get_most_recent_interest (context->interest_tracker) == interest_id) \
      {									\
	unity_music_player_set_##field (get_unity_music_player (context), field); \
      }									\
    g_dbus_method_invocation_return_value (invocation, NULL);		\
    return TRUE;							\
  }									\
  static gboolean							\
  on_handle_get_##field (UnityWebappsGenMusicPlayer *indicator,		\
			 GDBusMethodInvocation *invocation,		\
			 gint interest_id,				\
			 gpointer user_data)				\
  {									\
    PlayerState *state;							\
    UnityWebappsMusicPlayerContext *context;				\
    field_type field = 0;                                               \
    context = (UnityWebappsMusicPlayerContext *)user_data;		\
    state = g_hash_table_lookup (context->state_by_interest_id, GINT_TO_POINTER (interest_id));	\
    state = g_hash_table_lookup (context->state_by_interest_id, GINT_TO_POINTER (interest_id));	\
    if (state)                                                          \
      field = state->field;                                             \
    g_dbus_method_invocation_return_value (invocation,                  \
                                           g_variant_new (field_variant_string_arg, field, NULL)); \
    return TRUE;							\
  }

#define FIELD_VARIANT_STRING_INTEGER "(i)"
#define FIELD_VARIANT_STRING_BOOLEAN "(b)"
ADD_HANDLER(playback_state, gint, FIELD_VARIANT_STRING_INTEGER)
ADD_HANDLER(can_go_next, gboolean, FIELD_VARIANT_STRING_BOOLEAN)
ADD_HANDLER(can_go_previous, gboolean, FIELD_VARIANT_STRING_BOOLEAN)
ADD_HANDLER(can_play, gboolean, FIELD_VARIANT_STRING_BOOLEAN)
ADD_HANDLER(can_pause, gboolean, FIELD_VARIANT_STRING_BOOLEAN)

#undef ADD_HANDLER

static void
export_object (GDBusConnection *connection,
	       UnityWebappsMusicPlayerContext *music_player_context)
{
  GError *error;

  music_player_context->music_player_service_interface = unity_webapps_gen_music_player_skeleton_new ();

  g_signal_connect (music_player_context->music_player_service_interface,
		    "handle-set-track",
		    G_CALLBACK (on_handle_set_track),
		    music_player_context);
  g_signal_connect (music_player_context->music_player_service_interface,
		    "handle-set-playlists",
		    G_CALLBACK (on_handle_set_playlists),
		    music_player_context);

#define ADD_HANDLER(field, name)						\
  g_signal_connect (music_player_context->music_player_service_interface,\
		    "handle-set-" name,\
		    G_CALLBACK (on_handle_set_##field),\
		    music_player_context);\
  g_signal_connect (music_player_context->music_player_service_interface,\
		    "handle-get-" name,\
		    G_CALLBACK (on_handle_get_##field),\
		    music_player_context);

  ADD_HANDLER (playback_state, "playback-state");
  ADD_HANDLER(can_go_next, "can-go-next");
  ADD_HANDLER(can_go_previous, "can-go-previous");
  ADD_HANDLER(can_play, "can-play");
  ADD_HANDLER(can_pause, "can-pause");
#undef ADD_HANDLER

  error = NULL;

  g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (music_player_context->music_player_service_interface),
				    connection,
				    UNITY_WEBAPPS_MUSIC_PLAYER_PATH,
				    &error);

  if (error != NULL)
    {
      g_error ("Error exporting Unity Webapps Music Player object: %s", error->message);

      g_error_free (error);

      return;
    }

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Exported Music Player object");
}

static void
on_manager_active_changed (UnityWebappsInterestManager *manager,
			   gint interest_id,
			   gboolean is_active,
			   gpointer user_data)
{
  PlayerState *state;
  UnityWebappsMusicPlayerContext *music_player_context = user_data;

  if (!is_active)
    return;

  if (!g_hash_table_size (music_player_context->state_by_interest_id))
    return;

  state = g_hash_table_lookup (music_player_context->state_by_interest_id, GINT_TO_POINTER (interest_id));
  if (!state)
    {
      state = player_state_new ();
      g_hash_table_replace (music_player_context->state_by_interest_id, GINT_TO_POINTER (interest_id), state);
    }

  UnityMusicPlayer *player = get_unity_music_player(music_player_context);
  if (player)
    {
      unity_music_player_set_current_track (player, state->metadata);
      unity_music_player_set_playback_state (player, state->playback_state);
      unity_music_player_set_can_go_next (player, state->can_go_next);
      unity_music_player_set_can_go_previous (player, state->can_go_previous);
      unity_music_player_set_can_play (player, state->can_play);
      unity_music_player_set_can_pause (player, state->can_pause);
    }
}

static void
on_manager_interest_removed (UnityWebappsInterestManager *manager,
			     UnityWebappsInterest *interest,
			     gpointer user_data)
{
  UnityWebappsMusicPlayerContext *music_player_context = user_data;

  g_hash_table_remove (music_player_context->state_by_interest_id, GINT_TO_POINTER (interest->id));
}

UnityWebappsMusicPlayerContext *
unity_webapps_music_player_context_new (GDBusConnection *connection,
					UnityWebappsInterestManager *interest_manager,
					UnityWebappsInterestTracker *interest_tracker,
					const gchar *desktop_name,
					const gchar *canonical_name,
					const gchar *label)
{
  UnityWebappsMusicPlayerContext *music_player_context;

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Creating new UnityWebappsMusicPlayerContext object");

  music_player_context = g_malloc0 (sizeof (UnityWebappsMusicPlayerContext));
  music_player_context->interest_manager = interest_manager;
  music_player_context->interest_tracker = interest_tracker;

  music_player_context->connection = g_object_ref (connection);

  music_player_context->playlist_names_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
  music_player_context->state_by_interest_id = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)player_state_free);

  g_signal_connect (music_player_context->interest_manager, "interest-removed",
		    G_CALLBACK (on_manager_interest_removed),
		    music_player_context);
  g_signal_connect (music_player_context->interest_manager, "active-changed",
		    G_CALLBACK (on_manager_active_changed),
		    music_player_context);

  export_object (connection, music_player_context);

  music_player_context->desktop_name = g_strdup (desktop_name);
  music_player_context->canonical_name = g_strdup (canonical_name);
  music_player_context->label = g_strdup (label);

  return music_player_context;
}

void
unity_webapps_music_player_context_free (UnityWebappsMusicPlayerContext *music_player_context)
{
  g_signal_handlers_disconnect_by_func(music_player_context->interest_manager, on_manager_interest_removed, music_player_context);
  g_signal_handlers_disconnect_by_func(music_player_context->interest_manager, on_manager_active_changed, music_player_context);

  UNITY_WEBAPPS_NOTE (MUSIC_PLAYER, "Finalizing UnityWebappsMusicPlayerContext object");
  g_object_unref (G_OBJECT (music_player_context->music_player_service_interface));

  g_free (music_player_context->desktop_name);
  g_free (music_player_context->canonical_name);
  g_free (music_player_context->label);

  g_hash_table_destroy (music_player_context->playlist_names_by_id);
  g_hash_table_destroy (music_player_context->state_by_interest_id);

  if (music_player_context->music_player)
    {
      unity_music_player_set_is_blacklisted (music_player_context->music_player, TRUE);

#ifdef HAVE_UNITY_MUSIC_PLAYER_EXPORT
      unity_music_player_unexport (music_player_context->music_player);
#endif
      g_object_unref (G_OBJECT (music_player_context->music_player));
    }
  g_object_unref (G_OBJECT (music_player_context->connection));


  g_free (music_player_context);
}
