/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org Code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nscore.h"
#include "prthread.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsIIOService.h"
#include "nsIURI.h"
#include "nsIProxyObjectManager.h"
#include "nsProxiedService.h"
#include "nsMsgI18N.h"
#include "nsNativeCharsetUtils.h"
#include "nsIOutputStream.h"

#include "nsMsgBaseCID.h"
#include "nsMsgCompCID.h"

#include "nsIMsgCompose.h"
#include "nsIMsgCompFields.h"
#include "nsIMsgAccountManager.h"

#include "nsNetCID.h"

#include "nsOutlookCompose.h"

#include "OutlookDebugLog.h"

#include "nsMimeTypes.h"
#include "nsMsgUtils.h"

#include "nsOutlookEditor.h"
#include "nsAutoPtr.h"

#include "nsMsgMessageFlags.h"
#include "nsMsgLocalFolderHdrs.h"

#include <algorithm>

static NS_DEFINE_CID( kMsgSendCID, NS_MSGSEND_CID);
static NS_DEFINE_CID( kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID);

// We need to do some calculations to set these numbers to something reasonable!
// Unless of course, CreateAndSendMessage will NEVER EVER leave us in the lurch
#define kHungCount 100000
#define kHungAbortCount 1000

#ifdef IMPORT_DEBUG
static const char *p_test_headers =
"Received: from netppl.fi (IDENT:monitor@get.freebsd.because.microsoftsucks.net [209.3.31.115])\n\
 by mail4.sirius.com (8.9.1/8.9.1) with SMTP id PAA27232;\n\
 Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\
Message-ID: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.fi>\n\
From: \"adsales@qualityservice.com\" <adsales@qualityservice.com>\n\
Subject: Re: Your College Diploma (36822)\n\
Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\
MIME-Version: 1.0\n\
Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\
Content-Transfer-Encoding: 7bit\n\
X-UIDL: 19990517.152941\n\
Status: RO";

static const char *p_test_body =
"Hello world?\n\
";
#else
#define p_test_headers nsnull
#define p_test_body nsnull
#endif

#define kWhitespace "\b\t\r\n "

//////////////////////////////////////////////////////////////////////////////////////////////////

// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and ReadFileState classes
class CCompositionFile {
public:
  // fifoBuffer is used for memory allocation optimization
  // convertCRs controls if we want to convert standalone CRs to CRLFs
  CCompositionFile(nsIFile* aFile, void* fifoBuffer, PRUint32 fifoBufferSize, bool convertCRs=false);

  operator bool() const { return m_fileSize; }

  // Reads up to and including the term sequence, or entire file if term isn't found
  // termSize may be used to include NULLs in the terminator sequences.
  // termSize value of -1 means "zero-terminated string" -> size is calculated with strlen
  nsresult ToString(nsCString& dest, const char* term=0, int termSize=-1);
  nsresult ToStream(nsIOutputStream *dest, const char* term=0, int termSize=-1);
  char LastChar() { return m_lastChar; }
private:
  nsCOMPtr<nsIFile>  m_pFile;
  nsCOMPtr<nsIInputStream> m_pInputStream;
  PRInt64 m_fileSize;
  PRInt64 m_fileReadPos;
  char* m_fifoBuffer;
  PRUint32 m_fifoBufferSize;
  char* m_fifoBufferReadPos; // next character to read
  char* m_fifoBufferWrittenPos; // if we have read less than buffer size then this will show it
  bool m_convertCRs;
  char m_lastChar;

  nsresult EnsureHasDataInBuffer();
  template <class _OutFn> nsresult ToDest(_OutFn dest, const char* term, int termSize);
};

//////////////////////////////////////////////////////////////////////////////////////////////////

// First off, a listener
class OutlookSendListener : public nsIMsgSendListener
{
public:
  OutlookSendListener() {
    m_done = PR_FALSE;
    m_location = nsnull;
  }

  virtual ~OutlookSendListener() { NS_IF_RELEASE( m_location); }

  // nsISupports interface
  NS_DECL_ISUPPORTS

