/*
 * Seahorse
 *
 * Copyright (C) 2005 Stefan Walter
 * Copyright (C) 2011 Collabora Ltd.
 *
 * 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 2 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/>.
 */
#include "config.h"

#include "seahorse-gpgme.h"
#include "seahorse-gpgme-exporter.h"
#include "seahorse-gpgme-key-op.h"
#include "seahorse-gpgme-key-deleter.h"
#include "seahorse-gpgme-photo.h"
#include "seahorse-gpgme-keyring.h"
#include "seahorse-gpgme-secret-deleter.h"
#include "seahorse-gpgme-uid.h"
#include "seahorse-pgp-backend.h"
#include "seahorse-pgp-key.h"

#include "seahorse-common.h"

#include "libseahorse/seahorse-object-list.h"
#include "libseahorse/seahorse-util.h"

#include <glib/gi18n.h>

#include <string.h>

enum {
    PROP_0,
    PROP_PUBKEY,
    PROP_SECKEY,
    PROP_VALIDITY,
    PROP_TRUST
};

struct _SeahorseGpgmeKey {
    SeahorsePgpKey parent_instance;

    gpgme_key_t pubkey;           /* The public key */
    gpgme_key_t seckey;          /* The secret key */
    gboolean has_secret;        /* Whether we have a secret key or not */

    GList *uids;            /* We keep a copy of the uids. */

    int list_mode;                  /* What to load our public key as */
    gboolean photos_loaded;        /* Photos were loaded */

    gint block_loading;            /* Loading is blocked while this flag is set */
};

static void       seahorse_gpgme_key_deletable_iface       (SeahorseDeletableIface *iface);
static void       seahorse_gpgme_key_exportable_iface      (SeahorseExportableIface *iface);

G_DEFINE_TYPE_WITH_CODE (SeahorseGpgmeKey, seahorse_gpgme_key, SEAHORSE_PGP_TYPE_KEY,
                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTABLE, seahorse_gpgme_key_exportable_iface);
                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_DELETABLE, seahorse_gpgme_key_deletable_iface);
);

static gboolean
load_gpgme_key (const gchar *keyid,
                int mode,
                int secret,
                gpgme_key_t *key)
{
    GError *error = NULL;
    gpgme_ctx_t ctx;
    gpgme_error_t gerr;

    ctx = seahorse_gpgme_keyring_new_context (&gerr);
    if (gerr != 0)
        return FALSE;

    gpgme_set_keylist_mode (ctx, mode);
    gerr = gpgme_op_keylist_start (ctx, keyid, secret);
    if (GPG_IS_OK (gerr)) {
        gerr = gpgme_op_keylist_next (ctx, key);
        gpgme_op_keylist_end (ctx);
    }

    gpgme_release (ctx);

    if (seahorse_gpgme_propagate_error (gerr, &error)) {
        g_message ("couldn't load GPGME key: %s", error->message);
        g_clear_error (&error);
        return FALSE;
    }

    return TRUE;
}

static void
load_key_public (SeahorseGpgmeKey *self, int list_mode)
{
    gpgme_key_t key = NULL;
    const gchar *keyid;
    gboolean ret;

    if (self->block_loading)
        return;

    list_mode |= self->list_mode;

    keyid = seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (self));
    ret = load_gpgme_key (keyid, list_mode, FALSE, &key);
    if (ret) {
        self->list_mode = list_mode;
        seahorse_gpgme_key_set_public (self, key);
        gpgme_key_unref (key);
    }
}

static gboolean
require_key_public (SeahorseGpgmeKey *self, int list_mode)
{
    if (!self->pubkey || (self->list_mode & list_mode) != list_mode)
        load_key_public (self, list_mode);
    return self->pubkey && (self->list_mode & list_mode) == list_mode;
}

static void
load_key_private (SeahorseGpgmeKey *self)
{
    gpgme_key_t key = NULL;
    const gchar *keyid;
    gboolean ret;

    if (!self->has_secret || self->block_loading)
        return;

    keyid = seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (self));
    ret = load_gpgme_key (keyid, GPGME_KEYLIST_MODE_LOCAL, TRUE, &key);
    if (ret) {
        seahorse_gpgme_key_set_private (self, key);
        gpgme_key_unref (key);
    }
}

static gboolean
require_key_private (SeahorseGpgmeKey *self)
{
    if (!self->seckey)
        load_key_private (self);
    return self->seckey != NULL;
}

static gboolean
require_key_uids (SeahorseGpgmeKey *self)
{
    return require_key_public (self, GPGME_KEYLIST_MODE_LOCAL);
}

