// email.cpp

// To do:
// 1. Need to do the same thing with email addresses for recipients
//    as for the originator (encoding)
// 2. Need to encode/decode the subject

#include <mimepp/mimepp.h>
#include "EmailMessage.h"

#ifdef WIN32
#  define strcasecmp stricmp
#  define strncasecmp strnicmp
#endif

static void _ParseMemoText_Text(const DwEntity& aEntity, EmailText& aText);
static void ParseMemoText_MessagePartial(const DwEntity& aEntity,
    EmailText& aText);
static void ParseMemoText_MessageExternalBody(const DwEntity& aEntity,
    EmailText& aText);
static void ParseMemoText_Multipart_Recursive(const DwEntity& aEntity,
    EmailText& aText);
static void _GetAttachment(const DwEntity& aEntity,
    EmailAttachment& aAttachment);
static DwBool _PartContainsText(const DwEntity& aEntity);
static void _SetSubject(DwMessage& aMessage, const char* aText,
    const char* aCharset, int aEncoding);
static int _RecommendEncoding(const EmailText& aText);


EmailMessage::EmailMessage()
{
}


EmailMessage::~EmailMessage()
{
}


void EmailMessage::ParseFrom(const DwString& aMessageString)
{
    DwMessage message(aMessageString);
    message.Parse();
    DwHeaders& headers = message.Headers();

    ParseOriginator(message, mOriginator);
    ParseRecipients(message, "to", mToRecipients);
    ParseRecipients(message, "cc", mCcRecipients);
    if (headers.HasDate()) {
        const DwDateTime& date = headers.Date();
        int year = date.Year();
        int month = date.Month();
        int day = date.Day();
        int hour = date.Hour();
        int minute = date.Minute();
        int second = date.Second();
        int zone = date.Zone();
        mDate.SetValues(year, month, day, hour, minute, second, zone);
    }
    ParseSubject(message, mSubject);
    ParseMemoText(message, mMemoText);
    ParseAttachments(message, mAttachments);
}


