/* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define G_LOG_DOMAIN "wp-plugin"

#include "plugin.h"
#include "log.h"
#include "private/registry.h"

/*! \defgroup wpplugin WpPlugin */
/*!
 * \struct WpPlugin
 *
 * WpPlugin is a base class for objects that provide functionality to the
 * WirePlumber daemon.
 *
 * Typically, a plugin is created within a module and then registered to
 * make it available for use by the daemon. The daemon is responsible for
 * calling wp_object_activate() on it after all modules have been loaded,
 * the core is connected and the initial discovery of global objects is
 * done.
 *
 * Being a WpObject subclass, the plugin inherits WpObject's activation system.
 * For most implementations, there is only need for activating one
 * feature, WP_PLUGIN_FEATURE_ENABLED, and this can be done by implementing
 * only WpPluginClass::enable() and WpPluginClass::disable().
 * For more advanced plugins that need to have more features, you may
 * implement directly the functions of WpObjectClass and ignore the ones of
 * WpPluginClass.
 *
 * \gproperties
 *
 * \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
 *   The name of this plugin}
 */

enum {
  PROP_0,
  PROP_NAME,
};

typedef struct _WpPluginPrivate WpPluginPrivate;
struct _WpPluginPrivate
{
  GQuark name_quark;
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpPlugin, wp_plugin, WP_TYPE_OBJECT)

static void
wp_plugin_init (WpPlugin * self)
{
}

static void
wp_plugin_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpPlugin *self = WP_PLUGIN (object);
  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    priv->name_quark = g_quark_from_string (g_value_get_string (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_plugin_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpPlugin *self = WP_PLUGIN (object);
  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);

  switch (property_id) {
  case PROP_NAME:
    g_value_set_string (value, g_quark_to_string (priv->name_quark));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static WpObjectFeatures
wp_plugin_get_supported_features (WpObject * self)
{
  return WP_PLUGIN_FEATURE_ENABLED;
}

enum {
  STEP_ENABLE = WP_TRANSITION_STEP_CUSTOM_START,
};

static guint
wp_plugin_activate_get_next_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  /* we only support ENABLED, so this is the only
     feature that can be in @em missing */
  g_return_val_if_fail (missing == WP_PLUGIN_FEATURE_ENABLED,
      WP_TRANSITION_STEP_ERROR);

  return STEP_ENABLE;
}

static void
wp_plugin_activate_execute_step (WpObject * object,
    WpFeatureActivationTransition * transition, guint step,
    WpObjectFeatures missing)
{
  switch (step) {
  case STEP_ENABLE: {
    WpPlugin *self = WP_PLUGIN (object);
    wp_info_object (self, "enabling plugin '%s'", wp_plugin_get_name (self));
    g_return_if_fail (WP_PLUGIN_GET_CLASS (self)->enable);
    WP_PLUGIN_GET_CLASS (self)->enable (self, WP_TRANSITION (transition));
    break;
  }
  case WP_TRANSITION_STEP_ERROR:
    break;
  default:
    g_assert_not_reached ();
  }
}

static void
wp_plugin_deactivate (WpObject * object, WpObjectFeatures features)
{
  if (features & WP_PLUGIN_FEATURE_ENABLED) {
    WpPlugin *self = WP_PLUGIN (object);
    wp_info_object (self, "disabling plugin '%s'", wp_plugin_get_name (self));
    if (WP_PLUGIN_GET_CLASS (self)->disable)
      WP_PLUGIN_GET_CLASS (self)->disable (self);
    wp_object_update_features (WP_OBJECT (self), 0, WP_PLUGIN_FEATURE_ENABLED);
  }
}

static void
wp_plugin_class_init (WpPluginClass * klass)
{
  GObjectClass * object_class = (GObjectClass *) klass;
  WpObjectClass * wpobject_class = (WpObjectClass *) klass;

  object_class->set_property = wp_plugin_set_property;
  object_class->get_property = wp_plugin_get_property;

  wpobject_class->get_supported_features = wp_plugin_get_supported_features;
  wpobject_class->activate_get_next_step = wp_plugin_activate_get_next_step;
  wpobject_class->activate_execute_step = wp_plugin_activate_execute_step;
  wpobject_class->deactivate = wp_plugin_deactivate;

  g_object_class_install_property (object_class, PROP_NAME,
      g_param_spec_string ("name", "name",
          "The name of this plugin", NULL,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}

/*!
 * \brief Registers the plugin to its associated core, making it available for use
 *
 * \ingroup wpplugin
 * \param plugin (transfer full): the plugin
 */
void
wp_plugin_register (WpPlugin * plugin)
{
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
  g_return_if_fail (WP_IS_CORE (core));

  wp_registry_register_object (wp_core_get_registry (core), plugin);
}

static gboolean
find_plugin_func (gpointer plugin, gpointer name_quark)
{
  if (!WP_IS_PLUGIN (plugin))
    return FALSE;

  WpPluginPrivate *priv = wp_plugin_get_instance_private (plugin);
  return priv->name_quark == GPOINTER_TO_UINT (name_quark);
}

/*!
 * \brief Looks up a plugin.
 *
 * \ingroup wpplugin
 * \param core the core
 * \param plugin_name the lookup name
 * \returns (transfer full) (nullable): the plugin matching the lookup name
 */
WpPlugin *
wp_plugin_find (WpCore * core, const gchar * plugin_name)
{
  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  GQuark q = g_quark_try_string (plugin_name);
  if (q == 0)
    return NULL;
  GObject *p = wp_registry_find_object (wp_core_get_registry (core),
      (GEqualFunc) find_plugin_func, GUINT_TO_POINTER (q));
  return p ? WP_PLUGIN (p) : NULL;
}

/*!
 * \brief Retreives the name of a plugin.
 *
 * \ingroup wpplugin
 * \param self the plugin
 * \returns the name of this plugin
 */
const gchar *
wp_plugin_get_name (WpPlugin * self)
{
  g_return_val_if_fail (WP_IS_PLUGIN (self), NULL);

  WpPluginPrivate *priv = wp_plugin_get_instance_private (self);
  return g_quark_to_string (priv->name_quark);
}

/**
 * \var _WpPluginClass::enable
 *
 * \brief Enables the plugin. The plugin is required to start any operations
 * only when this method is called and not before.
 *
 * When enabling the plugin is done, you must call wp_object_update_features()
 * with WP_PLUGIN_FEATURE_ENABLED marked as activated, or return an error
 * on \a transition.
 *
 * \param self the plugin
 * \param transition the activation transition
 */

/**
 * \var _WpPluginClass::disable
 *
 * \brief Disables the plugin. The plugin is required to stop all operations
 * and release all resources associated with it.
 *
 * \param self the plugin
 */