static gboolean
require_key_subkeys (SeahorseGpgmeKey *self)
{
    return require_key_public (self, GPGME_KEYLIST_MODE_LOCAL);
}

static void
load_key_photos (SeahorseGpgmeKey *self)
{
    gpgme_error_t gerr;

    if (self->block_loading)
        return;

    gerr = seahorse_gpgme_key_op_photos_load (self);
    if (!GPG_IS_OK (gerr))
        g_message ("couldn't load key photos: %s", gpgme_strerror (gerr));
    else
        self->photos_loaded = TRUE;
}

static gboolean
require_key_photos (SeahorseGpgmeKey *self)
{
    if (!self->photos_loaded)
        load_key_photos (self);
    return self->photos_loaded;
}

static void
renumber_actual_uids (SeahorseGpgmeKey *self)
{
    GArray *index_map;
    GList *photos, *uids, *l;
    guint index, i;

    g_assert (SEAHORSE_GPGME_IS_KEY (self));

    /*
     * This function is necessary because the uid stored in a gpgme_user_id_t
     * struct is only usable with gpgme functions.  Problems will be caused if
     * that uid is used with functions found in seahorse-gpgme-key-op.h.  This
     * function is only to be called with uids from gpgme_user_id_t structs.
     */

    ++self->block_loading;
    photos = seahorse_pgp_key_get_photos (SEAHORSE_PGP_KEY (self));
    uids = self->uids;
    --self->block_loading;

    /* First we build a bitmap of where all the photo uid indexes are */
    index_map = g_array_new (FALSE, TRUE, sizeof (gboolean));
    for (l = photos; l; l = g_list_next (l)) {
        index = seahorse_gpgme_photo_get_index (l->data);
        if (index >= index_map->len)
            g_array_set_size (index_map, index + 1);
        g_array_index (index_map, gboolean, index) = TRUE;
    }

    /* Now for each UID we add however many photo indexes are below the gpgme index */
    for (l = uids; l; l = g_list_next (l)) {
        index = seahorse_gpgme_uid_get_gpgme_index (l->data);
        for (i = 0; i < index_map->len && i < index; ++i) {
            if(g_array_index (index_map, gboolean, index))
                ++index;
        }
        seahorse_gpgme_uid_set_actual_index (l->data, index + 1);
    }

    g_array_free (index_map, TRUE);
}

static void
realize_uids (SeahorseGpgmeKey *self)
{
    gpgme_user_id_t guid;
    SeahorseGpgmeUid *uid;
    GList *results = NULL;
    gboolean changed = FALSE;
    GList *uids;

    uids = self->uids;
    guid = self->pubkey ? self->pubkey->uids : NULL;

    /* Look for out of sync or missing UIDs */
    while (uids != NULL) {
        g_return_if_fail (SEAHORSE_GPGME_IS_UID (uids->data));
        uid = SEAHORSE_GPGME_UID (uids->data);
        uids = g_list_next (uids);

        /* Bring this UID up to date */
        if (guid && seahorse_gpgme_uid_is_same (uid, guid)) {
            if (seahorse_gpgme_uid_get_userid (uid) != guid) {
                g_object_set (uid, "pubkey", self->pubkey, "userid", guid, NULL);
                changed = TRUE;
            }
            results = seahorse_object_list_append (results, uid);
            guid = guid->next;
        }
    }

    /* Add new UIDs */
    while (guid != NULL) {
        uid = seahorse_gpgme_uid_new (self, guid);
        changed = TRUE;
        results = seahorse_object_list_append (results, uid);
        g_object_unref (uid);
        guid = guid->next;
    }

    if (changed)
        seahorse_pgp_key_set_uids (SEAHORSE_PGP_KEY (self), results);
    seahorse_object_list_free (results);
}

static void
realize_subkeys (SeahorseGpgmeKey *self)
{
    gpgme_subkey_t gsubkey;
    SeahorseGpgmeSubkey *subkey;
    GList *list = NULL;

    if (self->pubkey) {

        for (gsubkey = self->pubkey->subkeys; gsubkey; gsubkey = gsubkey->next) {
            subkey = seahorse_gpgme_subkey_new (self->pubkey, gsubkey);
            list = seahorse_object_list_prepend (list, subkey);
            g_object_unref (subkey);
        }

        list = g_list_reverse (list);
    }

    seahorse_pgp_key_set_subkeys (SEAHORSE_PGP_KEY (self), list);
    seahorse_object_list_free (list);
}