void EmailMessage::SerializeTo(DwString& aMessageString)
{
    DwMessage message;
    DwHeaders& headers = message.Headers();
    DwBody& body = message.Body();

    // From

    DwMailbox* mailbox = new DwMailbox;
    mailbox->FromString(mOriginator.InetName().c_str());
    mailbox->Parse();
    if (mOriginator.PersonalName().length() > 0) {
        if (mOriginator.Charset().length() > 0 &&
            strcasecmp(mOriginator.Charset().c_str(), "us-ascii") != 0) {
            mailbox->SetFullName(mOriginator.PersonalName().c_str(),
                mOriginator.Charset().c_str());
        }
        else if (mOriginator.PersonalName()[0] != '"') {
            DwString from = "\"";
            from += mOriginator.PersonalName().c_str();
            from += "\"";
            mailbox->SetFullName(from);
        }
    }
    headers.From().AddMailbox(mailbox);

    // To

    int i;
    for (i=0; i < mToRecipients.Count(); ++i) {
        const EmailAddress& addr = mToRecipients.Get(i);
        DwMailbox* mailbox = new DwMailbox;
        mailbox->FromString(addr.InetName().c_str());
        mailbox->Parse();
        if (addr.PersonalName().length() > 0) {
            if (addr.Charset().length() > 0 &&
                strcasecmp(addr.Charset().c_str(), "us-ascii") != 0) {
                mailbox->SetFullName(addr.PersonalName().c_str(),
                    addr.Charset().c_str());
            }
            else if (addr.PersonalName()[0] != '"') {
                DwString name = "\"";
                name += addr.PersonalName().c_str();
                name += "\"";
                mailbox->SetFullName(name);
            }
        }
        headers.To().AddAddress(mailbox);
    }

    // Cc

    for (i=0; i < mCcRecipients.Count(); ++i) {
        const EmailAddress& addr = mCcRecipients.Get(i);
        DwMailbox* mailbox = new DwMailbox;
        mailbox->FromString(addr.InetName().c_str());
        mailbox->Parse();
        if (addr.PersonalName().length() > 0) {
            if (addr.Charset().length() > 0 &&
                strcasecmp(addr.Charset().c_str(), "us-ascii") != 0) {
                mailbox->SetFullName(addr.PersonalName().c_str(),
                    addr.Charset().c_str());
            }
            else if (addr.PersonalName()[0] != '"') {
                DwString name = "\"";
                name += addr.PersonalName().c_str();
                name += "\"";
                mailbox->SetFullName(name);
            }
        }
        headers.To().AddAddress(mailbox);
    }

    // Subject

    _SetSubject(message, mSubject.Text().c_str(),
        mSubject.Charset().c_str(), 0);

    // Date

    headers.Date().SetValuesLiteral(mDate.Year(), mDate.Month(), mDate.Day(),
        mDate.Hour(), mDate.Minute(), mDate.Second(), mDate.Zone());

    // Message-ID

    headers.MessageId().CreateDefault();

    // MIME-Version

    headers.MimeVersion().FromString("1.0");

    // Content-Type

    if (mAttachments.Count() == 0) {
        DwString text = mMemoText.Text().c_str();
        int encoding = _RecommendEncoding(mMemoText);
        if (encoding == 'B') {
            headers.ContentTransferEncoding().FromEnum(DwMime::kCteBase64);
            DwEncodeBase64(text, text);
        }
        else if (encoding == 'Q') {
            headers.ContentTransferEncoding().
                FromEnum(DwMime::kCteQuotedPrintable);
            DwEncodeQuotedPrintable(text, text);
        }
        body.FromString(text);
    }
    else /* if (mAttachments.size() > 0) */ {
        headers.ContentType().SetType(DwMime::kTypeMultipart);
        headers.ContentType().SetSubtype(DwMime::kSubtypeMixed);
        headers.ContentType().CreateBoundary();

        // Memo text part

        {
            DwBodyPart* part = new DwBodyPart;
            DwString text = mMemoText.Text().c_str();
            int encoding = _RecommendEncoding(mMemoText);
            DwHeaders& partHeaders = part->Headers();
            if (encoding == 'B') {
                partHeaders.ContentTransferEncoding().
                    FromEnum(DwMime::kCteBase64);
                DwEncodeBase64(text, text);
            }
            else if (encoding == 'Q') {
                partHeaders.ContentTransferEncoding().
                    FromEnum(DwMime::kCteQuotedPrintable);
                DwEncodeQuotedPrintable(text, text);
            }
            partHeaders.ContentType().SetType(DwMime::kTypeText);
            partHeaders.ContentType().SetSubtype(DwMime::kSubtypePlain);
            DwParameter* parameter = new DwParameter;
            parameter->SetAttribute("charset");
            parameter->SetValue(mMemoText.Charset().c_str());
            partHeaders.ContentType().AddParameter(parameter);
            part->Body().FromString(text);
            body.AddBodyPart(part);
        }

        // Attachments

        for (int i=0; i < mAttachments.Count(); ++i) {
            const EmailAttachment& attach = mAttachments.Get(i);
            DwBodyPart* part = new DwBodyPart;
            DwHeaders& partHeaders = part->Headers();
            partHeaders.ContentTransferEncoding().FromEnum(DwMime::kCteBase64);
            partHeaders.ContentType().SetTypeStr(attach.Type().c_str());
            partHeaders.ContentType().SetSubtypeStr(attach.Subtype().c_str());
            partHeaders.ContentType().SetName(attach.FileName().c_str());
            partHeaders.ContentDisposition().SetDispositionType(
                DwMime::kDispTypeAttachment);
            partHeaders.ContentDisposition().SetFilename(
                attach.FileName().c_str());
            DwString content;
            DwEncodeBase64(attach.Content(), content);
            part->Body().FromString(content);
            body.AddBodyPart(part);
        }
    }

    message.Assemble();
    aMessageString = message.AsString();
}


const EmailAddress& EmailMessage::Originator()
{
    return mOriginator;
}


void EmailMessage::Originator(const EmailAddress& aOriginator)
{
    mOriginator = aOriginator;
}


const EmailText& EmailMessage::Subject()
{
    return mSubject;
}


void EmailMessage::Subject(const EmailText& aSubject)
{
    mSubject = aSubject;
}


const EmailDate& EmailMessage::Date()
{
    return mDate;
}


void EmailMessage::Date(const EmailDate& aDate)
{
    mDate = aDate;
}


