/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2010-2011 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* 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 "gmdefs.h"
#include "GMDBus.h"
#include "GMSource.h"
#include "GMWindow.h"

#include <xine.h>
#include "GMPlayer.h"
#include "GMPlayerManager.h"
#include "GMMediaPlayerService.h"

#include "mpris_xml.h"

static void gm_mpris_track_to_dict(DBusMessageIter * iter,const GMTrack & track) {
  DBusMessageIter array;
  dbus_message_iter_open_container(iter,DBUS_TYPE_ARRAY,"{sv}",&array);
  gm_dbus_dict_append_string(&array,"title",track.title);
  gm_dbus_dict_append_string(&array,"artist",track.artist);
  gm_dbus_dict_append_string(&array,"album",track.album);
  gm_dbus_dict_append_string(&array,"tracknumber",GMStringVal(track.no));
  gm_dbus_dict_append_uint32(&array,"time",track.time);
  gm_dbus_dict_append_uint32(&array,"year",track.year);
  gm_dbus_dict_append_string(&array,"genre",track.genre);
  gm_dbus_dict_append_string(&array,"location",gm_make_url(track.mrl));
  dbus_message_iter_close_container(iter,&array);
  }

/*
static void gm_dbus_dict_append_track(DBusMessageIter * iter,const FXchar * key,const GMTrack & track) {
  DBusMessageIter entry;
  DBusMessageIter variant;
  dbus_message_iter_open_container(iter,DBUS_TYPE_DICT_ENTRY,0,&entry);
    dbus_message_iter_append_basic(&entry,DBUS_TYPE_STRING,&key);
    dbus_message_iter_open_container(&entry,DBUS_TYPE_VARIANT,"a{sv}",&variant);
      gm_mpris_track_to_dict(&variant,track);
    dbus_message_iter_close_container(&entry,&variant);
  dbus_message_iter_close_container(iter,&entry);
  }

*/

static const FXchar MPRIS_DBUS_NAME[]="org.mpris.gogglesmm";
static const FXchar MPRIS_DBUS_INTERFACE[]="org.freedesktop.MediaPlayer";
static const FXchar MPRIS_DBUS_PLAYER[]="/Player";
static const FXchar MPRIS_DBUS_ROOT[]="/";
static const FXchar MPRIS_DBUS_TRACKLIST[]="/TrackList";

static FXint mpris_get_caps(GMPlayerManager * p) {
  FXint caps=0;
  enum {
    MPRIS_CAPS_NONE                  = 0,
    MPRIS_CAPS_CAN_GO_NEXT           = 1 << 0,
    MPRIS_CAPS_CAN_GO_PREV           = 1 << 1,
    MPRIS_CAPS_CAN_PAUSE             = 1 << 2,
    MPRIS_CAPS_CAN_PLAY              = 1 << 3,
    MPRIS_CAPS_CAN_SEEK              = 1 << 4,
    MPRIS_CAPS_CAN_PROVIDE_METADATA  = 1 << 5,
    MPRIS_CAPS_CAN_HAS_TRACKLIST     = 1 << 6
   };
  if (p->can_play() || p->can_unpause()) caps|=MPRIS_CAPS_CAN_PLAY;
  if (p->can_pause()) caps|=MPRIS_CAPS_CAN_PAUSE;
  if (p->can_prev()) caps|=MPRIS_CAPS_CAN_GO_PREV;
  if (p->can_next()) caps|=MPRIS_CAPS_CAN_GO_NEXT;
  caps|=MPRIS_CAPS_CAN_PROVIDE_METADATA;
  return caps;
  }


static void gm_mpris_get_status(DBusMessageIter * iter,GMPlayerManager * p) {
  enum {
    MPRIS_STATUS_PLAYING = 0,
    MPRIS_STATUS_PAUSED  = 1,
    MPRIS_STATUS_STOPPED = 2,
    };

  FXint playstatus=0;
  FXint playmode=0;
  FXint playnext=0;
  FXint playrepeat=0;

  if (p->can_unpause())
    playstatus=MPRIS_STATUS_PAUSED;
  else if (p->can_pause())
    playstatus=MPRIS_STATUS_PLAYING;
  else
    playstatus=MPRIS_STATUS_STOPPED;

  DBusMessageIter str;
  dbus_message_iter_open_container(iter,DBUS_TYPE_STRUCT,NULL,&str);
  dbus_message_iter_append_basic(&str,DBUS_TYPE_INT32,&playstatus);
  dbus_message_iter_append_basic(&str,DBUS_TYPE_INT32,&playmode);
  dbus_message_iter_append_basic(&str,DBUS_TYPE_INT32,&playnext);
  dbus_message_iter_append_basic(&str,DBUS_TYPE_INT32,&playrepeat);
  dbus_message_iter_close_container(iter,&str);
  }

