/*
    ktnefwriter.cpp

    Copyright (C) 2002 Bo Thorsen  <bo@sonofthor.dk>

    This file is part of KTNEF, the KDE TNEF support library/program.

    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 TNEF data and
 * defines the KTNEFWriter class.
 *
 * @author Bo Thorsen
 */

#include "ktnefwriter.h"
#include "ktnefproperty.h"
#include "ktnefpropertyset.h"
#include "ktnefdefs.h"

#include <kdebug.h>

#include <QtCore/QFile>
#include <QtCore/QDateTime>
#include <QtCore/QDataStream>
#include <QtCore/QList>
#include <QtCore/QByteArray>

#include <assert.h>

using namespace KTnef;

/**
 * Private class that helps to provide binary compatibility between releases.
 * @internal
 */
//@cond PRIVATE
class KTnef::KTNEFWriter::PrivateData
{
  public:
    PrivateData() { mFirstAttachNum = QDateTime::currentDateTime().toTime_t(); }
    KTNEFPropertySet properties;
    quint16 mFirstAttachNum;
};
//@endcond

KTNEFWriter::KTNEFWriter() : d( new KTnef::KTNEFWriter::PrivateData )
{
  // This is not something the user should fiddle with
  // First set the TNEF version
  QVariant v(0x00010000);
  addProperty( attTNEFVERSION, atpDWORD, v );

  // Now set the code page to something reasonable. TODO: Use the right one
  QVariant v1( (quint32)0x4e4 );
  QVariant v2( (quint32)0x0 );
  QList<QVariant> list;
  list << v1;
  list << v2;
  v = QVariant( list );
  addProperty( attOEMCODEPAGE, atpBYTE, list );
}

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

void KTNEFWriter::addProperty( int tag, int type, const QVariant &value )
{
  d->properties.addProperty( tag, type, value );
}

//@cond IGNORE
void addToChecksum( quint32 i, quint16 &checksum )
{
  checksum += i & 0xff;
  checksum += ( i >> 8 ) & 0xff;
  checksum += ( i >> 16 ) & 0xff;
  checksum += ( i >> 24 ) & 0xff;
}

void addToChecksum( QByteArray &cs, quint16 &checksum )
{
  int len = cs.length();
  for ( int i=0; i<len; i++ ) {
    checksum += (quint8)cs[i];
  }
}

void writeCString( QDataStream &stream, QByteArray &str )
{
  stream.writeRawData( str.data(), str.length() );
  stream << (quint8)0;
}

quint32 mergeTagAndType( quint32 tag, quint32 type )
{
  return ( ( type & 0xffff ) << 16 ) | ( tag & 0xffff );
}
//@endcond

/* This writes a TNEF property to the file.
 *
 * A TNEF property has a 1 byte type (LVL_MESSAGE or LVL_ATTACHMENT),
 * a 4 byte type/tag, a 4 byte length, the data and finally the checksum.
 *
 * The checksum is a 16 byte int with all bytes in the data added.
 */