const EmailText& EmailMessage::MemoText()
{
    return mMemoText;
}


void EmailMessage::MemoText(const EmailText& aText)
{
    mMemoText = aText;
}


//--------------------------------------------------------------------------
// Virtual functions
//--------------------------------------------------------------------------


void EmailMessage::ParseOriginator(DwMessage& aMessage, EmailAddress& aAddress)
{
    // Here we assume that there is only one author.  RFC 822 allows
    // more than one individual to be specified as the author, but only
    // if there is also a "Sender" header field.
    //
    // We return the originator's email address in the standard form.

    aAddress.Set("", "", "US-ASCII");
    DwHeaders& headers = aMessage.Headers();
    if (headers.HasFrom()) {
        DwMailboxList& fromList = headers.From();
        if (fromList.NumMailboxes() > 0) {
            // Create new DwMailbox using copy constructor
            const DwMailbox& from = fromList.MailboxAt(0);
            DwString inetName = from.LocalPart();
            inetName += "@";
            inetName += from.Domain();
            DwString personalName, charset;
            from.GetFullName(personalName, charset);
            aAddress.Set(inetName.c_str(), personalName.c_str(),
                charset.c_str());
        }
    }
}


void EmailMessage::ParseRecipients(DwMessage& aMessage, const string& aWhich,
    EmailAddressList& aAddresses)
{
    const char* which = aWhich.c_str();
    DwHeaders& headers = aMessage.Headers();

    // Iterate through all header fields, and get the addresses from each
    // "To" field.  Note: it has been recommended in a recent Internet
    // draft that multiple "To" fields not be used.  However, the official
    // standard (RFC 822) allows the use of multiple "To fields, so we will
    // accommodate it.

    int numFields = headers.NumFields();
    for (int i=0; i < numFields; ++i) {
        const DwField& field = headers.FieldAt(i);
        if (DwStrcasecmp(which, field.FieldNameStr()) == 0) {
            const DwAddressList& rcptList = *(const DwAddressList *)
                field.FieldBody();
            int numAddresses = rcptList.NumAddresses();
            for (int j=0; j < numAddresses; ++j) {
                const DwAddress& addr = rcptList.AddressAt(j);
                if (addr.IsMailbox()) {
                    const DwMailbox& rcpt = (const DwMailbox&) addr;
                    DwString inetName = rcpt.LocalPart();
                    inetName += "@";
                    inetName += rcpt.Domain();
                    DwString personalName, charset;
                    rcpt.GetFullName(personalName, charset);
                    EmailAddress eaddr(inetName.c_str(),
                        personalName.c_str(), charset.c_str());
                    aAddresses.Add(eaddr);
                }
                else if (addr.IsGroup()) {
                    const DwGroup& rcpt = (const DwGroup&) addr;
                    EmailAddress eaddr("", rcpt.GroupName().c_str(), "");
                    aAddresses.Add(eaddr);
                }
            }
        }
    }
}


void EmailMessage::ParseSubject(DwMessage& aMessage, EmailText& aSubject)
{
    aSubject.Set("", "");
    DwHeaders& headers = aMessage.Headers();
    if (headers.HasSubject()) {
        string xText;
        string xCharset;
        DwText& subject = headers.Subject();
        int numEncodedWords = subject.NumEncodedWords();
        for (int i=0; i < numEncodedWords; ++i) {
            DwEncodedWord& word = subject.EncodedWordAt(i);
            if (xText.length() > 0) {
                xText += " ";
            }
            xText += word.DecodedText().c_str();
            const DwString& charset = word.Charset();
            if (charset.length() > 0
                && DwStrcasecmp(charset, "us-ascii") != 0
                && xCharset.length() == 0) {

                xCharset = charset.c_str();
            }
        }
        aSubject.Set(xText, xCharset);
    }
}


