/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikatoggleaction.c
 * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
 * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
 * Copyright (C) 2023 Jehan
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "pikaaction.h"
#include "pikaaction-history.h"
#include "pikatoggleaction.h"


enum
{
  PROP_0,
  PROP_ACTIVE,
};

enum
{
  TOGGLED,
  LAST_SIGNAL
};

struct _PikaToggleActionPrivate
{
  gboolean active;
};

static void     pika_toggle_action_g_action_iface_init (GActionInterface *iface);

static void     pika_toggle_action_get_property        (GObject          *object,
                                                        guint             prop_id,
                                                        GValue           *value,
                                                        GParamSpec       *pspec);
static void     pika_toggle_action_set_property        (GObject          *object,
                                                        guint             prop_id,
                                                        const GValue     *value,
                                                        GParamSpec       *pspec);

static gboolean pika_toggle_action_get_enabled         (GAction          *action);
static const GVariantType *
                pika_toggle_action_get_state_type      (GAction          *action);
static GVariant *
                pika_toggle_action_get_state           (GAction          *action);

static void     pika_toggle_action_activate            (GAction          *action,
                                                        GVariant         *parameter);

static gboolean pika_toggle_action_real_toggle         (PikaToggleAction *action);

static void     pika_toggle_action_toggle              (PikaToggleAction *action);


G_DEFINE_TYPE_WITH_CODE (PikaToggleAction, pika_toggle_action,
                         PIKA_TYPE_ACTION_IMPL,
                         G_ADD_PRIVATE (PikaToggleAction)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, pika_toggle_action_g_action_iface_init))

#define parent_class pika_toggle_action_parent_class

static guint pika_toggle_action_signals[LAST_SIGNAL] = { 0 };

static void
pika_toggle_action_class_init (PikaToggleActionClass *klass)
{
  GObjectClass          *object_class = G_OBJECT_CLASS (klass);
  PikaToggleActionClass *toggle_class = PIKA_TOGGLE_ACTION_CLASS (klass);

  pika_toggle_action_signals[TOGGLED] =
    g_signal_new ("toggled",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToggleActionClass, toggled),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  object_class->get_property  = pika_toggle_action_get_property;
  object_class->set_property  = pika_toggle_action_set_property;

  toggle_class->toggle        = pika_toggle_action_real_toggle;

  /**
   * PikaToggleAction:active:
   *
   * If @action state is currently active.
   *
   * Calls to g_action_activate() will flip this property value.
   **/
  g_object_class_install_property (object_class, PROP_ACTIVE,
                                   g_param_spec_boolean ("active",
                                                         "Active state",
                                                         "If the action state is active",
                                                         TRUE,
                                                         PIKA_PARAM_READWRITE));
}

static void
pika_toggle_action_g_action_iface_init (GActionInterface *iface)
{
  iface->get_name       = (const gchar* (*) (GAction*)) pika_action_get_name;
  iface->activate       = pika_toggle_action_activate;

  iface->get_enabled    = pika_toggle_action_get_enabled;
  iface->get_state_type = pika_toggle_action_get_state_type;
  iface->get_state      = pika_toggle_action_get_state;
}

static void
pika_toggle_action_init (PikaToggleAction *action)
{
  action->priv = pika_toggle_action_get_instance_private (action);
}

static void
pika_toggle_action_get_property (GObject    *object,
                                 guint       prop_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
  PikaToggleAction *action = PIKA_TOGGLE_ACTION (object);

  switch (prop_id)
    {
    case PROP_ACTIVE:
      g_value_set_boolean (value, pika_toggle_action_get_active (action));
      break;

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

static void
pika_toggle_action_set_property (GObject      *object,
                                 guint         prop_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
  PikaToggleAction *action = PIKA_TOGGLE_ACTION (object);

  switch (prop_id)
    {
    case PROP_ACTIVE:
      pika_toggle_action_set_active (action, g_value_get_boolean (value));
      break;

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

static gboolean
pika_toggle_action_get_enabled (GAction *action)
{
  return pika_action_is_sensitive (PIKA_ACTION (action), NULL);
}

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

static GVariant *
pika_toggle_action_get_state (GAction *action)
{
  GVariant *state;

  state = g_variant_new_boolean (PIKA_TOGGLE_ACTION (action)->priv->active);

  return g_variant_ref_sink (state);
}

static void
pika_toggle_action_activate (GAction  *action,
                             GVariant *parameter)
{
  pika_toggle_action_toggle (PIKA_TOGGLE_ACTION (action));
}

static gboolean
pika_toggle_action_real_toggle (PikaToggleAction *action)
{
  gboolean value = pika_toggle_action_get_active (action);

  action->priv->active = ! value;
  pika_action_emit_change_state (PIKA_ACTION (action),
                                 g_variant_new_boolean (! value));

  /* Toggling always works for the base class. */
  return TRUE;
}

static void
pika_toggle_action_toggle (PikaToggleAction *action)
{
  if (PIKA_TOGGLE_ACTION_GET_CLASS (action)->toggle != NULL &&
      PIKA_TOGGLE_ACTION_GET_CLASS (action)->toggle (action))
    {
      g_signal_emit (action, pika_toggle_action_signals[TOGGLED], 0);
      g_object_notify (G_OBJECT (action), "state");
      g_object_notify (G_OBJECT (action), "active");

      pika_action_history_action_activated (PIKA_ACTION (action));
    }
}


/*  public functions  */

PikaAction *
pika_toggle_action_new (const gchar *name,
                        const gchar *label,
                        const gchar *short_label,
                        const gchar *tooltip,
                        const gchar *icon_name,
                        const gchar *help_id,
                        PikaContext *context)
{
  PikaAction *action;

  action = g_object_new (PIKA_TYPE_TOGGLE_ACTION,
                         "name",        name,
                         "label",       label,
                         "short-label", short_label,
                         "tooltip",     tooltip,
                         "icon-name",   icon_name,
                         "context",     context,
                         NULL);

  pika_action_set_help_id (PIKA_ACTION (action), help_id);

  return action;
}

void
pika_toggle_action_set_active (PikaToggleAction *action,
                               gboolean          active)
{
  if (action->priv->active != active)
    pika_toggle_action_toggle (action);
}

gboolean
pika_toggle_action_get_active (PikaToggleAction *action)
{
  return action->priv->active;
}


/* Protected functions. */

void
_pika_toggle_action_set_active (PikaToggleAction *action,
                                gboolean          active)
{
  action->priv->active = active;
}
