/******************************************************************************
 SMTPMessage.cc

	<Description>

	BASE CLASS = <NONE>

	Copyright © 1997 by Glenn W. Bach.  All rights reserved.
	
	Base code generated by Codemill v0.1.0

 *****************************************************************************/

#include <SMTPMessage.h>
#include <GNetDebugDir.h>
#include "gMailUtils.h"
#include "JXTimerTask.h"
#include "GPrefsMgr.h"
#include "GAddressBookMgr.h"

#include <JString.h>
#include <JRegex.h>
#include <JIndexRange.h>
#include <JMessageProtocol.h>	

#include <jTypes.h>
#include <jStreamUtil.h>
#include <jStrStreamUtil.h>
#include <jDirUtil.h>
#include <jTime.h>
#include <jErrno.h>

#include <GMGlobals.h>

#include <ace/Connector.h>
#include <ace/INET_Addr.h>
#include <ace/UNIX_Addr.h>

#include <ace/SOCK_Connector.h>
#include <ace/SOCK_Stream.h>

#include <ace/LSOCK_Connector.h>
#include <ace/LSOCK_Stream.h>

#include <sys/timeb.h>
#include <sys/utsname.h>
#include <fstream.h>
#include <strstream.h>
#include <iostream.h>
#include <unistd.h>
#include <jAssert.h>

#undef new
#undef delete

typedef JMessageProtocol<ACE_SOCK_STREAM>	InetLink;
typedef ACE_Connector<InetLink, ACE_SOCK_CONNECTOR>	INETConnector;

enum
{
	kStartUp = 1,
	kHello,
	kFrom,
	kTo,
	kCc,
	kBcc,
	kDataHeader,
	kData
};

const JCharacter* SMTPMessage::kMessageSent = "SMTPMessage::kMessageSent";
const JCharacter* SMTPMessage::kSendFailure = "SMTPMessage::kSendFailure";

static const JInteger 	kOKValue = 250;
static const JSize		kWaitVal = 25;

static const JRegex smtpPeriodStuff("^(\\.)+");

extern const JCharacter* kArrowVersionNumberStr;

/******************************************************************************
 Constructor

 *****************************************************************************/

SMTPMessage::SMTPMessage()
	:
	JBroadcaster()
{
	itsTo = new JString();
	assert(itsTo != NULL);

	itsFrom = new JString();
	assert(itsFrom != NULL);

	itsSubject = new JString();
	assert(itsSubject != NULL);

	itsReplyTo = new JString();
	assert(itsReplyTo != NULL);

	itsCC = new JString();
	assert(itsCC != NULL);

	itsBCC = new JString();
	assert(itsBCC != NULL);

	itsData = new JString();
	assert(itsData != NULL);

	itsAddedHeaders = new JString();
	assert(itsAddedHeaders != NULL);

	itsToNames = new JPtrArray<JString>;
	assert(itsToNames != NULL);
	itsToNames->SetCompareFunction(JCompareStringsCaseInsensitive);
	itsCcNames = new JPtrArray<JString>;
	assert(itsCcNames != NULL);
	itsCcNames->SetCompareFunction(JCompareStringsCaseInsensitive);
	itsBccNames = new JPtrArray<JString>;
	assert(itsBccNames != NULL);
	itsBccNames->SetCompareFunction(JCompareStringsCaseInsensitive);

	itsLink = NULL;
	itsOutboxStream = NULL;

	itsCurrentMode = kStartUp;
	itsCurrentIndex = 1;

	itsIsFinished 		= kFalse;
	itsSomethingRead	= kFalse;
	itsIsTryingToQuit	= kFalse;
}

/******************************************************************************
 Destructor

 *****************************************************************************/

SMTPMessage::~SMTPMessage()
{
	delete itsTo;
	delete itsFrom;
	delete itsSubject;
	delete itsReplyTo;
	delete itsCC;
	delete itsBCC;
	delete itsData;
	delete itsAddedHeaders;
	delete itsOutboxStream;
	delete itsLink;
	itsToNames->DeleteAll();
	delete itsToNames;
	itsCcNames->DeleteAll();
	delete itsCcNames;
	itsBccNames->DeleteAll();
	delete itsBccNames;
}

/******************************************************************************
 SetTo

 *****************************************************************************/

void
SMTPMessage::SetTo
	(
	const JString& to
	)
{
	*itsTo = to;
}

