/*********************************************************
 * Copyright (C) 2008-2019, 2021 VMware, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation version 2.1 and no 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 Lesser GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *********************************************************/

/**
 * @file serviceObj.c
 *
 * Implementation of the "ToolsCore_Service" gobject.
 */

#include "toolsCoreInt.h"
#include "serviceObj.h"
#include "svcSignals.h"


typedef struct ServiceProperty {
   guint       id;
   gchar      *name;
   gpointer    value;
} ServiceProperty;

static gpointer gToolsCoreServiceParentClass;

/**
 * Accumulator function for the "set option" signal. If a handler returns
 * TRUE, sets the result of the signal propagation to TRUE.
 *
 * @param[in]  ihint       Unused.
 * @param[out] retval      Return value of the signal.
 * @param[in]  handlerRet  Return value from the current handler.
 * @param[in]  data        Unused.
 *
 * @return TRUE
 */

static gboolean
ToolsCoreSetOptionAccumulator(GSignalInvocationHint *ihint,
                              GValue *retval,
                              const GValue *handlerRet,
                              gpointer data)
{
   if (!g_value_get_boolean(retval)) {
      g_value_set_boolean(retval, g_value_get_boolean(handlerRet));
   }
   return TRUE;
}


/**
 * Accumulator function for the "capabilities" signal. Just aggregates the
 * contents of all returned GArray instances into a "higher level" instance.
 *
 * @param[in]  ihint       Unused.
 * @param[out] retval      Return value of the signal.
 * @param[in]  handlerRet  Return value from the current handler.
 * @param[in]  data        Unused.
 *
 * @return TRUE.
 */

static gboolean
ToolsCoreCapabilitiesAccumulator(GSignalInvocationHint *ihint,
                                 GValue *retval,
                                 const GValue *handlerRet,
                                 gpointer data)
{
   GArray *caps = g_value_get_pointer(handlerRet);

   if (caps != NULL) {
      guint i;
      GArray *acc = g_value_get_pointer(retval);

      if (acc == NULL) {
         acc = g_array_new(FALSE, TRUE, sizeof (ToolsAppCapability));
         g_value_set_pointer(retval, acc);
      }

      for (i = 0; i < caps->len; i++) {
         g_array_append_val(acc, g_array_index(caps, ToolsAppCapability, i));
      }

      g_array_free(caps, TRUE);
   }

   return TRUE;
}


#if defined(_WIN32)
/**
 * Accumulator function for the "service control" signal. Updates the return
 * value according to the signal's documentation.
 *
 * The gobject library initializes the return value to "0" regardless of
 * what the signal emitter sets it to. So the accumulator does two things
 * to have a non-zero default return value:
 *
 *    - if the current return value is zero, it's set to the default return
 *      value (ERROR_CALL_NOT_IMPLEMENTED).
 *    - the return value is always offset by one; so the signal emitter
 *      should decrement the return value when looking at it.
 *
 * @param[in]     ihint       Unused.
 * @param[in,out] retval      Return value of the signal (offset by 1).
 * @param[in]     handlerRet  Return value from the current handler.
 * @param[in]     data        Unused.
 *
 * @return TRUE.
 */

static gboolean
ToolsCoreServiceControlAccumulator(GSignalInvocationHint *ihint,
                                   GValue *retval,
                                   const GValue *handlerRet,
                                   gpointer data)
{
   guint ret = g_value_get_uint(retval);
   guint handlerVal = g_value_get_uint(handlerRet);

   if (ret == 0) {
      ret = ERROR_CALL_NOT_IMPLEMENTED + 1;
   }

   switch (ret) {
   case ERROR_CALL_NOT_IMPLEMENTED + 1:
      ret = handlerVal + 1;
      break;

   case NO_ERROR + 1:
      if (handlerVal != ERROR_CALL_NOT_IMPLEMENTED) {
         ret = handlerVal + 1;
      }
      break;

   default:
      break;
   }

   g_value_set_uint(retval, ret);
   return TRUE;
}
#endif


