/*
  key.h - wraps a gpgme key
  Copyright (C) 2003, 2005 Klarälvdalens Datakonsult AB

  This file is part of GPGME++.

  GPGME++ is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  GPGME++ 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 Library General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with GPGME++; see the file COPYING.LIB.  If not, write to the
  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

// -*- c++ -*-
#ifndef __GPGMEPP_KEY_H__
#define __GPGMEPP_KEY_H__

#include "global.h"
#include "notation.h"

#include "gpgmefw.h"

#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include <ctime>

namespace GpgME
{

class Context;

class Subkey;
class UserID;
class TofuInfo;
class RevocationKey;

typedef std::shared_ptr< std::remove_pointer<gpgme_key_t>::type > shared_gpgme_key_t;

enum class TrustSignatureTrust : char {
    None = 0,
    Partial,
    Complete,
};

//
// class Key
//

class GPGMEPP_EXPORT Key
{
    friend class ::GpgME::Context;
    struct Null {
		Null() {}
	};
public:
    Key();
    /* implicit */ Key(const Null &);
    Key(const shared_gpgme_key_t &key);
    Key(gpgme_key_t key, bool acquireRef);

    static const Null null;

    Key(const Key &other) = default;
    const Key &operator=(Key other)
    {
        swap(other);
        return *this;
    }

    const Key &mergeWith(const Key &other);

    void swap(Key &other)
    {
        using std::swap;
        swap(this->key, other.key);
    }

    bool isNull() const
    {
        return !key;
    }

    UserID userID(unsigned int index) const;
    Subkey subkey(unsigned int index) const;

    unsigned int numUserIDs() const;
    unsigned int numSubkeys() const;

    std::vector<UserID> userIDs() const;
    std::vector<Subkey> subkeys() const;

    RevocationKey revocationKey(unsigned int index) const;
    unsigned int numRevocationKeys() const;
    std::vector<RevocationKey> revocationKeys() const;

    bool isRevoked() const;
    bool isExpired() const;
    bool isDisabled() const;
    bool isInvalid() const;

    /*! Shorthand for isNull || isRevoked || isExpired ||
     *                          isDisabled || isInvalid */
    bool isBad() const;

    /** Returns true, if the key can be used for encryption (i.e. it's not bad
     *  and has an encryption subkey) or if the primary subkey can encrypt. */
    bool canEncrypt() const;
    /** Returns true, if the key can be used for signing (i.e. it's not bad
     *  and has a signing subkey) or if the primary subkey can sign. */
    bool canSign() const;
    /** Returns true, if the key can be used for certification (i.e. it's not bad
     *  and has a certification subkey) or if the primary subkey can certify. */
    bool canCertify() const;
    /** Returns true, if the key can be used for authentication (i.e. it's not bad
     *  and has a authentication subkey) or if the primary subkey can authenticate. */
    bool canAuthenticate() const;
    bool isQualified() const;
    bool isDeVs() const;
    bool isBetaCompliance() const;

    /** Returns true, if the key has a certification subkey. */
    bool hasCertify() const;
    /** Returns true, if the key has a signing subkey. */
    bool hasSign() const;
    /** Returns true, if the key has an encryption subkey. */
    bool hasEncrypt() const;
    /** Returns true, if the key has an authentication subkey. */
    bool hasAuthenticate() const;

    bool hasSecret() const;

    /*!
      @return true if this is a X.509 root certificate (currently
      equivalent to something like
      strcmp( chainID(), subkey(0).fingerprint() ) == 0 )
    */
    bool isRoot() const;

    enum OwnerTrust { Unknown = 0, Undefined = 1, Never = 2,
                      Marginal = 3, Full = 4, Ultimate = 5
                    };

    OwnerTrust ownerTrust() const;
    char ownerTrustAsString() const;

    Protocol protocol() const;
    const char *protocolAsString() const;

    const char *issuerSerial() const;
    const char *issuerName() const;
    const char *chainID() const;

    /*! Returns a pointer to the key ID of the primary (sub)key. */
    const char *keyID() const;
    const char *shortKeyID() const;
    const char *primaryFingerprint() const;

    unsigned int keyListMode() const;

    /*! Update information about this key.
     * Starts a keylisting for this key with validity
     * and tofu information gathering. Blocks for
     * how long the keylisting takes.*/
    void update();

    /**
     * @brief Add a user id to this key.
     *
     * Needs gnupg 2.1.13 and the key needs to be updated
     * afterwards to see the new uid.
     *
     * @param uid should be fully formatted and UTF-8 encoded.
     *
     * @returns a possible error.
     **/
    Error addUid(const char *uid);

    /**
     * @brief try to locate the best pgp key for a given mailbox.
     *
     * Boils down to gpg --locate-key <mbox>
     * This may take some time if remote sources are also
     * used.
     *
     * @param mbox should be a mail address does not need to be normalized.
     *
     * @returns The best key for a mailbox or a null key.
     */
    static Key locate(const char *mbox);

    /* @enum Origin
     * @brief The Origin of the key. */
    enum Origin : unsigned int {
        OriginUnknown = 0,
        OriginKS = 1,
        OriginDane = 3,
        OriginWKD = 4,
        OriginURL = 5,
        OriginFile = 6,
        OriginSelf = 7,
        OriginOther = 31,
    };
    /*! Get the origin of the key.
     *
     * @returns the Origin. */
    Origin origin() const;

    /*! Get the last update time.
     *
     * @returns the last update time. */
    time_t lastUpdate() const;