/******************************************************************************
 SetFrom

 *****************************************************************************/

void
SMTPMessage::SetFrom
	(
	const JString& from
	)
{
	*itsFrom = from;
}

/******************************************************************************
 SetSubject

 *****************************************************************************/

void
SMTPMessage::SetSubject
	(
	const JString& subject
	)
{
	*itsSubject = subject;
}

/******************************************************************************
 SetReplyTo

 *****************************************************************************/

void
SMTPMessage::SetReplyTo
	(
	const JString& replyto
	)
{
	*itsReplyTo = replyto;
}

/******************************************************************************
 SetCC

 *****************************************************************************/

void
SMTPMessage::SetCC
	(
	const JString& cc
	)
{
	*itsCC = cc;
}

/******************************************************************************
 SetBCC

 *****************************************************************************/

void
SMTPMessage::SetBCC
	(
	const JString& bcc
	)
{
	*itsBCC = bcc;
}

/******************************************************************************
 SetData

 *****************************************************************************/

void
SMTPMessage::SetData
	(
	const JString& data
	)
{
	*itsData = data;
}

/******************************************************************************
 AddHeaders

 *****************************************************************************/

void
SMTPMessage::AddHeaders
	(
	const JString& headers
	)
{
	*itsAddedHeaders = headers;
}

/******************************************************************************
 HandleAddresses

 *****************************************************************************/

void
SMTPMessage::HandleAddresses()
{
	JPtrArray<JString> names;
	GParseNameList(*itsTo, names);
	ReplaceAliases(names);
	while (!names.IsEmpty())
		{
		JString* str = names.GetFirstElement();
		if (itsToNames->InsertSorted(str,kFalse))
			{
			names.RemoveElement(1);
			}
		else
			{
			names.DeleteElement(1);
			}
		}
	
	GParseNameList(*itsCC, names);
	ReplaceAliases(names);
	while (!names.IsEmpty())
		{
		JString* str = names.GetFirstElement();
		JIndex findex;
		if (itsToNames->SearchSorted(str, JOrderedSetT::kFirstMatch, &findex))
			{
			names.DeleteElement(1);
			}
		else
			{
			if (itsCcNames->InsertSorted(str,kFalse))
				{
				names.RemoveElement(1);
				}
			else
				{
				names.DeleteElement(1);
				}
			}
		}

	GParseNameList(*itsBCC, names);
	ReplaceAliases(names);
	while (!names.IsEmpty())
		{
		JString* str = names.GetFirstElement();
		JIndex findex;
		if (itsToNames->SearchSorted(str, JOrderedSetT::kFirstMatch, &findex))
			{
			names.DeleteElement(1);
			}
		else
			{
			if (itsCcNames->SearchSorted(str, JOrderedSetT::kFirstMatch, &findex))
				{
				names.DeleteElement(1);
				}
			else
				{
				if (itsBccNames->InsertSorted(str,kFalse))
					{
					names.RemoveElement(1);
					}
				else
					{
					names.DeleteElement(1);
					}
				}
			}
		}
	
}

/******************************************************************************
 ReplaceAliases

 *****************************************************************************/

void
SMTPMessage::ReplaceAliases
	(
	JPtrArray<JString>& names
	)
{
	JSize i = 1;
	while (i <= names.GetElementCount())
		{
		JString name = *(names.NthElement(i));
		JString alias;
		JString fcc;
		if (GGetAddressBookMgr()->NameIsAlias(name, alias, fcc))
			{
			names.DeleteElement(i);
			GParseNameList(alias, names);
//			GParseNameList(fcc, names);
			}
		else
			{
			i++;
			}
		}
}


/******************************************************************************
 Send

 *****************************************************************************/

