// SPDX-FileCopyrightText: 2010-2026 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
// SPDX-License-Identifier: BSD-2-Clause

#include <gpaste-daemon/gpaste-item.h>

#include <string.h>

#define GCR_API_SUBJECT_TO_CHANGE
#include <gcr/gcr.h>

typedef struct
{
    gchar   *uuid;
    gchar   *value;
    GSList  *special_values;
    gchar   *display_string;
    guint64  size;
    gboolean favourite;
} GPasteItemPrivate;

G_PASTE_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (Item, item, G_TYPE_OBJECT)

/**
 * g_paste_item_get_uuid:
 * @self: a #GPasteItem instance
 *
 * Get the uuid of the given item
 *
 * Returns: read-only string containing the uuid
 */
G_PASTE_VISIBLE const gchar *
g_paste_item_get_uuid (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), NULL);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    return priv->uuid;
}

/**
 * g_paste_item_get_value:
 * @self: a #GPasteItem instance
 *
 * Get the value of the given item (text, uris or path to the image)
 *
 * Returns: read-only string containing the value
 */
G_PASTE_VISIBLE const gchar *
g_paste_item_get_value (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), NULL);

    return G_PASTE_ITEM_GET_CLASS (self)->get_value (self);
}

/**
 * g_paste_item_get_real_value:
 * @self: a #GPasteItem instance
 *
 * Get the real value of the given item (text, uris or path to the image)
 * This is different from get_value only for #GPastePasswordItem
 *
 * Returns: read-only string containing the real value
 */
G_PASTE_VISIBLE const gchar *
g_paste_item_get_real_value (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), NULL);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    return priv->value;
}

/**
 * g_paste_item_get_special_values:
 * @self: a #GPasteItem instance
 *
 * Get the special values (special mime types) for an item
 *
 * Returns: (element-type GPasteBinaryData): read-only list containing the special values
 */
G_PASTE_VISIBLE const GSList *
g_paste_item_get_special_values (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), NULL);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    return priv->special_values;
}

/**
 * g_paste_item_get_display_string:
 * @self: a #GPasteItem instance
 *
 * Get the string we should use to display the #GPasteItem
 *
 * Returns: read-only display string
 */
G_PASTE_VISIBLE const gchar *
g_paste_item_get_display_string (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), NULL);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);
    const gchar *display_string = priv->display_string;

    return (display_string) ? display_string : priv->value;
}

/**
 * g_paste_item_equals:
 * @self: a #GPasteItem instance
 * @other: another #GPasteItem instance
 *
 * Compare the two instances
 *
 * One dispatch is enough: every implementation of the vfunc is symmetric,
 * because the default one starts by comparing the two kinds.
 *
 * Returns: true if equals, false otherwise
 */
G_PASTE_VISIBLE gboolean
g_paste_item_equals (GPasteItem *self,
                     GPasteItem *other)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), FALSE);
    g_return_val_if_fail (G_PASTE_IS_ITEM (other), FALSE);

    if (self == other)
        return TRUE;

    return G_PASTE_ITEM_GET_CLASS (self)->equals (self, other);
}

/**
 * g_paste_item_get_kind:
 * @self: a #GPasteItem instance
 *
 * Get the kind of #GPasteItem. Use g_paste_item_kind_to_string() to serialize it.
 *
 * Returns: the #GPasteItemKind
 */
G_PASTE_VISIBLE GPasteItemKind
g_paste_item_get_kind (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), G_PASTE_ITEM_KIND_INVALID);

    const GPasteItemClass *klass = G_PASTE_ITEM_GET_CLASS (self);

    g_return_val_if_fail (klass->get_kind, G_PASTE_ITEM_KIND_INVALID);

    return klass->get_kind (self);
}

/**
 * g_paste_item_is_favourite:
 * @self: a #GPasteItem instance
 *
 * Whether the item is pinned, and so exempt from the history's automatic
 * eviction policies
 *
 * Returns: %TRUE if the item is a favourite
 */
G_PASTE_VISIBLE gboolean
g_paste_item_is_favourite (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), FALSE);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    return priv->favourite;
}

