/*
 * Copyright (C) 2003-2007  Justin Karneges <justin@affinix.com>
 * Copyright (C) 2004,2005  Brad Hards <bradh@frogmouth.net>
 *
 * 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 Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 *
 */

#include "qca_securemessage.h"

#include "qca_safeobj.h"
#include "qca_safetimer.h"
#include "qcaprovider.h"

namespace QCA {

Provider::Context *getContext(const QString &type, const QString &provider);

//----------------------------------------------------------------------------
// SecureMessageKey
//----------------------------------------------------------------------------
class SecureMessageKey::Private : public QSharedData
{
public:
    SecureMessageKey::Type type;
    PGPKey                 pgp_pub, pgp_sec;
    CertificateChain       cert_pub;
    PrivateKey             cert_sec;

    Private()
    {
        type = SecureMessageKey::None;
    }

    // set the proper type, and reset the opposite data structures if needed
    void ensureType(SecureMessageKey::Type t)
    {
        // if we were non-null and changed, we may need to reset some things
        if (type != SecureMessageKey::None && t != type) {
            if (type == SecureMessageKey::X509) {
                cert_pub = CertificateChain();
                cert_sec = PrivateKey();
            } else if (type == SecureMessageKey::PGP) {
                pgp_pub = PGPKey();
                pgp_sec = PGPKey();
            }
        }
        type = t;
    }
};

SecureMessageKey::SecureMessageKey()
    : d(new Private)
{
}

SecureMessageKey::SecureMessageKey(const SecureMessageKey &from)
    : d(from.d)
{
}

SecureMessageKey::~SecureMessageKey()
{
}

SecureMessageKey &SecureMessageKey::operator=(const SecureMessageKey &from)
{
    d = from.d;
    return *this;
}

bool SecureMessageKey::isNull() const
{
    return (d->type == None);
}

SecureMessageKey::Type SecureMessageKey::type() const
{
    return d->type;
}

PGPKey SecureMessageKey::pgpPublicKey() const
{
    return d->pgp_pub;
}

PGPKey SecureMessageKey::pgpSecretKey() const
{
    return d->pgp_sec;
}

void SecureMessageKey::setPGPPublicKey(const PGPKey &pub)
{
    d->ensureType(SecureMessageKey::PGP);
    d->pgp_pub = pub;
}

void SecureMessageKey::setPGPSecretKey(const PGPKey &sec)
{
    d->ensureType(SecureMessageKey::PGP);
    Q_ASSERT(sec.isSecret());
    d->pgp_sec = sec;
}

CertificateChain SecureMessageKey::x509CertificateChain() const
{
    return d->cert_pub;
}

PrivateKey SecureMessageKey::x509PrivateKey() const
{
    return d->cert_sec;
}

void SecureMessageKey::setX509CertificateChain(const CertificateChain &c)
{
    d->ensureType(SecureMessageKey::X509);
    d->cert_pub = c;
}

void SecureMessageKey::setX509PrivateKey(const PrivateKey &k)
{
    d->ensureType(SecureMessageKey::X509);
    d->cert_sec = k;
}

void SecureMessageKey::setX509KeyBundle(const KeyBundle &kb)
{
    setX509CertificateChain(kb.certificateChain());
    setX509PrivateKey(kb.privateKey());
}

bool SecureMessageKey::havePrivate() const
{
    if (d->type == SecureMessageKey::PGP && !d->pgp_sec.isNull())
        return true;
    else if (d->type == SecureMessageKey::X509 && !d->cert_sec.isNull())
        return true;
    return false;
}

QString SecureMessageKey::name() const
{
    if (d->type == SecureMessageKey::PGP && !d->pgp_pub.isNull())
        return d->pgp_pub.primaryUserId();
    else if (d->type == SecureMessageKey::X509 && !d->cert_pub.isEmpty())
        return d->cert_pub.primary().commonName();
    else
        return QString();
}

//----------------------------------------------------------------------------
// SecureMessageSignature
//----------------------------------------------------------------------------
class SecureMessageSignature::Private : public QSharedData
{
public:
    SecureMessageSignature::IdentityResult r;
    Validity                               v;
    SecureMessageKey                       key;
    QDateTime                              ts;

