/*
 *  Copyright (C) 2007-2008 CMM Sigma Andrzej Chmielowiec 
<cmmsigma@cmmsigma.eu>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program 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 General Public License for more details.
 */

// QtSmtp.cpp: implementation of the QtSmtp class.
//
//////////////////////////////////////////////////////////////////////

#include "QtSmtp.h"
#include <QHostInfo>
#include <QByteArray>
#include <QStringList>
#include <QDateTime>
#include <QDir>

QtSmtp::QtSmtp(QObject *parent) : QObject(parent)
{
	mRcptIndex = 0;
	mCCIndex = 0;

	mTelnet = new QtTelnet(this);
	QObject::connect(mTelnet, SIGNAL(message(const QStringList &)), this, SLOT(telnetMessage(const QStringList &)));
	QObject::connect(mTelnet, SIGNAL(connectionError(const QString &)), this, SLOT(telnetConnectionError(const QString &)));
	QObject::connect(mTelnet, SIGNAL(connected()), this, SLOT(telnetConnected()));
	mState = DISCONNECTED;
}


QtSmtp::~QtSmtp()
{
	delete mTelnet;
}

//////////////////////////////////////////////////////////////////////
// telnet slots
//////////////////////////////////////////////////////////////////////

void QtSmtp::telnetConnected()
{
	//tr::trace("telnetConnected()");
	mState = CONNECTED;
}


void QtSmtp::telnetMessage(const QStringList &lines)
{
	processSending(lines);
}


void QtSmtp::telnetConnectionError(const QString &error)
{
	//tr::trace("telnetConnectionError()");
	mState = DISCONNECTED;
	emit errorOccured(error);
}

//////////////////////////////////////////////////////////////////////
// smtp setup
//////////////////////////////////////////////////////////////////////

void QtSmtp::setServer(const QString host)
{
	mHost = host;
}


void QtSmtp::setUserPassword(const QString user, const QString password)
{
	mUser = QString(QByteArray(user.toAscii()).toBase64());
	mPassword = QString(QByteArray(password.toAscii()).toBase64());
}


void QtSmtp::setSender(const QString sender, const QString name)
{
	mSender.user = sender;
	mSender.name = name.isEmpty() ? sender : name;
}


void QtSmtp::addRecipient(const QString recipient, const QString name)
{
	mRcpt.append(UserItem(recipient, name));
}


void QtSmtp::addCC(const QString recipient, const QString name)
{
	mCC.append(UserItem(recipient, name));
}


void QtSmtp::setMessage(const QString message)
{
	mMessage = message;
}


void QtSmtp::setSubject(const QString subject)
{
	mSubject = subject;
}


void QtSmtp::setMsgId(const QString msgId)
{
	QDateTime dt = QDateTime::currentDateTime();
	mMsgID.sprintf("%s@%d", (const char *)msgId.toAscii(), dt.toTime_t());
}


void QtSmtp::addAttachment(const QByteArray &content, const QString filename)
{
	mAttachments.append(AttachmentItem(content, filename));
}


void QtSmtp::addFileAttachment(const QString filename)
{
	QFile file(filename);
	if(false == file.open(QIODevice::ReadOnly))
	{
		//tr::trace("Cannot open file %s", qPrintable(filename));
		emit errorOccured(tr("Error opening %1 file.").arg(filename));
	}
	else
	{
		AttachmentItem item;
		item.body = file.readAll();
		file.close();

		int slash = filename.lastIndexOf(QDir::separator());
		
		if(slash == -1)
			item.file = filename;
		else
			item.file = filename.right(filename.size() - slash - 1);

		mAttachments.append(item);
	}
}

//////////////////////////////////////////////////////////////////////
// public methods
//////////////////////////////////////////////////////////////////////

void QtSmtp::beginNewMail()
{
	//tr::trace("beginNewMail()");

	mUser.clear();
	mPassword.clear();
	mHost.clear();
	mSender.clear();
	mMessage.clear();
	mSubject.clear();
	mMsgID.clear();

	mRcpt.clear();
	mCC.clear();
	mAttachments.clear();
}


