//  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 <revision.h>
#include <build.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>

#include <locale.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include <glib/gprintf.h>
#include <glibmm/miscutils.h>
#include <gtk/gtk.h>

#include <boost/format.hpp>

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus.h>

#include <bmp/dbus.hh>
#include <src/paths.hh>

namespace
{

  GMainLoop* mainloop = NULL;

  gboolean arg_show_version    = FALSE;
  gboolean arg_show_configure  = FALSE;
  gboolean arg_add_files       = FALSE;
  gboolean arg_add_uris        = FALSE;
  gboolean arg_no_network      = FALSE;
  gboolean arg_p_pause         = FALSE;
  gboolean arg_p_next          = FALSE;
  gboolean arg_p_prev          = FALSE;

  GOptionEntry options[] =
  {
      {"no-network",  'n', 0, G_OPTION_ARG_NONE, &arg_no_network,     N_("Start BMPx in Offline Mode"), NULL},
      {"version",     'v', 0, G_OPTION_ARG_NONE, &arg_show_version,   N_("Display version"), NULL},
      {"configure",   'c', 0, G_OPTION_ARG_NONE, &arg_show_configure, N_("Display configure arguments"), NULL},
      {"files",       'f', 0, G_OPTION_ARG_NONE, &arg_add_files,      N_("Play Files"), NULL},
      {"uris",        'u', 0, G_OPTION_ARG_NONE, &arg_add_uris,       N_("Play URIs"), NULL},
      {"ppaus",        0,  0, G_OPTION_ARG_NONE, &arg_p_pause,        N_("Pause Playback"), NULL},
      {"pnext",        0,  0, G_OPTION_ARG_NONE, &arg_p_next,         N_("Next"), NULL},
      {"pprev",        0,  0, G_OPTION_ARG_NONE, &arg_p_prev,         N_("Previous"), NULL},

      { NULL }
  };

  static const char *features[] =
  {
#ifdef HAVE_SM
      N_("X11 Session Management"),
#endif

#ifdef HAVE_HAL
      N_("HAL support"),
#endif

#ifdef HAVE_ALSA
      N_("ALSA Support (Linux)"),
#endif

#ifdef HAVE_SUNAUDIO
      N_("SUN Audio support"),
#endif

#ifdef HAVE_OFA
      N_("MusicIP support"),
#endif //HAVE_OFA

#ifdef HAVE_MP4V2
      N_("M4A/AAC taglib support"),
#endif //HAVE_MP4V2

#ifdef HAVE_SID
      N_("SID taglib support"),
#endif //HAVE_SID

#ifdef HAVE_MOD
      N_("Tracker modules taglib support")
#endif //HAVE_MOD

    };

  void
  print_version ()
  {
    g_print ("%s %s",
             PACKAGE,
             VERSION);

    if (std::strlen (RV_REVISION))
      g_print (" (%s-R%s)",
               RV_LAST_CHANGED_DATE,
               RV_REVISION);

    g_print (_("\nCopyright (c) 2005-2007 BMP Project <http://www.beep-media-player.org>\n\n"));

    g_print (_("Built on %s for %s with:\n"),
             BUILD_DATE,
             BUILD_ARCH);

    for (unsigned int i = 0; i < G_N_ELEMENTS (features); i++)
      g_print ("\t* %s\n", _(features[i]));

    g_print ("\n");
  }

  void
  print_configure ()
  {
    g_print ("%s\n",
             CONFIGURE_ARGS);
  }

  void
  setup_i18n ()
  {
    setlocale (LC_ALL, "");

    bindtextdomain (PACKAGE, LOCALE_DIR);
    bind_textdomain_codeset (PACKAGE, "UTF-8");
    textdomain (PACKAGE);
  }

  void
  app_startup_completed (DBusGProxy* proxy,
                         gpointer    data)
  {
    g_main_loop_quit (mainloop);
  }

  void
  app_quit (DBusGProxy* proxy,
            gpointer    data)
  {
    g_main_loop_quit (mainloop);
    g_main_loop_unref (mainloop);

    std::exit (EXIT_SUCCESS);
  }

  void
  display_startup_crash ()
  {
    // We assume BMPx started up, but crashed again
    if (!g_getenv ("DISPLAY"))
      {
        char *message = g_strconcat
          (_("\n       BMPx seems to have crashed.\n\n       Please try starting it from a terminal using '"),
           PREFIX,
           _("/libexec/beep-media-player-2-bin --no-log'\n       for further information on what could have caused the crash\n"),
           _("       and report it to our IRC channel, #bmp on irc.freenode.net\n\n"), NULL);
        g_printf (message);
        g_free (message);
      }
    else
      {
        gtk_init (0, NULL);
        GtkWidget *dialog = gtk_message_dialog_new_with_markup (NULL, GtkDialogFlags (0), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                  "BMPx tried to start up, but crashed.\n\n"
                                  "Please file a bug at:\n\n"
                                  "http://bugs.beep-media-player.org");

        gtk_window_set_title (GTK_WINDOW (dialog), _("BMPx Crashed - BMP"));
        gtk_dialog_run (GTK_DIALOG (dialog));
        gtk_widget_destroy (dialog);
      }
    exit (EXIT_FAILURE);
  }