void
SMTPMessage::Send()
{
	HandleAddresses();
	itsOutboxStream = new ofstream(GGetPrefsMgr()->GetCurrentOutbox(), ios::out|ios::app);
	assert(itsOutboxStream != NULL);

	*itsOutboxStream << "From ";
	itsFrom->Print(*itsOutboxStream);
	*itsOutboxStream << " ";
	
	struct timeb tp;
	ftime(&tp);
	JString date(ctime(&(tp.time)));
	date.Print(*itsOutboxStream);

	jistrstream(is, date.GetCString(), date.GetLength());
	JString dow;
	JString mon;
	JString day;
	JString rest;
	dow = JReadUntilws(is);
	mon = JReadUntilws(is);
	day = JReadUntilws(is);	
	JReadAll(is, &rest);

	*itsOutboxStream << "Date: ";
	dow.Print(*itsOutboxStream);
	*itsOutboxStream << ", ";
	day.Print(*itsOutboxStream);
	*itsOutboxStream << " ";
	mon.Print(*itsOutboxStream);
	*itsOutboxStream << " ";
	rest.Print(*itsOutboxStream);

	JString addrStr = GGetPrefsMgr()->GetSMTPHost() + ":25";
	ACE_INET_Addr addr(addrStr);
	
	itsLink = new InetLink;
	assert(itsLink != NULL);
	
	INETConnector* connector = new INETConnector;
	assert( connector != NULL );

	ACE_Time_Value maxWait(kWaitVal);

	if (connector->connect(itsLink, addr) == -1)
		{
		JString notice 	= "No response from sendmail : Error number ";
		int errNumber 	= jerrno();
		notice += JString(errNumber);
		JGetUserNotification()->ReportError(notice);
		Broadcast(SendFailure());
		JXTimerTask* task = new JXTimerTask(1000,kTrue);
		JXGetApplication()->InstallIdleTask(task);
		ListenTo(task);
		itsLink = NULL;
		return;
		}

	ListenTo(itsLink);
}

/******************************************************************************
 Receive

 *****************************************************************************/

void
SMTPMessage::Receive
	(
	JBroadcaster* 					sender,
	const JBroadcaster::Message& 	message
	)
{
	if ((sender == itsLink) && !itsIsFinished)
		{
		if (message.Is(JMessageProtocolT::kMessageReady))
			{
			itsSomethingRead = kTrue;
			ReadReturnValue();
			}
		else if (message.Is(JMessageProtocolT::kReceivedDisconnect))
			{
			JGetUserNotification()->ReportError("The connection to sendmail closed early.");
			Broadcast(SendFailure());
			JXTimerTask* task = new JXTimerTask(1000,kTrue);
			JXGetApplication()->InstallIdleTask(task);
			ListenTo(task);
			itsLink = NULL;
			}
		}
	else if (message.Is(JXTimerTask::kTimerWentOff))
		{
		delete this;
		}
}

/******************************************************************************
 ReadReturnValue

 *****************************************************************************/

void
SMTPMessage::ReadReturnValue()
{
	JString line;
	JBoolean ok = itsLink->GetNextMessage(&line);
	assert(ok);

	if (itsCurrentMode != kDataHeader)
		{
		GMGetSMTPDebugDir()->AddText(line);
		}

	if ((itsCurrentMode != kStartUp) && (itsCurrentMode != kDataHeader))
		{
		JIndex findex;
		ok = line.LocateSubstring(" ", &findex);
		assert(ok);
		assert(findex > 1);
		JString number = line.GetSubstring(1, findex - 1);
		assert(number.IsInteger());
		JInteger value;
		number.ConvertToInteger(&value);
		if (value != kOKValue)
			{
			if (!itsIsTryingToQuit)
				{
				line.Prepend("Sendmail error: ");
				JGetUserNotification()->ReportError(line);
				}
			itsIsFinished = kTrue;
			Broadcast(SendFailure());
			if (!itsIsTryingToQuit)
				{
				JXTimerTask* task = new JXTimerTask(1000,kTrue);
				JXGetApplication()->InstallIdleTask(task);
				ListenTo(task);
				}
			return;
			}
		}

	if (itsCurrentMode < kTo)
		{
		itsCurrentMode++;
		}
	else if (itsCurrentMode == kTo)
		{
		if (itsCurrentIndex < itsToNames->GetElementCount())
			{
			itsCurrentIndex++;
			}
		else if (itsCcNames->GetElementCount() != 0)
			{
			itsCurrentMode = kCc;
			itsCurrentIndex = 1;
			}
		else if (itsBccNames->GetElementCount() != 0)
			{
			itsCurrentMode = kBcc;
			itsCurrentIndex = 1;
			}
		else
			{
			itsCurrentMode = kDataHeader;
			}
		}
	else if (itsCurrentMode == kCc)
		{
		if (itsCurrentIndex < itsCcNames->GetElementCount())
			{
			itsCurrentIndex++;
			}
		else if (itsBccNames->GetElementCount() != 0)
			{
			itsCurrentMode = kBcc;
			itsCurrentIndex = 1;
			}
		else
			{
			itsCurrentMode = kDataHeader;
			}
		}
	else if (itsCurrentMode == kBcc)
		{
		if (itsCurrentIndex < itsBccNames->GetElementCount())
			{
			itsCurrentIndex++;
			}
		else
			{
			itsCurrentMode = kDataHeader;
			}
		}
	else if (itsCurrentMode == kData)
		{
		itsIsFinished = kTrue;
		Broadcast(MessageSent());
		GMGetSMTPDebugDir()->AddText("\n-------------------------------------\n");
		if (!itsIsTryingToQuit)
			{
			JXTimerTask* task = new JXTimerTask(1000,kTrue);
			JXGetApplication()->InstallIdleTask(task);
			ListenTo(task);
			}
		return;
		}
	else
		{
		itsCurrentMode++;
		}
	
	SendNextData();
}

