//  BMP
//  Copyright (C) 2005-2007 BMP development.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License Version 2
//  as published by the Free Software Foundation.
//
//  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, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  --
//
//  The BMPx project hereby grants permission for non-GPL compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif //HAVE_CONFIG_H

#include <cstring>
#include <glibmm.h>

#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <bmp/dbus.hh>

#include "debug.hh"
#include "main.hh"
#include "network.hh"
#include "ui-tools.hh"

#include "core.hh"
#include "shell.hh"

namespace Bmp
{
  namespace DBus
  {
    DBusGConnection * dbus_session_bus = 0;
    gboolean          dbus_connected = FALSE;
  }
}

namespace Bmp
{

  //// MPRIS / Object

  char const* mpris_identity = "BMP " VERSION;

#define TYPE_DBUS_OBJ_MPRIS_ROOT (MprisRoot::get_type ())
#define DBUS_OBJ_MPRIS_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_DBUS_OBJ_MPRIS_ROOT, MprisRoot))

  struct MprisRootClass
  {
    GObjectClass parent;
  };

  struct Core::MprisRoot
  {
    GObject parent;

    static gpointer parent_class;

    static GType
    get_type ();

    static MprisRoot *
    create ();

    static void
    class_init (gpointer klass,
                gpointer class_data);

    static GObject *
    constructor (GType                   type,
                 guint                   n_construct_properties,
                 GObjectConstructParam * construct_properties);

    static gboolean
    identity (MprisRoot * self,
              char ** identity,
              GError **  error);
  };

  gpointer Core::MprisRoot::parent_class = 0;

#define mpris_root_identity          identity
#include "dbus-obj-root-glue.h"

  void
  Core::MprisRoot::class_init (gpointer klass,
                         gpointer class_data)
  {
    parent_class = g_type_class_peek_parent (klass);
    GObjectClass *gobject_class = reinterpret_cast<GObjectClass*>(klass);
    gobject_class->constructor  = &MprisRoot::constructor;
  }

  GObject *
  Core::MprisRoot::constructor (GType                   type,
                                guint                   n_construct_properties,
                                GObjectConstructParam*  construct_properties)
  {
    GObject *object = G_OBJECT_CLASS (parent_class)->constructor (type, n_construct_properties, construct_properties);
    return object;
  }

  Core::MprisRoot*
  Core::MprisRoot::create ()
  {
    dbus_g_object_type_install_info (TYPE_DBUS_OBJ_MPRIS_ROOT, &dbus_glib_mpris_root_object_info);
    MprisRoot * self = DBUS_OBJ_MPRIS_ROOT (g_object_new (TYPE_DBUS_OBJ_MPRIS_ROOT, NULL));

    if (DBus::dbus_connected)
    {
      dbus_g_connection_register_g_object (DBus::dbus_session_bus, BMP_DBUS_PATH__MPRIS_ROOT, G_OBJECT(self));
      debug("core","object: '/', interface '%s', service '%s'", BMP_DBUS_INTERFACE__MPRIS, BMP_DBUS_SERVICE);
    }
    return self;
  }

  GType
  Core::MprisRoot::get_type ()
  {
    static GType type = 0;

    if (G_UNLIKELY (type == 0))
    {
      static GTypeInfo const type_info =
      {
        sizeof (MprisRootClass),
        NULL,
        NULL,
        &class_init,
        NULL,
        NULL,
        sizeof (MprisRoot),
        0,
        NULL
      };
      type = g_type_register_static (G_TYPE_OBJECT, "MPRIS_ROOT", &type_info, GTypeFlags (0));
    }

    return type;
  }

  gboolean
  Core::MprisRoot::identity (MprisRoot*     self,
                           char**   identity,
                           GError** error)
  {
    *identity = g_strdup (mpris_identity);
    return TRUE;
  }

  //// Not MPRIS specced - our own interface 

#define TYPE_DBUS_OBJ_BMP (BMP::get_type ())
#define DBUS_OBJ_BMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_DBUS_OBJ_BMP, BMP))

  struct BMPClass
  {
    GObjectClass parent;
  };

  struct Core::BMP
  {
    GObject parent;
    Core * core;

    enum
    {
      SIGNAL_STARTUP_COMPLETE,
      SIGNAL_SHUTDOWN_COMPLETE,
      SIGNAL_QUIT,
      N_SIGNALS,
    };

    static guint signals[N_SIGNALS];

    static gpointer parent_class;

    static GType
    get_type ();

    static BMP *
    create (Core & core);

    static void
    class_init (gpointer klass,
                gpointer class_data);

    static GObject *
    constructor (GType                   type,
                 guint                   n_construct_properties,
                 GObjectConstructParam * construct_properties);

    static gboolean
    ui_raise (BMP * self,
              GError **      error);

    static gboolean
    startup (BMP * self,
             int            no_network,
             GError **      error);

    static void
    startup_complete (BMP * self);
  
    static void
    shutdown_complete (BMP * self);

    static void
    quit (BMP * self);
  };

  gpointer Core::BMP::parent_class       = 0;
  guint    Core::BMP::signals[N_SIGNALS] = { 0 };

// HACK: Hackery to rename functions in glue
#define bmp_startup                  startup
#define bmp_ui_raise                 ui_raise

#include "dbus-obj-BMP-glue.h"