private:
    gpgme_key_t impl() const
    {
        return key.get();
    }
    shared_gpgme_key_t key;
};

//
// class Subkey
//

class GPGMEPP_EXPORT Subkey
{
public:
    Subkey();
    Subkey(const shared_gpgme_key_t &key, gpgme_sub_key_t subkey);
    Subkey(const shared_gpgme_key_t &key, unsigned int idx);

    Subkey(const Subkey &other) = default;
    const Subkey &operator=(Subkey other)
    {
        swap(other);
        return *this;
    }

    void swap(Subkey &other)
    {
        using std::swap;
        swap(this->key, other.key);
        swap(this->subkey, other.subkey);
    }

    bool isNull() const
    {
        return !key || !subkey;
    }

    Key parent() const;

    const char *keyID() const;
    const char *fingerprint() const;

    time_t creationTime() const;
    time_t expirationTime() const;
    bool neverExpires() const;

    bool isRevoked() const;
    bool isExpired() const;
    bool isInvalid() const;
    bool isDisabled() const;

    /*! Shorthand for isNull || isRevoked || isExpired ||
     *                          isDisabled || isInvalid */
    bool isBad() const;

    bool canEncrypt() const;
    bool canSign() const;
    bool canCertify() const;
    bool canAuthenticate() const;
    bool canRenc() const;
    bool canTimestamp() const;
    bool isGroupOwned() const;
    bool isQualified() const;
    bool isDeVs() const;
    bool isBetaCompliance() const;
    bool isCardKey() const;

    bool isSecret() const;

    /*! Returns true if this is the primary (sub)key. */
    bool isPrimaryKey() const;

    /** Same as gpgme_pubkey_algo_t */
    enum PubkeyAlgo {
        AlgoUnknown = 0,
        AlgoRSA     = 1,
        AlgoRSA_E   = 2,
        AlgoRSA_S   = 3,
        AlgoKyber   = 8,
        AlgoELG_E   = 16,
        AlgoDSA     = 17,
        AlgoECC     = 18,
        AlgoELG     = 20,
        AlgoECDSA   = 301,
        AlgoECDH    = 302,
        AlgoEDDSA   = 303,
        AlgoMax     = 1 << 31
    };

    PubkeyAlgo publicKeyAlgorithm() const;

    /**
      @brief Get the public key algorithm name.

      This only works for the pre 2.1 algorithms for ECC NULL is returned.

      @returns a statically allocated string with the name of the public
               key algorithm, or NULL if that name is not known.
    */
    const char *publicKeyAlgorithmAsString() const;