void EmailMessage::ParseMemoText(DwMessage& aMessage, EmailText& aMemoText)
{
    aMemoText.Set("", "US-ASCII");

    // Assign default values

    int type = DwMime::kTypeText;
    int subtype = DwMime::kSubtypePlain;
    DwHeaders& headers = aMessage.Headers();
    DwBody& body = aMessage.Body();
    if (headers.HasContentType()) {
        type = headers.ContentType().Type();
        subtype = headers.ContentType().Subtype();
    }
    switch (type) {
    case DwMime::kTypeText:
        ParseMemoText_Text(aMessage, aMemoText);
        break;
    case DwMime::kTypeMessage:
        ParseMemoText_Message(aMessage, aMemoText);
        break;
    case DwMime::kTypeMultipart: 
        ParseMemoText_Multipart(aMessage, aMemoText);
        break;
    case DwMime::kTypeAudio:
    case DwMime::kTypeImage:
    case DwMime::kTypeVideo:
    case DwMime::kTypeApplication:
    case DwMime::kTypeModel:
    default:
        // No memo text -- just an attachment
        break;
    }
}


void EmailMessage::ParseMemoText_Text(DwMessage& aMessage, EmailText& aText)
{
    _ParseMemoText_Text(aMessage, aText);
}


void EmailMessage::ParseMemoText_Message(DwMessage& aMessage, EmailText& aText)
{
    DwHeaders& headers = aMessage.Headers();
    DwBody& body = aMessage.Body();
    int subtype = headers.ContentType().Subtype();
    switch (subtype) {
    case DwMime::kSubtypePartial:
        ParseMemoText_MessagePartial(aMessage, aText);
        break;
    case DwMime::kSubtypeExternalBody:
        ParseMemoText_MessageExternalBody(aMessage, aText);
        break;
    case DwMime::kSubtypeRfc822:
    default:
        aText.Set(body.AsString().c_str(), "US-ASCII");
        break;
    }
}


void EmailMessage::ParseMemoText_Multipart(DwMessage& aMessage,
    EmailText& aText)
{
    // If the message is a multipart message, then the text memo should
    // be the first text body part.  We can't guarantee that every MUA
    // follows this convention, but when smart people think about it,
    // this is the only right way.  The reason is, that it is the first
    // text that is seen by a non-MIME capable MUA.  For best compatibility
    // with non-MIME capable MUA's, the memo text MUST come first.

    // One common structure is that the message is a multipart/mixed,
    // and the first body part is a multipart/alternative containing
    // a text/plain and a text/html (Netscape, Microsoft Outlook, among
    // others).

    aText.Set("", "US-ASCII");

    ParseMemoText_Multipart_Recursive(aMessage, aText);
}


void EmailMessage::ParseAttachments(DwMessage& aMessage,
    EmailAttachmentList& aList)
{
    int type = DwMime::kTypeText;
    int subtype = DwMime::kSubtypePlain;
    DwHeaders& headers = aMessage.Headers();
    DwBody& body = aMessage.Body();
    if (headers.HasContentType()) {
        type = headers.ContentType().Type();
        subtype = headers.ContentType().Subtype();
    }
    switch (type) {
    case DwMime::kTypeText:
        // If the message type is text, then there are no attachments
        break;
    case DwMime::kTypeAudio:
    case DwMime::kTypeImage:
    case DwMime::kTypeVideo:
    case DwMime::kTypeApplication:
    case DwMime::kTypeModel:
    default:
        ParseAttachments_Simple(aMessage, aList);
        break;
    case DwMime::kTypeMessage:
        ParseAttachments_Message(aMessage, aList);
        break;
    case DwMime::kTypeMultipart:
        ParseAttachments_Multipart(aMessage, aList);
        break;
    }
}


void EmailMessage::ParseAttachments_Simple(DwMessage& aMessage,
    EmailAttachmentList& aAttachments)
{
    EmailAttachment attachment;
    _GetAttachment(aMessage, attachment);
    aAttachments.Add(attachment);
}


void EmailMessage::ParseAttachments_Message(DwMessage& aMessage,
    EmailAttachmentList& aAttachments)
{
    DwBody& body = aMessage.Body();
    DwMessage* message = body.Message();
    if (message != 0) {
        EmailAttachment attachment;
        string type = aMessage.Headers().ContentType().TypeStr().c_str();
        string subtype = aMessage.Headers().ContentType().SubtypeStr().c_str();
        attachment.MediaType(type, subtype);
        attachment.Content(message->AsString());
        aAttachments.Add(attachment);
    }
}


