/*  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 as published by
 *  the Free Software Foundation; either version 2 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, 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.
 */

#include <config.h>
#include <glib.h>
#include <cstdlib>
#include <cstring>

#include <X11/SM/SMlib.h>
#include <X11/ICE/ICElib.h>
#include <gdk/gdk.h>
#include <unistd.h>

namespace Session
{

#ifdef HAVE_SM
  namespace
  {
    SmcConn     smc_conn = NULL;
    IceConn     ice_conn = NULL;
    char       *client_id = NULL;
    GIOChannel *ice_io_channel = NULL;
    int         ice_handler_id;

    void
    print_information (SmcConn smc_conn)
    {
      char *str;

      g_message (G_STRLOC ": XSMP Version: %d  Revision: %d",
                 SmcProtocolVersion (smc_conn),
                 SmcProtocolRevision (smc_conn));

      str = SmcVendor (smc_conn);
      g_message (G_STRLOC ": Session manager: %s ", str);
      free (str);

      str = SmcRelease (smc_conn);
      g_message (G_STRLOC ": Release: %s", str);
      free (str);
    }

    void
    save_yourself (SmcConn   smc_conn,
                   SmPointer client_data,
                   int       save_type,
                   Bool      shutdown,
                   int       interact_style,
                   Bool      fast)
    {
      //g_message (G_STRLOC ": Save yourself");

      switch (interact_style)
        {
        case SmInteractStyleNone:
          //g_message (G_STRLOC ": No interaction");
          break;

        case SmInteractStyleErrors:
          //g_message (G_STRLOC ": Interaction only under an error condition");
          break;

        case SmInteractStyleAny:
          //g_message (G_STRLOC ": Any interaction");

          // SmcInteractRequest (smc_conn);
          // SmcInteractDone (smc_conn, False);
          break;
        }

      switch (save_type)
        {
        case SmSaveLocal:
          //g_message (G_STRLOC ": Save local state");
          break;

        case SmSaveGlobal:
          //g_message (G_STRLOC ": Save global state");
          break;

        case SmSaveBoth:
          //g_message (G_STRLOC ": Save both global and local state");
          break;
        }

      if (shutdown)
        {
          //g_message (G_STRLOC ": Is shutting down");
          //bmp_system_control_quit (bmp_system_control, NULL);
        }

      SmcSaveYourselfDone (smc_conn, True);
    }

    void
    die (SmcConn   conenction,
                 SmPointer client_data)
    {
      //g_message (G_STRLOC ": Session die");
    }

    void
    save_complete (SmcConn   smc_conn,
                           SmPointer client_data)
    {
      //g_message (G_STRLOC ": Save complete");
    }

    void
    shutdown_cancelled (SmcConn   smc_conn,
                                SmPointer client_data)
    {
      //g_message (G_STRLOC ": Shutdown cancelled");
    }

    gboolean
    ice_handler (GIOChannel   *io_channel,
                 GIOCondition  condition,
                 gpointer      data)
    {
      IceConn ice_conn = reinterpret_cast<IceConn> (data);

      if (condition == G_IO_IN)
        {
          IceReplyWaitInfo info;
          Bool ready;
          IceProcessMessagesStatus status;

          status = IceProcessMessages (ice_conn, &info, &ready);

          switch (status)
            {
            case IceProcessMessagesSuccess:
              //g_message (G_STRLOC ": ICE message processing ok");
              break;

            case IceProcessMessagesIOError:
              g_warning (G_STRLOC ": I/O error occurred while processing ICE messages");
              break;

            case IceProcessMessagesConnectionClosed:
              g_warning (G_STRLOC ": ICE connection already closed!");
              break;
            }

          if (ready)
            {
              //g_message (G_STRLOC ": Reply is ready");
            }
        }

      return TRUE;
    }