void
seahorse_gpgme_key_realize (SeahorseGpgmeKey *self)
{
    SeahorseUsage usage;
    guint flags;

    if (!self->pubkey)
        return;

    if (!require_key_public (self, GPGME_KEYLIST_MODE_LOCAL))
        g_return_if_reached ();

    g_return_if_fail (self->pubkey);
    g_return_if_fail (self->pubkey->subkeys);

    /* Update the sub UIDs */
    realize_uids (self);
    realize_subkeys (self);

    /* The flags */
    flags = SEAHORSE_FLAG_EXPORTABLE | SEAHORSE_FLAG_DELETABLE;

    if (!self->pubkey->disabled && !self->pubkey->expired &&
        !self->pubkey->revoked && !self->pubkey->invalid) {
        if (seahorse_gpgme_key_get_validity (self) >= SEAHORSE_VALIDITY_MARGINAL)
            flags |= SEAHORSE_FLAG_IS_VALID;
        if (self->pubkey->can_encrypt)
            flags |= SEAHORSE_FLAG_CAN_ENCRYPT;
        if (self->seckey && self->pubkey->can_sign)
            flags |= SEAHORSE_FLAG_CAN_SIGN;
    }

    if (self->pubkey->expired)
        flags |= SEAHORSE_FLAG_EXPIRED;

    if (self->pubkey->revoked)
        flags |= SEAHORSE_FLAG_REVOKED;

    if (self->pubkey->disabled)
        flags |= SEAHORSE_FLAG_DISABLED;

    if (seahorse_gpgme_key_get_trust (self) >= SEAHORSE_VALIDITY_MARGINAL &&
        !self->pubkey->revoked && !self->pubkey->disabled &&
        !self->pubkey->expired)
        flags |= SEAHORSE_FLAG_TRUSTED;

    /* The type */
    if (self->seckey) {
        usage = SEAHORSE_USAGE_PRIVATE_KEY;
        flags |= SEAHORSE_FLAG_PERSONAL;
    } else {
        usage = SEAHORSE_USAGE_PUBLIC_KEY;
    }

    g_object_set (self,
                  "usage", usage,
                  "object-flags", flags,
                  NULL);

    seahorse_pgp_key_realize (SEAHORSE_PGP_KEY (self));
}

void
seahorse_gpgme_key_ensure_signatures (SeahorseGpgmeKey *self)
{
    require_key_public (self, GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_SIGS);
}

void
seahorse_gpgme_key_refresh (SeahorseGpgmeKey *self)
{
    if (self->pubkey)
        load_key_public (self, self->list_mode);
    if (self->seckey)
        load_key_private (self);
    if (self->photos_loaded)
        load_key_photos (self);
}

static GList*
seahorse_gpgme_key_get_uids (SeahorsePgpKey *base)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (base);
    require_key_uids (self);
    return SEAHORSE_PGP_KEY_CLASS (seahorse_gpgme_key_parent_class)->get_uids (base);
}

static void
seahorse_gpgme_key_set_uids (SeahorsePgpKey *base, GList *uids)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (base);

    SEAHORSE_PGP_KEY_CLASS (seahorse_gpgme_key_parent_class)->set_uids (base, uids);

    /* Keep our own copy of the UID list */
    seahorse_object_list_free (self->uids);
    self->uids = seahorse_object_list_copy (uids);

    renumber_actual_uids (self);
}

static GList*
seahorse_gpgme_key_get_subkeys (SeahorsePgpKey *base)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (base);
    require_key_subkeys (self);
    return SEAHORSE_PGP_KEY_CLASS (seahorse_gpgme_key_parent_class)->get_subkeys (base);
}

static GList*
seahorse_gpgme_key_get_photos (SeahorsePgpKey *base)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (base);
    require_key_photos (self);
    return SEAHORSE_PGP_KEY_CLASS (seahorse_gpgme_key_parent_class)->get_photos (base);
}

static void
seahorse_gpgme_key_set_photos (SeahorsePgpKey *base, GList *photos)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (base);
    self->photos_loaded = TRUE;
    SEAHORSE_PGP_KEY_CLASS (seahorse_gpgme_key_parent_class)->set_photos (base, photos);
    renumber_actual_uids (self);
}

static SeahorseDeleter *
seahorse_gpgme_key_create_deleter (SeahorseDeletable *deletable)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (deletable);
    if (self->seckey)
        return seahorse_gpgme_secret_deleter_new (self);
    else
        return seahorse_gpgme_key_deleter_new (self);
}

static gboolean
seahorse_gpgme_key_get_deletable (SeahorseDeletable *deletable)
{
    gboolean can;
    g_object_get (deletable, "deletable", &can, NULL);
    return can;
}

