/* dzl-settings-flag-action.c
 *
 * Copyright (C) 2015 Christian Hergert <chergert@redhat.com>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "dzl-settings-flag-action"

#include "config.h"

#include "actions/dzl-settings-flag-action.h"
#include "util/dzl-macros.h"

struct _DzlSettingsFlagAction
{
  GObject parent_instance;

  GSettings *settings;

  gchar *schema_id;
  gchar *schema_key;
  gchar *flag_nick;
  gchar *name;
};

static void action_iface_init (GActionInterface *iface);

G_DEFINE_TYPE_EXTENDED (DzlSettingsFlagAction, dzl_settings_flag_action, G_TYPE_OBJECT, 0,
                        G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, action_iface_init))

enum {
  PROP_0,
  PROP_SCHEMA_ID,
  PROP_SCHEMA_KEY,
  PROP_FLAG_NICK,
  LAST_PROP,

  PROP_ENABLED,
  PROP_NAME,
  PROP_STATE,
  PROP_STATE_TYPE,
  PROP_PARAMETER_TYPE,
};

static GParamSpec *properties [LAST_PROP];

/**
 * dzl_settings_flag_action_new:
 *
 * This creates a new action that can be used to toggle an individual flag in
 * a #GSettings key which is of a flags type.
 *
 * Returns: (transfer full): A new #GAction.
 */
GAction *
dzl_settings_flag_action_new (const gchar *schema_id,
                              const gchar *schema_key,
                              const gchar *flag_nick)
{
  return g_object_new (DZL_TYPE_SETTINGS_FLAG_ACTION,
                       "schema-id", schema_id,
                       "schema-key", schema_key,
                       "flag-nick", flag_nick,
                       NULL);
}

static void
dzl_settings_flag_action_finalize (GObject *object)
{
  DzlSettingsFlagAction *self = (DzlSettingsFlagAction *)object;

  g_clear_pointer (&self->schema_id, g_free);
  g_clear_pointer (&self->schema_key, g_free);
  g_clear_pointer (&self->flag_nick, g_free);

  G_OBJECT_CLASS (dzl_settings_flag_action_parent_class)->finalize (object);
}

