/*
 * libcdoc
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser 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/>.
 *
 */

#ifndef __RECIPIENT_H__
#define __RECIPIENT_H__

#include "CDoc.h"

#include <string>
#include <vector>
#include <map>
#include <cstdint>

namespace libcdoc {

struct Lock;

/**
 * @brief A descriptor of encryption method and key to be used in container
 *
 * Recipient determines all the relevant properties to encrypt the FMK for a certain target.
 */
struct CDOC_EXPORT Recipient {
    /**
     * @brief The recipient type
     */
	enum Type : uint8_t {
        /**
         * Uninitialized recipient
         */
		NONE,
        /**
         * @brief Symmetric key (or password)
         */
		SYMMETRIC_KEY,
        /**
         * @brief Public key
         */
        PUBLIC_KEY,
#ifdef HAS_KEYSHARES
        /**
         * @brief n of n shared symmetric key
         */
        KEYSHARE
#endif
	};

	Recipient() = default;

    /**
     * @brief The recipient type
     */
	Type type = Type::NONE;
    /**
     * @brief The public key type
     */
	PKType pk_type = PKType::ECC;
    /**
     * @brief The number of iterations for PBKDF. Value 0 means directly provided symmetric key.
     */
	int32_t kdf_iter = 0;
    /**
     * @brief The recipient's label (if empty the lock label will be autogenerated)
     */
	std::string label;
    /**
     * @brief Recipient's public key (for all PKI types)
     */
    std::vector<uint8_t> rcpt_key;
    /**
     * @brief The recipient's certificate (if present)
     */
    std::vector<uint8_t> cert;
#ifdef HAS_KEYSHARES
    /**
     * @brief The recipient id for share server (PNOEE-XXXXXXXXXXX)
     */
    std::string id;
#endif
    /**
     * @brief The keyserver or share server list id (if present)
     */
    std::string server_id;
    /**
     * @brief The requested capsule expiry timestamp (0 - use server default)
     * 
     */
    uint64_t expiry_ts = 0;

    /**
     * @brief test whether the Recipient structure is initialized
     * @return true if not initialized
     */
	bool isEmpty() const { return type == Type::NONE; }
    /**
     * @brief check whether Recipient is based on symmetric key
     * @return true if type is SYMMETRIC_KEY
     */
    bool isSymmetric() const { return type == Type::SYMMETRIC_KEY; }
    /**
     * @brief check whether Recipient is based on public key
     * @return true if type is CERTIFICATE, PUBLIC_KEY or SERVER
     */
    bool isPKI() const { return (type == Type::PUBLIC_KEY); }
    /**
     * @brief check whether Recipient is based on certificate
     * @return true if type is CERTIFICATE
     */
    bool isCertificate() const { return (type == Type::PUBLIC_KEY) && !cert.empty(); }
    /**
     * @brief check whether Recipient is keyserver
     * @return true if type is SERVER
     */
    bool isKeyServer() const { return (type == Type::PUBLIC_KEY) && !server_id.empty(); }
#ifdef HAS_KEYSHARES
    /**
     * @brief check whether Recipient is keyshare
     * @return true if type is KEYSHARE
     */
    bool isKeyShare() const { return type == Type::KEYSHARE; }
#endif

    /**
     * @brief Clear all values and set type to NONE
     */
    void clear() { type = Type::NONE; pk_type = PKType::ECC; label.clear(); kdf_iter = 0; rcpt_key.clear(); cert.clear(); }

    /**
     * @brief A convenience method to check whether two recipients are both public key based and have the same keys.
     * @param other another Recipient
     * @return true if the public keys are identical
     */
	bool isTheSameRecipient(const Recipient &other) const;
    /**
     * @brief A convenience method to check whether a recipient is public key based and has the given keys.
     * @param public_key a public key to test
     * @return true if the public keys are identical
     */
    bool isTheSameRecipient(const std::vector<uint8_t>& public_key) const;

    /**
     * @brief Create a new symmetric key based Recipient
     * @param label the label text
     * @param kdf_iter the number of PBKDF iterations (0 if full key is provided)
     * @return a new Recipient structure
     */
	static Recipient makeSymmetric(std::string label, int32_t kdf_iter);
    /**
     * @brief Create a new public key based Recipient
     * @param label the label text
     * @param public_key the public key value
     * @param pk_type the algorithm type (either ECC or RSA)
     * @return a new Recipient structure
     */
    static Recipient makePublicKey(std::string label, std::vector<uint8_t> public_key, PKType pk_type);
    /**
     * @brief Create a new public key based Recipient
     * @param lock Lock to derive parameters from
     * @return a new Recipient structure
     */
    static Recipient makePublicKey(const Lock &lock);
    /**
     * @brief Create a new certificate based Recipient
     * @param label the label text
     * @param cert the certificate value (der-encoded)
     * @return a new Recipient structure
     */
    static Recipient makeCertificate(std::string label, std::vector<uint8_t> cert);

    /**
     * @brief Create a new capsule server based Recipient
     * If the label is empty, a machine-readable label text (public key version) is automatically generated according to CDoc2 specification.
     * 
     * @param label the label text
     * @param public_key the public key value
     * @param pk_type the algorithm type (either ECC or RSA)
     * @param server_id the keyserver id
     * @return a new Recipient structure
     */
    static Recipient makeServer(std::string label, std::vector<uint8_t> public_key, PKType pk_type, std::string server_id);

    /**
     * @brief Create a new capsule server based Recipient
     * If the label is empty, a machine-readable label text (either eID or certificate version) is automatically generated according to CDoc2 specification.
     * 
     * @param label the label text
     * @param cert the recipient's certificate (der-encoded) 
     * @param server_id the keyserver id
     * @return a new Recipient structure
     */
    static Recipient makeServer(std::string label, std::vector<uint8_t> cert, std::string server_id);

    /**
     * @brief Create a new capsule server based Recipient
     *
     * @param lock Lock to derive parameters from
     * @param server_id the keyserver id
     * @return a new Recipient structure
     */
    static Recipient makeServer(const Lock &lock, std::string server_id);

#ifdef HAS_KEYSHARES
    /**
     * @brief Create new keyshare recipient
     * 
     * @param label the label text
     * @param server_id the id of share server group
     * @param recipient_id the recipient id (PNOEE-01234567890)
     * @return Recipient a new Recipient structure
     */
    static Recipient makeShare(std::string label, std::string server_id, std::string recipient_id);
#endif

    /**
     * @brief Get the label for this recipient
     * 
     * Either returns user-specified label or generate machine-readable if empty
     * 
     * @param extra additional parameter values to use
     * @return a label value
     */
    std::string getLabel(std::map<std::string_view, std::string_view> extra) const;

    /**
     * @brief Set a property for automatic label generation
     * 
     * @param key the property name
     * @param value the property value
     */
    void setLabelValue(std::string_view key, std::string_view value) {
        lbl_parts[std::string(key)] = value;
    }

    /**
     * @brief Validate recipient record
     * 
     * @return true if Recipient is valid
     */
    bool validate() const;

    bool operator== (const Recipient& other) const = default;
protected:
	Recipient(Type _type) : type(_type) {};
private:
    std::map<std::string,std::string> lbl_parts;
};

} // namespace libcdoc

#endif // RECIPIENT_H
