// html_ex.cpp
//
// This program takes the name of a file as its parameter.  The file should
// contain one complete email message.  The program writes to standard
// output the conversion of the message to HTML.  This program illustrates
// the use of the EmailMessage and related classes for parsing email
// messages.
//

#include <assert.h>
#include <stdio.h>
#include <string>
#include <mimepp/mimepp.h>
#include "EmailMessage.h"

using std::string;
using std::ifstream;
using std::cout;

#if defined(WIN32)
#define strcasecmp _stricmp
#define snprintf _snprintf
#endif

//
// Look at the MIME type and create a file name.  Really, this just means
// create the right file extension.  There are better ways to do this --
// for example, you could check with the Window registry.  We should
// probably come back later and improve this, at least to handle the most
// common types (PNG, GIF, JPG, WAV, etc).
//
static void fileNameFromContentType(const string& aContentType,
    const string& aContentSubtype, const string& aBase, string& aFileName)
{
    aFileName = aBase;
    const char* contentType = aContentType.c_str();
    const char* contentSubtype = aContentSubtype.c_str();
    if (strcasecmp(contentType, "text") == 0) {
        if (strcasecmp(contentSubtype, "html") == 0) {
            aFileName += ".htm";
        }
    }
    else {
        aFileName += ".dat";
    }
}

//
// Remove the single or double quotes from the string
//
static void stripQuotes(string& aStr)
{
    const char DQUOTE = 34;
    const char SQUOTE = 39;
    int len = (int) aStr.length();
    if (len >= 2) {
        if (aStr[len-1] == DQUOTE || aStr[len-1] == SQUOTE) {
            aStr = aStr.substr(0, len-1);
        }
        if (aStr[0] == DQUOTE || aStr[0] == SQUOTE) {
            aStr = aStr.substr(1);
        }
    }
}

//
// Encode the characters that must be encoded in HTML ('&', '<', and '>')
//
static void encodeHTML(string& aStr)
{
    const string& src = aStr;
    size_t len = src.length();
    string dst;
    dst.reserve((size_t)(1.2*len));
    for (size_t pos=0; pos < len; ++pos) {
        char ch = src[pos];
        if (ch == '&') {
            dst += "&amp;";
        }
        else if (ch == '<') {
            dst += "&lt;";
        }
        else if (ch == '>') {
            dst += "&gt;";
        }
        else {
            dst += ch;
        }
    }
    aStr = dst;
}

//
// Wrap a single long line by inserting '\n' characters
//
static void wrapOneLongLine(int aMaxLineLen, string& aLine)
{
    assert(aMaxLineLen > 0);
    int count = 0;
    string wrappedLine;
    while (aLine.length() > aMaxLineLen) {
        string::size_type pos = aMaxLineLen;
        char ch = aLine[pos];
        while (pos > 0 && ch != ' ' && ch != '\t') {
            --pos;
            ch = aLine[pos];
        }
        while (pos > 0 && (ch == ' ' || ch == '\t')) {
            --pos;
            ch = aLine[pos];
        }
        if (pos > 0) {
            ++pos;
            if (count > 0) {
                wrappedLine += "\n";
            }
            ++count;
            wrappedLine += aLine.substr(0, pos);
            ch = aLine[pos];
            while (ch == ' ' || ch == '\t') {
                ++pos;
                if (pos < aLine.length()) {
                    ch = aLine[pos];
                }
                else {
                    ch = 0;
                }
            }
            aLine = aLine.substr(pos);
        }
        else /* if (pos == 0) */ {
            pos = 0;
            ch = aLine[pos];
            while (ch != ' ' && ch != '\t') {
                ++pos;
                if (pos < aLine.length()) {
                    ch = aLine[pos];
                }
                else {
                    ch = ' ';
                }
            }
            if (count > 0) {
                wrappedLine += "\n";
            }
            ++count;
            wrappedLine += aLine.substr(0, pos);
            if (pos < aLine.length()) {
                ch = aLine[pos];
            }
            else {
                ch = 0;
            }
            while (ch == ' ' || ch == '\t') {
                ++pos;
                if (pos < aLine.length()) {
                    ch = aLine[pos];
                }
                else {
                    ch = 0;
                }
            }
            aLine = aLine.substr(pos);
        }
    }
    if (aLine.length() > 0) {
        if (count > 0) {
            wrappedLine += "\n";
        }
        wrappedLine += aLine;
    }
    aLine = wrappedLine;
}

//
// Wrap all lines in aStr that are longer than aMaxLineLen
//
void wrapLongLines(int aMaxLineLen, string& aStr)
{
    const string& src = aStr;
    string::size_type srcLen = src.length();
    string dst;
    dst.reserve((size_t)(1.2*srcLen));

    //
    // Get first line
    //
    string::size_type lineStart = 0;
    string::size_type lineEnd = 0;
    lineEnd = src.find('\n', lineStart);
    if (lineEnd == string::npos) {
        lineEnd = srcLen;
    }
    string::size_type lineLen = lineEnd - lineStart;
    while (lineStart < srcLen) {
        if (lineLen <= aMaxLineLen) {
            dst += src.substr(lineStart, lineLen);
            dst += "\n";
        }
        else /* if (lineLen > aMaxLineLen) */ {
            string line = src.substr(lineStart, lineLen);
            wrapOneLongLine(aMaxLineLen, line);
            dst += line;
            dst += "\n";
        }
        //
        // Get next line
        //
        lineStart = lineEnd + 1;
        if (lineStart < srcLen) {
            lineEnd = src.find('\n', lineStart);
            if (lineEnd == string::npos) {
                lineEnd = srcLen;
            }
        }
        else /* if (lineStart >= srcLen) */ {
            lineEnd = lineStart;
        }
        lineLen = lineEnd - lineStart;
    }

    aStr = dst;
}