    Private()
    {
        r = SecureMessageSignature::NoKey;
        v = ErrorValidityUnknown;
    }
};

SecureMessageSignature::SecureMessageSignature()
    : d(new Private)
{
}

SecureMessageSignature::SecureMessageSignature(IdentityResult          r,
                                               Validity                v,
                                               const SecureMessageKey &key,
                                               const QDateTime        &ts)
    : d(new Private)
{
    d->r   = r;
    d->v   = v;
    d->key = key;
    d->ts  = ts;
}

SecureMessageSignature::SecureMessageSignature(const SecureMessageSignature &from)
    : d(from.d)
{
}

SecureMessageSignature::~SecureMessageSignature()
{
}

SecureMessageSignature &SecureMessageSignature::operator=(const SecureMessageSignature &from)
{
    d = from.d;
    return *this;
}

SecureMessageSignature::IdentityResult SecureMessageSignature::identityResult() const
{
    return d->r;
}

Validity SecureMessageSignature::keyValidity() const
{
    return d->v;
}

SecureMessageKey SecureMessageSignature::key() const
{
    return d->key;
}

QDateTime SecureMessageSignature::timestamp() const
{
    return d->ts;
}

//----------------------------------------------------------------------------
// SecureMessage
//----------------------------------------------------------------------------
enum ResetMode
{
    ResetSession        = 0,
    ResetSessionAndData = 1,
    ResetAll            = 2
};

class SecureMessage::Private : public QObject
{
    Q_OBJECT
public:
    SecureMessage       *q;
    MessageContext      *c;
    SecureMessageSystem *system;

    bool                  bundleSigner, smime;
    SecureMessage::Format format;
    SecureMessageKeyList  to;
    SecureMessageKeyList  from;

    QByteArray                 in;
    bool                       success;
    SecureMessage::Error       errorCode;
    QByteArray                 detachedSig;
    QString                    hashName;
    SecureMessageSignatureList signers;
    QString                    dtext;

    QList<int> bytesWrittenArgs;
    SafeTimer  readyReadTrigger, bytesWrittenTrigger, finishedTrigger;

    Private(SecureMessage *_q)
        : readyReadTrigger(this)
        , bytesWrittenTrigger(this)
        , finishedTrigger(this)
    {
        q      = _q;
        c      = nullptr;
        system = nullptr;

        readyReadTrigger.setSingleShot(true);
        bytesWrittenTrigger.setSingleShot(true);
        finishedTrigger.setSingleShot(true);
        connect(&readyReadTrigger, &SafeTimer::timeout, this, &Private::t_readyRead);
        connect(&bytesWrittenTrigger, &SafeTimer::timeout, this, &Private::t_bytesWritten);
        connect(&finishedTrigger, &SafeTimer::timeout, this, &Private::t_finished);

        reset(ResetAll);
    }

    void init()
    {
        connect(c, &MessageContext::updated, this, &Private::updated);
    }