/**
 * g_paste_item_set_favourite:
 * @self: a #GPasteItem instance
 * @favourite: whether to pin the item
 *
 * Pin the item, or let it go again.
 *
 * This only records the flag. Everything that follows from it — re-electing the
 * biggest item, re-running the size and memory caps, persisting the change — is
 * #GPasteHistory's business, so go through g_paste_history_set_favourite() for
 * an item that is in a history.
 */
G_PASTE_VISIBLE void
g_paste_item_set_favourite (GPasteItem *self,
                            gboolean    favourite)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    priv->favourite = favourite;
}

/**
 * g_paste_item_get_size:
 * @self: a #GPasteItem instance
 *
 * Get the size of the #GPasteItem
 *
 * Returns: The size of its contents
 */
G_PASTE_VISIBLE guint64
g_paste_item_get_size (GPasteItem *self)
{
    g_return_val_if_fail (G_PASTE_IS_ITEM (self), 0);

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    return priv->size;
}

/**
 * g_paste_item_set_size:
 * @self: a #GPasteItem instance
 * @size: the new size
 *
 * Set the size of the item
 */
G_PASTE_VISIBLE void
g_paste_item_set_size (GPasteItem *self,
                       guint64     size)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    priv->size = size;
}

/**
 * g_paste_item_add_size:
 * @self: a #GPasteItem instance
 * @size: the size to add
 *
 * Add some size of the item
 */
G_PASTE_VISIBLE void
g_paste_item_add_size (GPasteItem *self,
                       guint64     size)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    priv->size += size;
}

/**
 * g_paste_item_remove_size:
 * @self: a #GPasteItem instance
 * @size: the size to remove
 *
 * Remove some size from the item
 */
G_PASTE_VISIBLE void
g_paste_item_remove_size (GPasteItem *self,
                          guint64     size)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    g_return_if_fail (priv->size >= size);

    priv->size -= size;
}

/**
 * g_paste_item_set_display_string:
 * @self: a #GPasteItem instance
 * @display_string: (transfer full) (nullable): the new display string
 *
 * Set the string to display
 */
G_PASTE_VISIBLE void
g_paste_item_set_display_string (GPasteItem *self,
                                 gchar      *display_string)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    if (priv->display_string)
        priv->size -= (strlen (priv->display_string) + 1);

    g_set_str_take (&priv->display_string, display_string);

    /* Measure what we ended up holding, not what we were handed: g_set_str_take
     * frees @display_string and keeps the old pointer when the two compare
     * equal, so reading @display_string here would be a use-after-free — and
     * the resulting bogus length underflows @size, which then reads as
     * permanently over max-memory-usage and starts evicting the history. */
    if (priv->display_string)
        priv->size += strlen (priv->display_string) + 1;
}

/**
 * g_paste_item_add_special_value:
 * @self: a #GPasteItem instance
 * @binary_data: (transfer full): the binary data to add
 *
 * Add the special value (special mime type) for an item, taking full ownership
 * of the #GPasteBinaryData reference.
 */
G_PASTE_VISIBLE void
g_paste_item_add_special_value (GPasteItem       *self,
                                GPasteBinaryData *binary_data)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    priv->size += g_bytes_get_size (g_paste_binary_data_get_bytes (binary_data));
    priv->special_values = g_slist_prepend (priv->special_values, binary_data);
}

/**
 * g_paste_item_set_state:
 * @self: a #GPasteItem instance
 * @state: a #GPasteItemState
 *
 * Set whether this item is Active or Idle
 */
G_PASTE_VISIBLE void
g_paste_item_set_state (GPasteItem     *self,
                        GPasteItemState state)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));

    g_debug ("item: set state: %d", state);

    G_PASTE_ITEM_GET_CLASS (self)->set_state (self, state);
}

/**
 * g_paste_item_set_uuid:
 * @self: a #GPasteItem instance
 * @uuid: the new uuid
 *
 * Set the uuid of the item
 */
G_PASTE_VISIBLE void
g_paste_item_set_uuid (GPasteItem  *self,
                       const gchar *uuid)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));
    g_return_if_fail (g_uuid_string_is_valid (uuid));

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    g_set_str (&priv->uuid, uuid);
}