/*
 *******************************************************************************
 * ToolsCoreServiceGetProperty --                                         */ /**
 *
 * Gets the value of a property in the object.
 *
 * @param[in]  object   The instance.
 * @param[in]  id       Property ID.
 * @param[out] value    Where to set the value.
 * @param[in]  pspec    Unused.
 *
 *******************************************************************************
 */

static void
ToolsCoreServiceGetProperty(GObject *object,
                            guint id,
                            GValue *value,
                            GParamSpec *pspec)
{
   ToolsCoreService *self = (ToolsCoreService *) object;

   id -= 1;

   g_mutex_lock(&self->lock);

   if (id < self->props->len) {
      ServiceProperty *p = &g_array_index(self->props, ServiceProperty, id);
      g_value_set_pointer(value, p->value);
   }

   g_mutex_unlock(&self->lock);
}


/*
 *******************************************************************************
 * ToolsCoreServiceSetProperty --                                         */ /**
 *
 * Sets a property in the given object. If the property is found, a "notify"
 * signal is sent so that interested listeners can act on the change.
 *
 * @param[in] object The instance.
 * @param[in] id     Property ID.
 * @param[in] value  Value to set.
 * @param[in] pspec  Unused.
 *
 *******************************************************************************
 */

static void
ToolsCoreServiceSetProperty(GObject *object,
                            guint id,
                            const GValue *value,
                            GParamSpec *pspec)
{
   ServiceProperty *p = NULL;
   ToolsCoreService *self = (ToolsCoreService *) object;

   id -= 1;

   g_mutex_lock(&self->lock);

   if (id < self->props->len) {
      p = &g_array_index(self->props, ServiceProperty, id);
      p->value = g_value_get_pointer(value);
   }

   g_mutex_unlock(&self->lock);

   if (p != NULL) {
      g_object_notify(object, p->name);
   }
}


/*
 *******************************************************************************
 * ToolsCoreServiceCtor --                                                */ /**
 *
 * Object constructor. Initialize internal state.
 *
 * @param[in] type      Object type.
 * @param[in] nparams   Param count.
 * @param[in] params    Construction parameters.
 *
 * @return A new instance.
 *
 *******************************************************************************
 */

static GObject *
ToolsCoreServiceCtor(GType type,
                     guint nparams,
                     GObjectConstructParam *params)
{
   GObject *object;
   ToolsCoreService *self;

   object = G_OBJECT_CLASS(gToolsCoreServiceParentClass)->constructor(type,
                                                                      nparams,
                                                                      params);

   self = TOOLSCORE_SERVICE(object);
   g_mutex_init(&self->lock);
   self->props = g_array_new(FALSE, FALSE, sizeof (ServiceProperty));

   return object;
}


/*
 *******************************************************************************
 * ToolsCoreServiceDtor --                                                */ /**
 *
 * Object destructor. Frees memory associated with the object. Goes through the
 * list of properties to make sure all of them have been cleaned up before the
 * service exits, printing a warning otherwise.
 *
 * @param[in]  object   The object being destructed.
 *
 *******************************************************************************
 */

static void
ToolsCoreServiceDtor(GObject *object)
{
   ToolsCoreService *self = (ToolsCoreService *) object;
   guint i;

   for (i = 0; i < self->props->len; i++) {
      ServiceProperty *p = &g_array_index(self->props, ServiceProperty, i);
      if (p->value != NULL) {
         g_warning("Property '%s' was not cleaned up before shut down.",
                   p->name);
      }
      g_free(p->name);
   }

   g_array_free(self->props, TRUE);
   g_mutex_clear(&self->lock);
}


/**
 * Initializes the ToolsCoreService class. Sets up the signals that are sent
 * by the vmtoolsd service.
 *
 * @param[in]  klass    The class instance to initialize.
 */