    void
    set_properties (SmcConn      smc_conn,
                    const gchar *session_id,
                    const gchar *cmd)
    {
      SmPropValue program_val, restart_val[3], userid_val, cwd_val;

      SmProp *props[5];

      SmProp program_prop = { SmProgram, SmARRAY8 };
      SmProp restart_prop = { SmRestartCommand, SmLISTofARRAY8 };
      SmProp clone_prop   = { SmCloneCommand, SmLISTofARRAY8 };
      SmProp userid_prop  = { SmUserID, SmARRAY8 };
      SmProp cwd_prop     = { SmCurrentDirectory, SmARRAY8 };

      props[0] = &program_prop;
      props[1] = &restart_prop;
      props[2] = &clone_prop;
      props[3] = &userid_prop;
      props[4] = &cwd_prop;

      program_prop.vals = &program_val;
      program_prop.num_vals = 1;

      restart_prop.vals = &restart_val[0];
      restart_prop.num_vals = session_id ? 3 : 1;

      clone_prop.vals = &restart_val[0];
      clone_prop.num_vals = 1;

      userid_prop.vals = &userid_val;
      userid_prop.num_vals = 1;

      cwd_prop.vals = &cwd_val;
      cwd_prop.num_vals = 1;

      program_val.value = const_cast<char *> (PACKAGE);
      program_val.length = std::strlen (static_cast<const char *> (program_val.value));

      restart_val[0].value = const_cast<gchar *> (cmd);
      restart_val[0].length = std::strlen (static_cast<const char *> (restart_val[0].value));

      if (session_id)
        {
          restart_val[1].value = const_cast<char *> ("--sm-client-id");
          restart_val[1].length = std::strlen (static_cast<const char *> (restart_val[1].value));
          restart_val[2].value = const_cast<gchar *> (session_id);
          restart_val[2].length = std::strlen (static_cast<const char *> (restart_val[2].value));
        }

      userid_val.value = g_strdup_printf ("%d", geteuid ());
      userid_val.length = std::strlen (static_cast<const char *> (userid_val.value));

      cwd_val.value = getcwd (NULL, 0);
      cwd_val.length = std::strlen (static_cast<const gchar *> (cwd_val.value));

      SmcSetProperties (smc_conn, G_N_ELEMENTS (props), props);

      g_free (userid_val.value);
      g_free (cwd_val.value);
    }

  } // anonymous namespace

  bool
  start (gint          argc,
         gchar       **argv,
         const gchar  *last_session_id)
  {
    char error[256] = "";

    SmcCallbacks callbacks =
      {
        {save_yourself, NULL},
        {die, NULL},
        {save_complete, NULL},
        {shutdown_cancelled, NULL}
      };

    if (last_session_id)
      g_message ("Restoring last session (%s)\n", last_session_id);

    smc_conn = SmcOpenConnection (NULL, NULL, 1, 0,
                                  SmcSaveYourselfProcMask |
                                  SmcDieProcMask |
                                  SmcSaveCompleteProcMask |
                                  SmcShutdownCancelledProcMask,
                                  &callbacks,
                                  const_cast<char *> (last_session_id),
                                  &client_id,
                                  sizeof(error), error);

    if (smc_conn)
      {
        g_message (G_STRLOC ": Connection opened, client id is %s", client_id);
        print_information (smc_conn);

        set_properties (smc_conn, client_id, argv[0]);

        ice_conn = SmcGetIceConnection (smc_conn);
        ice_io_channel = g_io_channel_unix_new (IceConnectionNumber (ice_conn));

        ice_handler_id = g_io_add_watch (ice_io_channel, G_IO_IN, ice_handler, ice_conn);

        gdk_set_sm_client_id (client_id);

        return true;
      }
    else
      {
        return false;
      }
  }

  void
  end ()
  {
    SmcCloseStatus status;
    GError *error = NULL;

    if (!smc_conn)
      return;

    g_source_remove (ice_handler_id);
    g_io_channel_unref (ice_io_channel);

    status = SmcCloseConnection (smc_conn, 0, NULL);

    if (error)
      {
        g_message (G_STRLOC ": Error shutting down ICE IO channel: %s",
                   error->message);
        g_error_free (error);
      }

    free (client_id);

    switch (status)
      {
      case SmcClosedNow:
        //g_message (G_STRLOC ": Connection closed now");
        break;

      case SmcClosedASAP:
        //g_message (G_STRLOC ": Connection closed ASAP");
        break;

      case SmcConnectionInUse:
        //g_message (G_STRLOC ": Connection in use");
        break;

      default:
        g_message (G_STRLOC ": ??");
      }
  }

#else

  bool
  start (gint          argc,
         gchar       **argv,
         const gchar  *session_id)
  {
    g_message ("Session management is disabled, support is not built");

    return true;
  }

  void
  end (void)
  {
    // empty
  }

#endif

} // namespace Session