int main(int argc, char** argv)
{
    if (argc < 2) {
        cout << "Please specify file name on command line" << endl;
        exit(1);
    }
    //
    // Initialize the library
    //
#if ! defined(DW_WIN32) || defined(DW_NO_DLL)
    // Note: DwInitialize() is called in DllMain(), if you are using DLLs
    DwInitialize();
#endif
    //
    // Read message from file
    //
    FILE *in = fopen(argv[1], "r");
    if (in == 0) {
        cout << "Can't open file " << argv[1] << endl;
        exit(1);
    }
    fseek(in, 0, SEEK_END);
    int fileSize = (int) ftell(in);
    fseek(in, 0, SEEK_SET);
    DwString messageStr = "";
    messageStr.reserve(fileSize);
    while (1) {
        char ch = getc(in);
        if (feof(in)) {
            break;
        }
        messageStr += ch;
    }
    fclose(in);
    in = 0;
    //
    // Parse the message
    //
    EmailMessage emailMessage;
    emailMessage.ParseFrom(messageStr);
    //
    // From
    //
    string from = emailMessage.Originator().PersonalName();
    stripQuotes(from);
    encodeHTML(from);
    //
    // To
    //
    string to;
    int i;
    for (i=0; i < emailMessage.ToRecipients().Count(); ++i) {
        if (to.length() > 0) {
            to += ", ";
        }
        string name = emailMessage.ToRecipients().Get(i).PersonalName();
        stripQuotes(name);
        to += name;
    }
    encodeHTML(to);
    //
    // Cc
    //
    string cc;
    for (i=0; i < emailMessage.CcRecipients().Count(); ++i) {
        if (cc.length() > 0) {
            cc += ", ";
        }
        string name = emailMessage.CcRecipients().Get(i).PersonalName();
        stripQuotes(name);
        cc += name;
    }
    encodeHTML(cc);
    //
    // Subject
    //
    string subject = emailMessage.Subject().Text();
    encodeHTML(subject);
    //
    // Date
    //
    string date = emailMessage.Date().DisplayString();
    //
    // Memo text
    //
    string memo = emailMessage.MemoText().Text();
    wrapLongLines(76, memo);
    encodeHTML(memo);
    string msgCharset = emailMessage.MemoText().Charset();
    if (strcasecmp(msgCharset.c_str(), "us-ascii") == 0) {
        msgCharset = "iso-8859-1";
    }
    //
    // Attachments
    //
    string attachments;
    for (i=0; i < emailMessage.Attachments().Count(); ++i) {
        const DwString& content = emailMessage.Attachments().Get(i).Content();
        const string& type = emailMessage.Attachments().Get(i).Type();
        const string& subtype = emailMessage.Attachments().Get(i).Subtype();
        int size = emailMessage.Attachments().Get(i).Size();
        string fileName = emailMessage.Attachments().Get(i).FileName();
        if (fileName.length() == 0) {
            char s[20];
            snprintf(s, sizeof(s), "attach-%d", i);
            string base = s;
            fileNameFromContentType(type, subtype, base, fileName);
        }
        // This thing of putting encoded words into parameters (e.g. the
        // file name) is not allowed by MIME, but Netscape does it.  So,
        // let's just deal with it.  -- dws
        if (fileName.length() >= 9
            && fileName[0] == '='
            && fileName[1] == '?'
            && fileName[fileName.length()-2] == '?'
            && fileName[fileName.length()-1] == '=') {
            DwEncodedWord word(fileName.c_str());
            word.Parse();
            fileName = string(word.DecodedText().c_str());
        }
        if (attachments.length() > 0) {
            attachments += " ";
        }
        attachments += "<a href=\"";
        attachments += fileName;
        attachments += "\">";
        attachments += fileName;
        attachments += "</a>";
        FILE *out = fopen(fileName.c_str(), "wb");
        if (out != 0) {
            fwrite(content.data(), 1, size, out);
            fclose(out);
        }
    }
    //
    // Write HTML to standard output
    //
    cout << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
    cout << "<html>\n";
    cout << "<head>\n";
    cout << "<meta charset=\"" << msgCharset << "\">\n";
    cout << "<title>" << subject << "</title>\n";
    cout << "</head>\n";
    cout << "<body>\n";
    cout << "<b>Date:</b> " << date << "<br>\n";
    cout << "<b>From:</b> " << from << "<br>\n";
    cout << "<b>To:</b> " << to << "<br>\n";
    cout << "<b>Subject:</b> " << subject << "<br>\n";
    cout << "<b>Attachments:</b> " << attachments << "<br>\n";
    cout << "<hr>\n";
    cout << "<pre>" << memo << "</pre>\n";
    cout << "</body>\n";
    cout << "</html>\n";

    return 0;
}