void EmailMessage::ParseAttachments_Multipart(DwMessage& aMessage,
    EmailAttachmentList& aAttachments)
{
    DwHeaders& headers = aMessage.Headers();
    DwBody& body = aMessage.Body();
    int numParts = body.NumBodyParts();
    if (numParts > 0) {
        const DwBodyPart& part = body.BodyPartAt(0);
        if (! _PartContainsText(part)) {
            EmailAttachment attachment;
            _GetAttachment(part, attachment);
            aAttachments.Add(attachment);
        }
    }
    for (int i=1; i < numParts; ++i) {
        const DwBodyPart& part = body.BodyPartAt(i);
        EmailAttachment attachment;
        _GetAttachment(part, attachment);
        aAttachments.Add(attachment);
    }
}


//--------------------------------------------------------------------------
// Static functions
//--------------------------------------------------------------------------


static void _ParseMemoText_Text(const DwEntity& aEntity,
    EmailText& aText)
{
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();

    DwString text = body.AsString();

    // Check content-transfer-encoding, and decode if necessary

    DwString cte;
    if (headers.HasContentTransferEncoding()) {
        cte = headers.ContentTransferEncoding().AsString();
    }
    if (0 == DwStrcasecmp(cte, "quoted-printable")) {
        DwDecodeQuotedPrintable(text, text);
        DwToLocalEol(text, text);
    }
    else if (0 == DwStrcasecmp(cte, "base64")) {
        DwDecodeBase64(text, text);
        DwToLocalEol(text, text);
    }

    // Get the charset, if it's there

    string charset = "US-ASCII";
    if (headers.HasContentType()) {
        int numParams = headers.ContentType().NumParameters();
        for (int i=0; i < numParams; ++i) {
            const DwParameter& param =
                headers.ContentType().ParameterAt(i);
            const DwString& attrName = param.Attribute();
            if (0 == DwStrcasecmp(attrName, "charset")) {
                charset = param.Value().c_str();
                break;
            }
        }
    }

    aText.Set(text.c_str(), charset);
}


static void ParseMemoText_MessagePartial(const DwEntity& aEntity,
    EmailText& aText)
{
    string number;
    string total;
    DwMediaType& contentType = aEntity.Headers().ContentType();
    int numParams = contentType.NumParameters();
    for (int i=0; i < numParams; ++i) {
        const DwParameter& param = contentType.ParameterAt(i);
        const DwString& attr = param.Attribute();
        const DwString& val = param.Value();
        if (DwStrcasecmp(attr, "number") == 0) {
            number = val.c_str();
        }
        if (DwStrcasecmp(attr, "total") == 0) {
            total = val.c_str();
        }
    }
    if (total == "") {
        total = "?";
    }
    string text = "[This is part ";
    text += number;
    text += " of a total of ";
    text += total;
    text += " parts]";
    aText.Set(text, "US-ASCII");
}


static void ParseMemoText_MessageExternalBody(const DwEntity& aEntity,
    EmailText& aText)
{
    string text = "[The body of this message is stored elsewhere]"
        DW_EOL DW_EOL;

    // List the attributes, which provide information about how to obtain
    // the message content

    DwMediaType& contentType = aEntity.Headers().ContentType();
    int numParams = contentType.NumParameters();
    for (int i=0; i < numParams; ++i) {
        const DwParameter& param = contentType.ParameterAt(i);
        text += param.Attribute().c_str();
        text += ":";
        text += param.Value().c_str();
        text += DW_EOL;
    }
    text += DW_EOL;

    // List the contained message, which contains the header fields of the
    // external message

    DwMessage* containedMessage = aEntity.Body().Message();
    if (containedMessage != 0) {
        text += containedMessage->AsString().c_str();
    }
    aText.Set(text, "US-ASCII");
}


static void ParseMemoText_Multipart_Recursive(const DwEntity& aEntity,
    EmailText& aText)
{
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();

    // Assign default values
    int type    = DwMime::kTypeText;
    int subtype = DwMime::kSubtypePlain;
    if (headers.HasContentType()) {
        type    = headers.ContentType().Type();
        subtype = headers.ContentType().Subtype();
    }

    switch (type) {
    case DwMime::kTypeMultipart: 
        if (body.NumBodyParts() > 0) {
            const DwBodyPart& part = body.BodyPartAt(0);
            ParseMemoText_Multipart_Recursive(part, aText);
        }
        break;
    case DwMime::kTypeText:
        _ParseMemoText_Text(aEntity, aText);
        break;
    case DwMime::kTypeAudio:
    case DwMime::kTypeImage:
    case DwMime::kTypeVideo:
    case DwMime::kTypeApplication:
    case DwMime::kTypeMessage:
    case DwMime::kTypeModel:
    default:
        break;
    }
}


