/*
  Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
  Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>

  Parts based on KMail code by:

  This library 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.

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

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

#include "util.h"

#include "composer/composer.h"
#include "singlepartjob.h"

#include <QTextCodec>
#include <QTextEdit>

#include <KCharsets>
#include <KDebug>
#include <KLocalizedString>
#include <KMessageBox>

#include <kmime/kmime_charfreq.h>
#include <kmime/kmime_content.h>
#include <kmime/kmime_util.h>
#include <mailtransport/messagequeuejob.h>
#include <akonadi/item.h>
#include <akonadi/kmime/messagestatus.h>
#include <akonadi/agentinstance.h>
#include <akonadi/agentinstancecreatejob.h>
#include <akonadi/agentmanager.h>
#include <messagecore/helpers/messagehelpers.h>

KMime::Content* setBodyAndCTE( QByteArray &encodedBody, KMime::Headers::ContentType* contentType, KMime::Content* ret ){
    MessageComposer::Composer composer;
    MessageComposer::SinglepartJob cteJob( &composer );

    cteJob.contentType()->setMimeType( contentType->mimeType() );
    cteJob.contentType()->setCharset( contentType->charset() );
    cteJob.setData( encodedBody );
    cteJob.exec();
    cteJob.content()->assemble();

    ret->contentTransferEncoding()->setEncoding( cteJob.contentTransferEncoding()->encoding() );
    ret->setBody( cteJob.content()->encodedBody() );

    return ret;
}

KMime::Content* MessageComposer::Util::composeHeadersAndBody( KMime::Content* orig, QByteArray encodedBody, Kleo::CryptoMessageFormat format, bool sign, QByteArray hashAlgo )
{
    KMime::Content* result = new KMime::Content;

    // called should have tested that the signing/encryption failed
    Q_ASSERT( !encodedBody.isEmpty() );

    if( !( format & Kleo::InlineOpenPGPFormat ) ) { // make a MIME message
        kDebug() << "making MIME message, format:" << format;
        makeToplevelContentType( result, format, sign, hashAlgo );

        if( makeMultiMime( format, sign ) ) {   // sign/enc PGPMime, sign SMIME

            const QByteArray boundary = KMime::multiPartBoundary();
            result->contentType()->setBoundary( boundary );

            result->assemble();
            //kDebug() << "processed header:" << result->head();

            // Build the encapsulated MIME parts.
            // Build a MIME part holding the code information
            // taking the body contents returned in ciphertext.
            KMime::Content* code = new KMime::Content;
            setNestedContentType( code, format, sign );
            setNestedContentDisposition( code, format, sign );

            if ( sign ) {                         // sign PGPMime, sign SMIME
                if( format & Kleo::AnySMIME ) {     // sign SMIME
                    code->contentTransferEncoding()->setEncoding( KMime::Headers::CEbase64 );
                    code->contentTransferEncoding()->needToEncode();
                    code->setBody( encodedBody );
                } else {                            // sign PGPMmime
                    setBodyAndCTE( encodedBody, orig->contentType(), code );
                }
                result->addContent( orig );
                result->addContent( code );
            } else {                              // enc PGPMime
                setBodyAndCTE( encodedBody, orig->contentType(), code );

                // Build a MIME part holding the version information
                // taking the body contents returned in
                // structuring.data.bodyTextVersion.
                KMime::Content* vers = new KMime::Content;
                vers->contentType()->setMimeType( "application/pgp-encrypted" );
                vers->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
                vers->contentTransferEncoding()->setEncoding( KMime::Headers::CE7Bit );
                vers->setBody( "Version: 1" );

                result->addContent( vers );
                result->addContent( code );
            }
        } else {                                //enc SMIME, sign/enc SMIMEOpaque
            result->contentTransferEncoding()->setEncoding( KMime::Headers::CEbase64 );
            result->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
            result->contentDisposition()->setFilename( QString::fromLatin1( "smime.p7m" ) );

            result->assemble();
            //kDebug() << "processed header:" << result->head();

            result->setBody( encodedBody );
        }
    } else {                                  // sign/enc PGPInline
        result->setHead( orig->head() );
        result->parse();

        // fixing ContentTransferEncoding
        setBodyAndCTE( encodedBody, orig->contentType(), result );
    }
    return result;
}

// set the correct top-level ContentType on the message
void MessageComposer::Util::makeToplevelContentType( KMime::Content* content, Kleo::CryptoMessageFormat format, bool sign, QByteArray hashAlgo )
{
    switch ( format ) {
    default:
    case Kleo::InlineOpenPGPFormat:
    case Kleo::OpenPGPMIMEFormat:
        if( sign ) {
            content->contentType()->setMimeType( QByteArray( "multipart/signed" ) );
            content->contentType()->setParameter( QString::fromLatin1( "protocol" ), QString::fromAscii( "application/pgp-signature" ) );
            content->contentType()->setParameter( QString::fromLatin1( "micalg" ), QString::fromAscii( QByteArray(QByteArray("pgp-") + hashAlgo) ).toLower() );

        } else {
            content->contentType()->setMimeType( QByteArray( "multipart/encrypted" ) );
            content->contentType()->setParameter( QString::fromLatin1( "protocol" ), QString::fromAscii( "application/pgp-encrypted" ) );
        }
        return;
    case Kleo::SMIMEFormat:
        if ( sign ) {
            kDebug() << "setting headers for SMIME";
            content->contentType()->setMimeType( QByteArray( "multipart/signed" ) );
            content->contentType()->setParameter( QString::fromLatin1( "protocol" ), QString::fromAscii( "application/pkcs7-signature" ) );
            content->contentType()->setParameter( QString::fromLatin1( "micalg" ), QString::fromAscii( hashAlgo ).toLower() );
            return;
        }
        // fall through (for encryption, there's no difference between
        // SMIME and SMIMEOpaque, since there is no mp/encrypted for
        // S/MIME)
    case Kleo::SMIMEOpaqueFormat:

        kDebug() << "setting headers for SMIME/opaque";
        content->contentType()->setMimeType( QByteArray( "application/pkcs7-mime" ) );

        if( sign ) {
            content->contentType()->setParameter( QString::fromLatin1( "smime-type" ), QString::fromAscii( "signed-data" ) );
        } else {
            content->contentType()->setParameter( QString::fromLatin1( "smime-type" ), QString::fromAscii( "enveloped-data" ) );
        }
        content->contentType()->setParameter( QString::fromLatin1( "name" ), QString::fromAscii( "smime.p7m" ) );
    }
}


void MessageComposer::Util::setNestedContentType( KMime::Content* content, Kleo::CryptoMessageFormat format, bool sign )
{
    switch( format ){
    case Kleo::OpenPGPMIMEFormat:
        if( sign ) {
            content->contentType()->setMimeType( QByteArray( "application/pgp-signature" ) );
            content->contentType()->setParameter( QString::fromLatin1( "name" ), QString::fromAscii( "signature.asc" ) );
            content->contentDescription()->from7BitString( "This is a digitally signed message part." );
        } else {
            content->contentType()->setMimeType( QByteArray( "application/octet-stream" ) );
        }
        return;
    case Kleo::SMIMEFormat:
        if ( sign ) {
            content->contentType()->setMimeType( QByteArray( "application/pkcs7-signature" ) );
            content->contentType()->setParameter( QString::fromLatin1( "name" ), QString::fromAscii( "smime.p7s" ) );
            return;
        }
        // fall through:
    default:
    case Kleo::InlineOpenPGPFormat:
    case Kleo::SMIMEOpaqueFormat:
        ;
    }
}


void MessageComposer::Util::setNestedContentDisposition( KMime::Content* content, Kleo::CryptoMessageFormat format, bool sign )
{
    if ( !sign && format & Kleo::OpenPGPMIMEFormat ) {
        content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
        content->contentDisposition()->setFilename( QString::fromLatin1( "msg.asc" ) );
    } else if ( sign && format & Kleo::SMIMEFormat ) {
        content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
        content->contentDisposition()->setFilename( QString::fromLatin1( "smime.p7s" ) );
    }
}


bool MessageComposer::Util::makeMultiMime( Kleo::CryptoMessageFormat format, bool sign )
{
    switch ( format ) {
    default:
    case Kleo::InlineOpenPGPFormat:
    case Kleo::SMIMEOpaqueFormat:   return false;
    case Kleo::OpenPGPMIMEFormat:   return true;
    case Kleo::SMIMEFormat:         return sign; // only on sign - there's no mp/encrypted for S/MIME
    }
}

bool MessageComposer::Util::makeMultiPartSigned( Kleo::CryptoMessageFormat f )
{
    return makeMultiMime( f, true );
}

QByteArray MessageComposer::Util::selectCharset( const QList<QByteArray> &charsets, const QString &text )
{
    foreach( const QByteArray &name, charsets ) {
        // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because
        // the former knows us-ascii is latin1.
        QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( name ) );
        if( !codec ) {
            kWarning() << "Could not get text codec for charset" << name;
            continue;
        }
        if( codec->canEncode( text ) ) {
            // Special check for us-ascii (needed because us-ascii is not exactly latin1).
            if( name == "us-ascii" && !KMime::isUsAscii( text ) ) {
                continue;
            }
            kDebug() << "Chosen charset" << name;
            return name;
        }
    }
    kDebug() << "No appropriate charset found.";
    return QByteArray();
}

QStringList MessageComposer::Util::AttachmentKeywords()
{
    return i18nc(
                "comma-separated list of keywords that are used to detect whether "
                "the user forgot to attach his attachment. Do not add space between words.",
                "attachment,attached" ).split( QLatin1Char( ',' ) );
}

QString MessageComposer::Util::cleanedUpHeaderString( const QString &s )
{
    // remove invalid characters from the header strings
    QString res( s );
    res.remove( QChar::fromLatin1( '\r' ) );
    res.replace( QChar::fromLatin1( '\n' ), QString::fromLatin1( " " ) );
    return res.trimmed();
}

void MessageComposer::Util::addSendReplyForwardAction(const KMime::Message::Ptr &message, MailTransport::MessageQueueJob *qjob)
{
    QList<Akonadi::Item::Id> originalMessageId;
    QList<Akonadi::MessageStatus> linkStatus;
    if ( MessageCore::Util::getLinkInformation( message, originalMessageId, linkStatus ) ) {
        Q_FOREACH( Akonadi::Item::Id id, originalMessageId )
        {
            if ( linkStatus.first() == Akonadi::MessageStatus::statusReplied() ) {
                qjob->sentActionAttribute().addAction( MailTransport::SentActionAttribute::Action::MarkAsReplied, QVariant( id ) );
            } else if ( linkStatus.first() == Akonadi::MessageStatus::statusForwarded() ) {
                qjob->sentActionAttribute().addAction( MailTransport::SentActionAttribute::Action::MarkAsForwarded, QVariant( id ) );
            }
        }
    }
}

bool MessageComposer::Util::sendMailDispatcherIsOnline( QWidget *parent )
{
    Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance( QLatin1String( "akonadi_maildispatcher_agent" ) );
    if( !instance.isValid() ) {
        const int rc = KMessageBox::warningYesNo(parent,i18n("The mail dispatcher is not set up, so mails cannot be sent. Do you want to create a mail dispatcher?"),
                                                 i18n("No mail dispatcher."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("no_maildispatcher"));
        if (rc == KMessageBox::Yes) {
            const Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QLatin1String("akonadi_maildispatcher_agent"));
            Q_ASSERT(type.isValid());
            Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob(type); // async. We'll have to try again later.
            job->start();
        }
        return false;
    }
    if ( instance.isOnline() )
        return true;
    else {
        const int rc = KMessageBox::warningYesNo( parent,i18n("The mail dispatcher is offline, so mails cannot be sent. Do you want to make it online?"),
                                                  i18n("Mail dispatcher offline."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("maildispatcher_put_online"));
        if (rc == KMessageBox::Yes) {
            instance.setIsOnline( true );
            return true;
        }
    }
    return false;
}

QString MessageComposer::Util::rot13(const QString &s)
{
    QString r(s);

    const int rLenght(r.length());
    for (int i=0; i<rLenght; ++i) {
        const QChar rChar(r.at(i));
        if ( ( rChar >= QLatin1Char('A') && rChar <= QLatin1Char('M') ) ||
             ( rChar >= QLatin1Char('a') && rChar <= QLatin1Char('m') ) ) {
            r[i] = (char)((int)QChar(r[i]).toLatin1() + 13);
        } else {
            if  ( ( rChar >= QLatin1Char('N') && rChar <= QLatin1Char('Z') ) ||
                  ( rChar >= QLatin1Char('n') && rChar <= QLatin1Char('z') ) ) {
                r[i] = (char)((int)QChar(r[i]).toLatin1() - 13);
            }
        }
    }

    return r;
}

void MessageComposer::Util::addTextBox(QTextEdit *edit)
{
    QTextCursor cursor = edit->textCursor();
    if ( cursor.hasSelection() )
    {
        QString s = cursor.selectedText();
        s.prepend( QLatin1String(",----[  ]\n| " ));
        s.replace( QChar::ParagraphSeparator,QLatin1String("\n| ") );
        s.append( QLatin1String("\n`----") );
        edit->insertPlainText( s );
    } else {
        //int oldPos = cursor.position();
        cursor.movePosition( QTextCursor::StartOfBlock );
        cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
        const QString s = cursor.selectedText();
        const QString str = QString::fromLatin1(",----[  ]\n| %1\n`----").arg(s);
        cursor.insertText( str );
        //cursor.setPosition( qMax( 0, oldPos - 2 ) );
        edit->setTextCursor( cursor );
    }
}

void MessageComposer::Util::removeNotNecessaryHeaders(const KMime::Message::Ptr &msg)
{
    msg->removeHeader( "X-KMail-SignatureActionEnabled" );
    msg->removeHeader( "X-KMail-EncryptActionEnabled" );
    msg->removeHeader( "X-KMail-CryptoMessageFormat" );
}
