/*  -*- c++ -*-
    kmime_mdn.cpp

    KMime, the KDE Internet mail/usenet news message library.
    Copyright (c) 2002 Marc Mutz <mutz@kde.org>

    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.
*/
/**
  @file
  This file is part of the API for handling @ref MIME data and
  provides functions for supporting Message Disposition Notifications (MDNs),
  also known as email return receipts.

  @brief
  Provides support for Message Disposition Notifications.

  @authors Marc Mutz \<mutz@kde.org\>
*/

#include "kmime_mdn.h"
#include "kmime_version.h"
#include "kmime_util.h"

#include <klocalizedstring.h>
#include <kdebug.h>

#include <QtCore/QByteArray>
#include <QtCore/QList>

#include <unistd.h> // gethostname

namespace KMime {

namespace MDN {

static const struct {
  DispositionType dispositionType;
  const char * string;
  const char * description;
} dispositionTypes[] = {
  { Displayed, "displayed",
    I18N_NOOP( "The message sent on ${date} to ${to} with subject "
               "\"${subject}\" has been displayed. This is no guarantee that "
               "the message has been read or understood." ) },
  { Deleted, "deleted",
    I18N_NOOP( "The message sent on ${date} to ${to} with subject "
               "\"${subject}\" has been deleted unseen. This is no guarantee "
               "that the message will not be \"undeleted\" and nonetheless "
               "read later on." ) },
  { Dispatched, "dispatched",
    I18N_NOOP( "The message sent on ${date} to ${to} with subject "
               "\"${subject}\" has been dispatched. This is no guarantee "
               "that the message will not be read later on." ) },
  { Processed, "processed",
    I18N_NOOP( "The message sent on ${date} to ${to} with subject "
               "\"${subject}\" has been processed by some automatic means." ) },
  { Denied, "denied",
    I18N_NOOP( "The message sent on ${date} to ${to} with subject "
               "\"${subject}\" has been acted upon. The sender does not wish "
               "to disclose more details to you than that." ) },
  { Failed, "failed",
    I18N_NOOP( "Generation of a Message Disposition Notification for the "
               "message sent on ${date} to ${to} with subject \"${subject}\" "
               "failed. Reason is given in the Failure: header field below." ) }
};

static const int numDispositionTypes =
  sizeof dispositionTypes / sizeof *dispositionTypes;

static const char *stringFor( DispositionType d )
{
  for ( int i = 0 ; i < numDispositionTypes ; ++i ) {
    if ( dispositionTypes[i].dispositionType == d ) {
      return dispositionTypes[i].string;
    }
  }
  return 0;
}

//
// disposition-modifier
//
static const struct {
  DispositionModifier dispositionModifier;
  const char * string;
} dispositionModifiers[] = {
  { Error, "error" },
  { Warning, "warning" },
  { Superseded, "superseded" },
  { Expired, "expired" },
  { MailboxTerminated, "mailbox-terminated" }
};

static const int numDispositionModifiers =
  sizeof dispositionModifiers / sizeof * dispositionModifiers;

static const char *stringFor( DispositionModifier m ) {
  for ( int i = 0 ; i < numDispositionModifiers ; ++i ) {
    if ( dispositionModifiers[i].dispositionModifier == m ) {
      return dispositionModifiers[i].string;
    }
  }
  return 0;
}

//
// action-mode (part of disposition-mode)
//

static const struct {
  ActionMode actionMode;
  const char * string;
} actionModes[] = {
  { ManualAction, "manual-action" },
  { AutomaticAction, "automatic-action" }
};

static const int numActionModes =
  sizeof actionModes / sizeof *actionModes;

static const char *stringFor( ActionMode a ) {
  for ( int i = 0 ; i < numActionModes ; ++i ) {
    if ( actionModes[i].actionMode == a ) {
      return actionModes[i].string;
    }
  }
  return 0;
}

//
// sending-mode (part of disposition-mode)
//

static const struct {
  SendingMode sendingMode;
  const char * string;
} sendingModes[] = {
  { SentManually, "MDN-sent-manually" },
  { SentAutomatically, "MDN-sent-automatically" }
};

static const int numSendingModes =
  sizeof sendingModes / sizeof *sendingModes;

static const char *stringFor( SendingMode s ) {
  for ( int i = 0 ; i < numSendingModes ; ++i ) {
    if ( sendingModes[i].sendingMode == s ) {
      return sendingModes[i].string;
    }
  }
  return 0;
}

static QByteArray dispositionField( DispositionType d, ActionMode a, SendingMode s,
                                    const QList<DispositionModifier> & m ) {

  // mandatory parts: Disposition: foo/baz; bar
  QByteArray result = "Disposition: ";
  result += stringFor( a );
  result += '/';
  result += stringFor( s );
  result += "; ";
  result += stringFor( d );

  // optional parts: Disposition: foo/baz; bar/mod1,mod2,mod3
  bool first = true;
  for ( QList<DispositionModifier>::const_iterator mt = m.begin();
        mt != m.end() ; ++mt ) {
    if ( first ) {
      result += '/';
      first = false;
    } else {
      result += ',';
    }
    result += stringFor( *mt );
  }
  return result + '\n';
}

static QByteArray finalRecipient( const QString &recipient )
{
  if ( recipient.isEmpty() ) {
    return QByteArray();
  } else {
    return "Final-Recipient: rfc822; "
      + encodeRFC2047String( recipient, "utf-8" ) + '\n';
  }
}

static QByteArray orginalRecipient( const QByteArray & recipient )
{
  if ( recipient.isEmpty() ) {
    return QByteArray();
  } else {
    return "Original-Recipient: " + recipient + '\n';
  }
}

static QByteArray originalMessageID( const QByteArray &msgid )
{
  if ( msgid.isEmpty() ) {
    return QByteArray();
  } else {
    return "Original-Message-ID: " + msgid + '\n';
  }
}

static QByteArray reportingUAField() {
  char hostName[256];
  if ( gethostname( hostName, 255 ) ) {
    hostName[0] = '\0'; // gethostname failed: pretend empty string
  } else {
    hostName[255] = '\0'; // gethostname may have returned 255 chars (man page)
  }
  return QByteArray( "Reporting-UA: " ) + QByteArray( hostName ) +
    QByteArray( "; KMime " KMIME_VERSION_STRING "\n" );
}

QByteArray dispositionNotificationBodyContent( const QString &r,
                                               const QByteArray &o,
                                               const QByteArray &omid,
                                               DispositionType d,
                                               ActionMode a,
                                               SendingMode s,
                                               const QList<DispositionModifier> &m,
                                               const QString &special )
{
  // in Perl: chomp(special)
  QString spec;
  if ( special.endsWith( QLatin1Char( '\n' ) ) ) {
    spec = special.left( special.length() - 1 );
  } else {
    spec = special;
  }

  // std headers:
  QByteArray result = reportingUAField();
  result += orginalRecipient( o );
  result += finalRecipient( r );
  result += originalMessageID( omid );
  result += dispositionField( d, a, s, m );

  // headers that are only present for certain disposition {types,modifiers}:
  if ( d == Failed ) {
    result += "Failure: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
  } else if ( m.contains( Error ) ) {
    result += "Error: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
  } else if ( m.contains( Warning ) ) {
    result += "Warning: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
  }

  return result;
}

QString descriptionFor( DispositionType d,
                        const QList<DispositionModifier> & )
{
  for ( int i = 0 ; i < numDispositionTypes ; ++i ) {
    if ( dispositionTypes[i].dispositionType == d ) {
      return i18n( dispositionTypes[i].description );
    }
  }
  kWarning() << "KMime::MDN::descriptionFor(): No such disposition type:"
             << ( int )d;
  return QString();
}

} // namespace MDN
} // namespace KMime