static DwBool _PartContainsText(const DwEntity& aEntity)
{
    DwBool containsText = DwFalse;

    int type = DwMime::kTypeText;
    int subtype = DwMime::kSubtypePlain;
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();
    if (headers.HasContentType()) {
        type = headers.ContentType().Type();
        subtype = headers.ContentType().Subtype();
    }

    int i, numParts;
    switch (type) {
    case DwMime::kTypeText:
        containsText = DwTrue;
        break;
    case DwMime::kTypeMultipart:
        numParts = body.NumBodyParts();
        for (i=0; i < numParts; ++i) {
            const DwBodyPart& part = body.BodyPartAt(i);
            if (_PartContainsText(part)) {
                containsText = DwTrue;
                break;
            }
        }
        break;
    case DwMime::kTypeAudio:
    case DwMime::kTypeImage:
    case DwMime::kTypeVideo:
    case DwMime::kTypeApplication:
    case DwMime::kTypeModel:
    case DwMime::kTypeMessage:
    default:
        break;
    }
    return containsText;
}


static void _GetAttachment(const DwEntity& aEntity,
    EmailAttachment& aAttachment)
{
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();
    string typeStr = headers.ContentType().TypeStr().c_str();
    string subtypeStr = headers.ContentType().SubtypeStr().c_str();
    string filenameStr;
    if (headers.HasContentDisposition()) {
        filenameStr = headers.ContentDisposition().Filename().c_str();
    }
    if (filenameStr[0] == 0) {
        filenameStr = headers.ContentType().Name().c_str();
    }
    string descStr;
    if (headers.HasContentDescription()) {
        descStr = headers.ContentDescription().AsString().c_str();
    }
    aAttachment.MediaType(typeStr, subtypeStr);
    aAttachment.FileName(filenameStr);
    aAttachment.Description(descStr);
    DwString content = body.AsString();
    if (headers.HasContentTransferEncoding()) {
        const DwString& cte = headers.ContentTransferEncoding().AsString();
        if (DwStrcasecmp(cte, "base64") == 0) {
            DwDecodeBase64(content, content);
        }
        else if (DwStrcasecmp(cte, "quoted-printable") == 0) {
            DwDecodeQuotedPrintable(content, content);
        }
        else if (DwStrcasecmp(cte, "x-uuencode") == 0) {
            DwUuencode decoder;
            decoder.SetAsciiChars(content);
            decoder.Decode();
            content = decoder.BinaryChars();
        }
    }
    aAttachment.Content(content);
}