bool KTNEFWriter::writeProperty( QDataStream &stream, int &bytes, int tag ) const
{
  QMap<int,KTNEFProperty*>& properties = d->properties.properties();
  QMap<int,KTNEFProperty*>::Iterator it = properties.find( tag );

  if ( it == properties.end() ) {
    return false;
  }

  KTNEFProperty *property = *it;

  quint32 i;
  quint16 checksum = 0;
  QList<QVariant> list;
  QString s;
  QByteArray cs, cs2;
  QDateTime dt;
  QDate date;
  QTime time;
  switch( tag ) {
  case attMSGSTATUS:
    // quint8
    i = property->value().toUInt() & 0xff;
    checksum = i;

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)1;
    stream << (quint8)i;

    bytes += 10;
    break;

  case attMSGPRIORITY:
  case attREQUESTRES:
    // quint16
    i = property->value().toUInt() & 0xffff;
    addToChecksum( i, checksum );

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)2;
    stream << (quint16)i;

    bytes += 11;
    break;

  case attTNEFVERSION:
    // quint32
    i = property->value().toUInt();
    addToChecksum( i, checksum );

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)4;
    stream << (quint32)i;

    bytes += 13;
    break;

  case attOEMCODEPAGE:
    // 2 quint32
    list = property->value().toList();
    assert( list.count() == 2 );

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)8;

    i = list[0].toInt();
    addToChecksum( i, checksum );
    stream << (quint32)i;
    i = list[1].toInt();
    addToChecksum( i, checksum );
    stream << (quint32)i;

    bytes += 17;
    break;

  case attMSGCLASS:
  case attSUBJECT:
  case attBODY:
  case attMSGID:
    // QCString
    cs = property->value().toString().toLocal8Bit();
    addToChecksum( cs, checksum );

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)cs.length()+1;
    writeCString( stream, cs );

    bytes += 9 + cs.length()+1;
    break;

  case attFROM:
    // 2 QString encoded to a TRP structure
    list = property->value().toList();
    assert( list.count() == 2 );

    cs = list[0].toString().toLocal8Bit();                           // Name
    cs2 = QString( QLatin1String( "smtp:" ) + list[1].toString() ).toLocal8Bit(); // Email address
    i = 18 + cs.length() + cs2.length(); // 2 * sizof(TRP) + strings + 2x'\0'

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)i;

    // The stream has to be aligned to 4 bytes for the strings
    // TODO: Or does it? Looks like Outlook doesn't do this
    // bytes += 17;
    // Write the first TRP structure
    stream << (quint16)4;                   // trpidOneOff
    stream << (quint16)i;                   // totalsize
    stream << (quint16)( cs.length() + 1 ); // sizeof name
    stream << (quint16)( cs2.length() + 1 );// sizeof address

    // if ( bytes % 4 != 0 )
      // Align the buffer

    // Write the strings
    writeCString( stream, cs );
    writeCString( stream, cs2 );

    // Write the empty padding TRP structure (just zeroes)
    stream << (quint32)0 << (quint32)0;

    addToChecksum( 4, checksum );
    addToChecksum( i, checksum );
    addToChecksum( cs.length()+1, checksum );
    addToChecksum( cs2.length()+1, checksum );
    addToChecksum( cs, checksum );
    addToChecksum( cs2, checksum );

    bytes += 10;
    break;

  case attDATESENT:
  case attDATERECD:
  case attDATEMODIFIED:
    // QDateTime
    dt = property->value().toDateTime();
    time = dt.time();
    date = dt.date();

    stream << (quint8)LVL_MESSAGE;
    stream << mergeTagAndType( tag, property->type() );
    stream << (quint32)14;

    i = (quint16)date.year();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)date.month();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)date.day();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)time.hour();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)time.minute();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)time.second();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    i = (quint16)date.dayOfWeek();
    addToChecksum( i, checksum );
    stream << (quint16)i;
    break;
/*
  case attMSGSTATUS:
    {
      quint8 c;
      quint32 flag = 0;
      if ( c & fmsRead ) flag |= MSGFLAG_READ;
      if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED;
      if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT;
      if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH;
      if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT;
      d->stream_ >> c;

      i = property->value().toUInt();
      stream << (quint8)LVL_MESSAGE;
      stream << (quint32)type;
      stream << (quint32)2;
      stream << (quint8)i;
      addToChecksum( i, checksum );
      // from reader: d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
    }
    kDebug() << "Message Status" << "(length=" << i2 << ")";
    break;
*/

  default:
    kDebug() << "Unknown TNEF tag:" << tag;
    return false;
  }

  stream << (quint16)checksum;
  return true;
}

bool KTNEFWriter::writeFile( QIODevice &file ) const
{
  if ( !file.open( QIODevice::WriteOnly ) ) {
    return false;
  }

  QDataStream stream( &file );
  return writeFile( stream );
}

bool KTNEFWriter::writeFile( QDataStream &stream ) const
{
  stream.setByteOrder( QDataStream::LittleEndian );

  // Start by writing the opening TNEF stuff
  stream << TNEF_SIGNATURE;

  // Store the PR_ATTACH_NUM value for the first attachment
  // ( must be stored even if *no* attachments are stored )
  stream << d->mFirstAttachNum;

  // Now do some writing
  bool ok = true;
  int bytesWritten = 0;
  ok &= writeProperty( stream, bytesWritten, attTNEFVERSION );
  ok &= writeProperty( stream, bytesWritten, attOEMCODEPAGE );
  ok &= writeProperty( stream, bytesWritten, attMSGCLASS );
  ok &= writeProperty( stream, bytesWritten, attMSGPRIORITY );
  ok &= writeProperty( stream, bytesWritten, attSUBJECT );
  ok &= writeProperty( stream, bytesWritten, attDATESENT );
  ok &= writeProperty( stream, bytesWritten, attDATESTART );
  ok &= writeProperty( stream, bytesWritten, attDATEEND );
  // ok &= writeProperty( stream, bytesWritten, attAIDOWNER );
  ok &= writeProperty( stream, bytesWritten, attREQUESTRES );
  ok &= writeProperty( stream, bytesWritten, attFROM );
  ok &= writeProperty( stream, bytesWritten, attDATERECD );
  ok &= writeProperty( stream, bytesWritten, attMSGSTATUS );
  ok &= writeProperty( stream, bytesWritten, attBODY );
  return ok;
}