static void
seahorse_gpgme_key_deletable_iface (SeahorseDeletableIface *iface)
{
    iface->create_deleter = seahorse_gpgme_key_create_deleter;
    iface->get_deletable = seahorse_gpgme_key_get_deletable;
}

static GList *
seahorse_gpgme_key_create_exporters (SeahorseExportable *exportable,
                                     SeahorseExporterType type)
{
    GList *result = NULL;

    if (type != SEAHORSE_EXPORTER_TYPE_TEXTUAL)
        result = g_list_append (result, seahorse_gpgme_exporter_new (G_OBJECT (exportable), FALSE, FALSE));
    result = g_list_append (result, seahorse_gpgme_exporter_new (G_OBJECT (exportable), TRUE, FALSE));

    return result;
}

static gboolean
seahorse_gpgme_key_get_exportable (SeahorseExportable *exportable)
{
    gboolean can;
    g_object_get (exportable, "exportable", &can, NULL);
    return can;
}

static void
seahorse_gpgme_key_exportable_iface (SeahorseExportableIface *iface)
{
    iface->create_exporters = seahorse_gpgme_key_create_exporters;
    iface->get_exportable = seahorse_gpgme_key_get_exportable;
}

gpgme_key_t
seahorse_gpgme_key_get_public (SeahorseGpgmeKey *self)
{
    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (self), NULL);
    if (require_key_public (self, GPGME_KEYLIST_MODE_LOCAL))
        return self->pubkey;
    return NULL;
}

void
seahorse_gpgme_key_set_public (SeahorseGpgmeKey *self, gpgme_key_t key)
{
    GObject *obj;

    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self));

    if (self->pubkey)
        gpgme_key_unref (self->pubkey);
    self->pubkey = key;
    if (self->pubkey) {
        gpgme_key_ref (self->pubkey);
        self->list_mode |= self->pubkey->keylist_mode;
    }

    obj = G_OBJECT (self);
    g_object_freeze_notify (obj);
    seahorse_gpgme_key_realize (self);
    g_object_notify (obj, "fingerprint");
    g_object_notify (obj, "validity");
    g_object_notify (obj, "trust");
    g_object_notify (obj, "expires");
    g_object_notify (obj, "length");
    g_object_notify (obj, "algo");
    g_object_thaw_notify (obj);
}

gpgme_key_t
seahorse_gpgme_key_get_private (SeahorseGpgmeKey *self)
{
    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (self), NULL);
    if (require_key_private (self))
        return self->seckey;
    return NULL;
}

void
seahorse_gpgme_key_set_private (SeahorseGpgmeKey *self, gpgme_key_t key)
{
    GObject *obj;

    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self));

    if (self->seckey)
        gpgme_key_unref (self->seckey);
    self->seckey = key;
    if (self->seckey)
        gpgme_key_ref (self->seckey);

    obj = G_OBJECT (self);
    g_object_freeze_notify (obj);
    seahorse_gpgme_key_realize (self);
    g_object_thaw_notify (obj);
}

SeahorseValidity
seahorse_gpgme_key_get_validity (SeahorseGpgmeKey *self)
{
    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (self), SEAHORSE_VALIDITY_UNKNOWN);

    if (!require_key_public (self, GPGME_KEYLIST_MODE_LOCAL))
        return SEAHORSE_VALIDITY_UNKNOWN;

    g_return_val_if_fail (self->pubkey, SEAHORSE_VALIDITY_UNKNOWN);
    g_return_val_if_fail (self->pubkey->uids, SEAHORSE_VALIDITY_UNKNOWN);

    if (self->pubkey->revoked)
        return SEAHORSE_VALIDITY_REVOKED;
    if (self->pubkey->disabled)
        return SEAHORSE_VALIDITY_DISABLED;
    return seahorse_gpgme_convert_validity (self->pubkey->uids->validity);
}

SeahorseValidity
seahorse_gpgme_key_get_trust (SeahorseGpgmeKey *self)
{
    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (self), SEAHORSE_VALIDITY_UNKNOWN);
    if (!require_key_public (self, GPGME_KEYLIST_MODE_LOCAL))
        return SEAHORSE_VALIDITY_UNKNOWN;

    return seahorse_gpgme_convert_validity (self->pubkey->owner_trust);
}

void
seahorse_gpgme_key_refresh_matching (gpgme_key_t key)
{
    SeahorseGpgmeKey *gkey;

    g_return_if_fail (key->subkeys->keyid);

    gkey = seahorse_gpgme_keyring_lookup (seahorse_pgp_backend_get_default_keyring (NULL),
                                          key->subkeys->keyid);
    if (gkey != NULL)
        seahorse_gpgme_key_refresh (gkey);
}

