/*
  Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>

  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 "maintextjob.h"

#include "contentjobbase_p.h"
#include "globalpart.h"
#include "multipartjob.h"
#include "singlepartjob.h"
#include "textpart.h"
#include "util.h"

#include <QTextCodec>

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

#include <kmime/kmime_content.h>

#include <kpimtextedit/textedit.h>

using namespace Message;

class Message::MainTextJobPrivate : public ContentJobBasePrivate
{
  public:
    MainTextJobPrivate( MainTextJob *qq )
      : ContentJobBasePrivate( qq )
      , textPart( 0 )
    {
    }

    bool chooseSourcePlainText();
    bool chooseCharsetAndEncode();
    bool chooseCharset();
    bool encodeTexts();
    SinglepartJob *createPlainTextJob();
    SinglepartJob *createHtmlJob();
    SinglepartJob *createImageJob( const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image );

    TextPart *textPart;
    QByteArray chosenCharset;
    QString sourcePlainText;
    QByteArray encodedPlainText;
    QByteArray encodedHtml;

    Q_DECLARE_PUBLIC( MainTextJob )
};

bool MainTextJobPrivate::chooseSourcePlainText()
{
  Q_Q( MainTextJob );
  Q_ASSERT( textPart );
  if( textPart->isWordWrappingEnabled() ) {
    sourcePlainText = textPart->wrappedPlainText();
    if( sourcePlainText.isEmpty() &&
        !textPart->cleanPlainText().isEmpty() ) {
      q->setError( JobBase::BugError );
      q->setErrorText( i18n( "Asked to use word wrapping, but not given wrapped plain text." ) );
      return false;
    }
  } else {
    sourcePlainText = textPart->cleanPlainText();
    if( sourcePlainText.isEmpty() &&
        !textPart->wrappedPlainText().isEmpty() ) {
      q->setError( JobBase::BugError );
      q->setErrorText( i18n( "Asked not to use word wrapping, but not given clean plain text." ) );
      return false;
    }
  }
  return true;
}

bool MainTextJobPrivate::chooseCharsetAndEncode()
{
  Q_Q( MainTextJob );

  const QList<QByteArray> charsets = q->globalPart()->charsets( true );
  if( charsets.isEmpty() ) {
    q->setError( JobBase::BugError );
    q->setErrorText( i18n( "No charsets were available for encoding. Please check your configuration and make sure it contains at least one charset for sending." ) );
    return false;
  }

  Q_ASSERT( textPart );
  QString toTry = sourcePlainText;
  if( textPart->isHtmlUsed() ) {
    toTry = textPart->cleanHtml();
  }
  chosenCharset = Util::selectCharset( charsets, toTry );
  if( !chosenCharset.isEmpty() ) {
    // Good, found a charset that encodes the data without loss.
    return encodeTexts();
  } else {
    // No good charset was found.
    if( q->globalPart()->isGuiEnabled() && textPart->warnBadCharset() ) {
      // Warn the user and give them a chance to go back.
      int result = KMessageBox::warningYesNo(
          q->globalPart()->parentWidgetForGui(),
          i18n( "Encoding the message with %1 will lose some characters.\n"
                "Do you want to continue?", QString::fromLatin1( charsets.first() ) ),
          i18n( "Some Characters Will Be Lost" ),
          KGuiItem( i18n("Lose Characters") ),
          KGuiItem( i18n("Change Encoding") )
        );
      if( result == KMessageBox::No ) {
        q->setError( JobBase::UserCancelledError );
        q->setErrorText( i18n( "User decided to change the encoding." ) );
        return false;
      } else {
        chosenCharset = charsets.first();
        return encodeTexts();
      }
    } else if( textPart->warnBadCharset() ) {
      // Should warn user but no Gui available.
      kDebug() << "warnBadCharset but Gui is disabled.";
      q->setError( JobBase::UserError );
      q->setErrorText( i18n( "The selected encoding (%1) cannot fully encode the message.",
                             QString::fromLatin1( charsets.first() ) ) );
      return false;
    } else {
      // OK to go ahead with a bad charset.
      chosenCharset = charsets.first();
      return encodeTexts();

      // FIXME: This is based on the assumption that QTextCodec will replace
      // unknown characters with '?' or some other meaningful thing.  The code in
      // QTextCodec indeed uses '?', but this behaviour is not documented.
    }
  }

  // Should not reach here.
  Q_ASSERT( false );
  return false;
}

bool MainTextJobPrivate::encodeTexts()
{
  Q_Q( MainTextJob );
  QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( chosenCharset ) );
  if( !codec ) {
    kError() << "Could not get text codec for charset" << chosenCharset;
    q->setError( JobBase::BugError );
    q->setErrorText( i18n( "Could not get text codec for charset \"%1\".", QString::fromLatin1( chosenCharset ) ) );
    return false;
  }
  encodedPlainText = codec->fromUnicode( sourcePlainText );
  if( !textPart->cleanHtml().isEmpty() )
    encodedHtml = codec->fromUnicode( textPart->cleanHtml() );
  kDebug() << "Done.";
  return true;
}

SinglepartJob *MainTextJobPrivate::createPlainTextJob()
{
  SinglepartJob *cjob = new SinglepartJob; // No parent.
  cjob->contentType()->setMimeType( "text/plain" );
  cjob->contentType()->setCharset( chosenCharset );
  cjob->setData( encodedPlainText );
  // TODO standard recommends Content-ID.
  return cjob;
}

SinglepartJob *MainTextJobPrivate::createHtmlJob()
{
  SinglepartJob *cjob = new SinglepartJob; // No parent.
  cjob->contentType()->setMimeType( "text/html" );
  cjob->contentType()->setCharset( chosenCharset );
  QByteArray data = KPIMTextEdit::TextEdit::imageNamesToContentIds( encodedHtml,
      textPart->embeddedImages() );
  cjob->setData( data );
  // TODO standard recommends Content-ID.
  return cjob;
}

SinglepartJob *MainTextJobPrivate::createImageJob( const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image )
{
  Q_Q( MainTextJob );

  // The image is a PNG encoded with base64.
  SinglepartJob *cjob = new SinglepartJob; // No parent.
  cjob->contentType()->setMimeType( "image/png" );
  const QByteArray charset = Util::selectCharset( q->globalPart()->charsets( true ), image->imageName );
  Q_ASSERT( !charset.isEmpty() );
  cjob->contentType()->setName( image->imageName, charset );
  cjob->contentTransferEncoding()->setEncoding( KMime::Headers::CEbase64 );
  cjob->contentTransferEncoding()->setDecoded( false ); // It is already encoded.
  cjob->contentID()->setIdentifier( image->contentID.toLatin1() );
  kDebug() << "cid" << cjob->contentID()->identifier();
  cjob->setData( image->image );
  return cjob;
}

MainTextJob::MainTextJob( TextPart *textPart, QObject *parent )
  : ContentJobBase( *new MainTextJobPrivate( this ), parent )
{
  Q_D( MainTextJob );
  d->textPart = textPart;
}

MainTextJob::~MainTextJob()
{
}

TextPart *MainTextJob::textPart() const
{
  Q_D( const MainTextJob );
  return d->textPart;
}

void MainTextJob::setTextPart( TextPart *part )
{
  Q_D( MainTextJob );
  d->textPart = part;
}

void MainTextJob::doStart()
{
  Q_D( MainTextJob );
  Q_ASSERT( d->textPart );

  // Word wrapping.
  if( !d->chooseSourcePlainText() ) {
    // chooseSourcePlainText has set an error.
    Q_ASSERT( error() );
    emitResult();
    return;
  }

  // Charset.
  if( !d->chooseCharsetAndEncode() ) {
    // chooseCharsetAndEncode has set an error.
    Q_ASSERT( error() );
    emitResult();
    return;
  }

  // Assemble the Content.
  SinglepartJob *plainJob = d->createPlainTextJob();
  if( d->encodedHtml.isEmpty() ) {
    kDebug() << "Making text/plain";
    // Content is text/plain.
    appendSubjob( plainJob );
  } else {
    MultipartJob *alternativeJob = new MultipartJob;
    alternativeJob->setMultipartSubtype( "alternative" );
    alternativeJob->appendSubjob( plainJob ); // text/plain first.
    alternativeJob->appendSubjob( d->createHtmlJob() ); // text/html second.
    if( !d->textPart->hasEmbeddedImages() ) {
      kDebug() << "Have no images.  Making multipart/alternative.";
      // Content is multipart/alternative.
      appendSubjob( alternativeJob );
    } else {
      kDebug() << "Have related images.  Making multipart/related.";
      // Content is multipart/related with a multipart/alternative sub-Content.
      MultipartJob *multipartJob = new MultipartJob;
      multipartJob->setMultipartSubtype( "related" );
      multipartJob->appendSubjob( alternativeJob );
      foreach( const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image, d->textPart->embeddedImages() ) {
        multipartJob->appendSubjob( d->createImageJob( image ) );
      }
      appendSubjob( multipartJob );
    }
  }
  ContentJobBase::doStart();
}

void MainTextJob::process()
{
  Q_D( MainTextJob );
  // The content has been created by our subjob.
  Q_ASSERT( d->subjobContents.count() == 1 );
  d->resultContent = d->subjobContents.first();
  emitResult();
}

#include "maintextjob.moc"
