// smtp.cpp

#include <stdio.h>
#include <string.h>
#include <mimepp/mimepp.h>
#include "smtp.h"

static void AppendMailboxes(DwAddressList& aSrcList, DwMailboxList& aDestList);


Smtp::Smtp()
{
    mServer = "";
    mSender = "";
    mRecipient = "";
    mErrorHandler = 0;
}


Smtp::Smtp(const char* aServer, const char* aSender,
    const char* aRecipient)
{
    if (aServer != 0) {
        mServer = aServer;
    }
    else {
        mServer = "";
    }
    if (aSender != 0) {
        mSender = aSender;
    }
    else {
        mSender = "";
    }
    if (aRecipient != 0) {
        mRecipient = aRecipient;
    }
    else {
        mRecipient = "";
    }
    mErrorHandler = 0;
}


void Smtp::SetServer(const char* aServer)
{
    if (aServer != 0) {
        mServer = aServer;
    }
    else {
        mServer = "";
    }
}


const char* Smtp::Server()
{
    return mServer;
}


void Smtp::SetSender(const char* aSender)
{
    if (aSender != 0) {
        mSender = aSender;
    }
    else {
        mSender = "";
    }
}


const char* Smtp::Sender()
{
    return mSender;
}


void Smtp::SetRecipient(const char* aRecipient)
{
    if (aRecipient != 0) {
        mRecipient = aRecipient;
    }
    else {
        mRecipient = "";
    }
}


const char* Smtp::Recipient()
{
    return mRecipient;
}


void Smtp::SetErrorHandler(SmtpErrorHandler* aErrorHandler)
{
    mErrorHandler = aErrorHandler;
}


// Returns non-zero if any errors occurred