static void
seahorse_gpgme_key_init (SeahorseGpgmeKey *self)
{
}

static void
seahorse_gpgme_key_object_constructed (GObject *object)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (object);

    G_OBJECT_CLASS (seahorse_gpgme_key_parent_class)->constructed (object);

    seahorse_gpgme_key_realize (self);
}

static void
seahorse_gpgme_key_get_property (GObject *object, guint prop_id,
                               GValue *value, GParamSpec *pspec)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (object);

    switch (prop_id) {
    case PROP_PUBKEY:
        g_value_set_boxed (value, seahorse_gpgme_key_get_public (self));
        break;
    case PROP_SECKEY:
        g_value_set_boxed (value, seahorse_gpgme_key_get_private (self));
        break;
    case PROP_VALIDITY:
        g_value_set_uint (value, seahorse_gpgme_key_get_validity (self));
        break;
    case PROP_TRUST:
        g_value_set_uint (value, seahorse_gpgme_key_get_trust (self));
        break;
    }
}

static void
seahorse_gpgme_key_set_property (GObject *object, guint prop_id, const GValue *value,
                               GParamSpec *pspec)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (object);

    switch (prop_id) {
    case PROP_PUBKEY:
        seahorse_gpgme_key_set_public (self, g_value_get_boxed (value));
        break;
    case PROP_SECKEY:
        seahorse_gpgme_key_set_private (self, g_value_get_boxed (value));
        break;
    }
}

static void
seahorse_gpgme_key_object_dispose (GObject *obj)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (obj);

    if (self->pubkey)
        gpgme_key_unref (self->pubkey);
    if (self->seckey)
        gpgme_key_unref (self->seckey);
    self->pubkey = self->seckey = NULL;

    seahorse_object_list_free (self->uids);
    self->uids = NULL;

    G_OBJECT_CLASS (seahorse_gpgme_key_parent_class)->dispose (obj);
}

static void
seahorse_gpgme_key_object_finalize (GObject *obj)
{
    SeahorseGpgmeKey *self = SEAHORSE_GPGME_KEY (obj);

    g_assert (self->pubkey == NULL);
    g_assert (self->seckey == NULL);
    g_assert (self->uids == NULL);

    G_OBJECT_CLASS (seahorse_gpgme_key_parent_class)->finalize (G_OBJECT (self));
}

static void
seahorse_gpgme_key_class_init (SeahorseGpgmeKeyClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    SeahorsePgpKeyClass *pgp_class = SEAHORSE_PGP_KEY_CLASS (klass);

    gobject_class->constructed = seahorse_gpgme_key_object_constructed;
    gobject_class->dispose = seahorse_gpgme_key_object_dispose;
    gobject_class->finalize = seahorse_gpgme_key_object_finalize;
    gobject_class->set_property = seahorse_gpgme_key_set_property;
    gobject_class->get_property = seahorse_gpgme_key_get_property;

    pgp_class->get_uids = seahorse_gpgme_key_get_uids;
    pgp_class->set_uids = seahorse_gpgme_key_set_uids;
    pgp_class->get_subkeys = seahorse_gpgme_key_get_subkeys;
    pgp_class->get_photos = seahorse_gpgme_key_get_photos;
    pgp_class->set_photos = seahorse_gpgme_key_set_photos;

    g_object_class_install_property (gobject_class, PROP_PUBKEY,
        g_param_spec_boxed ("pubkey", "GPGME Public Key", "GPGME Public Key that this object represents",
                            SEAHORSE_GPGME_BOXED_KEY,
                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (gobject_class, PROP_SECKEY,
        g_param_spec_boxed ("seckey", "GPGME Secret Key", "GPGME Secret Key that this object represents",
                            SEAHORSE_GPGME_BOXED_KEY,
                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_override_property (gobject_class, PROP_VALIDITY, "validity");
    g_object_class_override_property (gobject_class, PROP_TRUST, "trust");
}

SeahorseGpgmeKey*
seahorse_gpgme_key_new (SeahorsePlace *sksrc,
                        gpgme_key_t pubkey,
                        gpgme_key_t seckey)
{
    g_return_val_if_fail (pubkey || seckey, NULL);

    return g_object_new (SEAHORSE_GPGME_TYPE_KEY,
                         "place", sksrc,
                         "pubkey", pubkey,
                         "seckey", seckey,
                         NULL);
}