  /* void OnStartSending (in string aMsgID, in PRUint32 aMsgSize); */
  NS_IMETHOD OnStartSending(const char *aMsgID, PRUint32 aMsgSize) {return NS_OK;}

  /* void OnProgress (in string aMsgID, in PRUint32 aProgress, in PRUint32 aProgressMax); */
  NS_IMETHOD OnProgress(const char *aMsgID, PRUint32 aProgress, PRUint32 aProgressMax) {return NS_OK;}

  /* void OnStatus (in string aMsgID, in wstring aMsg); */
  NS_IMETHOD OnStatus(const char *aMsgID, const PRUnichar *aMsg) {return NS_OK;}

  /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile); */
  NS_IMETHOD OnStopSending(const char *aMsgID, nsresult aStatus, const PRUnichar *aMsg,
               nsIFile *returnFile) {
    m_done = PR_TRUE;
    NS_IF_ADDREF(m_location = returnFile);
    return NS_OK;
  }

   /* void OnSendNotPerformed */
   NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) {return NS_OK;}

  /* void OnGetDraftFolderURI (); */
  NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;}

  static nsresult CreateSendListener( nsIMsgSendListener **ppListener);
  void Reset() { m_done = PR_FALSE; NS_IF_RELEASE( m_location);}

public:
  PRBool m_done;
  nsIFile * m_location;
};

NS_IMPL_THREADSAFE_ISUPPORTS1(OutlookSendListener, nsIMsgSendListener)