    /** @brief Same as publicKeyAlgorithmAsString but static. */
    static const char *publicKeyAlgorithmAsString(PubkeyAlgo algo);

    /**
       @brief Get the key algo string like GnuPG 2.1 prints it.

       This returns combinations of size and algorithm. Like
       bp512 or rsa2048. Misnamed because publicKeyAlgorithmAsString
       already used the older pubkey_algo_name.
       Actually uses gpgme_pubkey_algo_string.

       @returns the key algorithm as string. Empty string on error.
    */
    std::string algoName() const;

    unsigned int length() const;

    const char *cardSerialNumber() const;

    /*!
     * Returns the keygrip(s) of the subkey in hex digit form or NULL if not available.
     * For combined algorithms the keygrips are delimited by comma.
     *
     * \sa keyGrips
     */
    const char *keyGrip() const;

    /*!
     * Returns the list of keygrips of the subkey in hex digit form.
     *
     * \note The individual keygrips are not necessarily null-terminated.
     */
    std::vector<std::string_view> keyGrips() const;

private:
    shared_gpgme_key_t key;
    gpgme_sub_key_t subkey;
};

//
// class UserID
//

class GPGMEPP_EXPORT UserID
{
public:
    class Signature;

    UserID();
    UserID(const shared_gpgme_key_t &key, gpgme_user_id_t uid);
    UserID(const shared_gpgme_key_t &key, unsigned int idx);

    UserID(const UserID &other) = default;
    const UserID &operator=(UserID other)
    {
        swap(other);
        return *this;
    }

    void swap(UserID &other)
    {
        using std::swap;
        swap(this->key, other.key);
        swap(this->uid, other.uid);
    }

    bool isNull() const
    {
        return !key || !uid;
    }

    Key parent() const;

    unsigned int numSignatures() const;
    Signature signature(unsigned int index) const;
    std::vector<Signature> signatures() const;

    const char *id() const;
    const char *name() const;
    const char *email() const;
    const char *comment() const;
    const char *uidhash() const;

    enum Validity { Unknown = 0, Undefined = 1, Never = 2,
                    Marginal = 3, Full = 4, Ultimate = 5
                  };

    Validity validity() const;
    char validityAsString() const;

    bool isRevoked() const;
    bool isInvalid() const;

    /*! Shorthand for isNull || isRevoked || isInvalid */
    bool isBad() const;

    /** TOFU info for this userid.
     * @returns The TOFU stats or a null TofuInfo.
     */
    GpgME::TofuInfo tofuInfo() const;

    /*! Wrapper around gpgme_addrspec_from_uid.
     *
     * The input string should match the format of
     * a user id string.
     *
     * @returns a normalized mail address if found
     * or an empty string. */
    static std::string addrSpecFromString(const char *uid);

    /*! Wrapper around gpgme_addrspec_from_uid.
     *
     * @returns a normalized mail address for this userid
     * or an empty string. */
    std::string addrSpec() const;

    /*! Revoke the user id.
     *
     * Key needs update afterwards.
     *
     * @returns an error on error.*/
    Error revoke();

    /*! Get the origin of the key.
     *
     * @returns the Origin. */
    Key::Origin origin() const;

    /*! Get the last update time.
     *
     * @returns the last update time. */
    time_t lastUpdate() const;

    /*! Get a remark made by the key provided.
     * A remark is a signature notation on
     * this user id made by the key with the
     * name "rem@gnupg.org". Returns an error if the
     * parent key of this user id was not listed with the
     * keylist mode flags for signatures and signature notations.
     *
     * @param key The key for which comments should be searched.
     * @param error Set to GPG_ERR_NO_DATA if the keylist did
     *              not include signature notations.
     *
     * @returns The value of the comment or NULL if none exists.
     **/
    const char *remark(const Key &key,
                       Error &error) const;