/******************************************************************************
 SendNextData

 *****************************************************************************/

void
SMTPMessage::SendNextData()
{
	if (itsCurrentMode == kHello)
		{
		JCharacter hostname[255];
		gethostname(hostname, 255);
		JString senddata = "HELO ";
		senddata += hostname;
		SendLine(senddata);
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kFrom)
		{
		JString senddata = "MAIL FROM: " + *itsFrom;
		SendLine(senddata);
		*itsOutboxStream << "From: ";
		itsTo->Print(*itsOutboxStream);
		*itsOutboxStream << endl;
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kTo)
		{
		assert(itsCurrentIndex <= itsToNames->GetElementCount());
		JString senddata;
		JString name = *(itsToNames->NthElement(itsCurrentIndex));
		senddata = "RCPT TO: " + name;
		SendLine(senddata);
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kCc)
		{
		assert(itsCurrentIndex <= itsCcNames->GetElementCount());
		JString senddata;
		JString name = *(itsCcNames->NthElement(itsCurrentIndex));
		senddata = "RCPT TO: " + name;
		SendLine(senddata);
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kBcc)
		{
		assert(itsCurrentIndex <= itsBccNames->GetElementCount());
		JString senddata;
		JString name = *(itsBccNames->NthElement(itsCurrentIndex));
		senddata = "RCPT TO: " + name;
		SendLine(senddata);
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kDataHeader)
		{
		JString senddata = "DATA";
		SendLine(senddata);
		senddata += "\n";
		GMGetSMTPDebugDir()->AddText(senddata);
		}
	else if (itsCurrentMode == kData)
		{
		if (GGetPrefsMgr()->UsingCustomFromLine())
			{
			JString from	 = GGetPrefsMgr()->GetCustomFromString();
			if (!from.IsEmpty())
				{
				JString senddata = "From: " + from;
				SendLine(senddata);
				senddata += "\n";
				GMGetSMTPDebugDir()->AddText(senddata);
				*itsOutboxStream << "From: ";
				from.Print(*itsOutboxStream);
				*itsOutboxStream << endl;
				}
			}

		struct utsname buf;
		uname(&buf);
		
		JString senddata = "X-Mailer: Arrow " + JString(kArrowVersionNumberStr);
		senddata += JString(" (X11; ") + buf.sysname + JString(" ") +  + JString(buf.release);
		senddata += JString("; ") + buf.machine + JString(")");
		SendLine(senddata);
		senddata.Print(*itsOutboxStream);
		*itsOutboxStream << endl;
		GMGetSMTPDebugDir()->AddText(senddata);

		senddata = "To: ";
		assert(itsToNames->GetElementCount() > 0);
		senddata += *(itsToNames->NthElement(1));
		for (JSize i = 2; i <= itsToNames->GetElementCount(); i++)
			{
			senddata += ",\n\t" + *(itsToNames->NthElement(i));
			}
		SendLine(senddata);
		senddata.Print(*itsOutboxStream);
		*itsOutboxStream << endl;		
		GMGetSMTPDebugDir()->AddText(senddata);
		
		if (!itsReplyTo->IsEmpty())
			{
			senddata = "Reply-To: " + *itsReplyTo;
			SendLine(senddata);
			GMGetSMTPDebugDir()->AddText(senddata);
			}

		if (itsCcNames->GetElementCount() != 0)
			{
			JSize count = itsCcNames->GetElementCount();
			senddata = "Cc: ";
			senddata += *(itsCcNames->NthElement(1));
			for (JSize i = 2; i <= count; i++)
				{
				senddata += ",\n\t" + *(itsCcNames->NthElement(i));
				}
			SendLine(senddata);
			senddata.Print(*itsOutboxStream);
			*itsOutboxStream << endl;
			GMGetSMTPDebugDir()->AddText(senddata);
			}

		if (!itsSubject->IsEmpty())
			{
			senddata = "Subject: " + *itsSubject;
			SendLine(senddata);
			*itsOutboxStream << "Subject: ";
			itsSubject->Print(*itsOutboxStream);
			*itsOutboxStream << endl;
			GMGetSMTPDebugDir()->AddText(senddata);
			}

		if (!itsAddedHeaders->IsEmpty())
			{
			senddata = *itsAddedHeaders;
			SendLine(senddata);
			GMGetSMTPDebugDir()->AddText(senddata);
			}
			
		SendLine("");
		*itsOutboxStream << endl;
		
		GMGetSMTPDebugDir()->AddText("\nData not shown ...\n");
		itsData->Print(*itsOutboxStream);
		SendData();
		*itsOutboxStream << endl;
		*itsOutboxStream << endl;

		SendLine("");
		SendLine(".");
		SendLine("QUIT");
		}
}

