/*
 * rb-ipod-playlist-source.c - Rhythmbox iPod Playlist.
 * Copyright 2006, Alessandro Decina
 *
 * Authors:
 *   Alessandro Decina <alessandro@nnva.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License, version 2, as published by the Free Software Foundation.
 *
 * 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
*/

#include <string.h>
#include "rb-util.h"
#include "rb-debug.h"
#include "rb-ipod-manager.h"
#include "rb-ipod-playlist-source.h"

G_DEFINE_TYPE (RBiPodPlaylistSource, rb_ipod_playlist_source,
		RB_TYPE_STATIC_PLAYLIST_SOURCE);

#define RB_IPOD_PLAYLIST_SOURCE_GET_PRIVATE(object) \
	G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_IPOD_PLAYLIST_SOURCE, \
			RBiPodPlaylistSourcePrivate)

#define RB_IPOD_PLAYLIST_SOURCE_POPUP_PATH "/iPodPlaylistSourcePopup"

enum {
	PROP_IPOD_MANAGER,
	PROP_IPOD_PLAYLIST
};

struct _RBiPodPlaylistSourcePrivate {
	RBiPodManager *ipod_manager;
	RhythmDB *db;
	Itdb_Playlist *playlist;
	gulong track_added_id;
	gulong track_read_id;
	gboolean noremove;
};

static void rb_ipod_playlist_source_get_property (GObject *object,
		guint propid, GValue *value, GParamSpec *pspec);
static void rb_ipod_playlist_source_set_property (GObject *object,
		guint propid, const GValue *value, GParamSpec *pspec);
static void rb_ipod_playlist_source_dispose (GObject *object);

static gboolean impl_show_popup (RBSource *source);

#ifdef ENABLE_TRACK_TRANSFER
static void name_changed_cb (RBiPodPlaylistSource *source,
		GParamSpec *pspec, gpointer data);
static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
static void impl_paste (RBSource *source, GList *entries);
static GList *impl_cut (RBSource *source);
static void impl_delete (RBSource *source);
static void impl_delete_thyself (RBSource *asource);
#endif

static void connect_manager_signal_handlers (RBiPodPlaylistSource *source);
static void disconnect_manager_signal_handlers (RBiPodPlaylistSource *source);