nsresult OutlookSendListener::CreateSendListener( nsIMsgSendListener **ppListener)
{
  NS_PRECONDITION(ppListener != nsnull, "null ptr");
  NS_ENSURE_ARG_POINTER(ppListener);

  *ppListener = new OutlookSendListener();
  if (! *ppListener)
    return NS_ERROR_OUT_OF_MEMORY;

  NS_ADDREF(*ppListener);
  return NS_OK;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

#define hackBeginA "begin"
#define hackBeginW NS_L(hackBeginA)
#define hackEndA "\015\012end"
#define hackEndW NS_L(hackEndA)
#define hackCRLFA "crlf"
#define hackCRLFW NS_L(hackCRLFA)
#define hackAmpersandA "amp"
#define hackAmpersandW NS_L(hackAmpersandA)

nsOutlookCompose::nsOutlookCompose()
{
  m_pListener = nsnull;
  m_pMsgSend = nsnull;
  m_pSendProxy = nsnull;
  m_pMsgFields = nsnull;
  m_pIdentity = nsnull;

  m_optimizationBufferSize = 16*1024;
  m_optimizationBuffer = new char[m_optimizationBufferSize];
}

nsOutlookCompose::~nsOutlookCompose()
{
  NS_IF_RELEASE(m_pSendProxy);
  NS_IF_RELEASE(m_pMsgSend);
  NS_IF_RELEASE(m_pListener);
  NS_IF_RELEASE(m_pMsgFields);
  if (m_pIdentity) {
    nsresult rv = m_pIdentity->ClearAllValues();
    NS_ASSERTION(NS_SUCCEEDED(rv),"failed to clear values");
    if (NS_FAILED(rv))
      return;

    NS_RELEASE(m_pIdentity);
  }
  delete[] m_optimizationBuffer;
  ClearReplaceCids();
}

void nsOutlookCompose::ClearReplaceCids()
{
  std::for_each(m_replacedCids.begin(), m_replacedCids.end(), ClearReplaceCid);
  m_replacedCids.clear();
}

nsresult nsOutlookCompose::CreateIdentity( void)
{
  if (m_pIdentity)
    return NS_OK;

  nsresult rv;
  NS_WITH_PROXIED_SERVICE(nsIMsgAccountManager, accMgr,
                          NS_MSGACCOUNTMANAGER_CONTRACTID,
                          NS_PROXY_TO_MAIN_THREAD, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = accMgr->CreateIdentity(&m_pIdentity);
  nsString name;
  name.AssignLiteral("Import Identity");
  if (m_pIdentity) {
    m_pIdentity->SetFullName(name);
    m_pIdentity->SetIdentityName(name);
    m_pIdentity->SetEmail(NS_LITERAL_CSTRING("import@import.service"));
  }

  return rv;
}

nsresult nsOutlookCompose::CreateComponents( void)
{
  nsresult rv = NS_OK;

  NS_IF_RELEASE(m_pMsgFields);
  if (!m_pMsgSend) {
    rv = CallCreateInstance( kMsgSendCID, &m_pMsgSend);
    if (NS_SUCCEEDED( rv) && m_pMsgSend) {
      rv = NS_GetProxyForObject( NS_PROXY_TO_MAIN_THREAD, NS_GET_IID(nsIMsgSend),
                  m_pMsgSend, NS_PROXY_SYNC, (void **)&m_pSendProxy);
      if (NS_FAILED( rv)) {
        m_pSendProxy = nsnull;
        NS_RELEASE(m_pMsgSend);
      }
    }
  }
  if (!m_pListener && NS_SUCCEEDED( rv)) {
    rv = OutlookSendListener::CreateSendListener( &m_pListener);
  }

  if (NS_SUCCEEDED(rv) && m_pMsgSend) {
      rv = CallCreateInstance( kMsgCompFieldsCID, &m_pMsgFields);
    if (NS_SUCCEEDED(rv) && m_pMsgFields) {
      // IMPORT_LOG0( "nsOutlookCompose - CreateComponents succeeded\n");
      m_pMsgFields->SetForcePlainText( PR_FALSE);
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
}

nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg)
{
  nsresult rv = CreateComponents();
  NS_ENSURE_SUCCESS(rv, rv);
  rv = CreateIdentity();
  NS_ENSURE_SUCCESS(rv, rv);

  // IMPORT_LOG0( "Outlook Compose created necessary components\n");

  CMapiMessageHeaders* headers = msg.GetHeaders();

  nsString val;
  headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, val);
  m_pMsgFields->SetFrom(val);
  headers->UnfoldValue(CMapiMessageHeaders::hdrTo, val);
  m_pMsgFields->SetTo(val);
  headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, val);
  m_pMsgFields->SetSubject(val);
  m_pMsgFields->SetCharacterSet( msg.GetBodyCharset() );
  headers->UnfoldValue(CMapiMessageHeaders::hdrCc, val);
  m_pMsgFields->SetCc(val);
  headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, val);
  m_pMsgFields->SetReplyTo(val);

  nsCAutoString asciiHeaderVal;
  LossyCopyUTF16toASCII(headers->Value(CMapiMessageHeaders::hdrMessageID),
                        asciiHeaderVal); // Message-Id cannot fold
  m_pMsgFields->SetMessageId(asciiHeaderVal.get());
  // We only use those headers that may need to be processed by Thunderbird
  // to create a good rfc822 document, or need to be encoded (like To and Cc).
  // These will replace the originals on import. All the other headers
  // will be copied to the destination unaltered in CopyComposedMessage().

  nsMsgAttachedFile *pAttach = msg.GetAttachments();

  nsString bodyW;
  // Bug 593907
  if (GenerateHackSequence(msg.GetBody(), msg.GetBodyLen()))
    HackBody(msg.GetBody(), msg.GetBodyLen(), bodyW);
  // End Bug 593907

  // We only get the editor interface when there's embedded content.
  // Otherwise pEditor remains NULL. That way we only import with the pseudo
  // editor when it helps.
  nsRefPtr<nsOutlookEditor> pOutlookEditor = new nsOutlookEditor(bodyW.IsEmpty() ? msg.GetBody() : bodyW.get());
  nsCOMPtr<nsIEditor> pEditor;

  if (msg.BodyIsHtml()) {
    for (unsigned int i = 0; i<msg.EmbeddedAttachmentsCount(); i++) {
      nsIURI *uri;
      const char* cid;
      const char* name;
      if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name))
        pOutlookEditor->AddEmbeddedImage(uri, NS_ConvertASCIItoUTF16(cid).get(), NS_ConvertASCIItoUTF16(name).get());
    }
  }

  nsCString bodyA;
  if (pOutlookEditor && pOutlookEditor->HasEmbeddedContent()) {
    // There's embedded content that we need to import, so query for the editor interface
    pEditor = do_QueryObject(pOutlookEditor);
  }
  else {
    if (bodyW.IsEmpty())
      msg.GetBody(bodyA);
    else
      nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA);
  }

  // IMPORT_LOG0( "Outlook compose calling CreateAndSendMessage\n");
  rv = m_pSendProxy->CreateAndSendMessage(
                    pEditor,                      // editor shell
                    m_pIdentity,                  // dummy identity
                    nsnull,                       // account key
                    m_pMsgFields,                 // message fields
                    PR_FALSE,                     // digest = NO
                    PR_TRUE,                      // dont_deliver = YES, make a file
                    mode,                         // mode
                    nsnull,                       // no message to replace
                    msg.BodyIsHtml() ? "text/html" : "text/plain",           // body type
                    bodyA.get(),                  // body pointer
                    bodyA.Length(),               // body length
                    nsnull,                       // remote attachment data
                    pAttach,                      // local attachments
                    nsnull,                       // related part
                    nsnull,                       // parent window
                    nsnull,                       // progress listener
                    m_pListener,                  // listener
                    nsnull,                       // password
                    EmptyCString(),               // originalMsgURI
                    nsnull);                      // message compose type
  // IMPORT_LOG0( "Returned from CreateAndSendMessage\n");

  OutlookSendListener *pListen = (OutlookSendListener *)m_pListener;
  if (NS_FAILED(rv)) {
    IMPORT_LOG1( "*** Error, CreateAndSendMessage FAILED: 0x%lx\n", rv);
  }
  else {
    // wait for the listener to get done!
    PRInt32 abortCnt = 0;
    PRInt32 cnt = 0;
    PRInt32 sleepCnt = 1;
    while (!pListen->m_done && (abortCnt < kHungAbortCount)) {
      PR_Sleep(sleepCnt);
      cnt++;
      if (cnt > kHungCount) {
        abortCnt++;
        sleepCnt *= 2;
        cnt = 0;
      }
    }

    if (abortCnt >= kHungAbortCount) {
      IMPORT_LOG0( "**** Create and send message hung\n");
//      IMPORT_LOG1( "Headers: %s\n", m_Headers.get());
//      IMPORT_LOG1( "Body: %s\n", m_Body.get());
      rv = NS_ERROR_FAILURE;
    }
  }

  if (pAttach)
    msg.DisposeAttachments(pAttach);

  if (pListen->m_location) {
    pListen->m_location->Clone(pMsg);
    rv = NS_OK;
  }
  else {
    rv = NS_ERROR_FAILURE;
    IMPORT_LOG0( "*** Error, Outlook compose unsuccessful\n");
  }

  pListen->Reset();

  if (NS_SUCCEEDED(rv) && pEditor) {
    PRUint32 embedCount = pOutlookEditor->EmbeddedObjectsCount();
    for (PRUint32 i=0; i<embedCount; i++) {
      CidReplacePair *pair = new CidReplacePair;
      if (NS_SUCCEEDED(pOutlookEditor->GetCids(i, pair->cidOrig, pair->cidNew)))
        m_replacedCids.push_back(pair);
      else delete pair;
    }
  }

  return rv;
}

