#ifndef QTNG_PKEY_H
#define QTNG_PKEY_H

#include <QtCore/qsharedpointer.h>
#include "cipher.h"

QTNETWORKNG_NAMESPACE_BEGIN


class PrivateKey;
class PrivateKeyWriterPrivate;
class PrivateKeyReaderPrivate;
class PublicKeyPrivate;
class PublicKey
{
public:
    enum Algorithm {
        Opaque = 0,
        Rsa = 1,
        Dsa = 2,
        Ec = 3,
    };

    enum RsaPadding { // copy from openssl header
        PKCS1_PADDING = 1,
        NO_PADDING = 3,
        PKCS1_OAEP_PADDING = 4,
//        SSLV23_PADDING = 2,
//        X931_PADDING = 5,
//        PKCS1_PSS_PADDING = 6,
    };

public:
    PublicKey();
    ~PublicKey();
    PublicKey(const PublicKey &other);
    PublicKey(PublicKey &&other):d_ptr(0)  {qSwap(d_ptr, other.d_ptr); }
public:
    Qt::HANDLE handle() const;
    bool isNull() const;
    bool isValid() const;
    Algorithm algorithm() const;
    int bits() const;
    bool verify(const QByteArray &data, const QByteArray &hash, MessageDigest::Algorithm hashAlgo);
    QByteArray encrypt(const QByteArray &data);
    QByteArray digest(MessageDigest::Algorithm algorithm = MessageDigest::Sha256) const;
public:
    QByteArray rsaPublicEncrypt(const QByteArray &data, RsaPadding padding = PKCS1_PADDING); // RSA_PKCS1_OAEP_PADDING?
    QByteArray rsaPublicDecrypt(const QByteArray &data, RsaPadding padding = PKCS1_PADDING);
public:
    static PublicKey load(const QByteArray &data, Ssl::EncodingFormat format = Ssl::Pem);
    QByteArray save(Ssl::EncodingFormat format = Ssl::Pem) const;
public:
    PublicKey &operator=(const PublicKey &other);
    bool operator ==(const PublicKey &other) const;
    bool operator ==(const PrivateKey &) const { return false; }
    bool operator !=(const PublicKey &other) const { return !(*this == other); }
    bool operator !=(const PrivateKey &) const { return true; }
protected:
    PublicKeyPrivate * d_ptr;
    Q_DECLARE_PRIVATE(PublicKey)
    friend class PrivateKeyWriterPrivate;
    friend class PrivateKeyReaderPrivate;
};

class PrivateKey: public PublicKey
{
public:
    PrivateKey(): PublicKey() {}
    PrivateKey(const PrivateKey &other): PublicKey(other) {}
    PrivateKey(PrivateKey &&other): PublicKey(other) {}
    PrivateKey &operator=(const PublicKey &other) { PublicKey::operator =(other); return *this; }
    PrivateKey &operator=(const PrivateKey &other) { PublicKey::operator =(other); return *this; }
    bool operator ==(const PrivateKey &other) const;
    bool operator ==(const PublicKey &) const { return false; }
    bool operator !=(const PrivateKey &other) const { return !(*this == other); }
    bool operator !=(const PublicKey &) const { return true; }
public:
    PublicKey publicKey() const;
    QByteArray sign(const QByteArray &data, MessageDigest::Algorithm hashAlgo);
    QByteArray decrypt(const QByteArray &data);
public:
    QByteArray rsaPrivateEncrypt(const QByteArray &data, RsaPadding padding = PKCS1_PADDING);
    QByteArray rsaPrivateDecrypt(const QByteArray &data, RsaPadding padding = PKCS1_PADDING); // RSA_PKCS1_OAEP_PADDING?
public:
    static PrivateKey generate(Algorithm algo, int bits);
    static PrivateKey load(const QByteArray &data, Ssl::EncodingFormat format = Ssl::Pem, const QByteArray &password = QByteArray());
    QByteArray save(Ssl::EncodingFormat format = Ssl::Pem, const QByteArray &password = QByteArray()) const;
    QByteArray savePublic(Ssl::EncodingFormat format = Ssl::Pem) const { return PublicKey::save(format); }
private:
    friend class PublicKeyPrivate;
    friend class PrivateKeyReaderPrivate;
    friend class PrivateKeyWriterPrivate;
};


class PasswordCallback
{
public:
    virtual ~PasswordCallback() = default;
    virtual QByteArray get(bool writing) = 0;
};


class PrivateKeyWriter
{
public:
    explicit PrivateKeyWriter(const PrivateKey &key);
    explicit PrivateKeyWriter(const PublicKey &key);
    ~PrivateKeyWriter();
public:
    PrivateKeyWriter &setCipher(Cipher::Algorithm algo, Cipher::Mode mode);
    PrivateKeyWriter &setPassword(const QByteArray &password);
    PrivateKeyWriter &setPassword(QSharedPointer<PasswordCallback> callback);
    PrivateKeyWriter &setPublicOnly(bool publicOnly);
    QByteArray asPem();
    QByteArray asDer();
    bool save(const QString &filePath);
private:
    Q_DECLARE_PRIVATE(PrivateKeyWriter)
    PrivateKeyWriterPrivate * const d_ptr;
};


class PrivateKeyReader
{
public:
    PrivateKeyReader();
    ~PrivateKeyReader();
public:
    PrivateKeyReader &setPassword(const QByteArray &password);
    PrivateKeyReader &setPassword(QSharedPointer<PasswordCallback> callback);
    PrivateKeyReader &setFormat(Ssl::EncodingFormat format);
    PrivateKey read(const QByteArray &data);
    PublicKey readPublic(const QByteArray &data);
    PrivateKey read(const QString &filePath);
    PublicKey readPublic(const QString &filePath);
private:
    Q_DECLARE_PRIVATE(PrivateKeyReader)
    PrivateKeyReaderPrivate * const d_ptr;
};

QTNETWORKNG_NAMESPACE_END

#endif // QTNG_PKEY_H
