/* gtd-notification.c
 *
 * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
 * Copyright (C) 2022 Jamie Murphy <hello@itsjamie.dev>
 *
 * 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 "GtdNotification"

#include "gtd-notification.h"
#include "gtd-object.h"

#include <glib/gi18n.h>

/**
 * SECTION: gtd-notification
 * @short_description: An auxiliary class around #AdwToast
 * @title: GtdNotification
 * @stability: Stable
 *
 * The #GtdNotification represents a notification shown at the top of
 * the window. It is an auxiliary class around #AdwToast, and is used only
 * to store toast data and callbacks.
 *
 * A notification may have a dismissal action that is called when the toast
 * is dismissed, whether by user interaction or a timeout
 *
 * Optionally, the notification may have a secondary action
 * (see gtd_notification_set_secondary_action()), shown as a button.
 *
 * A user should not create a UI for a #GtdNotification and should instead
 * pass it to a #GtdManager, via gtd_manager_send_notification()
 *
 * Example:
 * |[
 * GtdNotification *notification;
 *
 * notification = gtd_notification_new ("Something happened!");
 *
 * gtd_notification_set_dismissal_action (notification,
 *                                        called_when_notification_is_dismissed,
 *                                        self);
 *
 * gtd_notification_set_secondary_action (notification,
 *                                        "Details",
 *                                        show_details,
 *                                        self);
 * [...]
 * ]|
 */

typedef struct
{
  gchar              *text;

  GtdNotificationActionFunc dismissal_action;
  gboolean            has_dismissal_action;
  gpointer            dismissal_action_data;

  GtdNotificationActionFunc secondary_action;
  gboolean            has_secondary_action;
  gpointer            secondary_action_data;
  gchar              *secondary_action_name;
} GtdNotificationPrivate;

struct _GtdNotification
{
  GtdObject               parent;

  /*< private >*/
  GtdNotificationPrivate *priv;
};

G_DEFINE_TYPE_WITH_PRIVATE (GtdNotification, gtd_notification, GTD_TYPE_OBJECT)

enum
{
  PROP_0,
  PROP_HAS_DISMISSAL_ACTION,
  PROP_HAS_SECONDARY_ACTION,
  PROP_SECONDARY_ACTION_NAME,
  PROP_TEXT,
  LAST_PROP
};

enum
{
  EXECUTED,
  NUM_SIGNALS
};

static guint signals[NUM_SIGNALS] = { 0, };