static void
rb_ipod_playlist_source_class_init (RBiPodPlaylistSourceClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);

	object_class->get_property = rb_ipod_playlist_source_get_property;
	object_class->set_property = rb_ipod_playlist_source_set_property;
	object_class->dispose = rb_ipod_playlist_source_dispose;

	g_object_class_install_property (object_class, PROP_IPOD_MANAGER,
					 g_param_spec_object ("ipod-manager",
							      "iPod Manager",
							      "Rhythmbox iPod Manager",
							      RB_TYPE_IPOD_MANAGER,
							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property (object_class, PROP_IPOD_PLAYLIST,
					 g_param_spec_pointer ("ipod-playlist",
							       "iPod Playlist",
							       "iPod Playlist",
							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	source_class->impl_show_popup = impl_show_popup;
#ifdef ENABLE_TRACK_TRANSFER
	source_class->impl_receive_drag = impl_receive_drag;
	source_class->impl_paste = impl_paste;
	source_class->impl_cut = impl_cut;
	source_class->impl_delete = impl_delete;
	source_class->impl_delete_thyself = impl_delete_thyself;
#endif

	g_type_class_add_private (klass, sizeof (RBiPodPlaylistSourcePrivate));
}

static void rb_ipod_playlist_source_init (RBiPodPlaylistSource *source)
{
	source->priv = RB_IPOD_PLAYLIST_SOURCE_GET_PRIVATE (source);
#ifdef ENABLE_TRACK_TRANSFER
	g_signal_connect (source,
			  "notify::name", G_CALLBACK (name_changed_cb),
			  NULL);
#endif
}

static void rb_ipod_playlist_source_dispose (GObject *object)
{
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (object);

	if (source->priv->ipod_manager != NULL) {
		disconnect_manager_signal_handlers (source);
		g_object_unref (source->priv->ipod_manager);
		source->priv->ipod_manager = NULL;
	}

	if (source->priv->db != NULL) {
		g_object_unref (source->priv->db);
		source->priv->db = NULL;
	}

	G_OBJECT_CLASS (rb_ipod_playlist_source_parent_class)->dispose (object);
}

static void
rb_ipod_playlist_source_get_property (GObject *object, guint propid,
				      GValue *value, GParamSpec *pspec)
{
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (object);

	switch (propid) {
		case PROP_IPOD_MANAGER:
			g_value_set_object (value, source->priv->ipod_manager);
			break;

		case PROP_IPOD_PLAYLIST:
			g_value_set_pointer (value, source->priv->playlist);
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
	}
}

static void
rb_ipod_playlist_source_set_property (GObject *object, guint propid,
				      const GValue *value, GParamSpec *pspec)
{
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (object);

	switch (propid) {
		case PROP_IPOD_MANAGER:
			if (source->priv->ipod_manager) {
				disconnect_manager_signal_handlers (source);
				g_object_unref (source->priv->ipod_manager);
				g_object_unref (source->priv->db);
			}
			source->priv->ipod_manager =
				RB_IPOD_MANAGER (g_value_dup_object (value));
				 g_object_get (G_OBJECT (source->priv->ipod_manager),
					       "db", &source->priv->db,
					       NULL);
			connect_manager_signal_handlers (source);
			break;
		case PROP_IPOD_PLAYLIST:
			source->priv->playlist = g_value_get_pointer (value);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
	}
}

RBSource *
rb_ipod_playlist_source_new (RBiPodSource *ipod_source,
		Itdb_Playlist *playlist)
{
	RBSource *source;
	GObject *shell, *manager;
	RhythmDBEntryType entry_type;
	gboolean is_local;

	g_return_val_if_fail (ipod_source != NULL, NULL);
	g_return_val_if_fail (playlist != NULL, NULL);

#ifdef ENABLE_TRACK_TRANSFER
	if (itdb_playlist_is_mpl (playlist) ||
			itdb_playlist_is_podcasts (playlist))
		is_local = FALSE;
	else
		is_local = TRUE;
#else
	is_local = FALSE;
#endif

	g_object_get (ipod_source, "shell", &shell,
			"entry-type", &entry_type,
			"ipod-manager", &manager, NULL);
	g_object_unref (shell);

	source = RB_SOURCE (g_object_new (RB_TYPE_IPOD_PLAYLIST_SOURCE,
					  "name", playlist->name,
					  "shell", shell,
					  "ipod-playlist", playlist,
					  "ipod-manager", manager,
					  "is-local", is_local,
					  "entry-type", entry_type,
					  NULL));
	g_object_unref (manager);

	return source;
}

void
rb_ipod_playlist_source_delete_no_remove (RBiPodPlaylistSource *source)
{
	source->priv->noremove = TRUE;
	rb_source_delete_thyself (RB_SOURCE (source));
}

static gboolean
impl_show_popup (RBSource *asource)
{
	_rb_source_show_popup (asource, RB_IPOD_PLAYLIST_SOURCE_POPUP_PATH);
	return TRUE;
}

static void
manager_track_read_cb (RBiPodManager *manager,
		       Itdb_Playlist *playlist, Itdb_Track *track,
		       RBiPodPlaylistSource *source)
{
	RBStaticPlaylistSource *static_pl = RB_STATIC_PLAYLIST_SOURCE (source);
	const gchar *uri;

	if (playlist != source->priv->playlist)
		return;

	uri = rb_ipod_track_get_ipod_uri (track);

	rb_debug ("read %s from playlist %s", uri, playlist->name);
	rb_static_playlist_source_add_location (static_pl, uri, -1);
}

#ifdef ENABLE_TRACK_TRANSFER
static void
name_changed_cb (RBiPodPlaylistSource *source,
		 GParamSpec *pspec, gpointer data)
{
	gchar *name;

	g_object_get (source, "name", &name, NULL);
	rb_ipod_manager_playlist_rename (source->priv->ipod_manager,
					 source->priv->playlist, name);
	g_free (name);
}

static gboolean
impl_receive_drag (RBSource *asource, GtkSelectionData *data)
{
	GList *entries = NULL;
	GList *list, *walk;
	GObject *shell;
	RhythmDB *db;

	g_object_get (asource, "shell", &shell, NULL);
	g_object_get (shell, "db", &db, NULL);
	g_object_unref (shell);

	list = rb_uri_list_parse ((const char *) data->data);
	for (walk = list; walk; walk = g_list_next (walk)) {
		RhythmDBEntry *entry;
		gchar *uri = walk->data;
	
		if (uri == NULL)
			/* ??! */
			continue;
	
		entry = rhythmdb_entry_lookup_by_location (db, uri);
		if (entry == NULL) {
			rb_debug ("ignoring %s as it's not in the library",
					uri);
			continue;
		}

		entries = g_list_prepend (entries, entry);
	}
	g_list_free (list);
	g_object_unref (db);

	rb_source_paste (asource, entries);
	return TRUE;
}

static GList *
impl_cut (RBSource *asource)
{
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (asource);
	GList *walk, *sel;

	sel = RB_SOURCE_CLASS (rb_ipod_playlist_source_parent_class)->impl_cut (asource);

	for (walk = sel; walk; walk = g_list_next (walk)) {
		RhythmDBEntry *entry = (RhythmDBEntry *) walk->data;
		const gchar *uri;
	
		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
		if (rb_ipod_manager_playlist_remove_uri (source->priv->ipod_manager,
							 source->priv->playlist, uri) == FALSE) {
			rb_debug ("couldn't remove uri %s from playlist %s", uri, source->priv->playlist->name);
		}
	}

	return sel;
}

static void
impl_delete (RBSource *asource)
{
	GList *sel = impl_cut (asource);
	g_list_free (sel);
}

static void
impl_paste (RBSource *asource, GList *entries) {
	GList *walk;
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (asource);
	RBiPodManager *manager = source->priv->ipod_manager;
	Itdb_Track *track;
	RhythmDBEntryType ipod_type;

	g_object_get (asource, "entry-type", &ipod_type, NULL);

	for (walk = entries; walk != NULL; walk = g_list_next (walk)) {
		RhythmDBEntry *entry = (RhythmDBEntry *) walk->data;
		RhythmDBEntryType entry_type;
		const gchar *uri;

		entry_type = rhythmdb_entry_get_entry_type (entry);
		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);

		if (itdb_playlist_is_podcasts (source->priv->playlist) &&
		    entry_type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
			rb_debug ("%s is not a podcast", uri);
			continue;
		}

		track = itdb_track_new ();
		rb_ipod_track_set_from_entry (source->priv->db, track, entry);

		if (entry_type == ipod_type)
			rb_ipod_track_set_ipod_uri (track, uri);
		else
			rb_ipod_track_set_source_uri (track, uri);

		if (rb_ipod_manager_playlist_add_track (manager, source->priv->playlist, track) == FALSE) {
			itdb_track_free (track);
		}
	}
}

static void
impl_delete_thyself (RBSource *asource)
{
	RBiPodPlaylistSource *source = RB_IPOD_PLAYLIST_SOURCE (asource);

	if (source->priv->noremove == FALSE)
		rb_ipod_manager_remove_playlist (source->priv->ipod_manager, source->priv->playlist);

	RB_SOURCE_CLASS (rb_ipod_playlist_source_parent_class)->impl_delete_thyself (asource);
}

static void
manager_track_added_cb (RBiPodManager *manager,
		Itdb_Playlist *playlist, Itdb_Track *track,
		RBiPodPlaylistSource *source)
{
	RBStaticPlaylistSource *static_pl = RB_STATIC_PLAYLIST_SOURCE (source);
	const gchar *uri;

	if (playlist != source->priv->playlist)
		return;

	uri = rb_ipod_track_get_ipod_uri (track);

	rb_debug ("adding %s to playlist %s", uri, playlist->name);
	rb_static_playlist_source_add_location (static_pl, uri, -1);
}
#endif

static void
connect_manager_signal_handlers (RBiPodPlaylistSource *source)
{
	RBiPodManager *manager = source->priv->ipod_manager;
#ifdef ENABLE_TRACK_TRANSFER
	source->priv->track_added_id =
		g_signal_connect_object (manager, "track-added",
					 G_CALLBACK (manager_track_added_cb),
					 source, 0);
#endif

	source->priv->track_read_id =
		g_signal_connect_object (manager, "track-read",
					 G_CALLBACK (manager_track_read_cb),
					 source, 0);
}

static void
disconnect_manager_signal_handlers (RBiPodPlaylistSource *source)
{
	RBiPodManager *manager = source->priv->ipod_manager;

	g_signal_handler_disconnect (manager, source->priv->track_added_id);
	g_signal_handler_disconnect (manager, source->priv->track_read_id);
}