void QtSmtp::sendMail()
{
	if(mHost.isEmpty())
	{
		emit errorOccured(tr("Please specify SMTP server name."));
	}
	else if(mSender.isEmpty())
	{
		emit errorOccured(tr("Please specify Sender e-mail address."));
	}
	else if(mRcpt.isEmpty())
	{
		emit errorOccured(tr("Please specify Recipient(s) e-mail address."));
	}
	else
	{
		mRcptIndex = 0;
		mCCIndex = 0;
		QDateTime dt = QDateTime::currentDateTime();
		if(mMsgID.isEmpty())
			mMsgID.sprintf("%s@%d", (const char *)QByteArray("sToken").toBase64(), dt.toTime_t());

		mState = DISCONNECTED;
		mTelnet->connectToHost(mHost, 25);
	}
}

//////////////////////////////////////////////////////////////////////
// protected methods
//////////////////////////////////////////////////////////////////////

void QtSmtp::processSending(const QStringList &lines)
{
	switch(mState)
	{
		case DISCONNECTED:
			break;
		case CONNECTED:
			if(checkResponse(lines, 220))
			{
				emit progressStep();

				//tr::trace("case CONNECTED -> EHLO");

				mState = EHLO;
				mTelnet->sendData("EHLO " + mHost);
			}
			break;
		case EHLO:
			if(checkResponse(lines, 250))
			{
				emit progressStep();

				checkOptions(lines);

				if(mServerOptions.authRequired && mServerOptions.authLogin)
				{
					//tr::trace("case EHLO -> AUTHLOGIN");

					if(mUser.isEmpty() || mPassword.isEmpty())
					{
						//tr::trace("no user or/and password!");

						mState = DISCONNECTED;
						mTelnet->sendData("QUIT");
						mTelnet->close();
						emit errorOccured(tr("SMTP Server requires authentication. Please provide user and password."));
					}
					else
					{
						mState = AUTHLOGIN;
						mTelnet->sendData("AUTH LOGIN");
					}
				}
				else
				{
					//tr::trace("case EHLO -> MAILFROM");
					mState = MAILFROM;
					mTelnet->sendData("MAIL FROM:<" + mSender.user + ">");
				}
			}
			break;
		case AUTHLOGIN:
			if(checkResponse(lines, 334))
			{
				//tr::trace("case AUTHLOGIN -> USER");
				mState = USER;
				mTelnet->sendData(mUser);
			}
			break;
		case USER:
			if(checkResponse(lines, 334))
			{
				//tr::trace("case USER -> PASSWORD");
				mState = PASSWORD;
				mTelnet->sendData(mPassword);
			}
			break;
		case PASSWORD:
			if(checkResponse(lines, 235))
			{
				emit progressStep();

				//tr::trace("case PASSWORD -> MAILFROM");
				mState = MAILFROM;
				mTelnet->sendData("MAIL FROM:<" + mSender.user + ">");
			}
			break;
		case MAILFROM:
			if(checkResponse(lines, 250))
			{
				emit progressStep();

				if(mRcptIndex >= mRcpt.size() - 1)
				{
					if(mCC.size() == 0) // no CC
					{
						//tr::trace("case MAILFROM -> CC #: %d", mRcptIndex);
						mState = CC;
					}
					else
					{
						//tr::trace("case MAILFROM -> RCPT #: %d", mRcptIndex);
						mState = RCPT;
					}
				}

				if(mRcptIndex < mRcpt.size())
				{
					UserItem rcpt = mRcpt.at(mRcptIndex ++);
					mTelnet->sendData("RCPT TO:<" + rcpt.user + ">");
				}
			}
			break;
		case RCPT:
			if(checkResponse(lines, 250))
			{
				emit progressStep();

				if(mCCIndex >= mCC.size() - 1)
					mState = CC;

				if(mCCIndex < mCC.size())
				{
					//tr::trace("case RCPT -> CC #: %d", mCCIndex);

					UserItem rcpt = mCC.at(mCCIndex ++);
					mTelnet->sendData("RCPT TO:<" + rcpt.user + ">");
				}
			}
			break;
		case CC:
			if(checkResponse(lines, 250))
			{
				emit progressStep();

				//tr::trace("case RCPT -> DATA");
				mState = DATA;
				mTelnet->sendData("DATA");
			}
			break;
		case DATA:
			if(checkResponse(lines, 354))
			{
				emit progressStep();

				//tr::trace("case DATA -> END");
				mState = END;
				mTelnet->sendData("Message-ID: <" + mMsgID + ">");
				mTelnet->sendData("From: " + mSender.name + " <" + mSender.user + ">");

				sendToRecipients();
				sendToCC();

				mTelnet->sendData("Subject: " + mSubject);

				if(mAttachments.size() == 0) // send as plain message
				{
					mTelnet->sendData(mMessage);
				}
				else
				{
					sendMultipart();
				}

				mTelnet->sendData(".");
			}
			break;
		case END:
			if(checkResponse(lines, 250))
			{
				emit progressStep();

				//tr::trace("case END -> DISCONNECTED");
				mState = DISCONNECTED;
				mTelnet->sendData("QUIT");
				mTelnet->close();
				emit messageSend();
			}
			break;
	}
}