static void
dzl_settings_flag_action_get_property (GObject    *object,
                                       guint       prop_id,
                                       GValue     *value,
                                       GParamSpec *pspec)
{
  DzlSettingsFlagAction *self = DZL_SETTINGS_FLAG_ACTION (object);

  switch (prop_id)
    {
    case PROP_ENABLED:
      g_value_set_boolean (value, self->schema_id != NULL);
      break;

    case PROP_SCHEMA_ID:
      g_value_set_string (value, self->schema_id);
      break;

    case PROP_NAME:
      g_value_set_string (value, g_action_get_name (G_ACTION (self)));
      break;

    case PROP_SCHEMA_KEY:
      g_value_set_string (value, self->schema_key);
      break;

    case PROP_FLAG_NICK:
      g_value_set_string (value, self->flag_nick);
      break;

    case PROP_STATE:
    case PROP_STATE_TYPE:
    case PROP_PARAMETER_TYPE:
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_settings_flag_action_set_property (GObject      *object,
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
{
  DzlSettingsFlagAction *self = DZL_SETTINGS_FLAG_ACTION (object);

  switch (prop_id)
    {
    case PROP_SCHEMA_ID:
      g_free (self->schema_id);
      self->schema_id = g_value_dup_string (value);
      break;

    case PROP_SCHEMA_KEY:
      g_free (self->schema_key);
      self->schema_key = g_value_dup_string (value);
      break;

    case PROP_FLAG_NICK:
      g_free (self->flag_nick);
      self->flag_nick = g_value_dup_string (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
dzl_settings_flag_action_class_init (DzlSettingsFlagActionClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = dzl_settings_flag_action_finalize;
  object_class->get_property = dzl_settings_flag_action_get_property;
  object_class->set_property = dzl_settings_flag_action_set_property;

  g_object_class_override_property (object_class, PROP_NAME, "name");
  g_object_class_override_property (object_class, PROP_STATE, "state");
  g_object_class_override_property (object_class, PROP_STATE_TYPE, "state-type");
  g_object_class_override_property (object_class, PROP_PARAMETER_TYPE, "parameter-type");
  g_object_class_override_property (object_class, PROP_ENABLED, "enabled");

  properties [PROP_SCHEMA_ID] =
    g_param_spec_string ("schema-id",
                         "Schema Id",
                         "Schema Id",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  properties [PROP_SCHEMA_KEY] =
    g_param_spec_string ("schema-key",
                         "Schema Key",
                         "Schema Key",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties [PROP_FLAG_NICK] =
    g_param_spec_string ("flag-nick",
                         "Flag Nick",
                         "Flag Nick",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, LAST_PROP, properties);
}

static void
dzl_settings_flag_action_init (DzlSettingsFlagAction *self)
{
}

static GSettings *
dzl_settings_flag_action_get_settings (DzlSettingsFlagAction *self)
{
  g_assert (DZL_IS_SETTINGS_FLAG_ACTION (self));

  if (self->settings == NULL)
    self->settings = g_settings_new (self->schema_id);

  return self->settings;
}

static const gchar *
dzl_settings_flag_action_get_name (GAction *action)
{
  DzlSettingsFlagAction *self = (DzlSettingsFlagAction *)action;

  if (self->name == NULL)
    self->name = g_strdup_printf ("%s-%s", self->schema_key, self->flag_nick);

  return self->name;
}

static const GVariantType *
dzl_settings_flag_action_get_parameter_type (GAction *action)
{
  return NULL;
}

static const GVariantType *
dzl_settings_flag_action_get_state_type (GAction *action)
{
  return G_VARIANT_TYPE_BOOLEAN;
}

static GVariant *
dzl_settings_flag_action_get_state_hint (GAction *action)
{
  return NULL;
}

static void
dzl_settings_flag_action_change_state (GAction  *action,
                                       GVariant *value)
{
}

static GVariant *
dzl_settings_flag_action_get_state (GAction  *action)
{
  DzlSettingsFlagAction *self = (DzlSettingsFlagAction *)action;
  GSettings *settings = dzl_settings_flag_action_get_settings (self);
  g_auto(GStrv) flags = g_settings_get_strv (settings, self->schema_key);
  gboolean state = g_strv_contains ((const gchar * const *)flags, self->flag_nick);
  return g_variant_new_boolean (state);
}

static gboolean
dzl_settings_flag_action_get_enabled (GAction *action)
{
  DzlSettingsFlagAction *self = (DzlSettingsFlagAction *)action;

  return self->schema_id && self->schema_key && self->flag_nick;
}

static void
dzl_settings_flag_action_activate (GAction  *action,
                                   GVariant *parameter)
{
  DzlSettingsFlagAction *self = (DzlSettingsFlagAction *)action;
  GSettings *settings;
  GPtrArray *ar;
  gboolean found = FALSE;
  gchar **strv;
  guint i;

  g_assert (DZL_IS_SETTINGS_FLAG_ACTION (action));
  g_assert (parameter == NULL);

  settings = dzl_settings_flag_action_get_settings (self);
  strv = g_settings_get_strv (settings, self->schema_key);
  ar = g_ptr_array_new ();

  for (i = 0; strv [i]; i++)
    {
      if (g_strcmp0 (strv [i], self->flag_nick) == 0)
        found = TRUE;
      else
        g_ptr_array_add (ar, strv [i]);
    }

  if (!found)
    g_ptr_array_add (ar, self->flag_nick);

  g_ptr_array_add (ar, NULL);

  g_settings_set_strv (settings, self->schema_key, (const gchar * const *)ar->pdata);

  g_strfreev (strv);
}

static void
action_iface_init (GActionInterface *iface)
{
  iface->activate = dzl_settings_flag_action_activate;
  iface->change_state = dzl_settings_flag_action_change_state;
  iface->get_enabled = dzl_settings_flag_action_get_enabled;
  iface->get_name = dzl_settings_flag_action_get_name;
  iface->get_parameter_type = dzl_settings_flag_action_get_parameter_type;
  iface->get_state = dzl_settings_flag_action_get_state;
  iface->get_state_hint = dzl_settings_flag_action_get_state_hint;
  iface->get_state_type = dzl_settings_flag_action_get_state_type;
}
