/***************************************************************************
**
**  This file is part of QGpGuiTools.
**
**  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 file 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
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-12-09
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <zlib.h>
#include <QtNetwork>

#include "SendMail.h"
#include "Application.h"

namespace QGpGuiTools {

static const char * tmpDirName="qtbdebugreport";

SendMail::SendMail(const QString& smtp,
                         const QString& from,
                         const QString& to,
                         const QString& subject,
                         const QString& body,
                         const QStringList * files)
{
  TRACE;
  _socket=new QTcpSocket(this);
  connect(_socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
  connect(_socket, SIGNAL(connected()), this, SLOT(connected()));
  connect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(dataWritten(qint64)));
  _message="MIME-Version: 1.0\nFrom: "+from
           +"\nTo: "+to+"\nSubject: "+subject+"\n";
  if(files) {
    _message+="Content-Type: multipart/mixed; "
              "boundary=\"----------Part879845462132654653138\"\n"
              "This is a multi-part message in MIME format.\n"
              "------------Part879845462132654653138\n"
              "Content-Type: text/plain; charset=us-ascii; format=flowed\n"
              "Content-Transfer-Encoding: 7bit\n";
   _files=*files;
 }
  _smtp=smtp;
  _message+="\n"+body+"\n";
  _from=from;
  _to=to;
  _state=Init;
}

SendMail::~SendMail()
{
  TRACE;
  delete _t;
}

void SendMail::send()
{
  TRACE;
  addAttachments();
  emit status(tr("Connecting to %1").arg(_smtp));
  _socket->connectToHost(_smtp, 25);
  _t=new QTextStream(_socket);
}

void SendMail::dataWritten(qint64 nbytes)
{
  TRACE;
  _dataWritten+=nbytes;
  emit dataProgressValue(_dataWritten);
}

void SendMail::connected()
{
  TRACE;
  emit status(tr("Connected to %1").arg(_socket->peerName()));
}

void SendMail::readyRead()
{
  TRACE;
  // smtp is line-oriented
  if(!_socket->canReadLine()) return;

  QString responseLine, response;
  do {
    responseLine=_socket->readLine();
    App::log(responseLine+"\n");
    response+=responseLine;
  } while(_socket->canReadLine() && responseLine[3]!=' ' );
  responseLine.truncate(3);

  if(_state==Init && responseLine[0]=='2') {
    // banner was okay, let's go on
    *_t << "HELO there\r\n" << Qt::flush;
    _state=Mail;
  }
  else if(_state==Mail && responseLine[0]=='2') {
    // HELO response was okay (well, it has to be)
    *_t << "MAIL FROM: <" << _from << ">\r\n" << Qt::flush;
    _state=Rcpt;
  }
  else if(_state==Rcpt && responseLine[0]=='2') {
    *_t << "RCPT TO: <" << _to << ">\r\n" << Qt::flush;
    _state=Data;
  }
  else if(_state==Data && responseLine[0]=='2') {
    *_t << "DATA\r\n" << Qt::flush;
    _state=Body;
  }
  else if(_state==Body && responseLine[0]=='3') {
    *_t << _message << ".\r\n" << Qt::flush;
    emit dataProgressMaximum(_socket->bytesToWrite());
    _dataWritten=0;
    emit status(tr( "Sending data ..." ));
    _state=Quit;
  }
  else if(_state==Quit && responseLine[0]=='2') {
    *_t << "QUIT\r\n" << Qt::flush;
    // here, we just close.
    _state=Close;
    emit status(tr("Message sent"));
    emit finished(true);
  }
  else if(_state==Close) {
    deleteLater();
    return;
  }
  else {
    // something broke.
    Message::warning(MSG_ID, tr("Sending mail"),
                         tr("Unexpected reply from smtp server:\n\n%1").arg(response));
    emit finished(false);
    _state=Close;
  }
}

void SendMail::addAttachments()
{
  TRACE;
  if(_files.isEmpty()) return;
  // Create tmp file
  QDir tmp (QDir::temp());
  if( !tmp.exists(tmpDirName)) tmp.mkdir(tmpDirName);
  if(tmp.cd(tmpDirName)) cleanTmpDir(tmp);
  for(QStringList::ConstIterator it=_files.begin();it!=_files.end();++it) {
    QFileInfo fi(*it);
    FILE * inFile=fopen(fi.filePath().toLatin1().data(),"rb");
    if(!inFile) {
      Message::warning(MSG_ID, tr("Sending attachments"),
                               tr("Cannot read file %1.").arg(fi.filePath()));
      continue;
    }
    QString outFileName=tmp.absoluteFilePath(fi.fileName()+".gz");
    gzFile outFile=gzopen(outFileName.toLatin1().data(),"wb");
    if(!outFile) {
      Message::warning(MSG_ID, tr("Sending attachments"),
                               tr("Cannot open temporary file %1.").arg(outFileName));
      fclose(inFile);
      continue;
    }
    emit status(tr("Zipping %1").arg(fi.fileName()));
    uchar buf[2048];
    while(!feof(inFile)) {
      int bufLen=fread (buf, sizeof(uchar), 2048, inFile);
      gzwrite (outFile, buf, bufLen);
    }
    fclose(inFile);
    gzclose (outFile);
    emit status(tr("Encoding %1").arg(fi.fileName()));
    // Re-open zipped file
    inFile=fopen(outFileName.toLatin1().data(),"rb");
    if(!inFile) {
      Message::warning(MSG_ID, tr("Sending attachments"),
                               tr("Cannot open temporary zipped file %1.").arg(outFileName));
      continue;
    }

    _message+="------------Part879845462132654653138\n"
              "Content-Type: application/gzip; name=\"";
    _message+=fi.fileName();
    _message+=".gz\"\nContent-Transfer-Encoding: base64\n"
              "Content-Disposition: inline; filename=\"";
    _message+=fi.fileName();
    _message+=".gz\"\n\n";

    while(!feof(inFile)) {
      int bufLen=fread (buf, sizeof(uchar), 48, inFile); // Correspond to 1 line of PEM base64
      for(int i=0;i<bufLen;i+=3) {
        _message+=encode(buf+i, bufLen-i);
      }
      _message+="\n";
    }
  }
  cleanTmpDir(tmp);
  emit dataProgressValue(0);
}

void SendMail::cleanTmpDir(QDir& tmp)
{
  TRACE;
  QStringList fl=tmp.entryList(QDir::AllEntries);
  for(QStringList::Iterator it=fl.begin();it!=fl.end();++it) tmp.remove(*it);
  tmp.cdUp();
  tmp.rmdir(tmpDirName);
}

/*!
  Base64 encoding of 3 bytes pointed by \a buf, return four encoded characters.
  \a bufLen contains the number of available bytes.
*/
QString SendMail::encode(uchar * buf, int bufLen)
{
  TRACE;
  static const char * charTable="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  QString str;
  switch (bufLen) {
  case 1:
    str+=charTable[ (buf[0] & 0xfc) >> 2 ];
    str+=charTable[ (buf[0] & 0x03) << 4 ];
    str+="==";
    return str;
  case 2:
    str+=charTable[ (buf[0] & 0xfc) >> 2 ];
    str+=charTable[ (( buf[0] & 0x03) << 4) + (( buf[1] & 0xf0) >> 4) ];
    str+=charTable[ (buf[1] & 0x0f) << 2 ];
    str+="=";
    return str;
  default:
    str+=charTable[ (buf[0] & 0xfc) >> 2 ];
    str+=charTable[ (( buf[0] & 0x03) << 4) + (( buf[1] & 0xf0) >> 4) ];
    str+=charTable[ (( buf[1] & 0x0f) << 2) + (( buf[2] & 0xc0) >> 6) ];
    str+=charTable[ buf[2] & 0x3f ];
    return str;
  }
}

} // namespace QGpGuiTools