/******************************************************************************
 SendLine (private)

 ******************************************************************************/

void
SMTPMessage::SendLine
	(
	const JCharacter* line
	)
{
	JString sLine(line);
	SendLine(sLine);
}

void
SMTPMessage::SendLine
	(
	const JString& line
	)
{
	JString sLine(line);
	sLine += "\r";
	itsLink->SendMessage(sLine);
}

/******************************************************************************
 SendData (private)

 ******************************************************************************/

void
SMTPMessage::SendData()
{
	// Need to '.' stuff
	JIndexRange range(1,0);
	while (smtpPeriodStuff.MatchAfter(*itsData, range, &range))
		{
		itsData->InsertSubstring(".", range.last + 1);
		range += 1;
		}

	JIndex findex = 1;
	while (itsData->IndexValid(findex) && itsData->LocateNextSubstring("\n", &findex))
		{
		itsData->ReplaceSubstring(findex, findex, "\r\n");
		findex += 2;
		}
	itsLink->SendData(*itsData);
}

/******************************************************************************
 BlockUntilOkOrFail

 *****************************************************************************/

void
SMTPMessage::BlockUntilOkOrFail()
{
	if (itsIsFinished)
		{
		return;
		}
		
	JBoolean success	= kFalse;
	itsIsFinished 		= kFalse;
	itsIsTryingToQuit	= kTrue;

	JSize i = 1;
	while((i <= 30) && !itsIsFinished)
		{
		do
			{
			itsSomethingRead = kFalse;
			itsLink->handle_input(0);
			}
		while (itsSomethingRead);

		JWait(2);
		i++;
		}
	if (itsIsFinished)
		{
		delete this;
		}
}

/******************************************************************************
 Required templates

	These are defined because the linker asked for them.

 ******************************************************************************/

#define JTemplateName ACE_Connector
#define JTemplateType InetLink, ACE_SOCK_CONNECTOR
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType

#define JTemplateName ACE_Map_Manager
#define JTemplateType ACE_HANDLE, ACE_Svc_Tuple<InetLink>*, ACE_SYNCH_RW_MUTEX
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType


#define JTemplateName ACE_Map_Iterator
#define JTemplateType ACE_HANDLE, ACE_Svc_Tuple<InetLink>*, ACE_SYNCH_RW_MUTEX
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType

#define JTemplateName ACE_Map_Reverse_Iterator
#define JTemplateType ACE_HANDLE, ACE_Svc_Tuple<InetLink>*, ACE_SYNCH_RW_MUTEX
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType

#define JTemplateName ACE_Map_Iterator_Base
#define JTemplateType ACE_HANDLE, ACE_Svc_Tuple<InetLink>*, ACE_SYNCH_RW_MUTEX
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType

#define JTemplateName ACE_Map_Entry
#define JTemplateType ACE_HANDLE, ACE_Svc_Tuple<InetLink>*
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType

#define JTemplateName ACE_Svc_Tuple
#define JTemplateType InetLink
#include <instantiate_template.h>
#undef JTemplateName
#undef JTemplateType