    void reset(ResetMode mode)
    {
        if (c)
            c->reset();

        bytesWrittenArgs.clear();
        readyReadTrigger.stop();
        bytesWrittenTrigger.stop();
        finishedTrigger.stop();

        if (mode >= ResetSessionAndData) {
            in.clear();
            success   = false;
            errorCode = SecureMessage::ErrorUnknown;
            detachedSig.clear();
            hashName = QString();
            signers.clear();
        }

        if (mode >= ResetAll) {
            bundleSigner = true;
            format       = SecureMessage::Binary;
            to.clear();
            from.clear();
        }
    }

public Q_SLOTS:
    void updated()
    {
        bool sig_read    = false;
        bool sig_written = false;
        bool sig_done    = false;
        int  written     = 0;
        {
            const QByteArray a = c->read();
            if (!a.isEmpty()) {
                sig_read = true;
                in.append(a);
            }

            const int x = c->written();
            if (x > 0) {
                sig_written = true;
                written     = x;
            }
        }

        if (c->finished()) {
            sig_done = true;

            success   = c->success();
            errorCode = c->errorCode();
            dtext     = c->diagnosticText();
            if (success) {
                detachedSig = c->signature();
                hashName    = c->hashName();
                signers     = c->signers();
            }
            reset(ResetSession);
        }

        if (sig_read)
            readyReadTrigger.start();
        if (sig_written) {
            bytesWrittenArgs += written;
            bytesWrittenTrigger.start();
        }
        if (sig_done)
            finishedTrigger.start();
    }

    void t_readyRead()
    {
        emit q->readyRead();
    }

    void t_bytesWritten()
    {
        emit q->bytesWritten(bytesWrittenArgs.takeFirst());
    }