  void
  name_owner_changed (DBusGProxy* proxy,
                      char*       name,
                      char*       old_owner,
                      char*       new_owner,
                      gpointer    data)
  {
    if (!name || (name && std::strcmp (name, BMP_DBUS_SERVICE)))
      return;

    if (std::strlen (old_owner) && !std::strlen (new_owner))
      {
        display_startup_crash ();
      }
  }

  void
  print_error (DBusError* error)
  {
    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "DBus Error: (%s): %s", error->name, error->message);
  }

  void
  print_g_error (GError** error)
  {
    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "Error: %s", (*error)->message);
    g_error_free (*error);
    *error = NULL;
  }

} // anonymous namespace

int
main (int    argc,
      char **argv)
{
  GError* error = NULL;
  DBusError dbus_error;

  setup_i18n ();
  g_type_init ();
  dbus_g_type_specialized_init ();
  mainloop = g_main_loop_new (NULL, FALSE);
  GOptionContext* option_context = g_option_context_new (_(" - Run BMP and/or perform actions on BMP"));
  g_option_context_add_main_entries (option_context, options, PACKAGE);

  if (!g_option_context_parse (option_context, &argc, &argv, &error))
  {
      g_printerr (_("\nInvalid argument: '%s'\nPlease run '%s --help' for help on usage\n\n"), error->message, argv[0]);
      g_error_free (error);
      return EXIT_FAILURE;
  }

  if (arg_show_version)
  {
      print_version ();
      return EXIT_SUCCESS;
  }

  if (arg_show_configure)
  {
      print_configure ();
      return EXIT_SUCCESS;
  }


  bool startup_only = (argc == 1);

  DBusGConnection* dbus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);

  if (!dbus)
  {
      if (error)
        g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "DBus Error: %s", error->message);

      if (!g_getenv ("DISPLAY"))
      {
          g_printerr (_("%s: Couldn't connect to session bus: %s\n\n"), argv[0], error->message);
          return EXIT_FAILURE;
      }

      gtk_init (&argc, &argv);

      boost::format error_fmt (_("<big><b>BMP/DBus Error</b></big>\n\nBMP can not be started trough DBus activation.\n"
                                 "The following error occured trying to start up BMP:\n\n'<b>%s</b>'\n\n"
                                 "DBus might not be running at all or have even crashed, please consult further "
                                 "help with this problem from DBus or BMP support."));

      std::string message;

      if (error)
        message = (error_fmt % error->message).str ();
      else
        message = (error_fmt % _("(Unknown error. Perhaps DBus is not running at all?)")).str ();

      GtkWidget *dialog = gtk_message_dialog_new_with_markup (NULL, GtkDialogFlags (0), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, message.c_str ());

      if (error)
      {
          g_error_free (error);
      }

      gtk_window_set_title (GTK_WINDOW (dialog), _("BMP/DBus Error - BMP"));
      gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);
      return EXIT_FAILURE;
  }

  DBusConnection* c_bus = dbus_g_connection_get_connection (dbus);
  dbus_connection_setup_with_g_main (c_bus, g_main_context_default());

  DBusGProxy* o_bus = dbus_g_proxy_new_for_name (dbus, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");

  guint request_name_result = 0;
  if (!dbus_g_proxy_call (o_bus, "RequestName", &error,
                          G_TYPE_STRING, "org.beepmediaplayer.startup",
                          G_TYPE_UINT, 0,
                          G_TYPE_INVALID,
                          G_TYPE_UINT, &request_name_result,
                          G_TYPE_INVALID))
  {
      g_error ("%s: Failed RequestName request: %s", G_STRFUNC, error->message);
      g_error_free (error);
      error = NULL;
      return EXIT_FAILURE;
  }

  switch (request_name_result)
  {
    case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
    {
        break;
    }

    case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
    case DBUS_REQUEST_NAME_REPLY_EXISTS:
    case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
    {
        g_object_unref (o_bus);
        g_object_unref (c_bus);
        return EXIT_SUCCESS;
    }
  }

  dbus_g_proxy_add_signal (o_bus, "NameOwnerChanged", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
  dbus_g_proxy_connect_signal (o_bus, "NameOwnerChanged", G_CALLBACK (name_owner_changed), NULL, NULL);

  gboolean was_running = TRUE;

  DBusGProxy* o_bmp = dbus_g_proxy_new_for_name_owner (dbus,
                                                       BMP_DBUS_SERVICE,
                                                       BMP_DBUS_PATH__BMP,
                                                       BMP_DBUS_INTERFACE__BMP,
                                                       &error);

  if (!o_bmp)
    was_running = FALSE;

  if (error)
  {
    g_error_free (error);
    error = NULL;
  }

  if (!o_bmp)
  {
    o_bmp = dbus_g_proxy_new_for_name (dbus,
                                       BMP_DBUS_SERVICE,
                                       BMP_DBUS_PATH__BMP,
                                       BMP_DBUS_INTERFACE__BMP);
  }

  dbus_g_proxy_call (o_bmp, "Startup", &error,
                     G_TYPE_INT,
                     arg_no_network ? ((int)1) : ((int)0),
                     G_TYPE_INVALID,
                     G_TYPE_INVALID);

  if (error)
  {
      g_message ("Domain: %s, code: %d, message: %s", g_quark_to_string (error->domain), error->code, error->message);
      g_error_free (error);
      error = NULL;
      display_startup_crash ();
  }

  if (!was_running)
  {
      dbus_g_proxy_add_signal (o_bmp, "StartupComplete", G_TYPE_INVALID);
      dbus_g_proxy_connect_signal (o_bmp, "StartupComplete", G_CALLBACK (app_startup_completed), NULL, NULL);

      dbus_g_proxy_add_signal (o_bmp, "Quit", G_TYPE_INVALID);
      dbus_g_proxy_connect_signal (o_bmp, "Quit", G_CALLBACK (app_quit), NULL, NULL);

      g_main_loop_run (mainloop);
  }

  g_main_loop_unref (mainloop);

  // Start Sentinel
  dbus_error_init (&dbus_error);

  DBusGProxy* o_player = dbus_g_proxy_new_for_name (dbus,  BMP_DBUS_SERVICE,
                                                           BMP_DBUS_PATH__MPRIS_PLAYER,
                                                           BMP_DBUS_INTERFACE__MPRIS);

  if (!was_running)
  {
      uint32_t reply = 0;
      dbus_bus_start_service_by_name (c_bus,
                                      "org.beepmediaplayer.sentinel",
                                      uint32_t (0),
                                      &reply,
                                      &dbus_error);

      if (dbus_error_is_set (&dbus_error))
      {
          print_error (&dbus_error);
          dbus_error_free (&dbus_error);
          goto abort;
      }
  }

  // Now BMP is running -> process args
  if (startup_only)
  {
      if (was_running)
      {
          if (!dbus_g_proxy_call (o_bmp, "UiRaise", &error,
                                  G_TYPE_INVALID,
                                  G_TYPE_INVALID))
          {
              if (error)
                print_g_error (&error);

              goto abort;
          }
      }
      goto end;
  }

  if (arg_p_pause)
  {
      if (!dbus_g_proxy_call (o_player, "Pause", &error,
                              G_TYPE_INVALID,
                              G_TYPE_INVALID))
      {
          if (error)
            print_g_error (&error);
          goto abort;
      }
  }
  else
  if (arg_p_prev)
  {
      if (!dbus_g_proxy_call (o_player, "Prev", &error,
                              G_TYPE_INVALID,
                              G_TYPE_INVALID))
      {
          if (error)
            print_g_error (&error);
          goto abort;
      }
  }
  else
  if (arg_p_next)
  {
      if (!dbus_g_proxy_call (o_player, "Next", &error,
                              G_TYPE_INVALID,
                              G_TYPE_INVALID))
      {
          if (error)
            print_g_error (&error);
          goto abort;
      }
  }
  else if (arg_add_files)
  {
      char** uri_list = g_new0 (char*, argc+1);

      int n;
      for (n = 1; n < argc; n++)
      {
          GError* error = NULL;

          std::string filename;

          if (argv[n][0] != G_DIR_SEPARATOR)
              filename = Glib::build_filename (Glib::getenv ("PWD"), argv[n]);
          else
              filename = argv[n];

          char* uri = g_filename_to_uri (filename.c_str (), NULL, &error);

          if (error)
          {
              g_error_free (error);
              error = NULL;
          }

          uri_list[n-1] = uri;
      }

      if (!dbus_g_proxy_call (o_player, "PlayUris", &error,
                              G_TYPE_STRV, uri_list,
                              G_TYPE_INVALID,
                              G_TYPE_INVALID))
      {
          if (error)
            print_g_error (&error);
          goto abort;
      }

      g_strfreev (uri_list);
  }
  else
  if (arg_add_uris || (argc > 1)) // we assume all remainders passed are URIs
  {
      char** uri_list = g_new0 (char*, argc+1);

      for (int n = 1; n < argc; n++)
      {
          uri_list[n-1] = g_strdup (argv[n]);
      }

      if (!dbus_g_proxy_call (o_player, "PlayUris", &error,
                              G_TYPE_STRV, uri_list,
                              G_TYPE_INVALID,
                              G_TYPE_INVALID))
      {
          if (error)
            print_g_error (&error);
          goto abort;
      }

      g_strfreev (uri_list);
  }

 end:

  if (o_player)
    g_object_unref (o_player);

  if (o_bus)
    g_object_unref (o_bus);

  if (o_bmp)
    g_object_unref (o_bmp);

  return EXIT_SUCCESS;

abort:

  if (o_player)
    g_object_unref (o_player);

  if (o_bus)
    g_object_unref (o_bus);

  if (o_bmp)
    g_object_unref (o_bmp);

  std::abort (); 
}