nsOutlookCompose::ReplaceCidInLine::ReplaceCidInLine(nsCString& line)
  : m_line(line)
{
  // If the line begins with Content-ID: string, process it! Otherwise, no need to waste time
  m_finishedReplacing = (line.Compare("Content-ID:", PR_TRUE, 11) != 0);
}

void nsOutlookCompose::ReplaceCidInLine::operator () (const CidReplacePair* pair)
{
  if (m_finishedReplacing)
    return; // Only one cid per line possible!
  PRInt32 pos = m_line.Find(pair->cidNew, PR_FALSE, 12);
  if (pos != kNotFound) {
    m_finishedReplacing = true; // Stop further search
    m_line.Replace(pos, pair->cidNew.Length(), pair->cidOrig);
  }
}

nsresult nsOutlookCompose::CopyComposedMessage(nsIFile *pSrc,
                                               nsIOutputStream *pDst,
                                               CMapiMessage& origMsg)
{
  // I'm unsure if we really need the convertCRs feature here.
  // The headers in the file are generated by TB, the body was generated by rtf reader that always used CRLF,
  // and the attachments were processed by TB either... However, I let it stay as it was in the original code.
  CCompositionFile f(pSrc, m_optimizationBuffer, m_optimizationBufferSize, true);
  if (!f) {
    IMPORT_LOG0( "*** Error, unexpected zero file size for composed message\n");
    return NS_ERROR_FAILURE;
  }

  // The "From ..." separates the messages. Without it, TB cannot see the messages in the mailbox file.
  // Thus, the lines that look like "From ..." in the message must be escaped (see EscapeFromSpaceLine())
  int fromLineLen;
  const char* fromLine = origMsg.GetFromLine(fromLineLen);
  PRUint32 written;
  nsresult rv = pDst->Write( fromLine, fromLineLen, &written);

  // Bug 219269
  // Write out the x-mozilla-status headers.
  char statusLine[50];
  PRUint32 msgFlags = 0;
  if (origMsg.IsRead())
    msgFlags |= nsMsgMessageFlags::Read;
  if (!origMsg.FullMessageDownloaded())
    msgFlags |= nsMsgMessageFlags::Partial;
  if (origMsg.IsForvarded())
    msgFlags |= nsMsgMessageFlags::Forwarded;
  if (origMsg.IsReplied())
    msgFlags |= nsMsgMessageFlags::Replied;
  if (origMsg.HasAttach())
    msgFlags |= nsMsgMessageFlags::Attachment;
  _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
  rv = pDst->Write(statusLine, strlen(statusLine), &written);
  _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
  rv = pDst->Write(statusLine, strlen(statusLine), &written);
  // End Bug 219269

  // well, isn't this a hoot!
  // Read the headers from the new message, get the ones we like
  // and write out only the headers we want from the new message,
  // along with all of the other headers from the "old" message!

  nsCString newHeadersStr;
  rv = f.ToString(newHeadersStr, MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers
  NS_ENSURE_SUCCESS(rv, rv);
  UpdateHeaders(*origMsg.GetHeaders(), newHeadersStr.get());
  rv = origMsg.GetHeaders()->ToStream(pDst);
  NS_ENSURE_SUCCESS(rv, rv);

  // Bug 593907
  if (!m_hackedPostfix.IsEmpty()) {
    nsCString hackedPartEnd;
    LossyCopyUTF16toASCII(m_hackedPostfix, hackedPartEnd);
    hackedPartEnd.Insert(hackEndA, 0);
    nsCString body;
    rv = f.ToString(body, hackedPartEnd.get(), hackedPartEnd.Length());
    UnhackBody(body);
    EscapeFromSpaceLine(pDst, const_cast<char*>(body.get()), body.get()+body.Length());
  }
  // End Bug 593907

  // I use the terminating sequence here to avoid a possible situation when a "From " line
  // gets split over two sequential reads and thus will not be escaped.
  // This is done by reading up to CRLF (one line every time), though it may be slower

  // Here I revert the changes that were made when the multipart/related message
  // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of
  // attachments were replaced with new ones.
  nsCString line;
  while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) {
    if (!m_replacedCids.empty())
      std::for_each(m_replacedCids.begin(), m_replacedCids.end(), ReplaceCidInLine(line));
    EscapeFromSpaceLine(pDst, const_cast<char*>(line.get()), line.get()+line.Length());
  }

  if (f.LastChar() != 0x0A) {
    rv = pDst->Write( MSG_LINEBREAK, 2, &written);
    if (written != 2)
      rv = NS_ERROR_FAILURE;
  }

  return rv;
}

nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode,
                                          CMapiMessage &msg,
                                          nsIOutputStream *pDst)
{
  nsCOMPtr<nsIFile> compositionFile;
  nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = CopyComposedMessage(compositionFile, pDst, msg);
  compositionFile->Remove(PR_FALSE);
  if (NS_FAILED( rv)) {
    IMPORT_LOG0( "*** Error copying composed message to destination mailbox\n");
  }
  return rv;
}

void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders,
                                    const CMapiMessageHeaders& newHeaders,
                                    CMapiMessageHeaders::SpecialHeader header,
                                    bool addIfAbsent)
{
  if (!addIfAbsent && !oldHeaders.Value(header))
    return;
  const wchar_t* newVal = newHeaders.Value(header);
  if (newVal) oldHeaders.SetValue(header, newVal);
  // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type: text/plain"
  //              so the body text can be displayed normally (instead of in an attachment).
  if (header == CMapiMessageHeaders::hdrContentType)
    if (!wcsicmp(oldHeaders.Value(header), L"application/ms-tnef"))
      oldHeaders.SetValue(header, L"text/plain");
}

void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders)
{
  // Well, ain't this a peach?
  // This is rather disgusting but there really isn't much to be done about it....

  // 1. For each "old" header, replace it with the new one if we want,
  // then right it out.
  // 2. Then if we haven't written the "important" new headers, write them out
  // 3. Terminate the headers with an extra eol.

  // Important headers:
  //  "Content-type",
  //  "MIME-Version",
  //  "Content-transfer-encoding"
  // consider "X-Accept-Language"?

  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentTransferEncoding);

  // Other replaced headers (only if they exist):
  //  "From",
  //  "To",
  //  "Subject",
  //  "Reply-to",
  //  "Cc"

  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false);
  UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false);
}