FXIMPLEMENT(GMMediaPlayerService,FXObject,NULL,0)

GMMediaPlayerService::GMMediaPlayerService(GMDBus * b) : bus(b),published(false){

  memset(&root_vtable,0,sizeof(DBusObjectPathVTable));
  root_vtable.message_function=&root_filter;
  memset(&player_vtable,0,sizeof(DBusObjectPathVTable));
  player_vtable.message_function=&player_filter;
  memset(&tracklist_vtable,0,sizeof(DBusObjectPathVTable));
  tracklist_vtable.message_function=&tracklist_filter;

  int result = dbus_bus_request_name(bus->connection(),MPRIS_DBUS_NAME,DBUS_NAME_FLAG_DO_NOT_QUEUE,NULL);
  if (result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ) {
    dbus_connection_register_object_path(bus->connection(),MPRIS_DBUS_ROOT,&root_vtable,this);
    dbus_connection_register_object_path(bus->connection(),MPRIS_DBUS_PLAYER,&player_vtable,this);
    dbus_connection_register_object_path(bus->connection(),MPRIS_DBUS_TRACKLIST,&tracklist_vtable,this);
    published=true;
    }
  }

GMMediaPlayerService::~GMMediaPlayerService(){
  if (published) {
    dbus_connection_unregister_object_path(bus->connection(),MPRIS_DBUS_ROOT);
    dbus_connection_unregister_object_path(bus->connection(),MPRIS_DBUS_PLAYER);
    dbus_connection_unregister_object_path(bus->connection(),MPRIS_DBUS_TRACKLIST);
    published=false;
    dbus_bus_release_name(bus->connection(),MPRIS_DBUS_NAME,NULL);
    }
  }


void GMMediaPlayerService::notify_track_change(const GMTrack & info) {
  if (published) {
    DBusMessage * msg = dbus_message_new_signal(MPRIS_DBUS_PLAYER,MPRIS_DBUS_INTERFACE,"TrackChange");
    if (msg) {
      DBusMessageIter iter;
      dbus_message_iter_init_append(msg,&iter);
      gm_mpris_track_to_dict(&iter,info);
      bus->send(msg);
      }
    }
  }

void GMMediaPlayerService::notify_status_change() {
  DBusMessage * msg = dbus_message_new_signal(MPRIS_DBUS_PLAYER,MPRIS_DBUS_INTERFACE,"StatusChange");
  if (msg) {
    DBusMessageIter iter;
    dbus_message_iter_init_append(msg,&iter);
    gm_mpris_get_status(&iter,GMPlayerManager::instance());
    bus->send(msg);
    }
  }

void GMMediaPlayerService::notify_caps_change() {
  if (published) {
    DBusMessage * msg = dbus_message_new_signal(MPRIS_DBUS_PLAYER,MPRIS_DBUS_INTERFACE,"CapsChange");
    if (msg) {
      FXint caps = mpris_get_caps(GMPlayerManager::instance());
      dbus_message_append_args(msg,DBUS_TYPE_INT32,&caps,DBUS_TYPE_INVALID);
      bus->send(msg);
      }
    }
  }