void KTNEFWriter::setSender( const QString &name, const QString &email )
{
  assert( !name.isEmpty() );
  assert( !email.isEmpty() );

  QVariant v1( name );
  QVariant v2( email );

  QList<QVariant> list;
  list << v1;
  list << v2;

  QVariant v( list );
  addProperty( attFROM, 0, list ); // What's up with the 0 here ??
}

void KTNEFWriter::setMessageType( MessageType m )
{
  // Note that the MessageType list here is probably not long enough,
  // more entries are most likely needed later

  QVariant v;
  switch( m ) {
  case Appointment:
    v = QVariant( QString( "IPM.Appointment" ) );
    break;

  case MeetingCancelled:
    v = QVariant( QString( "IPM.Schedule.Meeting.Cancelled" ) );
    break;

  case MeetingRequest:
    v = QVariant( QString( "IPM.Schedule.Meeting.Request" ) );
    break;

  case MeetingNo:
    v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Neg" ) );
    break;

  case MeetingYes:
    v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Pos" ) );
    break;

  case MeetingTent:
    // Tent?
    v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Tent" ) );
    break;

  default:
    return;
  }

  addProperty( attMSGCLASS, atpWORD, v );
}

void KTNEFWriter::setMethod( Method )
{

}

void KTNEFWriter::clearAttendees()
{

}

void KTNEFWriter::addAttendee( const QString &cn, Role r,
                               PartStat p, bool rsvp,
                               const QString &mailto )
{
  Q_UNUSED( cn );
  Q_UNUSED( r );
  Q_UNUSED( p );
  Q_UNUSED( rsvp );
  Q_UNUSED( mailto );
}

// I assume this is the same as the sender?
// U also assume that this is like "Name <address>"
void KTNEFWriter::setOrganizer( const QString &organizer )
{
  int i = organizer.indexOf( '<' );

  if ( i == -1 ) {
    return;
  }

  QString name = organizer.left( i ).trimmed();

  QString email = organizer.right( i+1 );
  email = email.left( email.length()-1 ).trimmed();

  setSender( name, email );
}

void KTNEFWriter::setDtStart( const QDateTime &dtStart )
{
  QVariant v( dtStart );
  addProperty( attDATESTART, atpDATE, v );
}

void KTNEFWriter::setDtEnd( const QDateTime &dtEnd )
{
  QVariant v( dtEnd );
  addProperty( attDATEEND, atpDATE, v );
}

void KTNEFWriter::setLocation( const QString &/*location*/ )
{

}

void KTNEFWriter::setUID( const QString &uid )
{
  QVariant v( uid );
  addProperty( attMSGID, atpSTRING, v );
}

// Date sent
void KTNEFWriter::setDtStamp( const QDateTime &dtStamp )
{
  QVariant v( dtStamp );
  addProperty( attDATESENT, atpDATE, v );
}

void KTNEFWriter::setCategories( const QStringList &)
{

}

// I hope this is the body
void KTNEFWriter::setDescription( const QString &body )
{
  QVariant v( body );
  addProperty( attBODY, atpTEXT, v );
}

void KTNEFWriter::setSummary( const QString &s )
{
  QVariant v( s );
  addProperty( attSUBJECT, atpSTRING, v );
}

// TNEF encoding: Normal =  3, high = 2, low = 1
// MAPI encoding: Normal = -1, high = 0, low = 1
void KTNEFWriter::setPriority( Priority p )
{
  QVariant v( (quint32)p );
  addProperty( attMSGPRIORITY, atpSHORT, v );
}

void KTNEFWriter::setAlarm( const QString &description,
                            AlarmAction action,
                            const QDateTime &wakeBefore )
{
  Q_UNUSED( description );
  Q_UNUSED( action );
  Q_UNUSED( wakeBefore );
}