// Bug 593907
// This is just a workaround of the deficiency of the nsMsgComposeAndSend::EnsureLineBreaks().
// The import from Outlook will stay OK (I hope), but other messages may still suffer.
// However, I cannot deny the possibility that the (possible) recode of the body
// may interfere with this hack. A possible scenario is if a multi-byte character will either
// contain 0x0D 0x0A sequence, or end with 0x0D, after which MAC-style standalone LF will go.
// I hope that this possibility is insignificant (eg, utf-8 doesn't contain such sequences).
// This hack will slow down the import, but as the import is one-time procedure, I hope that
// the user will agree to wait a little longer to get better results.

// The process of composing the message differs depending on whether the editor is present or not.
// If the editor is absent, the "attachment1_body" parameter of CreateAndSendMessage() is taken as is,
// while in the presence o the editor, the body that is taken from it is further processed in the
// nsMsgComposeAndSend::GetBodyFromEditor(). Specifically, the TXTToHTML::ScanHTML() first calls
// UnescapeStr() to properly handle a limited number of HTML character entities (namely &amp; &lt; &gt; &quot;)
// and then calls ScanTXT() where escapes all ampersands and quotes again. As the UnescapeStr() works so
// selectively (i.e. handling only a subset of valid entities), the so often seen "&nbsp;" becomes "&amp;nbsp;"
// in the resulting body, which leads to text "&nbsp;" interspersed all over the imported mail. The same
// applies to html &#XXXX; (where XXXX is unicode codepoint).
// See also Bug 503690, where the same issue in Eudora import is reported.
// By the way, the root of the Bug 359303 lies in the same place - the nsMsgComposeAndSend::GetBodyFromEditor()
// changes the 0xA0 codes to 0x20 when it converts the body to plain text.
// We scan the body here to find all the & and convert them to the safe character sequense to revert later.

void nsOutlookCompose::HackBody(const wchar_t* orig, size_t origLen, nsString& hack)
{
  hack.SetCapacity(static_cast<size_t>(origLen*1.4));
  hack.Assign(hackBeginW);
  hack.Append(m_hackedPostfix);

  while (*orig) {
    if (*orig == L'&') {
      hack.Append(hackAmpersandW);
      hack.Append(m_hackedPostfix);
    } else if ((*orig == L'\x0D') && (*(orig+1) == L'\x0A')) {
      hack.Append(hackCRLFW);
      hack.Append(m_hackedPostfix);
      ++orig;
    } else
      hack.Append(*orig);
    ++orig;
  }

  hack.Append(hackEndW);
  hack.Append(m_hackedPostfix);
}