static void
ToolsCore_Service_class_init(gpointer _klass,
                             gpointer klassData)
{
   ToolsCoreServiceClass *klass = _klass;

   gToolsCoreServiceParentClass = g_type_class_peek_parent(_klass);

   g_signal_new(TOOLS_CORE_SIG_CAPABILITIES,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                ToolsCoreCapabilitiesAccumulator,
                NULL,
                g_cclosure_user_marshal_POINTER__POINTER_BOOLEAN,
                G_TYPE_POINTER,
                2,
                G_TYPE_POINTER,
                G_TYPE_BOOLEAN);
   g_signal_new(TOOLS_CORE_SIG_CONF_RELOAD,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE,
                1,
                G_TYPE_POINTER);
   g_signal_new(TOOLS_CORE_SIG_DUMP_STATE,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE,
                1,
                G_TYPE_POINTER);
   g_signal_new(TOOLS_CORE_SIG_RESET,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE,
                1,
                G_TYPE_POINTER);
   g_signal_new(TOOLS_CORE_SIG_NO_RPC,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE,
                1,
                G_TYPE_POINTER);
   g_signal_new(TOOLS_CORE_SIG_SET_OPTION,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                ToolsCoreSetOptionAccumulator,
                NULL,
                g_cclosure_user_marshal_BOOLEAN__POINTER_STRING_STRING,
                G_TYPE_BOOLEAN,
                3,
                G_TYPE_POINTER,
                G_TYPE_STRING,
                G_TYPE_STRING);
   g_signal_new(TOOLS_CORE_SIG_SHUTDOWN,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE,
                1,
                G_TYPE_POINTER);
#if defined(G_PLATFORM_WIN32)
   g_signal_new(TOOLS_CORE_SIG_SERVICE_CONTROL,
                G_OBJECT_CLASS_TYPE(klass),
                G_SIGNAL_RUN_LAST,
                0,
                ToolsCoreServiceControlAccumulator,
                NULL,
                g_cclosure_user_marshal_UINT__POINTER_POINTER_UINT_UINT_POINTER,
                G_TYPE_UINT,
                5,
                G_TYPE_POINTER,
                G_TYPE_POINTER,
                G_TYPE_UINT,
                G_TYPE_UINT,
                G_TYPE_POINTER);
#endif

   G_OBJECT_CLASS(klass)->constructor = ToolsCoreServiceCtor;
   G_OBJECT_CLASS(klass)->finalize = ToolsCoreServiceDtor;
   G_OBJECT_CLASS(klass)->set_property = ToolsCoreServiceSetProperty;
   G_OBJECT_CLASS(klass)->get_property = ToolsCoreServiceGetProperty;
}


/**
 * Initializes the ToolsCoreService type if it hasn't been done yet, and
 * return the type instance. This method is not thread safe.
 *
 * @return The ToolsCoreService type.
 */

GType
ToolsCore_Service_get_type(void)
{
   static GType type = 0;
   if (type == 0) {
      static const GTypeInfo info = {
         sizeof (ToolsCoreServiceClass),
         NULL,                               /* base_init */
         NULL,                               /* base_finalize */
         ToolsCore_Service_class_init,
         NULL,                               /* class_finalize */
         NULL,                               /* class_data */
         sizeof (ToolsCoreService),
         0,                                  /* n_preallocs */
         NULL,                               /* instance_init */
      };
      type = g_type_register_static(G_TYPE_OBJECT,
                                    "ToolsCoreService",
                                    &info,
                                    0);
   }
   return type;
}


/*
 *******************************************************************************
 * ToolsCoreService_RegisterProperty --                                   */ /**
 *
 * Installs a new property in the service object.
 *
 * @param[in] obj    Service object.
 * @param[in] prop   Property to install.
 *
 *******************************************************************************
 */

void
ToolsCoreService_RegisterProperty(ToolsCoreService *obj,
                                  ToolsServiceProperty *prop)
{
   static guint PROP_ID_SEQ = 0;

   ServiceProperty sprop;
   ToolsCoreServiceClass *klass = TOOLSCORESERVICE_GET_CLASS(obj);
   GParamSpec *pspec = g_param_spec_pointer(prop->name,
                                            prop->name,
                                            prop->name,
                                            G_PARAM_READWRITE);

   g_mutex_lock(&obj->lock);

   sprop.id = ++PROP_ID_SEQ;
   sprop.name = g_strdup(prop->name);
   sprop.value = NULL;
   g_array_append_val(obj->props, sprop);
   g_object_class_install_property(G_OBJECT_CLASS(klass), sprop.id, pspec);

   g_mutex_unlock(&obj->lock);
}