    void t_finished()
    {
        emit q->finished();
    }
};

SecureMessage::SecureMessage(SecureMessageSystem *system)
{
    d         = new Private(this);
    d->system = system;
    d->c      = static_cast<SMSContext *>(d->system->context())->createMessage();
    change(d->c);
    d->init();
}

SecureMessage::~SecureMessage()
{
    delete d;
}

SecureMessage::Type SecureMessage::type() const
{
    return d->c->type();
}

bool SecureMessage::canSignMultiple() const
{
    return d->c->canSignMultiple();
}

bool SecureMessage::canClearsign() const
{
    return (type() == OpenPGP);
}

bool SecureMessage::canSignAndEncrypt() const
{
    return (type() == OpenPGP);
}

void SecureMessage::reset()
{
    d->reset(ResetAll);
}

bool SecureMessage::bundleSignerEnabled() const
{
    return d->bundleSigner;
}

bool SecureMessage::smimeAttributesEnabled() const
{
    return d->smime;
}

SecureMessage::Format SecureMessage::format() const
{
    return d->format;
}

SecureMessageKeyList SecureMessage::recipientKeys() const
{
    return d->to;
}

SecureMessageKeyList SecureMessage::signerKeys() const
{
    return d->from;
}

void SecureMessage::setBundleSignerEnabled(bool b)
{
    d->bundleSigner = b;
}

void SecureMessage::setSMIMEAttributesEnabled(bool b)
{
    d->smime = b;
}

void SecureMessage::setFormat(Format f)
{
    d->format = f;
}

void SecureMessage::setRecipient(const SecureMessageKey &key)
{
    d->to = SecureMessageKeyList() << key;
}

void SecureMessage::setRecipients(const SecureMessageKeyList &keys)
{
    d->to = keys;
}

void SecureMessage::setSigner(const SecureMessageKey &key)
{
    d->from = SecureMessageKeyList() << key;
}

void SecureMessage::setSigners(const SecureMessageKeyList &keys)
{
    d->from = keys;
}

void SecureMessage::startEncrypt()
{
    d->reset(ResetSessionAndData);
    d->c->setupEncrypt(d->to);
    d->c->start(d->format, MessageContext::Encrypt);
}

void SecureMessage::startDecrypt()
{
    d->reset(ResetSessionAndData);
    d->c->start(d->format, MessageContext::Decrypt);
}

void SecureMessage::startSign(SignMode m)
{
    d->reset(ResetSessionAndData);
    d->c->setupSign(d->from, m, d->bundleSigner, d->smime);
    d->c->start(d->format, MessageContext::Sign);
}

void SecureMessage::startVerify(const QByteArray &sig)
{
    d->reset(ResetSessionAndData);
    if (!sig.isEmpty())
        d->c->setupVerify(sig);
    d->c->start(d->format, MessageContext::Verify);
}

void SecureMessage::startSignAndEncrypt()
{
    d->reset(ResetSessionAndData);
    d->c->setupEncrypt(d->to);
    d->c->setupSign(d->from, Message, d->bundleSigner, d->smime);
    d->c->start(d->format, MessageContext::SignAndEncrypt);
}

void SecureMessage::update(const QByteArray &in)
{
    d->c->update(in);
}

QByteArray SecureMessage::read()
{
    const QByteArray a = d->in;
    d->in.clear();
    return a;
}

int SecureMessage::bytesAvailable() const
{
    return d->in.size();
}

void SecureMessage::end()
{
    d->c->end();
}

bool SecureMessage::waitForFinished(int msecs)
{
    d->c->waitForFinished(msecs);
    d->updated();
    return d->success;
}

bool SecureMessage::success() const
{
    return d->success;
}

SecureMessage::Error SecureMessage::errorCode() const
{
    return d->errorCode;
}

QByteArray SecureMessage::signature() const
{
    return d->detachedSig;
}

QString SecureMessage::hashName() const
{
    return d->hashName;
}

bool SecureMessage::wasSigned() const
{
    return !d->signers.isEmpty();
}

bool SecureMessage::verifySuccess() const
{
    // if we're not done or there were no signers, then return false
    if (!d->success || d->signers.isEmpty())
        return false;

    // make sure all signers have a valid signature
    for (int n = 0; n < d->signers.count(); ++n) {
        if (d->signers[n].identityResult() != SecureMessageSignature::Valid)
            return false;
    }
    return true;
}

SecureMessageSignature SecureMessage::signer() const
{
    if (d->signers.isEmpty())
        return SecureMessageSignature();

    return d->signers.first();
}

SecureMessageSignatureList SecureMessage::signers() const
{
    return d->signers;
}

QString SecureMessage::diagnosticText() const
{
    return d->dtext;
}

//----------------------------------------------------------------------------
// SecureMessageSystem
//----------------------------------------------------------------------------
SecureMessageSystem::SecureMessageSystem(QObject *parent, const QString &type, const QString &provider)
    : QObject(parent)
    , Algorithm(type, provider)
{
}

SecureMessageSystem::~SecureMessageSystem()
{
}

//----------------------------------------------------------------------------
// OpenPGP
//----------------------------------------------------------------------------
OpenPGP::OpenPGP(QObject *parent, const QString &provider)
    : SecureMessageSystem(parent, QStringLiteral("openpgp"), provider)
{
}

OpenPGP::~OpenPGP()
{
}

//----------------------------------------------------------------------------
// CMS
//----------------------------------------------------------------------------
class CMS::Private
{
public:
    CertificateCollection trusted, untrusted;
    SecureMessageKeyList  privateKeys;
};

CMS::CMS(QObject *parent, const QString &provider)
    : SecureMessageSystem(parent, QStringLiteral("cms"), provider)
{
    d = new Private;
}

CMS::~CMS()
{
    delete d;
}

CertificateCollection CMS::trustedCertificates() const
{
    return d->trusted;
}

CertificateCollection CMS::untrustedCertificates() const
{
    return d->untrusted;
}

SecureMessageKeyList CMS::privateKeys() const
{
    return d->privateKeys;
}

void CMS::setTrustedCertificates(const CertificateCollection &trusted)
{
    d->trusted = trusted;
    static_cast<SMSContext *>(context())->setTrustedCertificates(trusted);
}

void CMS::setUntrustedCertificates(const CertificateCollection &untrusted)
{
    d->untrusted = untrusted;
    static_cast<SMSContext *>(context())->setUntrustedCertificates(untrusted);
}

void CMS::setPrivateKeys(const SecureMessageKeyList &keys)
{
    d->privateKeys = keys;
    static_cast<SMSContext *>(context())->setPrivateKeys(keys);
}

}

#include "qca_securemessage.moc"