void nsOutlookCompose::UnhackBody(nsCString& txt)
{
  nsCString hackedPostfixA;
  LossyCopyUTF16toASCII(m_hackedPostfix, hackedPostfixA);

  nsCString hackedString(hackBeginA);
  hackedString.Append(hackedPostfixA);
  PRInt32 begin = txt.Find(hackedString);
  if (begin == kNotFound)
    return;
  txt.Cut(begin, hackedString.Length());

  hackedString.Assign(hackEndA);
  hackedString.Append(hackedPostfixA);
  PRInt32 end = txt.Find(hackedString, PR_FALSE, begin);
  if (end == kNotFound)
    return; // ?
  txt.Cut(end, hackedString.Length());

  // 1. Remove all CRLFs from the selected range
  PRInt32 i = begin;
  while (i < end) {
    PRInt32 r = txt.Find(MSG_LINEBREAK, PR_FALSE, i, end-i);
    if (r == kNotFound)
      break;

    txt.Cut(r, 2);
    end -= 2;
    i = r;
  }

  // 2. Restore the original CRLFs
  hackedString.Assign(hackCRLFA);
  hackedString.Append(hackedPostfixA);
  i = begin;
  while (i < end) {
    PRInt32 r = txt.Find(hackedString, PR_FALSE, i, end-i);
    if (r == kNotFound)
      break;

    txt.Replace(r, hackedString.Length(), MSG_LINEBREAK, 2);
    end -= hackedString.Length()-2;
    i = r+2;
  }

  // 3. Restore the original ampersands
  hackedString.Assign(hackAmpersandA);
  hackedString.Append(hackedPostfixA);
  i = begin;
  while (i < end) {
    PRInt32 r = txt.Find(hackedString, PR_FALSE, i, end-i);
    if (r == kNotFound)
      break;

    txt.Replace(r, hackedString.Length(), '&');
    end -= hackedString.Length()-1;
    i = r+1;
  }
}

bool nsOutlookCompose::GenerateHackSequence(const wchar_t* body, size_t origLen)
{
  nsDependentString nsBody(body, origLen);
  const wchar_t* hack_base = L"hacked";
  int i = 0;
  do {
    if (++i == 0) { // Cycle complete :) - could not generate an unique string
      m_hackedPostfix.Truncate();
      return false;
    }
    m_hackedPostfix.Assign(hack_base);
    m_hackedPostfix.AppendInt(i);
  } while (nsBody.Find(m_hackedPostfix) != kNotFound);

  return true;
}
// End Bug 593907

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer,
                                   PRUint32 fifoBufferSize, bool convertCRs)
  : m_pFile(aFile), m_fileReadPos(0),
    m_fifoBuffer(static_cast<char*>(fifoBuffer)),
    m_fifoBufferSize(fifoBufferSize),
    m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)),
    m_fifoBufferWrittenPos(static_cast<char*>(fifoBuffer)),
    m_convertCRs(convertCRs),
    m_lastChar(0)
{
  m_pFile->GetFileSize(&m_fileSize);
  if (!m_fileSize) {
    IMPORT_LOG0( "*** Error, unexpected zero file size for composed message\n");
    return;
  }

  nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile);
  if (NS_FAILED(rv)) {
    IMPORT_LOG0( "*** Error, unable to open composed message file\n");
    return;
  }
}

nsresult CCompositionFile::EnsureHasDataInBuffer()
{
  if (m_fifoBufferReadPos < m_fifoBufferWrittenPos)
    return NS_OK;
  // Populate the buffer with new data!
  PRUint32 count = m_fifoBufferSize;
  if ((m_fileReadPos + count) > m_fileSize)
    count = m_fileSize - m_fileReadPos;
  if (!count)
    return NS_ERROR_FAILURE; // Isn't there a "No more data" error?

  PRUint32 bytesRead = 0;
  nsresult rv = m_pInputStream->Read( m_fifoBuffer, count, &bytesRead);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!bytesRead || (bytesRead > count))
    return( NS_ERROR_FAILURE);
  m_fifoBufferWrittenPos = m_fifoBuffer+bytesRead;
  m_fifoBufferReadPos = m_fifoBuffer;
  m_fileReadPos += bytesRead;

  return NS_OK;
}

