#ifndef INCLUDED_BOBCAT_DIFFIEHELLMAN_
#define INCLUDED_BOBCAT_DIFFIEHELLMAN_

#include <iosfwd>

#include <openssl/evp.h>
#include <bobcat/bigint>
#include <bobcat/exception>

namespace FBB
{

class DiffieHellman
{
    enum
    {
        MIN_PRIME_SIZE = 1024
    };

    BigInt d_prime;
    BigInt d_generator;
    BigInt d_privKey;
    BigInt d_pubKey;
    BigInt d_peerPubKey;           // public key of the other party
    BigInt d_common;                // common secret (after calling key)

    static char const *s_header;

    public:
                        // no copy/move constructors/operations
        DiffieHellman(DiffieHellman const &other) = delete;

            // The initiator calls this constructor to compute the common
            // DH parameters
        DiffieHellman(size_t primeLength = 1024, size_t generator = 5,  // 1
                      bool progress = false);

            // uses predefined prime 'prime'
        DiffieHellman(BigInt const &prime, size_t generator = 5);       // 2

            // this constructor reloads the public and private data
        DiffieHellman(std::string const &publicFileName,                // 3
                      std::string const &privateFileName);

            // Alternatively, use this constructor expecting istreams:
        DiffieHellman(std::istream &publicStream,                       // 4
                      std::istream &privateStream);

            // After calling save() (see below) the public and private keys
            // are available on files. Each party semds its public info to the
            // other party.  Next initiator and peer call 'key' to compute
            // their shared (symmetric encryption) key, using their own
            // private data and the other's public data.
        BigInt const &key(std::string const &peerPublicFileName); // 2
        BigInt const &key(std::istream &peerPublicStream);        // 3

        static BigInt prime(size_t primeLength, bool safe = true,
                            bool progress = false);

            // The initiator saves the public info (i.e., prime (p), generator
            // (g),and the initiator's public key (g^k % p)) on basename.pub
            // and the initiator's secret key (k) on basename.sec
        void save(std::string const &basename) const;

            // accessors (may return 0 if not yet determined)
            // use BigInt's bigEndian or littleEndian functions to obtain
            // the BigInt's 'sizeInBytes' bytes, returned as a series of
            // char values.
        BigInt const &common() const;
        BigInt const &generator() const;
        BigInt const &peerPublicKey() const;
        BigInt const &prime() const;
        BigInt const &privateKey() const;
        BigInt const &publicKey() const;

    private:
        static int callback(int indicator, int, BN_GENCB *);    // params 2, 3
                                                                // not used.
        void checkKeys(EVP_PKEY *keys);
        static EVP_PKEY_CTX *cptDomainContext(OSSL_PARAM *param);
        EVP_PKEY *cptDomainKey(EVP_PKEY_CTX *domainCtx, OSSL_PARAM *param);
        void cptKeys();

        static Exception &&exception();                                 // .ih
        BigInt getKey(EVP_PKEY *keys, char const *type) const;  // type:
        BigInt const &key();                                            // 1

        static EVP_PKEY *keyPair(EVP_PKEY *domainkey);

        OSSL_PARAM *osslParamBuild();
};

inline BigInt const &DiffieHellman::common() const
{
    return d_common;
}
inline BigInt const &DiffieHellman::generator() const
{
    return d_generator;
}
inline BigInt const &DiffieHellman::peerPublicKey() const
{
    return d_peerPubKey;
}
inline BigInt const &DiffieHellman::prime() const
{
    return d_prime;
}
inline BigInt const &DiffieHellman::privateKey() const
{
    return d_privKey;
}
inline BigInt const &DiffieHellman::publicKey() const
{
    return d_pubKey;
}

} // FBB

#endif