    /*! Get multiple remarks made by potentially multiple keys. */
    std::vector <std::string> remarks(std::vector<GpgME::Key> remarkers,
                                      Error &error) const;

private:
    shared_gpgme_key_t key;
    gpgme_user_id_t uid;
};

//
// class UserID::Signature
//

class GPGMEPP_EXPORT UserID::Signature
{
public:
    Signature();
    Signature(const shared_gpgme_key_t &key, gpgme_user_id_t uid, gpgme_key_sig_t sig);
    Signature(const shared_gpgme_key_t &key, gpgme_user_id_t uid, unsigned int idx);

    Signature(const Signature &other) = default;
    const Signature &operator=(Signature other)
    {
        swap(other);
        return *this;
    }

    void swap(Signature &other)
    {
        using std::swap;
        swap(this->key, other.key);
        swap(this->uid, other.uid);
        swap(this->sig, other.sig);
    }

    /*! Defines a canonical sort order for signatures of the same user ID. */
    bool operator<(const Signature &other) const;

    bool isNull() const
    {
        return !sig || !uid || !key ;
    }

    UserID parent() const;

    const char *signerKeyID() const;

    const char *algorithmAsString() const;
    unsigned int algorithm() const;
    time_t creationTime() const;
    time_t expirationTime() const;
    bool neverExpires() const;

    bool isRevokation() const;
    bool isInvalid() const;
    bool isExpired() const;
    bool isExportable() const;

    /*! Shorthand for isNull || isExpired || isInvalid */
    bool isBad() const;

    const char *signerUserID() const;
    const char *signerName() const;
    const char *signerEmail() const;
    const char *signerComment() const;

    unsigned int certClass() const;

    enum Status { NoError = 0, SigExpired, KeyExpired,
                  BadSignature, NoPublicKey, GeneralError
                };
    Status status() const;
    std::string statusAsString() const;

    const char *policyURL() const;

    unsigned int numNotations() const;
    GpgME::Notation notation(unsigned int idx) const;
    std::vector<GpgME::Notation> notations() const;

    bool isTrustSignature() const;
    TrustSignatureTrust trustValue() const;
    unsigned int trustDepth() const;
    const char *trustScope() const;

private:
    shared_gpgme_key_t key;
    gpgme_user_id_t uid;
    gpgme_key_sig_t sig;
};

//
// class RevocationKey
//

class GPGMEPP_EXPORT RevocationKey
{
public:
    RevocationKey();
    RevocationKey(const shared_gpgme_key_t &key, gpgme_revocation_key_t revkey);
    RevocationKey(const shared_gpgme_key_t &key, unsigned int idx);

    // Rule of Zero

    void swap(RevocationKey &other)
    {
        using std::swap;
        swap(this->key, other.key);
        swap(this->revkey, other.revkey);
    }

    bool isNull() const
    {
        return !key || !revkey;
    }

    Key parent() const;

    const char *fingerprint() const;

    bool isSensitive() const;

    int algorithm() const;

private:
    shared_gpgme_key_t key;
    gpgme_revocation_key_t revkey;
};

inline void swap(RevocationKey& v1, RevocationKey& v2)
{
    v1.swap(v2);
}

GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const UserID &uid);
GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const Subkey &subkey);
GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const Key &key);
GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const RevocationKey &revkey);

} // namespace GpgME

GPGMEPP_MAKE_STD_SWAP_SPECIALIZATION(Key)
GPGMEPP_MAKE_STD_SWAP_SPECIALIZATION(Subkey)
GPGMEPP_MAKE_STD_SWAP_SPECIALIZATION(UserID)
GPGMEPP_MAKE_STD_SWAP_SPECIALIZATION(UserID::Signature)

GPGMEPP_MAKE_STRCMP(ByFingerprint, .primaryFingerprint());
GPGMEPP_MAKE_STRCMP(ByKeyID, .keyID());
GPGMEPP_MAKE_STRCMP(ByShortKeyID, .shortKeyID());
GPGMEPP_MAKE_STRCMP(ByChainID, .chainID());

#endif // __GPGMEPP_KEY_H__