DBusHandlerResult GMMediaPlayerService::root_filter(DBusConnection *connection,DBusMessage * msg,void * /*ptr*/){
  DEBUG_DBUS_MESSAGE(msg);
  DBusMessage * reply=NULL;
  FXuint serial;
  GMPlayerManager      * p     = GMPlayerManager::instance();
  if (dbus_message_is_method_call(msg,"org.freedesktop.DBus.Introspectable","Introspect")){
    FXString xml(mpris_root_xml);
    char ** children=NULL;
    if (dbus_connection_list_registered(connection,"/",&children)) {
      for (FXint i=0;children[i]!=NULL;i++) {
        xml+=GMStringFormat("\t<node name=\"%s\"/>\n",children[i]);
        }
      dbus_free_string_array(children);
      }
    xml+="</node>";
    return gm_dbus_reply_string(connection,msg,xml.text());
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Identity")) {
    return gm_dbus_reply_string(connection,msg,"Goggles Music Manager");
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"MprisVersion")) {
    reply = dbus_message_new_method_return(msg);
    if (reply) {
      FXushort major=1,minor=0;
      DBusMessageIter iter;
      DBusMessageIter str;
      dbus_message_iter_init_append(reply,&iter);
      dbus_message_iter_open_container(&iter,DBUS_TYPE_STRUCT,NULL,&str);
      dbus_message_iter_append_basic(&str,DBUS_TYPE_UINT16,&major);
      dbus_message_iter_append_basic(&str,DBUS_TYPE_UINT16,&minor);
      dbus_message_iter_close_container(&iter,&str);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    return DBUS_HANDLER_RESULT_HANDLED;
    }
  if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Quit")) {
    gm_dbus_reply_if_needed(connection,msg);
    if (p->getMainWindow()) p->getMainWindow()->handle(p,FXSEL(SEL_COMMAND,GMWindow::ID_QUIT),NULL);
    return DBUS_HANDLER_RESULT_HANDLED;
    }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }


DBusHandlerResult  GMMediaPlayerService::player_filter(DBusConnection *connection,DBusMessage * msg,void * /*ptr*/){
  DEBUG_DBUS_MESSAGE(msg);
  DBusMessage * reply=NULL;
  FXuint serial;
  GMPlayerManager      * p     = GMPlayerManager::instance();

  if (dbus_message_is_method_call(msg,"org.freedesktop.DBus.Introspectable","Introspect")){
    return gm_dbus_reply_string(connection,msg,mpris_player_xml);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetCaps")) {
    FXint caps = mpris_get_caps(p);
    return gm_dbus_reply_int(connection,msg,caps);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"PositionGet")) {
    return gm_dbus_reply_int(connection,msg,p->getPlayer()->getPositionMS());
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"VolumeSet")) {
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"VolumeGet")) {
    return gm_dbus_reply_int(connection,msg,p->volume());
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetStatus")) {
    reply = dbus_message_new_method_return(msg);
    if (reply) {
      DBusMessageIter iter;
      dbus_message_iter_init_append(reply,&iter);
      gm_mpris_get_status(&iter,p);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    return DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetMetadata")) {
    reply = dbus_message_new_method_return(msg);
    if (reply) {
      DBusMessageIter iter;
      GMTrack track;
      p->getTrackInformation(track);
      dbus_message_iter_init_append(reply,&iter);
      gm_mpris_track_to_dict(&iter,track);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    return DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Play")){
    p->cmd_play();
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Stop")){
    p->cmd_stop();
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Pause")){
    p->cmd_pause();
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Next")){
    p->cmd_next();
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Prev")){
    p->cmd_prev();
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"Repeat")){
    return gm_dbus_reply_if_needed(connection,msg);
    }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }




DBusHandlerResult  GMMediaPlayerService::tracklist_filter(DBusConnection *connection,DBusMessage * msg,void * /*ptr*/){
  DEBUG_DBUS_MESSAGE(msg);
  DBusMessage * reply=NULL;
  FXuint serial;
  GMPlayerManager      * p     = GMPlayerManager::instance();

  if (dbus_message_is_method_call(msg,"org.freedesktop.DBus.Introspectable","Introspect")){
    return gm_dbus_reply_string(connection,msg,mpris_tracklist_xml);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetMetadata")) {
    reply = dbus_message_new_method_return(msg);
    if (reply) {
      DBusMessageIter iter;
      GMTrack track;
      p->getTrackInformation(track);
      dbus_message_iter_init_append(reply,&iter);
      gm_mpris_track_to_dict(&iter,track);
      dbus_connection_send(connection,reply,&serial);
      dbus_message_unref(reply);
      }
    return DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetLength")) {
    return gm_dbus_reply_int(connection,msg,0);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"GetCurrentTrack")) {
    return gm_dbus_reply_int(connection,msg,0);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"AddTrack")) {
    return gm_dbus_reply_int(connection,msg,1);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"DelTrack")) {
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"SetLoop")) {
    return gm_dbus_reply_if_needed(connection,msg);
    }
  else if (dbus_message_is_method_call(msg,MPRIS_DBUS_INTERFACE,"SetRandom")) {
    return gm_dbus_reply_if_needed(connection,msg);
    }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