const char CR = '\x0D', LF = '\x0A';

class CTermGuard {
public:
  CTermGuard(const char* term, int termSize)
    : m_term(term),
    m_termSize(term ? ((termSize!=-1) ? termSize : strlen(term)) : 0),
    m_matchPos(0)
  {}

   // if the guard triggered
  inline bool IsTriggered() const {
    return m_termSize && (m_matchPos == m_termSize); }
  // indicates if the guard has something to check
  inline bool IsChecking() const { return m_termSize; } 

  bool Check(char c) // returns true only if the whole sequence passed
  {
    if (!m_termSize) // no guard
      return false;
    if (m_matchPos >= m_termSize) // check past success!
      m_matchPos = 0;
    if (m_term[m_matchPos] == c) { // Sequence continues
      return (++m_matchPos == m_termSize); // If equal then sequence complete!
    }
    // Sequence broken
    m_matchPos = 0;
    return false;
  }
private:
  const char* m_term;
  int m_termSize;
  int m_matchPos;
};

template <class _OutFn>
nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize)
{
  CTermGuard guard(term, termSize);

  // We already know the required string size, so reduce future reallocations
  if (!guard.IsChecking() && !m_convertCRs) 
    dest.SetCapacity(m_fileSize - m_fileReadPos);

  bool wasCR = false;
  char c = 0;
  nsresult rv;
  while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) {
    if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm
      dest.Append(m_fifoBufferReadPos, m_fifoBufferWrittenPos-m_fifoBufferReadPos);
    }
    else { // Check character by character to convert CRs and find terminating sequence
      while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) {
        c = *m_fifoBufferReadPos;
        if (m_convertCRs && wasCR) {
          wasCR = false;
          if (c != LF) {
            dest.Append(&LF, 1);
            if (guard.Check(LF)) {
              c = LF; // save last char
              break;
            }
          }
        }
        dest.Append(&c, 1);
        m_fifoBufferReadPos++;

        if (guard.Check(c))
          break;

        if (m_convertCRs && (c == CR))
          wasCR = true;
      }
      if (guard.IsTriggered())
        break;
    }
  }

  // check for trailing CR (only if caller didn't specify the terminating sequence that ends with CR -
  // in this case he knows what he does!)
  if (m_convertCRs && !guard.IsTriggered() && (c == CR)) {
    c = LF;
    dest.Append(&c, 1);
  }

  NS_ENSURE_SUCCESS(rv, rv);

  m_lastChar = c;
  return NS_OK;
}

class dest_nsCString {
public:
  dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); }
  void SetCapacity(PRInt32 sz) { m_str.SetCapacity(sz); }
  nsresult Append(const char* buf, PRUint32 count) {
    m_str.Append(buf, count); return NS_OK; }
private:
  nsCString& m_str;
};

class dest_Stream {
public:
  dest_Stream(nsIOutputStream *dest) : m_stream(dest) {}
  void SetCapacity(PRInt32) { /*do nothing*/ }
  // const_cast here is due to the poor design of the EscapeFromSpaceLine()
  // that requires a non-constant pointer while doesn't modify its data
  nsresult Append(const char* buf, PRUint32 count) {
    return EscapeFromSpaceLine(m_stream, const_cast<char*>(buf), buf+count); }
private:
  nsIOutputStream *m_stream;
};

nsresult CCompositionFile::ToString(nsCString& dest, const char* term,
                                    int termSize)
{
  return ToDest(dest_nsCString(dest), term, termSize);
}

nsresult CCompositionFile::ToStream(nsIOutputStream *dest, const char* term,
                                    int termSize)
{
  return ToDest(dest_Stream(dest), term, termSize);
}