bool QtSmtp::checkResponse(const QStringList &lines, int okValue)
{
	bool result = false;
	if(lines.size() > 0)
	{
		QString line = lines.at(0);
		bool ok;
		int error = line.left(3).toInt(&ok);
		if(ok && error == okValue)
		{
			result = true;
		}
	}

	if(false == result)
	{
		mState = DISCONNECTED;
		mTelnet->sendData("QUIT");
		mTelnet->close();

		QString err;
		foreach(QString line, lines)
		{
			err += line;
		}
		emit errorOccured(err);
	}

	return result;
}


void QtSmtp::checkOptions(const QStringList &lines)
{
	mServerOptions.clear();

	foreach(QString line, lines)
	{
		int pos = line.indexOf("AUTH=", 0, Qt::CaseInsensitive);
		if(pos != -1)
			mServerOptions.authRequired = true;
		pos = line.indexOf("AUTH=LOGIN", 0, Qt::CaseInsensitive);
		if(pos != -1)
			mServerOptions.authLogin = true;
	}
}


void QtSmtp::sendToRecipients()
{
	//tr::trace("sendToRecipients() %d users", mRcpt.size());

	QString line = "To: ";
	int size = mRcpt.size();

	for(int i=0; i<size; i++)
	{
		if(i > 0)
			line += "\t";

		UserItem rcpt = mRcpt.at(i);

		if(false == rcpt.name.isEmpty())
		{
			line += "\"" + rcpt.name + "\" ";
		}

		line += "<" + rcpt.user + ">";

		if(i < size - 1)
		{
			line += ",";
		}

		mTelnet->sendData(line);

		line.clear();
	}
}


void QtSmtp::sendToCC()
{
	//tr::trace("sendToCC() %d users", mCC.size());

	QString line = "Cc: ";
	int size = mCC.size();

	for(int i=0; i<size; i++)
	{
		if(i > 0)
			line += "\t";

		UserItem rcpt = mCC.at(i);

		if(false == rcpt.name.isEmpty())
		{
			line += "\"" + rcpt.name + "\" ";
		}

		line += "<" + rcpt.user + ">";

		if(i < size - 1)
		{
			line += ",";
		}

		mTelnet->sendData(line);

		line.clear();
	}
}


void QtSmtp::sendMultipart()
{
	const QString boundary = "----aiNiud78hIJasd78sadb9JHI9sdh";

	mTelnet->sendData("MIME-version: 1.0");
	mTelnet->sendData("Content-type: multipart/mixed; boundary=\"" + boundary + "\"");
	mTelnet->sendData("");
	mTelnet->sendData("This is a multi-part message in MIME format.");
	mTelnet->sendData("");
	mTelnet->sendData("--" + boundary);
	mTelnet->sendData("Content-type: text/plain");
	mTelnet->sendData("");
	mTelnet->sendData(mMessage);
	mTelnet->sendData("--" + boundary);

	int size = mAttachments.size();
	for(int i=0; i<size; i++)
	{
		AttachmentItem item = mAttachments.at(i);
		QByteArray base64 = item.body.toBase64();

		mTelnet->sendData("Content-Type: application/octet-stream; name=\"" + item.file + "\"");
		mTelnet->sendData("Content-transfer-encoding: base64");
		mTelnet->sendData("Content-Disposition: attachment; filename=\"" + item.file + "\"");
		mTelnet->sendData("");

		const int partSize = 128;
		int parts = base64.size() / partSize;
		int rest = base64.size() % partSize;
		for(int j=0; j<parts; j++)
		{
			mTelnet->sendData(base64.mid(j * partSize, partSize));	
		}
		if(rest > 0)
		{
			mTelnet->sendData(base64.mid(parts * partSize, rest));
		}
		
		mTelnet->sendData("");
		mTelnet->sendData("--" + boundary + ((i >= size - 1) ? "--" : ""));
	}
	
	mTelnet->sendData("");
}

void QtSmtp::getStoredIDs()
{

}