int Smtp::SendMessageObject(DwMessage& aMessage)
{
    int returnVal = 0;

    // Initialize sender and recipient

    char sender[100];
	sender[0] = 0;
    char recipient[100];
    recipient[0] = 0;

    // If Server is not specified, then return an error

    if (mServer == 0 || mServer[0] == 0) {
        if (mErrorHandler != 0) {
            mErrorHandler->Error(aMessage, mServer, sender, recipient, -1,
                "No server");
            returnVal = -1;
        }
        return -1;
    }

    // If sender is not set, then get sender from message

    if (mSender != 0 && mSender[0] != 0) {
        strncpy(sender, mSender, sizeof(sender)-1);
        sender[sizeof(sender)-1] = 0;
    }
    else /* if (mSender == 0 || mSender[0] == 0) */ {
        if (aMessage.Headers().HasFrom()) {
            DwMailboxList from = aMessage.Headers().From();
            if (from.NumMailboxes() > 0) {
                DwMailbox addr = from.MailboxAt(0);
                // Parse the address to be sure we get the local part and
                // domain part correctly
                addr.Parse();
                DwString senderStr;
                senderStr = addr.LocalPart();
                senderStr += "@";
                senderStr += addr.Domain();
                strncpy(sender, senderStr.c_str(), sizeof(sender)-1);
				sender[sizeof(sender)-1] = 0;
            }
        }
        else /* if (! aMessage.Headers().HasFrom()) */ {
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient, -1,
                    "No sender");
                returnVal = -1;
            }
        }
    }

    // If recipient is not specified, then get recipient(s) from message.
    // We create an address list, then add all the recipients addresses
    // to it.

    DwHeaders headers = aMessage.Headers();

    DwMailboxList recipients;
    if (mRecipient != 0 && mRecipient[0] != 0) {
        strncpy(recipient, mRecipient, sizeof(recipient)-1);
        recipient[sizeof(recipient)-1] = 0;
        DwMailbox* pMailbox = new DwMailbox(recipient);
        pMailbox->Parse();
        recipients.AddMailbox(pMailbox);
    }
    else /* if (mRecipient == 0 || mRecipient[0] == 0) */ {

        // "To:" recipients

        if (headers.HasTo()) {
            DwAddressList& to = headers.To();
            AppendMailboxes(to, recipients);
        }

        // "Cc:" recipients

        if (headers.HasCc()) {
            DwAddressList& cc = headers.Cc();
            AppendMailboxes(cc, recipients);
        }

        // "Bcc:" recipients

        if (headers.HasBcc()) {
            DwAddressList& bcc = headers.Bcc();
            AppendMailboxes(bcc, recipients);
        }
    }

    // Remove bcc header field

    DwMessage messageNoBcc(aMessage);
    if (messageNoBcc.Headers().HasBcc()) {
        // Note: there may be more than one BCC field. This code section does
        // not correctly handle the case of more than one BCC field.
        DwHeaders& headers = messageNoBcc.Headers();
        int numFields = headers.NumFields();
        for (int i=0; i < numFields; ++i) {
            const DwString& fieldName = headers.FieldAt(i).FieldNameStr();
            if (DwStrcasecmp(fieldName, "bcc") == 0) {
                DwField* field = headers.RemoveFieldAt(i);
                delete field;
                break;
            }
        }
    }
    messageNoBcc.Assemble();

    // Convert to CR LF end of line characters (required by Internet mail)

    DwString messageStr = messageNoBcc.AsString();
    DwToCrLfEol(messageStr, messageStr);

    // Send to all recipients

    // *** Note: It might be possible to optimize slightly by opening one
    // connection to the SMTP server and sending multiple RCPT commands.

    int numRecipients = recipients.NumMailboxes();
    for (int i=0; i < numRecipients; ++i) {

        // Get next recipient

        DwMailbox& mailbox = recipients.MailboxAt(i);
        DwString recipientStr;
        recipientStr = mailbox.LocalPart();
        recipientStr += "@";
        recipientStr += mailbox.Domain();
		strncpy(recipient, recipientStr.c_str(), sizeof(recipient)-1);
		recipient[sizeof(recipient)-1] = 0;

        // Open connection to SMTP server

        DwSmtpClient smtpClient;
        int responseCode = smtpClient.Open(mServer);
        const char* responseMessage = smtpClient.Response().c_str();

        // Check for 2xx response code

        if (responseCode / 100 % 10 != 2) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            break;
        }

        // Send HELO command

        responseCode = smtpClient.Helo();
        responseMessage = smtpClient.Response().c_str();

        // Check for 2xx response code

        if (responseCode / 100 % 10 != 2) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            continue;
        }
        // Send MAIL command

        responseCode = smtpClient.Mail(sender);
        responseMessage = smtpClient.Response().c_str();

        // Check for 2xx response code

        if (responseCode / 100 % 10 != 2) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            continue;
        }

        // Send RCPT command

        responseCode = smtpClient.Rcpt(recipient);
        responseMessage = smtpClient.Response().c_str();

        // Check for 2xx response

        if (responseCode / 100 % 10 != 2) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            continue;
        }

        // Send DATA command

        responseCode = smtpClient.Data();
        responseMessage = smtpClient.Response().c_str();

        // Check for 3xx response code

        if (responseCode / 100 % 10 != 3) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            continue;
        }

        // Send message

        responseCode = smtpClient.SendData(messageStr);
        responseMessage = smtpClient.Response().c_str();

        // Check for 2xx response code

        if (responseCode / 100 % 10 != 2) {
            smtpClient.Close();
            if (mErrorHandler != 0) {
                mErrorHandler->Error(aMessage, mServer, sender, recipient,
                    responseCode, responseMessage);
                returnVal = -1;
            }
            continue;
        }

        // Close connection to SMTP server

        smtpClient.Close();
        
    }
    return returnVal;
}


int Smtp::SendMessageFile(const char* aFilename)
{
    int returnVal = 0;
    FILE* in = fopen(aFilename, "r");
    fseek(in, 0, SEEK_END);
    int fileSize = (int) ftell(in);
    fseek(in, 0, SEEK_SET);
    DwString messageStr;
    messageStr.reserve(fileSize);
    while (1) {
        int ch = getc(in);
        if (ch < 0) {
            break;
        }
        messageStr += (char) ch;
    }
    returnVal = SendMessageBuffer(messageStr);
    return returnVal;
}


int Smtp::SendMessageBuffer(const DwString& aMessageStr)
{
    DwMessage message(aMessageStr);
    message.Parse();
    int returnVal = SendMessageObject(message);
    return returnVal;
}


static void AppendMailboxes(DwAddressList& aSrcList, DwMailboxList& aDestList)
{
    int numAddresses = aSrcList.NumAddresses();
    for (int i=0; i < numAddresses; ++i) {
        DwAddress& addr = aSrcList.AddressAt(i);
        if (addr.IsMailbox()) {
            DwMailbox* pMailbox = (DwMailbox*) addr.Clone();
            aDestList.AddMailbox(pMailbox);
        }
        else /* if (addr.IsGroup()) */ {
            DwGroup& group = (DwGroup&) addr;
            DwMailboxList& list = group.MailboxList();
            int n = list.NumMailboxes();
            for (int j=0; j < n; ++j) {
                DwMailbox& mailbox = list.MailboxAt(j);
                DwMailbox* pMailbox = (DwMailbox*) mailbox.Clone();
                aDestList.AddMailbox(pMailbox);
            }
        }
    }
}