static void _SetSubject(DwMessage& aMessage, const char* aText,
    const char* aCharset, int aEncoding=0)
{
    int textLen = strlen(aText);
    int charsetLen = strlen(aCharset);

    // If the encoding type is ISO-8859-?, and if all characters are
    // 7-bit, then don't encode

    int num8BitChars = 0;
    int i;
    for (i=0; i < textLen; ++i) {
        int ch = aText[i] & 0xff;
        if (ch < 32 || 126 < ch) {
            ++num8BitChars;
        }
    }
    if (strcmp(aCharset, "US-ASCII") == 0
        || (strncmp(aCharset, "ISO-8859", 8) == 0 && num8BitChars == 0)) {

        DwString str = aText;
        DwFoldLine(str, 9);
        aMessage.Headers().Subject().FromString(str);
        aMessage.Headers().Subject().SetModified();
        aMessage.Headers().Subject().Parse();
        return;
    }

    // If no encoding is given, then determine a reasonable default:
    // Use Q encoding for:
    //    ISO-8859-1 (Latin-1)
    //    ISO-8859-2 (Latin-2)
    //    ISO-8859-3 (Latin-3)
    //    ISO-8859-4 (Latin-4)
    //    ISO-8859-9 (Latin-5)
    // Use B encoding for all others

    if (aEncoding == 0) {
        if (strcmp(aCharset, "ISO-8859-1") == 0
            || strcmp(aCharset, "ISO-8859-2") == 0
            || strcmp(aCharset, "ISO-8859-3") == 0
            || strcmp(aCharset, "ISO-8859-4") == 0
            || strcmp(aCharset, "ISO-8859-9") == 0) {

            aEncoding = 'Q';
        }
        else {
            aEncoding = 'B';
        }
    }
    else if (aEncoding != 'q' && aEncoding != 'Q'
        && aEncoding != 'b' && aEncoding != 'B') {

        aEncoding = 'B';
    }
    if (aEncoding == 'q' || aEncoding == 'Q') {
        aMessage.Headers().Subject().DeleteAllEncodedWords();
        int startPos = 0;
        int endPos = 0;
        int count = 0;
        while (endPos < textLen) {
            int ch = aText[endPos] & 0xff;
            if (ch < 32 || 126 < ch || ch == 61 || ch == 63) {
                count += 2;
            }
            ++count;
            ++endPos;
            if (count + charsetLen + 7 > 64) {
                DwEncodedWord* word = new DwEncodedWord;
                word->SetCharset(aCharset);
                word->SetEncodingType(aEncoding);
                DwString s(aText, startPos, endPos-startPos);
                word->SetDecodedText(s);
                aMessage.Headers().Subject().AddEncodedWord(word);
                count = 0;
                startPos = endPos;
            }
        }
        if (count > 0) {
            DwEncodedWord* word = new DwEncodedWord;
            word->SetCharset(aCharset);
            word->SetEncodingType(aEncoding);
            DwString s(aText, startPos, endPos-startPos);
            word->SetDecodedText(s);
            aMessage.Headers().Subject().AddEncodedWord(word);
        }
        aMessage.Headers().Subject().Assemble();
    }
    else /* if (aEncoding != 'q' && aEncoding != 'Q') */ {
        aMessage.Headers().Subject().DeleteAllEncodedWords();
        int startPos = 0;
        int endPos = 0;
        while (endPos < textLen) {
            endPos += 39;
            endPos = (endPos < textLen) ? endPos : textLen;
            DwEncodedWord* word = new DwEncodedWord;
            word->SetCharset(aCharset);
            word->SetEncodingType(aEncoding);
            DwString s(aText, startPos, endPos-startPos);
            word->SetDecodedText(s);
            aMessage.Headers().Subject().AddEncodedWord(word);
            startPos = endPos;
        }
        aMessage.Headers().Subject().Assemble();
    }
}


static int _RecommendEncoding(const EmailText& aText)
{
    // Determine the encoding type.  For all ISO-8859-X types, if there
    // are no 8-bit characters, then we don't need to encode.  Otherwise,
    // we use 'Q' encoding or 'B' encoding.

    // To do: add the Windows code pages charsets, such as 1252

    // Check for 8-bit characters.  If there are no 8-bit characters,
    // we may not have to encode the personal name

    const char* ptr = aText.Text().data();
    int len = (int) aText.Text().length();
    DwBool has8bitChars = DwFalse;
    for (int i=0; i < len; ++i) {
        int ch = ptr[i] & 0xff;
        if (ch < 32 || 126 < ch) {
            has8bitChars = DwTrue;
            break;
        }
    }

    int encoding;
    if (strcasecmp(aText.Charset().c_str(), "US-ASCII") == 0) {
        encoding = 0;
    }
    else if (strncasecmp(aText.Charset().c_str(), "ISO-8859", 8) == 0) {
        if (!has8bitChars) {
            encoding = 0;
        }
        else if (strcasecmp(aText.Charset().c_str(), "ISO-8859-1") == 0
            || strcasecmp(aText.Charset().c_str(), "ISO-8859-2") == 0
            || strcasecmp(aText.Charset().c_str(), "ISO-8859-3") == 0
            || strcasecmp(aText.Charset().c_str(), "ISO-8859-4") == 0
            || strcasecmp(aText.Charset().c_str(), "ISO-8859-9") == 0) {

            encoding = 'Q';
        }
        else {
            encoding = 'B';
        }
    }
    else {
        encoding = 'B';
    }
    return encoding;
}