static void
gtd_notification_finalize (GObject *object)
{
  GtdNotification *self = (GtdNotification *)object;
  GtdNotificationPrivate *priv = gtd_notification_get_instance_private (self);

  g_clear_pointer (&priv->secondary_action_name, g_free);
  g_clear_pointer (&priv->text, g_free);

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

static void
gtd_notification_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  GtdNotification *self = GTD_NOTIFICATION (object);

  switch (prop_id)
    {
    case PROP_HAS_DISMISSAL_ACTION:
      g_value_set_boolean (value, self->priv->has_dismissal_action);
      break;

    case PROP_HAS_SECONDARY_ACTION:
      g_value_set_boolean (value, self->priv->has_secondary_action);
      break;

    case PROP_SECONDARY_ACTION_NAME:
      g_value_set_string (value, self->priv->secondary_action_name ? self->priv->secondary_action_name : "");
      break;

    case PROP_TEXT:
      g_value_set_string (value, gtd_notification_get_text (self));
      break;

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

static void
gtd_notification_set_property (GObject      *object,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  GtdNotification *self = GTD_NOTIFICATION (object);

  switch (prop_id)
    {
    case PROP_SECONDARY_ACTION_NAME:
      gtd_notification_set_secondary_action (self,
                                             g_value_get_string (value),
                                             self->priv->secondary_action,
                                             self->priv->secondary_action_data);
      break;

    case PROP_TEXT:
      gtd_notification_set_text (self, g_value_get_string (value));
      break;

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

static void
gtd_notification_class_init (GtdNotificationClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = gtd_notification_finalize;
  object_class->get_property = gtd_notification_get_property;
  object_class->set_property = gtd_notification_set_property;

  /**
   * GtdNotification::has-dismissal-action:
   *
   * @TRUE if the notification has a dismissal action or @FALSE otherwise.
   */
  g_object_class_install_property (
        object_class,
        PROP_HAS_DISMISSAL_ACTION,
        g_param_spec_boolean ("has-dismissal-action",
                              "Whether the notification has a dismissal action",
                              "Whether the notification has the dismissal action.",
                              FALSE,
                              G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));

  /**
   * GtdNotification::has-secondary-action:
   *
   * @TRUE if the notification has a secondary action or @FALSE otherwise. The
   * secondary action is triggered only by user explicit input.
   */
  g_object_class_install_property (
        object_class,
        PROP_HAS_SECONDARY_ACTION,
        g_param_spec_boolean ("has-secondary-action",
                              "Whether the notification has a secondary action",
                              "Whether the notification has the secondary action, activated by the user",
                              FALSE,
                              G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));

  /**
   * GtdNotification::secondary-action-name:
   *
   * The main text of the notification, usually a markuped text.
   */
  g_object_class_install_property (
        object_class,
        PROP_SECONDARY_ACTION_NAME,
        g_param_spec_string ("secondary-action-name",
                             "Text of the secondary action button",
                             "The text of the secondary action button",
                             "",
                             G_PARAM_READWRITE));

  /**
   * GtdNotification::text:
   *
   * The main text of the notification, usually a markuped text.
   */
  g_object_class_install_property (
        object_class,
        PROP_TEXT,
        g_param_spec_string ("text",
                             "Notification message",
                             "The main message of the notification",
                             "",
                             G_PARAM_READWRITE));

  /**
   * GtdNotification::executed:
   *
   * The ::executed signal is emitted after the primary or secondary
   * #GtdNotification action is executed.
   */
  signals[EXECUTED] = g_signal_new ("executed",
                                    GTD_TYPE_NOTIFICATION,
                                    G_SIGNAL_RUN_FIRST,
                                    0,
                                    NULL,
                                    NULL,
                                    NULL,
                                    G_TYPE_NONE,
                                    0);
}

static void
gtd_notification_init (GtdNotification *self)
{
  self->priv = gtd_notification_get_instance_private (self);
  self->priv->secondary_action_name = NULL;
  self->priv->text = NULL;
}

/**
 * gtd_notification_new:
 * @text: (nullable): text of the notification
 *
 * Creates a new notification with @text.
 *
 * Returns: (transfer full): a new #GtdNotification
 */
GtdNotification*
gtd_notification_new (const gchar *text)
{
  return g_object_new (GTD_TYPE_NOTIFICATION,
                       "text", text,
                       NULL);
}

/**
 * gtd_notification_set_dismissal_action:
 * @notification: a #GtdNotification
 * @func: (closure user_data) (scope call) (nullable): the dismissal action function
 * @user_data: data passed to @func
 *
 * Sets the dismissal action of @notification
 */
void
gtd_notification_set_dismissal_action (GtdNotification           *notification,
                                     GtdNotificationActionFunc  func,
                                     gpointer                   user_data)
{
  GtdNotificationPrivate *priv;
  gboolean has_action;

  g_return_if_fail (GTD_IS_NOTIFICATION (notification));

  priv = notification->priv;
  has_action = (func != NULL);

  if (has_action != priv->has_dismissal_action)
    {
      priv->has_dismissal_action = has_action;

      priv->dismissal_action = has_action ? func : NULL;
      priv->dismissal_action_data = has_action ? user_data : NULL;

      g_object_notify (G_OBJECT (notification), "has-dismissal-action");
    }
}

/**
 * gtd_notification_set_secondary_action:
 * @notification: a #GtdNotification
 * @name: the name of the secondary action
 * @func: (closure user_data) (scope call) (nullable): the secondary action function
 * @user_data: data passed to @func
 *
 * Sets the secondary action of @notification, which is triggered
 * only on user explicit input.
 */
void
gtd_notification_set_secondary_action (GtdNotification           *notification,
                                       const gchar               *name,
                                       GtdNotificationActionFunc  func,
                                       gpointer                   user_data)
{
  GtdNotificationPrivate *priv;
  gboolean has_action;

  g_return_if_fail (GTD_IS_NOTIFICATION (notification));

  priv = notification->priv;
  has_action = (func != NULL);

  if (has_action != priv->has_secondary_action)
    {
      priv->has_secondary_action = has_action;

      priv->secondary_action = has_action ? func : NULL;
      priv->secondary_action_data = has_action ? user_data : NULL;

      if (priv->secondary_action_name != name)
        {
          g_clear_pointer (&priv->secondary_action_name, g_free);
          priv->secondary_action_name = g_strdup (name);

          g_object_notify (G_OBJECT (notification), "secondary-action-name");
        }

      g_object_notify (G_OBJECT (notification), "has-secondary-action");
    }
}

/**
 * gtd_notification_get_text:
 * @notification: a #GtdNotification
 *
 * Gets the text of @notification.
 *
 * Returns: (transfer none): the text of @notification.
 */
const gchar*
gtd_notification_get_text (GtdNotification *notification)
{
  g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), NULL);

  return notification->priv->text ? notification->priv->text : "";
}

/**
 * gtd_notification_set_text:
 * @notification: a #GtdNotification
 * @text: the user-visible text of @notification
 *
 * Sets the text of @notification to @text.
 */
void
gtd_notification_set_text (GtdNotification *notification,
                           const gchar     *text)
{
  GtdNotificationPrivate *priv;

  g_return_if_fail (GTD_IS_NOTIFICATION (notification));

  priv = notification->priv;

  if (g_strcmp0 (priv->text, text) != 0)
    {
      g_clear_pointer (&priv->text, g_free);
      priv->text = g_strdup (text);

      g_object_notify (G_OBJECT (notification), "text");
    }
}

/**
 * gtd_notification_execute_dismissal_action:
 * @notification: a #GtdNotification
 *
 * Executes the dismissal action of @notification if any.
 */
void
gtd_notification_execute_dismissal_action (GtdNotification *notification)
{
  GtdNotificationPrivate *priv;

  g_return_if_fail (GTD_IS_NOTIFICATION (notification));

  priv = notification->priv;

  if (priv->dismissal_action)
    priv->dismissal_action (notification, priv->dismissal_action_data);

  g_signal_emit (notification, signals[EXECUTED], 0);
}

/**
 * gtd_notification_execute_secondary_action:
 * @notification: a #GtdNotification
 *
 * Executes the secondary action of @notification if any.
 */
void
gtd_notification_execute_secondary_action (GtdNotification *notification)
{
  GtdNotificationPrivate *priv;

  g_return_if_fail (GTD_IS_NOTIFICATION (notification));

  priv = notification->priv;

  if (priv->secondary_action)
    {
      priv->secondary_action (notification, priv->secondary_action_data);

      g_signal_emit (notification, signals[EXECUTED], 0);
    }
}