  void
  Core::BMP::class_init (gpointer klass,
                         gpointer class_data)
  {
    parent_class = g_type_class_peek_parent (klass);

    GObjectClass *gobject_class = reinterpret_cast<GObjectClass*>(klass);
    gobject_class->constructor  = &BMP::constructor;

    signals[SIGNAL_STARTUP_COMPLETE] =
      g_signal_new ("startup-complete",
                    G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)),
                    GSignalFlags (G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
                    0,
                    NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[SIGNAL_SHUTDOWN_COMPLETE] =
      g_signal_new ("shutdown-complete",
                    G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)),
                    GSignalFlags (G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
                    0,
                    NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[SIGNAL_QUIT] =
      g_signal_new ("quit",
                    G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)),
                    GSignalFlags (G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
                    0,
                    NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
  }

  GObject *
  Core::BMP::constructor (GType                   type,
                          guint                   n_construct_properties,
                          GObjectConstructParam*  construct_properties)
  {
    GObject *object = G_OBJECT_CLASS (parent_class)->constructor (type, n_construct_properties, construct_properties);

    return object;
  }

  Core::BMP *
  Core::BMP::create (Core & core)
  {
    dbus_g_object_type_install_info (TYPE_DBUS_OBJ_BMP, &dbus_glib_bmp_object_info);

    BMP * self = DBUS_OBJ_BMP (g_object_new (TYPE_DBUS_OBJ_BMP, NULL));
    self->core = &core;

    if (DBus::dbus_connected)
    {
      dbus_g_connection_register_g_object (DBus::dbus_session_bus, BMP_DBUS_PATH__BMP, G_OBJECT(self));
      debug("core","object: '/%s', interface '%s', service '%s'", G_OBJECT_TYPE_NAME(self), BMP_DBUS_INTERFACE__BMP, BMP_DBUS_SERVICE);
    }

    return self;
  }

  GType
  Core::BMP::get_type ()
  {
    static GType type = 0;

    if (G_UNLIKELY (type == 0))
      {
        static GTypeInfo const type_info =
          {
            sizeof (BMPClass),
            NULL,
            NULL,
            &class_init,
            NULL,
            NULL,
            sizeof (BMP),
            0,
            NULL
          };

        type = g_type_register_static (G_TYPE_OBJECT, "BMP", &type_info, GTypeFlags (0));
      }

    return type;
  }

  gboolean
  Core::BMP::ui_raise (BMP* self, GError** error)
  {
    self->core->m_shell->raise ();
    self->core->m_shell->present ();
    return TRUE;
  }

  gboolean
  Core::BMP::startup  (BMP*   self,
                       int      no_network, 
                       GError** error)
  {
    self->core->m_disable_network = (no_network == 1) ? true : false; 
    return TRUE;
  }

  void
  Core::BMP::startup_complete (BMP* self)
  {
    g_signal_emit (self, signals[SIGNAL_STARTUP_COMPLETE], 0);
  }

  void
  Core::BMP::shutdown_complete (BMP* self)
  {
    g_signal_emit (self, signals[SIGNAL_SHUTDOWN_COMPLETE], 0);
  }

  void
  Core::BMP::quit (BMP *self)
  {
    g_signal_emit (self, signals[SIGNAL_QUIT], 0);
  }

  //// C++ Core
  
  void
  Core::dbus_init ()
  {
    DBusGProxy * dbus_session_bus_proxy;
    guint dbus_request_name_result;
    GError * error = NULL;

    DBus::dbus_session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
    if (!DBus::dbus_session_bus)
    {
      g_error_free (error);
      DBus::dbus_connected = false;
      return;
    }

    dbus_session_bus_proxy = dbus_g_proxy_new_for_name (DBus::dbus_session_bus,
                                                   "org.freedesktop.DBus",
                                                   "/org/freedesktop/DBus",
                                                   "org.freedesktop.DBus");

    if (!dbus_g_proxy_call (dbus_session_bus_proxy, "RequestName", &error,
                            G_TYPE_STRING, BMP_DBUS_SERVICE,
                            G_TYPE_UINT, 0,
                            G_TYPE_INVALID,
                            G_TYPE_UINT, &dbus_request_name_result,
                            G_TYPE_INVALID))
    {
      g_error ("%s: Failed RequestName request: %s", G_STRFUNC, error->message);
      g_error_free (error);
      error = NULL;
    }
    else
    {
      switch (dbus_request_name_result)
      {
        case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
        {
          DBus::dbus_connected = true;
          break;
        }

        case DBUS_REQUEST_NAME_REPLY_EXISTS:
        {
          DBus::dbus_connected = false;
          break;
        }
      }
    }
  }

  Core::Core ()
    : bmp               (0),
      m_shell           (0),
      m_disable_network (false)
  {
    dbus_init ();
    bmp = BMP::create (*this);
    mpris = MprisRoot::create ();
  }

  Core::~Core ()
  {
    if (bmp)
    {
      BMP::shutdown_complete (bmp);
      g_object_unref (bmp);
    }

    if (mpris)
    {
      g_object_unref (mpris);
    }
  }

  void
  Core::quit ()
  {
    BMP::quit (bmp);
  }

  void
  Core::ui_stop ()
  {
    delete m_shell;
  }

  void
  Core::ui_start ()
  {
    if (m_disable_network)
      NM::Obj()->Disable ();

    m_shell = PlayerShell::create();
    m_shell->present ();
    m_shell->init ();

    BMP::startup_complete (bmp);
    s_startup_complete_.emit ();

    while (Gtk::Main::events_pending()) Gtk::Main::iteration();
  }

  void
  Core::status_push_message (Glib::ustring const& message)
  {
    if (m_shell)
      m_shell->status_push_message (message);
  }

} // Bmp namespace