/**
 * g_paste_item_set_value:
 * @self: a #GPasteItem instance
 * @value: the new value
 *
 * Replace the value of the item, keeping its size accounting right. Meant for
 * re-anchoring an image item's canonical path, not for editing contents:
 * consumers cache no copy of the value, but they do rely on it being stable
 * once the item is persisted.
 */
G_PASTE_VISIBLE void
g_paste_item_set_value (GPasteItem  *self,
                        const gchar *value)
{
    g_return_if_fail (G_PASTE_IS_ITEM (self));
    g_return_if_fail (value);

    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);
    gboolean secure = G_PASTE_ITEM_GET_CLASS (self)->secure (self);

    /* Also guards against @value aliasing our current value, which would be
     * read after free below. */
    if (g_paste_str_equal (value, priv->value))
        return;

    priv->size -= strlen (priv->value) + 1;

    if (secure)
        gcr_secure_memory_strfree (priv->value);
    else
        g_free (priv->value);

    priv->value = (secure) ? gcr_secure_memory_strdup (value) : g_strdup (value);
    priv->size += strlen (priv->value) + 1;
}

static void
g_paste_item_dispose (GObject *object)
{
    GPasteItemPrivate *priv = g_paste_item_get_instance_private (G_PASTE_ITEM (object));

    g_clear_slist (&priv->special_values, g_object_unref);

    G_OBJECT_CLASS (g_paste_item_parent_class)->dispose (object);
}

static void
g_paste_item_finalize (GObject *object)
{
    GPasteItem *self = G_PASTE_ITEM (object);
    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    g_free (priv->uuid);
    if (G_PASTE_ITEM_GET_CLASS (self)->secure (self))
        gcr_secure_memory_strfree (priv->value);
    else
        g_free (priv->value);
    g_free (priv->display_string);

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

/* Two items match when they are of the same kind and carry the same value. The
 * kind comparison is what makes this symmetric, and hence what lets
 * g_paste_item_equals() dispatch only once. */
static gboolean
g_paste_item_default_equals (GPasteItem *self,
                             GPasteItem *other)
{
    if (self == other)
        return TRUE;

    if (g_paste_item_get_kind (self) != g_paste_item_get_kind (other))
        return FALSE;

    const GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);
    const GPasteItemPrivate *_priv = g_paste_item_get_instance_private (other);

    return g_paste_str_equal (priv->value, _priv->value);
}

static void
g_paste_item_default_set_state (GPasteItem     *self  G_GNUC_UNUSED,
                                GPasteItemState state G_GNUC_UNUSED)
{
}

static gboolean
g_paste_item_default_secure (GPasteItem *self G_GNUC_UNUSED)
{
    return FALSE;
}

static void
g_paste_item_class_init (GPasteItemClass *klass)
{
    klass->equals = g_paste_item_default_equals;
    klass->get_value = g_paste_item_get_real_value;
    klass->get_kind = NULL;
    klass->set_state = g_paste_item_default_set_state;
    klass->secure = g_paste_item_default_secure;

    G_OBJECT_CLASS (klass)->dispose = g_paste_item_dispose;
    G_OBJECT_CLASS (klass)->finalize = g_paste_item_finalize;
}

static void
g_paste_item_init (GPasteItem *self G_GNUC_UNUSED)
{
}

/**
 * g_paste_item_new: (skip)
 * @type: the type of the subclass to instantiate
 * @value: the value of the item
 *
 * Create a new instance of a #GPasteItem subclass. This is a GType-based factory
 * for the abstract base, not a constructor, so it is skipped for introspection.
 *
 * Returns: a newly allocated #GPasteItem
 *          free it with g_object_unref
 */
G_PASTE_VISIBLE GPasteItem *
g_paste_item_new (GType        type,
                  const gchar *value)
{
    g_return_val_if_fail (g_type_is_a (type, G_PASTE_TYPE_ITEM), NULL);
    g_return_val_if_fail (value, NULL);

    GPasteItem *self = g_object_new (type, NULL);
    GPasteItemPrivate *priv = g_paste_item_get_instance_private (self);

    priv->uuid = g_uuid_string_random ();
    priv->value = (G_PASTE_ITEM_GET_CLASS (self)->secure (self)) ? gcr_secure_memory_strdup (value) : g_strdup (value);
    priv->display_string = NULL;

    priv->size = strlen (priv->value) + 1;

    return self;
}
