/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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):
 *   Jean-Francois Ducarroz <ducarroz@netscape.com>
 *   Ben Bucksch <mozilla@bucksch.org>
 *   Håkan Waara <hwaara@chello.se>
 *   Pierre Phaneuf <pp@ludusdesign.com>
 *   Masayuki Nakano <masayuki@d-toybox.com>
 *
 * 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 "nsMsgCompose.h"

#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsIDOMNode.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsIDOMHTMLImageElement.h"
#include "nsIDOMHTMLLinkElement.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsISelection.h"
#include "nsISelectionController.h"
#include "nsIDOMNamedNodeMap.h"
#include "nsMsgI18N.h"
#include "nsICharsetConverterManager.h"
#include "nsMsgCompCID.h"
#include "nsMsgQuote.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIDocumentEncoder.h"    // for editor output flags
#include "nsXPIDLString.h"
#include "nsIMsgHeaderParser.h"
#include "nsMsgCompUtils.h"
#include "nsIMsgStringService.h"
#include "nsMsgComposeStringBundle.h"
#include "nsSpecialSystemDirectory.h"
#include "nsMsgSend.h"
#include "nsMailHeaders.h"
#include "nsMsgPrompts.h"
#include "nsMimeTypes.h"
#include "nsICharsetConverterManager.h"
#include "nsTextFormatter.h"
#include "nsIPlaintextEditor.h"
#include "nsIHTMLEditor.h"
#include "nsIEditorMailSupport.h"
#include "nsEscape.h"
#include "plstr.h"
#include "nsIDocShell.h"
#include "nsIRDFService.h"
#include "nsRDFCID.h"
#include "nsAbBaseCID.h"
#include "nsIAddrDatabase.h"
#include "nsIAddrBookSession.h"
#include "nsIAddressBook.h"
#include "nsCExternalHandlerService.h"
#include "nsIMIMEService.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIWindowMediator.h"
#include "nsISupportsArray.h"
#include "nsCOMArray.h"
#include "nsIIOService.h"
#include "nsIURL.h"
#include "nsIMsgMailSession.h"
#include "nsMsgBaseCID.h"
#include "nsIPrompt.h"
#include "nsMsgMimeCID.h"
#include "nsCOMPtr.h"
#include "nsDateTimeFormatCID.h"
#include "nsIDateTimeFormat.h"
#include "nsILocaleService.h"
#include "nsILocale.h"
#include "nsMsgComposeService.h"
#include "nsIMsgComposeProgressParams.h"
#include "nsMsgUtils.h"
#include "nsIMsgImapMailFolder.h"
#include "nsImapCore.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsNetUtil.h"
#include "nsMsgSimulateError.h"
#include "nsIAddrDatabase.h"
#include "nsILocalFile.h"
#include "nsIContentViewer.h"
#include "nsIMarkupDocumentViewer.h"
#include "nsIMsgMdnGenerator.h"
#include "plbase64.h"
#include "nsIUTF8ConverterService.h"
#include "nsUConvCID.h"
#include "nsIUnicodeNormalizer.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgProgress.h"
#include "nsMsgFolderFlags.h"

// Defines....
static NS_DEFINE_CID(kDateTimeFormatCID, NS_DATETIMEFORMAT_CID);

static void GetReplyHeaderInfo(PRInt32* reply_header_type, 
                               nsString& reply_header_locale,
                               nsXPIDLString& reply_header_authorwrote,
                               nsXPIDLString& reply_header_ondate,
                               nsString& reply_header_separator,
                               nsString& reply_header_colon,
                               nsXPIDLString& reply_header_originalmessage)
{
  nsresult  rv;
  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));

  *reply_header_type = 1;
  if(NS_SUCCEEDED(rv)) {
    prefBranch->GetIntPref("mailnews.reply_header_type", reply_header_type);

    NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_locale", EmptyString(), reply_header_locale);
    NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_authorwrote", NS_LITERAL_STRING("%s wrote"), reply_header_authorwrote);
    NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_ondate", NS_LITERAL_STRING("On %s"), reply_header_ondate);
    NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_separator", NS_LITERAL_STRING(", "), reply_header_separator);
    NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_colon", NS_LITERAL_STRING(":"), reply_header_colon);
    NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_originalmessage", NS_LITERAL_STRING("--- Original Message ---"), reply_header_originalmessage);
  }
}

static nsresult RemoveDuplicateAddresses(const char * addresses, const char * anothersAddresses, PRBool removeAliasesToMe, char** newAddress)
{
  nsresult rv;

  nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));
  if (parser)
    rv= parser->RemoveDuplicateAddresses("UTF-8", addresses, anothersAddresses, removeAliasesToMe, newAddress);
  else
    rv = NS_ERROR_FAILURE;

  return rv;
}

static void TranslateLineEnding(nsString& data)
{
  PRUnichar* rPtr;   //Read pointer
  PRUnichar* wPtr;   //Write pointer
  PRUnichar* sPtr;   //Start data pointer
  PRUnichar* ePtr;   //End data pointer

  rPtr = wPtr = sPtr = data.BeginWriting();
  ePtr = rPtr + data.Length();

  while (rPtr < ePtr)
  {
    if (*rPtr == 0x0D)
      if (rPtr + 1 < ePtr && *(rPtr + 1) == 0x0A)
      {
        *wPtr = 0x0A;
        rPtr ++;
      }
      else
        *wPtr = 0x0A;
    else
      *wPtr = *rPtr;

    rPtr ++;
    wPtr ++;
  }

  data.SetLength(wPtr - sPtr);
}

static void GetTopmostMsgWindowCharacterSet(nsXPIDLCString& charset, PRBool* charsetOverride)
{
  // HACK: if we are replying to a message and that message used a charset over ride
  // (as specified in the top most window (assuming the reply originated from that window)
  // then use that over ride charset instead of the charset specified in the message
  nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID));          
  if (mailSession)
  {
    nsCOMPtr<nsIMsgWindow>    msgWindow;
    mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
    if (msgWindow)
    {
      msgWindow->GetMailCharacterSet(getter_Copies(charset));
      msgWindow->GetCharsetOverride(charsetOverride);
    }
  }
}

nsMsgCompose::nsMsgCompose()
{
#if defined(DEBUG_ducarroz)
  printf("CREATE nsMsgCompose: %x\n", this);
#endif

  mQuotingToFollow = PR_FALSE;
  mInsertingQuotedContent = PR_FALSE;
  mWhatHolder = 1;
  m_window = nsnull;
  m_editor = nsnull;
  mQuoteStreamListener=nsnull;
  mCharsetOverride = PR_FALSE;
  mDeleteDraft = PR_FALSE;
  m_compFields = nsnull;    //m_compFields will be set during nsMsgCompose::Initialize
  mType = nsIMsgCompType::New;

  // For TagConvertible
  // Read and cache pref
  mConvertStructs = PR_FALSE;
  nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
  if (prefBranch)
    prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs);

  m_composeHTML = PR_FALSE;
  mRecycledWindow = PR_TRUE;
}


nsMsgCompose::~nsMsgCompose()
{
#if defined(DEBUG_ducarroz)
  printf("DISPOSE nsMsgCompose: %x\n", this);
#endif

  NS_IF_RELEASE(m_compFields);
  NS_IF_RELEASE(mQuoteStreamListener);
}

/* the following macro actually implement addref, release and query interface for our component. */
NS_IMPL_ISUPPORTS2(nsMsgCompose, nsIMsgCompose, nsISupportsWeakReference)

//
// Once we are here, convert the data which we know to be UTF-8 to UTF-16
// for insertion into the editor
//
nsresult 
GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, PRInt32 &aOffset)
{
  NS_ASSERTION((aChild && aParent), "bad args");
  nsresult result = NS_ERROR_NULL_POINTER;
  if (aChild && aParent)
  {
    nsCOMPtr<nsIDOMNodeList> childNodes;
    result = aParent->GetChildNodes(getter_AddRefs(childNodes));
    if ((NS_SUCCEEDED(result)) && (childNodes))
    {
      PRInt32 i=0;
      for ( ; NS_SUCCEEDED(result); i++)
      {
        nsCOMPtr<nsIDOMNode> childNode;
        result = childNodes->Item(i, getter_AddRefs(childNode));
        if ((NS_SUCCEEDED(result)) && (childNode))
        {
          if (childNode.get()==aChild)
          {
            aOffset = i;
            break;
          }
        }
        else if (!childNode)
          result = NS_ERROR_NULL_POINTER;
      }
    }
    else if (!childNodes)
      result = NS_ERROR_NULL_POINTER;
  }
  return result;
}

nsresult 
GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, PRInt32 *outOffset)
{
  NS_ASSERTION((outParent && outOffset), "bad args");
  nsresult result = NS_ERROR_NULL_POINTER;
  if (inChild && outParent && outOffset)
  {
    result = inChild->GetParentNode(getter_AddRefs(*outParent));
    if ( (NS_SUCCEEDED(result)) && (*outParent) )
    {
      result = GetChildOffset(inChild, *outParent, *outOffset);
    }
  }

  return result;
}

PRBool nsMsgCompose::IsEmbeddedObjectSafe(const char * originalScheme,
                                          const char * originalHost,
                                          const char * originalPath,
                                          nsIDOMNode * object)
{
  nsresult rv;

  nsCOMPtr<nsIDOMHTMLImageElement> image;
  nsCOMPtr<nsIDOMHTMLLinkElement> link;
  nsCOMPtr<nsIDOMHTMLAnchorElement> anchor;
  nsAutoString objURL;

  if (!object || !originalScheme || !originalPath) //having a null host is ok...
    return PR_FALSE;

  if ((image = do_QueryInterface(object)))
  {
    if (NS_FAILED(image->GetSrc(objURL)))
      return PR_FALSE;
  }
  else if ((link = do_QueryInterface(object)))
  {
    if (NS_FAILED(link->GetHref(objURL)))
      return PR_FALSE;
  }
  else if ((anchor = do_QueryInterface(object)))
  {
    if (NS_FAILED(anchor->GetHref(objURL)))
      return PR_FALSE;
  }
  else
    return PR_FALSE;

  if (!objURL.IsEmpty())
  {
    nsCOMPtr<nsIURI> uri;
    rv = NS_NewURI(getter_AddRefs(uri), objURL);
    if (NS_SUCCEEDED(rv) && uri)
    {
      nsCAutoString scheme;
      rv = uri->GetScheme(scheme);
      if (NS_SUCCEEDED(rv) && (nsCRT::strcasecmp(scheme.get(), originalScheme) == 0))
      {
        nsCAutoString host;
        rv = uri->GetAsciiHost(host);
        // mailbox url don't have a host therefore don't be too strict.
        if (NS_SUCCEEDED(rv) && (host.IsEmpty() || originalHost || (nsCRT::strcasecmp(host.get(), originalHost) == 0)))
        {
          nsCAutoString path;
          rv = uri->GetPath(path);
          if (NS_SUCCEEDED(rv))
          {
            const char * query = strrchr(path.get(), '?');
            if (query && nsCRT::strncasecmp(path.get(), originalPath, query - path.get()) == 0)
              return PR_TRUE; //This object is a part of the original message, we can send it safely.
          }
        }
      }
    }
  }

  return PR_FALSE;
}

/* Reset the uri's of embedded objects because we've saved the draft message, and the 
   original message doesn't exist anymore.
 */
nsresult nsMsgCompose::ResetUrisForEmbeddedObjects()
{
  nsCOMPtr<nsISupportsArray> aNodeList;
  PRUint32 numNodes;
  PRUint32 i;

  nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor));
  if (!mailEditor)
    return NS_ERROR_FAILURE;

  nsresult rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList));
  if ((NS_FAILED(rv) || (!aNodeList)))
    return NS_ERROR_FAILURE;

  if (NS_FAILED(aNodeList->Count(&numNodes)))
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIDOMNode> node;
  nsXPIDLCString curDraftIdURL;

  rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
  NS_ASSERTION((NS_SUCCEEDED(rv) && (curDraftIdURL)), "RemoveCurrentDraftMessage can't get draft id");

  // Skip if no draft id (probably a new draft msg).
  if (NS_SUCCEEDED(rv) && mMsgSend && !curDraftIdURL.IsEmpty())
  {
    // we don't currently handle imap urls
    if (StringBeginsWith(curDraftIdURL, NS_LITERAL_CSTRING("imap-message")))
      return NS_OK;

    nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
    rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr));
    NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
    if (NS_SUCCEEDED(rv) && msgDBHdr)
    {
      nsMsgKey oldDraftKey;

      // build up the old and new ?number= parts. This code assumes it is 
      // called *before* RemoveCurrentDraftMessage, so that curDraftIdURL
      // is the previous draft.
      // This code currently only works for local mail folders.
      // For imap folders, the url looks like <folder>%3E<UID>?part=...
      // We could handle the imap case as well, but it turns out 
      // not to be so important because the old message is still on
      // the imap server. If it turns out to be a problem, we can
      // deal with imap urls as well.
      msgDBHdr->GetMessageKey(&oldDraftKey);
      nsAutoString oldNumberPart(NS_LITERAL_STRING("?number="));
      oldNumberPart.AppendInt(oldDraftKey);
      nsAutoString newNumberPart;
      nsMsgKey newMsgKey;
      mMsgSend->GetMessageKey(&newMsgKey);
      newNumberPart.AppendInt(newMsgKey);

      nsCOMPtr<nsIDOMElement> domElement;
      for (i = 0; i < numNodes; i ++)
      {
        domElement = do_QueryElementAt(aNodeList, i);
        if (!domElement)
          continue;

        nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(domElement);
        if (!image)
          continue;
        // do we care about anything besides images?
        nsAutoString objURL;
        image->GetSrc(objURL);
        // the objURL is the full path to the mailbox, 
        // e.g., mailbox:///C/Documents%20Settings.../Local%20Folders/Drafts?number=
        // Find the ?number= part of the uri, and replace the
        // old number with the new msg key.

        PRInt32 numberIndex = objURL.Find(oldNumberPart);
        if (numberIndex != kNotFound)
        {
          objURL.Replace(numberIndex + 8, oldNumberPart.Length() - 8, newNumberPart);
          image->SetSrc(objURL);
        }
      }
    }
  }

  return NS_OK;
}


/* The purpose of this function is to mark any embedded object that wasn't a RFC822 part
   of the original message as moz-do-not-send.
   That will prevent us to attach data not specified by the user or not present in the
   original message.
*/
nsresult nsMsgCompose::TagEmbeddedObjects(nsIEditorMailSupport *aEditor)
{
  nsresult rv = NS_OK;
  nsCOMPtr<nsISupportsArray> aNodeList;
  PRUint32 count;
  PRUint32 i;

  if (!aEditor)
    return NS_ERROR_FAILURE;

  rv = aEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList));
  if ((NS_FAILED(rv) || (!aNodeList)))
    return NS_ERROR_FAILURE;

  if (NS_FAILED(aNodeList->Count(&count)))
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIDOMNode> node;

  nsCOMPtr<nsIURI> originalUrl;
  nsXPIDLCString originalScheme;
  nsXPIDLCString originalHost;
  nsXPIDLCString originalPath;

  // first, convert the rdf original msg uri into a url that represents the message...
  nsCOMPtr <nsIMsgMessageService> msgService;
  rv = GetMessageServiceFromURI(mOriginalMsgURI.get(), getter_AddRefs(msgService));
  if (NS_SUCCEEDED(rv))
  {
    rv = msgService->GetUrlForUri(mOriginalMsgURI.get(), getter_AddRefs(originalUrl), nsnull);
    if (NS_SUCCEEDED(rv) && originalUrl)
    {
      originalUrl->GetScheme(originalScheme);
      originalUrl->GetAsciiHost(originalHost);
      originalUrl->GetPath(originalPath);
    }
  }

  // Then compare the url of each embedded objects with the original message.
  // If they a not coming from the original message, they should not be sent
  // with the message.
  nsCOMPtr<nsIDOMElement> domElement;
  for (i = 0; i < count; i ++)
  {
    node = do_QueryElementAt(aNodeList, i);
    if (!node)
      continue;
    if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(),
                             originalPath.get(), node))
      continue; //Don't need to tag this object, it safe to send it.

    //The source of this object should not be sent with the message 
    domElement = do_QueryInterface(node);
    if (domElement)
      domElement->SetAttribute(NS_LITERAL_STRING("moz-do-not-send"), NS_LITERAL_STRING("true"));
  }

  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompose::GetInsertingQuotedContent(PRBool * aInsertingQuotedText)
{
  NS_ENSURE_ARG_POINTER(aInsertingQuotedText);
  *aInsertingQuotedText = mInsertingQuotedContent;
  return NS_OK;
}

NS_IMETHODIMP 
nsMsgCompose::SetInsertingQuotedContent(PRBool aInsertingQuotedText)
{
  mInsertingQuotedContent = aInsertingQuotedText;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix,
                                          nsString& aBuf,
                                          nsString& aSignature,
                                          PRBool aQuoted,
                                          PRBool aHTMLEditor)
{
  NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor\n");
  if (!m_editor)
    return NS_ERROR_FAILURE;

  // First, get the nsIEditor interface for future use
  nsCOMPtr<nsIDOMNode> nodeInserted;

  TranslateLineEnding(aPrefix);
  TranslateLineEnding(aBuf);
  TranslateLineEnding(aSignature);

  // We're going to be inserting stuff, and MsgComposeCommands
  // may have set the editor to readonly in the recycled case.
  // So set it back to writable.
  // Note!  enableEditableFields in gComposeRecyclingListener::onReopen
  // will redundantly set this flag to writable, but it gets there
  // too late.
  PRUint32 flags = 0;
  m_editor->GetFlags(&flags);
  flags &= ~nsIPlaintextEditor::eEditorReadonlyMask;
  m_editor->SetFlags(flags);

  m_editor->EnableUndo(PR_FALSE);

  // Ok - now we need to figure out the charset of the aBuf we are going to send
  // into the editor shell. There are I18N calls to sniff the data and then we need
  // to call the new routine in the editor that will allow us to send in the charset
  //

  // Now, insert it into the editor...
  nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
  nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
  nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor));
  m_editor->BeginTransaction();
  PRInt32 reply_on_top = 0;
  PRBool sig_bottom = PR_TRUE;
  m_identity->GetReplyOnTop(&reply_on_top);
  m_identity->GetSigBottom(&sig_bottom);
  PRBool sigOnTop = (reply_on_top == 1 && !sig_bottom);
  if (aQuoted)
  {
    mInsertingQuotedContent = PR_TRUE;
    if (!aPrefix.IsEmpty())
    {
      if (!aHTMLEditor)
        aPrefix.AppendLiteral("\n");
      textEditor->InsertText(aPrefix);
      m_editor->EndOfDocument();
    }

    if (!aBuf.IsEmpty() && mailEditor)
    {
      // XXX see bug #206793
      nsIDocShell *docshell = nsnull;
      nsCOMPtr<nsIScriptGlobalObject> globalObj = do_QueryInterface(m_window);
      if (globalObj && (docshell = globalObj->GetDocShell()))
        docshell->SetAppType(nsIDocShell::APP_TYPE_EDITOR);

      if (aHTMLEditor && !mCiteReference.IsEmpty())
        mailEditor->InsertAsCitedQuotation(aBuf,
                                           mCiteReference,
                                           PR_TRUE,
                                           getter_AddRefs(nodeInserted));
      else
        mailEditor->InsertAsQuotation(aBuf,
                                      getter_AddRefs(nodeInserted));

      m_editor->EndOfDocument();
    }

    mInsertingQuotedContent = PR_FALSE;

    (void)TagEmbeddedObjects(mailEditor);

    if (!aSignature.IsEmpty() )
    {
      //we cannot add it on top earlier, because TagEmbeddedObjects will mark all images in the signature as "moz-do-not-send"
      if( sigOnTop )
        m_editor->BeginningOfDocument();

      if (aHTMLEditor && htmlEditor)
        htmlEditor->InsertHTML(aSignature);
      else if (textEditor)
        textEditor->InsertText(aSignature);

      if( sigOnTop )
        m_editor->EndOfDocument();
    }
  }
  else
  {
    if (aHTMLEditor && htmlEditor)
    {
      mInsertingQuotedContent = PR_TRUE;
      htmlEditor->RebuildDocumentFromSource(aBuf);
      mInsertingQuotedContent = PR_FALSE;

      m_editor->EndOfDocument();

      // when forwarding a message as inline, tag any embedded objects
      // which refer to local images or files so we know not to include 
      // send them
      if (mType == nsIMsgCompType::ForwardInline)
        (void)TagEmbeddedObjects(mailEditor);

      if (!aSignature.IsEmpty())
        htmlEditor->InsertHTML(aSignature);
    }
    else if (textEditor)
    {
      if (!aBuf.IsEmpty())
      {
        if (mailEditor)
          mailEditor->InsertTextWithQuotations(aBuf);
        else
          textEditor->InsertText(aBuf);
        m_editor->EndOfDocument();
      }

      if (!aSignature.IsEmpty())
        textEditor->InsertText(aSignature);
    }
  }
  m_editor->EndTransaction();

  if (m_editor)
  {
    if (aBuf.IsEmpty())
      m_editor->BeginningOfDocument();
    else
    {
      switch (reply_on_top)
        {
          // This should set the cursor after the body but before the sig
          case 0  :
          {
            if (!textEditor)
            {
              m_editor->BeginningOfDocument();
              break;
            }

            nsCOMPtr<nsISelection> selection = nsnull; 
            nsCOMPtr<nsIDOMNode>      parent = nsnull; 
            PRInt32                   offset;
            nsresult                  rv;

            // get parent and offset of mailcite
            rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
            if (NS_FAILED(rv) || (!parent))
            {
              m_editor->BeginningOfDocument();
              break;
            }

            // get selection
            m_editor->GetSelection(getter_AddRefs(selection));
            if (!selection)
            {
              m_editor->BeginningOfDocument();
              break;
            }

            // place selection after mailcite
            selection->Collapse(parent, offset+1);

            // insert a break at current selection
            textEditor->InsertLineBreak();

            // i'm not sure if you need to move the selection back to before the
            // break. expirement.
            selection->Collapse(parent, offset+1);

            break;
          }

        case 2  : 
        {
          m_editor->SelectAll();
          break;
        }

        // This should set the cursor to the top!
        default : m_editor->BeginningOfDocument();    break;
      }
    }

    nsCOMPtr<nsISelectionController> selCon;
    m_editor->GetSelectionController(getter_AddRefs(selCon));

    if (selCon)
      selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_ANCHOR_REGION, PR_TRUE);
  }

  if (m_editor)
    m_editor->EnableUndo(PR_TRUE);
  SetBodyModified(PR_FALSE);

#ifdef MSGCOMP_TRACE_PERFORMANCE
  nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID));
  composeService->TimeStamp("Finished inserting data into the editor. The window is finally ready!", PR_FALSE);
#endif
  return NS_OK;
}

nsresult 
nsMsgCompose::SetQuotingToFollow(PRBool aVal)
{
  mQuotingToFollow = aVal;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompose::GetQuotingToFollow(PRBool* quotingToFollow)
{
  NS_ENSURE_ARG(quotingToFollow);
  *quotingToFollow = mQuotingToFollow;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompose::Initialize(nsIDOMWindowInternal *aWindow, nsIMsgComposeParams *params)
{
  NS_ENSURE_ARG_POINTER(params);
  nsresult rv;

  params->GetIdentity(getter_AddRefs(m_identity));

  if (aWindow)
  {
    m_window = aWindow;
    nsCOMPtr<nsIScriptGlobalObject> globalObj(do_QueryInterface(aWindow));
    if (!globalObj)
      return NS_ERROR_FAILURE;

    nsCOMPtr<nsIDocShellTreeItem>  treeItem =
      do_QueryInterface(globalObj->GetDocShell());
    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
    rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
    if (NS_FAILED(rv)) return rv;

    m_baseWindow = do_QueryInterface(treeOwner);

    globalObj->GetDocShell()->SetAppType(nsIDocShell::APP_TYPE_EDITOR);
  }

  MSG_ComposeFormat format;
  params->GetFormat(&format);

  MSG_ComposeType type;
  params->GetType(&type);

  nsXPIDLCString originalMsgURI;
  params->GetOriginalMsgURI(getter_Copies(originalMsgURI));
  params->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr));

  nsCOMPtr<nsIMsgCompFields> composeFields;
  params->GetComposeFields(getter_AddRefs(composeFields));

  nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  
  rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML);
  NS_ENSURE_SUCCESS(rv,rv);

  // Set return receipt flag and type, and if we should attach a vCard
  if (m_identity && composeFields)
  {
    PRBool requestReturnReceipt = PR_FALSE;
    rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = composeFields->SetReturnReceipt(requestReturnReceipt);
    NS_ENSURE_SUCCESS(rv, rv);

    PRInt32 receiptType = nsIMsgMdnGenerator::eDntType;
    rv = m_identity->GetReceiptHeaderType(&receiptType);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = composeFields->SetReceiptHeaderType(receiptType);
    NS_ENSURE_SUCCESS(rv, rv);

    PRBool attachVCard;
    rv = m_identity->GetAttachVCard(&attachVCard);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = composeFields->SetAttachVCard(attachVCard);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  params->GetSendListener(getter_AddRefs(mExternalSendListener));
  nsXPIDLCString smtpPassword;
  params->GetSmtpPassword(getter_Copies(smtpPassword));
  mSmtpPassword = (const char *)smtpPassword;

  if (aWindow)
  {
    // register the compose object with the compose service
    rv = composeService->RegisterComposeWindow(aWindow, this);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return CreateMessage(originalMsgURI, type, composeFields);
}

nsresult nsMsgCompose::SetDocumentCharset(const char *charset) 
{
  // Set charset, this will be used for the MIME charset labeling.
  m_compFields->SetCharacterSet(charset);

  // notify the change to editor
  m_editor->SetDocumentCharacterSet(nsDependentCString(charset));

  return NS_OK;
}

nsresult nsMsgCompose::RegisterStateListener(nsIMsgComposeStateListener *stateListener)
{
  nsresult rv = NS_OK;

  if (!stateListener)
    return NS_ERROR_NULL_POINTER;

  if (!mStateListeners)
  {
    rv = NS_NewISupportsArray(getter_AddRefs(mStateListeners));
    if (NS_FAILED(rv)) return rv;
  }
  nsCOMPtr<nsISupports> iSupports = do_QueryInterface(stateListener, &rv);
  if (NS_FAILED(rv)) return rv;

  // note that this return value is really a PRBool, so be sure to use
  // NS_SUCCEEDED or NS_FAILED to check it.
  return mStateListeners->AppendElement(iSupports);
}

nsresult nsMsgCompose::UnregisterStateListener(nsIMsgComposeStateListener *stateListener)
{
  if (!stateListener)
    return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;

  // otherwise, see if it exists in our list
  if (!mStateListeners)
    return (nsresult)PR_FALSE;      // yeah, this sucks, but I'm emulating the behaviour of
                                    // nsISupportsArray::RemoveElement()

  nsCOMPtr<nsISupports> iSupports = do_QueryInterface(stateListener, &rv);
  if (NS_FAILED(rv)) return rv;

  // note that this return value is really a PRBool, so be sure to use
  // NS_SUCCEEDED or NS_FAILED to check it.
  return mStateListeners->RemoveElement(iSupports);
}

nsresult nsMsgCompose::_SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char *accountKey, PRBool entityConversionDone)
{
  nsresult rv = NS_OK;

  // clear saved message id if sending, so we don't send out the same message-id.
  if (deliverMode == nsIMsgCompDeliverMode::Now || deliverMode == nsIMsgCompDeliverMode::Later)
    m_compFields->SetMessageId("");

  if (m_compFields && identity) 
  {
    // Pref values are supposed to be stored as UTF-8, so no conversion
    nsXPIDLCString email;
    nsXPIDLString fullName;
    nsXPIDLString organization;

    identity->GetEmail(getter_Copies(email));
    identity->GetFullName(getter_Copies(fullName));
    identity->GetOrganization(getter_Copies(organization));

    char * sender = nsnull;
    nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));
    if (parser) {
      // convert to UTF8 before passing to MakeFullAddress
      parser->MakeFullAddress(nsnull, NS_ConvertUCS2toUTF8(fullName).get(), email, &sender);
    }

  if (!sender)
    m_compFields->SetFrom(email);
  else
    m_compFields->SetFrom(sender);
  PR_FREEIF(sender);

    m_compFields->SetOrganization(organization);

#if defined(DEBUG_ducarroz) || defined(DEBUG_seth_)
  {
    printf("----------------------------\n");
    printf("--  Sending Mail Message  --\n");
    printf("----------------------------\n");
    printf("from: %s\n", m_compFields->GetFrom());
    printf("To: %s  Cc: %s  Bcc: %s\n", m_compFields->GetTo(), m_compFields->GetCc(), m_compFields->GetBcc());
    printf("Newsgroups: %s\n", m_compFields->GetNewsgroups());
    printf("Subject: %s  \nMsg: %s\n", m_compFields->GetSubject(), m_compFields->GetBody());
    nsCOMPtr<nsISupportsArray> attachmentsArray;
    m_compFields->GetAttachmentsArray(getter_AddRefs(attachmentsArray));
    if (attachmentsArray)
    {
      PRUint32 i;
      PRUint32 attachmentCount = 0;
      attachmentsArray->Count(&attachmentCount);

      nsCOMPtr<nsIMsgAttachment> element;
      for (i = 0; i < attachmentCount; i ++)
      {
        attachmentsArray->QueryElementAt(i, NS_GET_IID(nsIMsgAttachment), getter_AddRefs(element));
        if (element)
        {
          nsAutoString name;
          nsXPIDLCString url;
          element->GetName(name);
          element->GetUrl(getter_Copies(url));
          printf("Attachment %d: %s - %s\n",i + 1, NS_ConvertUTF16toUTF8(name).get(), url.get());
        }
      }
    }
    printf("----------------------------\n");
  }
#endif //DEBUG

    mMsgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID);
    if (mMsgSend)
    {
      PRBool      newBody = PR_FALSE;
      char        *bodyString = (char *)m_compFields->GetBody();
      PRInt32     bodyLength;
      const char  attachment1_type[] = TEXT_HTML;  // we better be "text/html" at this point

      if (!entityConversionDone)
      {
        // Convert body to mail charset
        char      *outCString;

        if (  bodyString && *bodyString )
        {
          // Apply entity conversion then convert to a mail charset. 
          PRBool isAsciiOnly;
          rv = nsMsgI18NSaveAsCharset(attachment1_type, m_compFields->GetCharacterSet(), 
                                      NS_ConvertUTF8toUTF16(bodyString).get(), &outCString,
                                      nsnull, &isAsciiOnly);
          if (NS_SUCCEEDED(rv)) 
          {
            if (m_compFields->GetForceMsgEncoding())
              isAsciiOnly = PR_FALSE;

            m_compFields->SetBodyIsAsciiOnly(isAsciiOnly);
            bodyString = outCString;
            newBody = PR_TRUE;
          }
        }
      }

      bodyLength = PL_strlen(bodyString);

      // Create the listener for the send operation...
      nsCOMPtr<nsIMsgComposeSendListener> composeSendListener = do_CreateInstance(NS_MSGCOMPOSESENDLISTENER_CONTRACTID);
      if (!composeSendListener)
        return NS_ERROR_OUT_OF_MEMORY;

      // right now, AutoSaveAsDraft is identical to SaveAsDraft as
      // far as the msg send code is concerned. This way, we don't have
      // to add an nsMsgDeliverMode for autosaveasdraft, and add cases for
      // it in the msg send code.
      if (deliverMode == nsIMsgCompDeliverMode::AutoSaveAsDraft)
        deliverMode = nsIMsgCompDeliverMode::SaveAsDraft;

      composeSendListener->SetMsgCompose(this);
      composeSendListener->SetDeliverMode(deliverMode);

      if (mProgress)
      {
        nsCOMPtr<nsIWebProgressListener> progressListener = do_QueryInterface(composeSendListener);
        mProgress->RegisterListener(progressListener);
      }

      // If we are composing HTML, then this should be sent as
      // multipart/related which means we pass the editor into the
      // backend...if not, just pass nsnull
      //
      nsCOMPtr<nsIMsgSendListener> sendListener = do_QueryInterface(composeSendListener);
      rv = mMsgSend->CreateAndSendMessage(
                    m_composeHTML ? m_editor.get() : nsnull,
                    identity,
                    accountKey,
                    m_compFields, 
                    PR_FALSE,                           // PRBool                            digest_p,
                    PR_FALSE,                           // PRBool                            dont_deliver_p,
                    (nsMsgDeliverMode)deliverMode,      // nsMsgDeliverMode                  mode,
                    nsnull,                             // nsIMsgDBHdr                       *msgToReplace, 
                    m_composeHTML?TEXT_HTML:TEXT_PLAIN, // const char                        *attachment1_type,
                    bodyString,                         // const char                        *attachment1_body,
                    bodyLength,                         // PRUint32                          attachment1_body_length,
                    nsnull,                             // const struct nsMsgAttachmentData  *attachments,
                    nsnull,                             // const struct nsMsgAttachedFile    *preloaded_attachments,
                    nsnull,                             // nsMsgSendPart                     *relatedPart,
                    m_window,                           // nsIDOMWindowInternal              *parentWindow;
                    mProgress,                          // nsIMsgProgress                    *progress,
                    sendListener,                       // listener
                    mSmtpPassword.get(),
                    mOriginalMsgURI,
                    mType);

      // Cleanup converted body...
      if (newBody)
        PR_FREEIF(bodyString);
    }
    else
        rv = NS_ERROR_FAILURE;
  }
  else
    rv = NS_ERROR_NOT_INITIALIZED;

  if (NS_FAILED(rv))
    NotifyStateListeners(eComposeProcessDone,rv);

  return rv;
}

NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char *accountKey, nsIMsgWindow *aMsgWindow, nsIMsgProgress *progress)
{
  nsresult rv = NS_OK;
  PRBool entityConversionDone = PR_FALSE;
  nsCOMPtr<nsIPrompt> prompt;

  // i'm assuming the compose window is still up at this point...
  if (!prompt && m_window)
     m_window->GetPrompter(getter_AddRefs(prompt));

  if (m_compFields && !m_composeHTML)
  {
    // The plain text compose window was used
    const char contentType[] = "text/plain";
    nsString msgBody;
    PRUint32 flags = nsIDocumentEncoder::OutputFormatted;
    if (m_editor)
    {
      // Reset message body previously stored in the compose fields
      // There is 2 nsIMsgCompFields::SetBody() functions using a pointer as argument,
      // therefore a casting is required.
      m_compFields->SetBody((const char *)nsnull);

      const char *charset = m_compFields->GetCharacterSet();
      if(UseFormatFlowed(charset))
          flags |= nsIDocumentEncoder::OutputFormatFlowed;

      rv = m_editor->OutputToString(NS_LITERAL_STRING("text/plain"), flags, msgBody);
    }
    else
    {
      m_compFields->GetBody(msgBody);
    }
    if (NS_SUCCEEDED(rv) && !msgBody.IsEmpty())
    {
      // Convert body to mail charset
      nsXPIDLCString outCString; 
      nsXPIDLCString fallbackCharset;
      PRBool isAsciiOnly;
      // check if the body text is covered by the current charset.
      rv = nsMsgI18NSaveAsCharset(contentType, m_compFields->GetCharacterSet(), 
                                  msgBody.get(), getter_Copies(outCString),
                                  getter_Copies(fallbackCharset), &isAsciiOnly);
      SET_SIMULATED_ERROR(SIMULATED_SEND_ERROR_14, rv, NS_ERROR_UENC_NOMAPPING);
      if (m_compFields->GetForceMsgEncoding())
        isAsciiOnly = PR_FALSE;
      if (NS_SUCCEEDED(rv) && !outCString.IsEmpty())
      {
        // body contains characters outside the repertoire of the current 
        // charset. ask whether to convert to UTF-8 or go back to reset
        // charset with a wider repertoire. (bug 233361) (if not mapi blind send)
        if (NS_ERROR_UENC_NOMAPPING == rv && m_editor) {
          PRBool needToCheckCharset;
          m_compFields->GetNeedToCheckCharset(&needToCheckCharset);
          if (needToCheckCharset) {
            PRInt32 answer = nsMsgAskAboutUncoveredCharacters(prompt);
            switch (answer) {
              case 0 : // convert to UTF-8
                CopyUTF16toUTF8(msgBody.get(), outCString);
                m_compFields->SetCharacterSet("UTF-8");
                break; 
              case 1 : // return to the editor 
                return NS_ERROR_MSG_MULTILINGUAL_SEND;
              case 2 : // send anyway
                break;
            }
          }
        }
        // re-label to the fallback charset
        else if (fallbackCharset)
          m_compFields->SetCharacterSet(fallbackCharset.get());
        m_compFields->SetBodyIsAsciiOnly(isAsciiOnly);
        m_compFields->SetBody(outCString.get());
        entityConversionDone = PR_TRUE;
      }
      else
        m_compFields->SetBody(NS_LossyConvertUTF16toASCII(msgBody).get());
    }
  }

  // Let's open the progress dialog
  if (progress)
  {
    mProgress = progress;

    if (deliverMode != nsIMsgCompDeliverMode::AutoSaveAsDraft)
    {
      nsAutoString msgSubject;
      m_compFields->GetSubject(msgSubject);

      PRBool showProgress = PR_FALSE;
      nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
      if (prefBranch)
      {
        prefBranch->GetBoolPref("mailnews.show_send_progress", &showProgress);
        if (showProgress)
        {
          nsCOMPtr<nsIMsgComposeProgressParams> params = do_CreateInstance(NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID, &rv);
          if (NS_FAILED(rv) || !params)
            return NS_ERROR_FAILURE;

          params->SetSubject(msgSubject.get());
          params->SetDeliveryMode(deliverMode);

          mProgress->OpenProgressDialog(m_window, aMsgWindow, "chrome://messenger/content/messengercompose/sendProgress.xul", params);
          mProgress->GetPrompter(getter_AddRefs(prompt));
        }
      }
    }

    mProgress->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_START, NS_OK);
  }

  PRBool attachVCard = PR_FALSE;
  if (m_compFields)
      m_compFields->GetAttachVCard(&attachVCard);

  if (attachVCard && identity && (deliverMode == nsIMsgCompDeliverMode::Now || deliverMode == nsIMsgCompDeliverMode::Later))
  {
      nsXPIDLCString escapedVCard;
      // make sure, if there is no card, this returns an empty string, or NS_ERROR_FAILURE
      rv = identity->GetEscapedVCard(getter_Copies(escapedVCard));

      if (NS_SUCCEEDED(rv) && !escapedVCard.IsEmpty()) 
      {
          nsCString vCardUrl;
          vCardUrl = "data:text/x-vcard;charset=utf-8;base64,";
          char *unescapedData = PL_strdup(escapedVCard);
          if (!unescapedData)
              return NS_ERROR_OUT_OF_MEMORY;
          nsUnescape(unescapedData);
          char *result = PL_Base64Encode(unescapedData, 0, nsnull);
          vCardUrl += result;
          PR_Free(result);
          PR_Free(unescapedData);

          nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
          if (NS_SUCCEEDED(rv) && attachment)
          {
              // [comment from 4.x]
              // Send the vCard out with a filename which distinguishes this user. e.g. jsmith.vcf
              // The main reason to do this is for interop with Eudora, which saves off 
              // the attachments separately from the message body
              nsXPIDLCString userid;
              (void)identity->GetEmail(getter_Copies(userid));
              PRInt32 index = userid.FindChar('@');
              if (index != kNotFound)
                  userid.Truncate(index);

              if (userid.IsEmpty()) 
                  attachment->SetName(NS_LITERAL_STRING("vcard.vcf"));
              else
              {
                  // Replace any dot with underscore to stop vCards
                  // generating false positives with some heuristic scanners
                  userid.ReplaceChar('.', '_');
                  userid.AppendLiteral(".vcf");
                  attachment->SetName(NS_ConvertASCIItoUCS2(userid));
              }

              attachment->SetUrl(vCardUrl.get());
              m_compFields->AddAttachment(attachment);
          }
      }
  }

  rv = _SendMsg(deliverMode, identity, accountKey, entityConversionDone);
  if (NS_FAILED(rv))
  {
    nsCOMPtr<nsIMsgSendReport> sendReport;
    if (mMsgSend)
      mMsgSend->GetSendReport(getter_AddRefs(sendReport));
    if (sendReport)
    {
      nsresult theError;
      sendReport->DisplayReport(prompt, PR_TRUE, PR_TRUE, &theError);
    }
    else
    {
      /* If we come here it's because we got an error before we could intialize a
         send report! Let's try our best...
      */
      switch (deliverMode)
      {
        case nsIMsgCompDeliverMode::Later:
          nsMsgDisplayMessageByID(prompt, NS_MSG_UNABLE_TO_SEND_LATER);
          break;
        case nsIMsgCompDeliverMode::AutoSaveAsDraft:
        case nsIMsgCompDeliverMode::SaveAsDraft:
          nsMsgDisplayMessageByID(prompt, NS_MSG_UNABLE_TO_SAVE_DRAFT);
          break;
        case nsIMsgCompDeliverMode::SaveAsTemplate:
          nsMsgDisplayMessageByID(prompt, NS_MSG_UNABLE_TO_SAVE_TEMPLATE);
          break;

        default:
          nsMsgDisplayMessageByID(prompt, NS_ERROR_SEND_FAILED);
          break;
      }
    }

    if (progress)
      progress->CloseProgressDialog(PR_TRUE);
  }

  return rv;
}

// XXX when do we break this ref to the listener?
NS_IMETHODIMP nsMsgCompose::SetRecyclingListener(nsIMsgComposeRecyclingListener *aRecyclingListener)
{
  mRecyclingListener = aRecyclingListener;
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetRecyclingListener(nsIMsgComposeRecyclingListener **aRecyclingListener)
{
  NS_ENSURE_ARG_POINTER(aRecyclingListener);
  *aRecyclingListener = mRecyclingListener;
  NS_IF_ADDREF(*aRecyclingListener);
  return NS_OK;
}

/* attribute boolean recycledWindow; */
NS_IMETHODIMP nsMsgCompose::GetRecycledWindow(PRBool *aRecycledWindow)
{
  NS_ENSURE_ARG_POINTER(aRecycledWindow);
  *aRecycledWindow = mRecycledWindow;
  return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::SetRecycledWindow(PRBool aRecycledWindow)
{
  mRecycledWindow = aRecycledWindow;
  return NS_OK;
}

/* attribute boolean deleteDraft */
NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(PRBool *aDeleteDraft)
{
  NS_ENSURE_ARG_POINTER(aDeleteDraft);
  *aDeleteDraft = mDeleteDraft;
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(PRBool aDeleteDraft)
{
  mDeleteDraft = aDeleteDraft;
  return NS_OK;
}

#if !defined(XP_MAC)
PRBool nsMsgCompose::IsLastWindow()
{
  nsresult rv;
  PRBool more;
  nsCOMPtr<nsIWindowMediator> windowMediator =
              do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv))
  {
    nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
    rv = windowMediator->GetEnumerator(nsnull,
               getter_AddRefs(windowEnumerator));
    if (NS_SUCCEEDED(rv))
    {
      nsCOMPtr<nsISupports> isupports;

      if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
        if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)))
          return !more;
    }
  }
  return PR_TRUE;
}
#endif /* XP_MAC */

NS_IMETHODIMP nsMsgCompose::CloseWindow(PRBool recycleIt)
{
  nsresult rv;

  nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  
  // unregister the compose object with the compose service
  rv = composeService->UnregisterComposeWindow(m_window);
  NS_ENSURE_SUCCESS(rv, rv);

#if !defined(XP_MAC)
  recycleIt = recycleIt && !IsLastWindow();
#endif /* XP_MAC */
  if (recycleIt)
  {
    rv = composeService->CacheWindow(m_window, m_composeHTML, mRecyclingListener);
    if (NS_SUCCEEDED(rv))
    {
      nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
      NS_ASSERTION(htmlEditor, "no editor");
      if (htmlEditor)
      {
        // XXX clear undo txn manager?

        rv = m_editor->EnableUndo(PR_FALSE);
        NS_ENSURE_SUCCESS(rv,rv);

        rv = htmlEditor->RebuildDocumentFromSource(EmptyString());
        NS_ENSURE_SUCCESS(rv,rv);

        rv = m_editor->EnableUndo(PR_TRUE);
        NS_ENSURE_SUCCESS(rv,rv);

        SetBodyModified(PR_FALSE);
      }
      if (mRecyclingListener)
      {
        mRecyclingListener->OnClose();

        /**
         * In order to really free the memory, we need to call the JS garbage collector for our window.
         * If we don't call GC, the nsIMsgCompose object hold by JS will not be released despite we set
         * the JS global that hold it to null. Each time we reopen a recycled window, we allocate a new
         * nsIMsgCompose that we really need to be releazed when we recycle the window. In fact despite
         * we call GC here atfer the release wont occurs right away. But if we don't call it, the release
         * will apppend only when we phisically close the window which will append only on quit.
         */
        nsCOMPtr<nsIScriptGlobalObject> sgo(do_QueryInterface(m_window));
        if (sgo)
        {
          nsIScriptContext *scriptContext = sgo->GetContext();
          if (scriptContext)
            scriptContext->GC();
        }
      }
      return NS_OK;
    }
  }

  //We are going away for real, we need to do some clean up first
  if (m_baseWindow)
  {
    if (m_editor)
    {
        /* The editor will be destroyed during yje close window.
         * Set it to null to be sure we wont uses it anymore
         */
      m_editor = nsnull;
    }
    nsIBaseWindow * window = m_baseWindow;
    m_baseWindow = nsnull;
    rv = window->Destroy();
  }

  return rv;
}

nsresult nsMsgCompose::Abort()
{
  if (mMsgSend)
    mMsgSend->Abort();

  if (mProgress)
    mProgress->CloseProgressDialog(PR_TRUE);

  return NS_OK;
}

nsresult nsMsgCompose::GetEditor(nsIEditor * *aEditor)
{ 
  NS_IF_ADDREF(*aEditor = m_editor);
  return NS_OK;
} 

nsresult nsMsgCompose::ClearEditor()
{
  m_editor = nsnull;
  return NS_OK;
}

// This used to be called BEFORE editor was created 
//  (it did the loadUrl that triggered editor creation)
// It is called from JS after editor creation
//  (loadUrl is done in JS)
NS_IMETHODIMP nsMsgCompose::InitEditor(nsIEditor* aEditor, nsIDOMWindow* aContentWindow)
{
  NS_ENSURE_ARG_POINTER(aEditor);
  NS_ENSURE_ARG_POINTER(aContentWindow);

  m_editor = aEditor;

  // Set the charset
  const nsDependentCString msgCharSet(m_compFields->GetCharacterSet());
  m_editor->SetDocumentCharacterSet(msgCharSet);

  nsCOMPtr<nsIScriptGlobalObject> globalObj = do_QueryInterface(aContentWindow);

  nsIDocShell *docShell = globalObj->GetDocShell();
  NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);

  nsCOMPtr<nsIContentViewer> childCV;
  NS_ENSURE_SUCCESS(docShell->GetContentViewer(getter_AddRefs(childCV)), NS_ERROR_FAILURE);
  if (childCV)
  {
    nsCOMPtr<nsIMarkupDocumentViewer> markupCV = do_QueryInterface(childCV);
    if (markupCV) {
      NS_ENSURE_SUCCESS(markupCV->SetDefaultCharacterSet(msgCharSet), NS_ERROR_FAILURE);
      NS_ENSURE_SUCCESS(markupCV->SetForceCharacterSet(msgCharSet), NS_ERROR_FAILURE);
    }
  }

  // This is what used to be done in mDocumentListener, 
  //   nsMsgDocumentStateListener::NotifyDocumentCreated()
  PRBool quotingToFollow = PR_FALSE;
  GetQuotingToFollow(&quotingToFollow);
  if (quotingToFollow)
    return BuildQuotedMessageAndSignature();
  else
  {
    NotifyStateListeners(eComposeFieldsReady, NS_OK);
    nsresult rv = BuildBodyMessageAndSignature();
    NotifyStateListeners(eComposeBodyReady, NS_OK);
    return rv;
  }
} 

nsresult nsMsgCompose::GetBodyModified(PRBool * modified)
{
  nsresult rv;

  if (! modified)
    return NS_ERROR_NULL_POINTER;

  *modified = PR_TRUE;

  if (m_editor)
  {
    rv = m_editor->GetDocumentModified(modified);
    if (NS_FAILED(rv))
      *modified = PR_TRUE;
  }

  return NS_OK;   
}

nsresult nsMsgCompose::SetBodyModified(PRBool modified)
{
  nsresult  rv = NS_OK;

  if (m_editor)
  {
    if (modified)
    {
      PRInt32  modCount = 0;
      m_editor->GetModificationCount(&modCount);
      if (modCount == 0)
        m_editor->IncrementModificationCount(1);
    }
    else
      m_editor->ResetModificationCount();
  }

  return rv;  
}

NS_IMETHODIMP 
nsMsgCompose::GetDomWindow(nsIDOMWindowInternal * *aDomWindow)
{
  NS_IF_ADDREF(*aDomWindow = m_window);
  return NS_OK;
}

nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields * *aCompFields)
{
  *aCompFields = (nsIMsgCompFields*)m_compFields;
  NS_IF_ADDREF(*aCompFields);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetComposeHTML(PRBool *aComposeHTML)
{
  *aComposeHTML = m_composeHTML;
  return NS_OK;
}

nsresult nsMsgCompose::GetWrapLength(PRInt32 *aWrapLength)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (NS_FAILED(rv)) return rv;

  return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength);
}

nsresult nsMsgCompose::CreateMessage(const char * originalMsgURI,
                                     MSG_ComposeType type,
                                     nsIMsgCompFields * compFields)
{
  nsresult rv = NS_OK;

  mType = type;
  mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None;

  mDeleteDraft = (type == nsIMsgCompType::Draft);
  nsCAutoString msgUri(originalMsgURI);
  // check if we're dealing with an opened .eml file msg
  PRBool fileUrl = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("file:"));
  if (fileUrl)
  {
    // strip out ?type=application/x-message-display because it confuses libmime
    PRInt32 typeIndex = msgUri.Find("?type=application/x-message-display");
    if (typeIndex != kNotFound)
    {
      msgUri.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1);
      // we also need to replace the next '&' with '?'
      if (msgUri.CharAt(typeIndex) == '&')
        msgUri.SetCharAt('?', typeIndex);
      originalMsgURI = msgUri.get();
    }
  }
  else // check if we're dealing with a displayed message/rfc822 attachment
  {
    PRInt32 typeIndex = msgUri.Find("&type=application/x-message-display");
    if (typeIndex != kNotFound)
    {
      msgUri.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1);
      // nsURLFetcher will check for "realtype=message/rfc822" and will set the
      // content type to message/rfc822 in the forwarded message.
      msgUri.Append("&realtype=message/rfc822");
      originalMsgURI = msgUri.get();
    }
  }
  if (compFields)
  {
    NS_IF_RELEASE(m_compFields);
    m_compFields = NS_REINTERPRET_CAST(nsMsgCompFields*, compFields);
    NS_ADDREF(m_compFields);
  }
  else
  {
    NS_NEWXPCOM(m_compFields, nsMsgCompFields);
    if (m_compFields)
      NS_ADDREF(m_compFields);
    else
      return NS_ERROR_OUT_OF_MEMORY;
  }

  if (m_identity)
  {
      nsXPIDLCString::const_iterator start, end;

      /* Setup reply-to field */
      nsXPIDLCString replyTo;
      m_identity->GetReplyTo(getter_Copies(replyTo));
      if (replyTo && *(const char *)replyTo)
      {
        nsXPIDLCString replyToStr;
        replyToStr.Assign(m_compFields->GetReplyTo());

        replyToStr.BeginReading(start);
        replyToStr.EndReading(end);

        if (FindInReadable(replyTo, start, end) == PR_FALSE) {
          if (replyToStr.Length() > 0)
            replyToStr.Append(',');
          replyToStr.Append(replyTo);
        }
        m_compFields->SetReplyTo(replyToStr.get());
      }

      /* Setup bcc field */
      PRBool doBcc;
      m_identity->GetDoBcc(&doBcc);
      if (doBcc) 
      {
        nsXPIDLCString bccStr;
        bccStr.Assign(m_compFields->GetBcc());

          bccStr.BeginReading(start);
          bccStr.EndReading(end);

          nsXPIDLCString bccList;
          m_identity->GetDoBccList(getter_Copies(bccList));
          if (FindInReadable(bccList, start, end) == PR_FALSE) {
            if (bccStr.Length() > 0)
              bccStr.Append(',');
            bccStr.Append(bccList);
          }

        m_compFields->SetBcc(bccStr.get());
      }
  }

  if (mType == nsIMsgCompType::Draft)
  {
    nsXPIDLCString curDraftIdURL;

    rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
    NS_ASSERTION((NS_SUCCEEDED(rv) && (curDraftIdURL)), "RemoveCurrentDraftMessage can't get draft id");

    // Skip if no draft id (probably a new draft msg).
    if (NS_SUCCEEDED(rv) && curDraftIdURL.get() && strlen(curDraftIdURL.get()))
    { 
      nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
      rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr));
      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
      if (msgDBHdr)
      {
        nsXPIDLCString queuedDisposition;
        msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(queuedDisposition));
        nsXPIDLCString originalMsgURIs;
        msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, getter_Copies(originalMsgURIs));
        mOriginalMsgURI = originalMsgURIs;
        if (!queuedDisposition.IsEmpty())
        {
          if (queuedDisposition.Equals("replied"))
             mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied;
          else if (queuedDisposition.Equals("forward"))
             mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded;
        }
      }
    }
  }

  // If we don't have an original message URI, nothing else to do...
  if (!originalMsgURI || *originalMsgURI == 0)
    return NS_OK;

  // store the original message URI so we can extract it after we send the message to properly
  // mark any disposition flags like replied or forwarded on the message.
  mOriginalMsgURI = originalMsgURI;

  // If we are forwarding inline, mime did already setup the compose fields therefore we should stop now
  if (type == nsIMsgCompType::ForwardInline )
    return rv;

  char *uriList = PL_strdup(originalMsgURI);
  if (!uriList)
    return NS_ERROR_OUT_OF_MEMORY;

  nsCOMPtr<nsIMimeConverter> mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);

  nsXPIDLCString charset;
  // use a charset of the original message
  nsXPIDLCString mailCharset;
  PRBool charsetOverride = PR_FALSE;
  GetTopmostMsgWindowCharacterSet(mailCharset, &mCharsetOverride);
  if (!mailCharset.IsEmpty())
  {
    charset = mailCharset;
    charsetOverride = mCharsetOverride;
  }
#ifdef DEBUG_jungshik
  printf ("charset=%s\n", charset.get());
  printf ("charsetOverride=%d\n", charsetOverride);
#endif

  // although the charset in which to _send_ the message might change,
  // the original message will be parsed for quoting using the charset it is
  // now displayed with
  mQuoteCharset = charset;

  PRBool isFirstPass = PR_TRUE;
  char *uri = uriList;
  char *nextUri;
  do 
  {
    nextUri = strstr(uri, "://");
    if (nextUri)
    {
      // look for next ://, and then back up to previous ','
      nextUri = strstr(nextUri + 1, "://");
      if (nextUri)
      {
        *nextUri = '\0';
        char *saveNextUri = nextUri;
        nextUri = strrchr(uri, ',');
        if (nextUri)
          *nextUri = '\0';
        *saveNextUri = ':';
      }
    }

    nsCOMPtr <nsIMsgDBHdr> msgHdr;
    if (mOrigMsgHdr)
      msgHdr = mOrigMsgHdr;
    else
    {
      rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr));
      NS_ENSURE_SUCCESS(rv,rv);
    }
    if (msgHdr)
    {
      nsXPIDLString subject;
      nsXPIDLCString decodedCString;

      if (!charsetOverride && charset.IsEmpty())
      {
        rv = msgHdr->GetCharset(getter_Copies(charset));
        if (NS_FAILED(rv)) return rv;
      }

      // save the charset of a message being replied to because
      // we need to use it when decoding RFC-2047-encoded author name
      // with |charsetOverride == PR_TRUE|
      nsCAutoString originCharset(charset); 

      // use send_default_charset if reply_in_default_charset is on.
      nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID));
      if (prefs)
      {
        PRBool replyInDefault = PR_FALSE;
        prefs->GetBoolPref("mailnews.reply_in_default_charset",
                           &replyInDefault);
        if (replyInDefault) {
          nsXPIDLString str;
          NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset",
                                                      EmptyString(), str);
          if (!str.IsEmpty())
            LossyCopyUTF16toASCII(str, charset);
        }
      }

      // No matter what, we should block x-windows-949 (our internal name)
      // from being used for outgoing emails (bug 234958)
      if (charset.Equals("x-windows-949",
            nsCaseInsensitiveCStringComparator()))
        charset = "EUC-KR";

      // get an original charset, used for a label, UTF-8 is used for the internal processing
      if (isFirstPass && !charset.IsEmpty())
        m_compFields->SetCharacterSet(charset);

      nsXPIDLCString subjectCStr;
      (void) msgHdr->GetSubject(getter_Copies(subjectCStr));
      rv = mimeConverter->DecodeMimeHeader(subjectCStr,
                getter_Copies(decodedCString),
                originCharset.get(), charsetOverride);
      if (NS_FAILED(rv)) return rv;

      CopyUTF8toUTF16(decodedCString, subject);

      // Check if (was: is present in the subject
      nsAString::const_iterator wasStart, wasEnd;
      subject.BeginReading(wasStart);
      subject.EndReading(wasEnd);
      PRBool wasFound = RFindInReadable(NS_LITERAL_STRING(" (was:"), wasStart, wasEnd);
      PRBool strip = PR_TRUE;

      if (wasFound) {
        // Check the number of references, to check if was: should be stripped
        // First, assume that it should be stripped; the variable will be set to
        // false later if stripping should not happen.
        PRUint16 numRef;
        msgHdr->GetNumReferences(&numRef);
        if (numRef) {
          // If there are references, look for the first message in the thread
          // firstly, get the database via the folder
          nsCOMPtr<nsIMsgFolder> folder;
          msgHdr->GetFolder(getter_AddRefs(folder));
          if (folder) {
            nsCOMPtr<nsIMsgDatabase> db;
            folder->GetMsgDatabase(nsnull, getter_AddRefs(db));

            if (db) {
              nsCAutoString reference;
              msgHdr->GetStringReference(0, reference);

              nsCOMPtr<nsIMsgDBHdr> refHdr;
              db->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr));

              if (refHdr) {
                nsXPIDLCString refSubject;
                rv = refHdr->GetSubject(getter_Copies(refSubject));
                if (NS_SUCCEEDED(rv)) {
                  nsACString::const_iterator start, end;
                  refSubject.BeginReading(start);
                  refSubject.EndReading(end);
                  if (FindInReadable(NS_LITERAL_CSTRING(" (was:"), start, end))
                    strip = PR_FALSE;
                }
              }
            }
          }
        }
        else
          strip = PR_FALSE;
      }

      if (strip && wasFound) {
        // Strip off the "(was: old subject)" part
        nsAString::const_iterator start;
        subject.BeginReading(start);
        subject.Assign(Substring(start, wasStart));
      }

      switch (type)
      {
        default: break;
        case nsIMsgCompType::Reply :
        case nsIMsgCompType::ReplyAll:
        case nsIMsgCompType::ReplyToGroup:
        case nsIMsgCompType::ReplyToSender:
        case nsIMsgCompType::ReplyToSenderAndGroup:
          {
            if (!isFirstPass)       // safeguard, just in case...
            {
              PR_Free(uriList);
              return rv;
            }
            mQuotingToFollow = PR_TRUE;

            subject.Insert(NS_LITERAL_STRING("Re: "), 0);
            m_compFields->SetSubject(subject);

            nsXPIDLCString author, authorEmailAddress;
            msgHdr->GetAuthor(getter_Copies(author));
            nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));
            if (parser) {
              // convert to UTF8 before passing to MakeFullAddress
              rv = parser->ExtractHeaderAddressMailboxes(nsnull, author.get(), 
                                                            getter_Copies(authorEmailAddress));
            }

            PRBool replyToSelfCheckAll = PR_FALSE;
            nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
            if (NS_SUCCEEDED(rv)) 
              prefBranch->GetBoolPref("mailnews.reply_to_self_check_all_ident", &replyToSelfCheckAll);

            nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
            NS_ENSURE_SUCCESS(rv,rv);

            nsCOMPtr<nsISupportsArray> identities;
            nsXPIDLCString accountKey;
            msgHdr->GetAccountKey(getter_Copies(accountKey));
            if(replyToSelfCheckAll)
            {
              // check all avaliable identities if the pref was set
              accountManager->GetAllIdentities(getter_AddRefs(identities));
            }
            else if (!accountKey.IsEmpty())
            {
               // check headers to see which account the message came in from (only works for pop3)
              nsCOMPtr<nsIMsgAccount> account;
              accountManager->GetAccount(accountKey, getter_AddRefs(account));

              if(account)
                account->GetIdentities(getter_AddRefs(identities));
            } 
            else 
            {
              // check identities only for the server of the folder that the message is in 
              nsCOMPtr <nsIMsgFolder> msgFolder;
              rv = msgHdr->GetFolder(getter_AddRefs(msgFolder));

              if (NS_SUCCEEDED(rv) && msgFolder){ 
                nsCOMPtr<nsIMsgIncomingServer> nsIMsgIncomingServer;
                rv = msgFolder->GetServer(getter_AddRefs(nsIMsgIncomingServer));

                if(NS_SUCCEEDED(rv) && nsIMsgIncomingServer) 
                  accountManager->GetIdentitiesForServer(nsIMsgIncomingServer, getter_AddRefs(identities));
              }
            }

            PRBool isReplyToOwnMsg = PR_FALSE;
            if(identities)
            {
              // go through the identities to see if any of them is the author of the email
              nsCOMPtr<nsIMsgIdentity> lookupIdentity;

              PRUint32 count = 0;
              identities->Count(&count);

              for (PRUint32 i = 0; i < count; i++) 
              {
                rv = identities->QueryElementAt(i, NS_GET_IID(nsIMsgIdentity),
                                          getter_AddRefs(lookupIdentity));
                if (NS_FAILED(rv))
                  continue;

                nsXPIDLCString curIdentityEmail;
                lookupIdentity->GetEmail(getter_Copies(curIdentityEmail));
                if (curIdentityEmail.Equals(authorEmailAddress))
                {
                  isReplyToOwnMsg = PR_TRUE;
                  break;
                }
              }
            }

            nsXPIDLCString toField;
            if (isReplyToOwnMsg)
              msgHdr->GetRecipients(getter_Copies(toField));
            else
              toField.Assign(author);

            rv = mimeConverter->DecodeMimeHeader(toField,
                getter_Copies(decodedCString),
                originCharset.get(), charsetOverride);
            if (NS_SUCCEEDED(rv) && decodedCString)
              m_compFields->SetTo(decodedCString);
            else
              m_compFields->SetTo(toField);

            // Setup quoting callbacks for later...
            mWhatHolder = 1;
            break;
          }
        case nsIMsgCompType::ForwardAsAttachment:
          {
            PRUint32 flags;

            msgHdr->GetFlags(&flags);
            if (flags & MSG_FLAG_HAS_RE)
              subject.Insert(NS_LITERAL_STRING("Re: "), 0);

            // Setup quoting callbacks for later...
            mQuotingToFollow = PR_FALSE;  //We don't need to quote the original message.
            nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
            if (NS_SUCCEEDED(rv) && attachment)
            {
              attachment->SetName(subject + NS_LITERAL_STRING(".eml"));
              attachment->SetUrl(uri);
              m_compFields->AddAttachment(attachment);
            }

            if (isFirstPass)
            {
              subject.Insert(NS_LITERAL_STRING("[Fwd: ").get(), 0);
              subject.Append(NS_LITERAL_STRING("]").get());
              m_compFields->SetSubject(subject); 
            }
            break;
          }
      }
    }
    isFirstPass = PR_FALSE;
    uri = nextUri + 1;
  }
  while (nextUri);
  PR_Free(uriList);
  return rv;
}

NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mProgress;
  NS_IF_ADDREF(*_retval);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mMsgSend;
  NS_IF_ADDREF(*_retval);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetExternalSendListener(nsIMsgSendListener **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mExternalSendListener;
  NS_IF_ADDREF(*_retval);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference)
{
  mCiteReference = citeReference;
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const char *folderURI)
{
  m_folderName = folderURI;
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(char ** folderURI)
{
  NS_ENSURE_ARG_POINTER(folderURI);
  *folderURI = ToNewCString(m_folderName);
  return (*folderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(char ** originalMsgURI)
{
  NS_ENSURE_ARG_POINTER(originalMsgURI);
  *originalMsgURI = ToNewCString(mOriginalMsgURI);
  return (*originalMsgURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

////////////////////////////////////////////////////////////////////////////////////
// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUPUT
// FROM LIBMIME. THIS IS FOR QUOTING
////////////////////////////////////////////////////////////////////////////////////
QuotingOutputStreamListener::~QuotingOutputStreamListener() 
{
  if (mUnicodeConversionBuffer)
    nsMemory::Free(mUnicodeConversionBuffer);
}

QuotingOutputStreamListener::QuotingOutputStreamListener(const char * originalMsgURI,
                                                         nsIMsgDBHdr *originalMsgHdr,
                                                         PRBool quoteHeaders,
                                                         PRBool headersOnly,
                                                         nsIMsgIdentity *identity,
                                                         const char *charset,
                                                         PRBool charetOverride, 
                                                         PRBool quoteOriginal)
{ 
  nsresult rv;
  mQuoteHeaders = quoteHeaders;
  mHeadersOnly = headersOnly;
  mIdentity = identity;
  mUnicodeBufferCharacterLength = 0;
  mUnicodeConversionBuffer = nsnull;
  mQuoteOriginal = quoteOriginal;

  if (! mHeadersOnly)
  {
    nsXPIDLString replyHeaderOriginalmessage;
    // For the built message body...
    if (originalMsgHdr && !quoteHeaders)
    {
      // Setup the cite information....
      nsXPIDLCString myGetter;
      if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter))))
      {
        if (!myGetter.IsEmpty())
        {
          nsCAutoString buf;
          mCiteReference.AssignLiteral("mid:");
          AppendASCIItoUTF16(NS_EscapeURL(myGetter, esc_FileBaseName | esc_Forced, buf),
                             mCiteReference);
        }
      }

      PRInt32 reply_on_top = 0;
      mIdentity->GetReplyOnTop(&reply_on_top);
      if (reply_on_top == 1)
        mCitePrefix.AppendLiteral("\n\n");


      PRBool header, headerDate;
      PRInt32 replyHeaderType;
      nsAutoString replyHeaderLocale;
      nsXPIDLString replyHeaderAuthorwrote;
      nsXPIDLString replyHeaderOndate;
      nsAutoString replyHeaderSeparator;
      nsAutoString replyHeaderColon;

      // Get header type, locale and strings from pref.
      GetReplyHeaderInfo(&replyHeaderType, 
                         replyHeaderLocale,
                         replyHeaderAuthorwrote,
                         replyHeaderOndate,
                         replyHeaderSeparator,
                         replyHeaderColon,
                         replyHeaderOriginalmessage);

      switch (replyHeaderType)
      {
        case 0: // No reply header at all
          header=PR_FALSE;
          headerDate=PR_FALSE;
          break;

        case 2: // Insert both the original author and date in the reply header (date followed by author)
        case 3: // Insert both the original author and date in the reply header (author followed by date)
          header=PR_TRUE;
          headerDate=PR_TRUE;
          break;

        case 4: // XXX implement user specified header
        case 1: // Default is to only view the author. We will reconsider this decision when bug 75377 is fixed.
        default:
          header=PR_TRUE;
          headerDate=PR_FALSE;
          break;
      }

      nsAutoString citePrefixDate;
      nsAutoString citePrefixAuthor;

      if (header)
      {
        if (headerDate)
        {
          nsCOMPtr<nsIDateTimeFormat> dateFormatter = do_CreateInstance(kDateTimeFormatCID, &rv);

          if (NS_SUCCEEDED(rv)) 
          {  
            PRTime originalMsgDate;
            rv = originalMsgHdr->GetDate(&originalMsgDate); 

            if (NS_SUCCEEDED(rv)) 
            {
              nsAutoString formattedDateString;
              nsCOMPtr<nsILocale> locale;
              nsCOMPtr<nsILocaleService> localeService(do_GetService(NS_LOCALESERVICE_CONTRACTID));

              // Format date using "mailnews.reply_header_locale", if empty then use application default locale.
              if (!replyHeaderLocale.IsEmpty())
                rv = localeService->NewLocale(replyHeaderLocale, getter_AddRefs(locale));

              if (NS_SUCCEEDED(rv))
              {
                rv = dateFormatter->FormatPRTime(locale,
                                                 kDateFormatShort,
                                                 kTimeFormatNoSeconds,
                                                 originalMsgDate,
                                                 formattedDateString);

                if (NS_SUCCEEDED(rv)) 
                {
                  // take care "On %s"
                  PRUnichar *formatedString = nsnull;
                  formatedString = nsTextFormatter::smprintf(replyHeaderOndate.get(), 
                                                             NS_ConvertUCS2toUTF8(formattedDateString.get()).get());
                  if (formatedString) 
                  {
                    citePrefixDate.Assign(formatedString);
                    nsTextFormatter::smprintf_free(formatedString);
                  }
                }
              }
            }
          }
        }


      nsXPIDLCString author;
      rv = originalMsgHdr->GetAuthor(getter_Copies(author));

      if (NS_SUCCEEDED(rv))
      {
        nsXPIDLCString decodedString;
        mMimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
        // Decode header, the result string is null if the input is non MIME encoded ASCII.
        if (mMimeConverter)
          mMimeConverter->DecodeMimeHeader(author.get(), getter_Copies(decodedString), charset, charetOverride);

        nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));

        if (parser)
        {
          nsXPIDLCString authorName;
          rv = parser->ExtractHeaderAddressName("UTF-8", decodedString ? decodedString.get() : author.get(),
                                                getter_Copies(authorName));
          // take care "%s wrote"
          PRUnichar *formatedString = nsnull;
          if (NS_SUCCEEDED(rv) && authorName)
            formatedString = nsTextFormatter::smprintf(replyHeaderAuthorwrote.get(), authorName.get());
          else
            formatedString = nsTextFormatter::smprintf(replyHeaderAuthorwrote.get(), author.get());
          if (formatedString) 
          {
            citePrefixAuthor.Assign(formatedString);
            nsTextFormatter::smprintf_free(formatedString);
          }
        }


        }
        if (replyHeaderType == 2)
        {
          mCitePrefix.Append(citePrefixDate);
          mCitePrefix.Append(replyHeaderSeparator);
          mCitePrefix.Append(citePrefixAuthor);
        }
        else if (replyHeaderType == 3) 
        {
          mCitePrefix.Append(citePrefixAuthor);
          mCitePrefix.Append(replyHeaderSeparator);
          mCitePrefix.Append(citePrefixDate);
        }
        else
          mCitePrefix.Append(citePrefixAuthor);
        mCitePrefix.Append(replyHeaderColon);
      }
    }

    if (mCitePrefix.IsEmpty())
    {
      if (replyHeaderOriginalmessage.IsEmpty())
      {
        // This is not likely to happen but load the string if it's not done already.
        PRInt32 replyHeaderType;
        nsAutoString replyHeaderLocale;
        nsXPIDLString replyHeaderAuthorwrote;
        nsXPIDLString replyHeaderOndate;
        nsAutoString replyHeaderSeparator;
        nsAutoString replyHeaderColon;
        GetReplyHeaderInfo(&replyHeaderType, 
                           replyHeaderLocale,
                           replyHeaderAuthorwrote,
                           replyHeaderOndate,
                           replyHeaderSeparator,
                           replyHeaderColon,
                           replyHeaderOriginalmessage);
      }
      mCitePrefix.AppendLiteral("\n\n");
      mCitePrefix.Append(replyHeaderOriginalmessage);
      mCitePrefix.AppendLiteral("\n");
    }
  }
}

/**
 * The formatflowed parameter directs if formatflowed should be used in the conversion.
 * format=flowed (RFC 2646) is a way to represent flow in a plain text mail, without
 * disturbing the plain text.
 */
nsresult
QuotingOutputStreamListener::ConvertToPlainText(PRBool formatflowed /* = PR_FALSE */)
{
  nsresult rv = ConvertBufToPlainText(mMsgBody, formatflowed);
  if (NS_FAILED(rv))
    return rv;
  return ConvertBufToPlainText(mSignature, formatflowed);
}

NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest *request, nsISupports * /* ctxt */)
{
  return NS_OK;
}

NS_IMETHODIMP QuotingOutputStreamListener::OnStopRequest(nsIRequest *request, nsISupports * /* ctxt */, nsresult status)
{
  nsresult rv = NS_OK;
  nsAutoString aCharset;

  nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
  if (compose) 
  {
    MSG_ComposeType type;
    compose->GetType(&type);

    // Assign cite information if available...
    if (!mCiteReference.IsEmpty())
      compose->SetCiteReference(mCiteReference);

    if (mHeaders && (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll || type == nsIMsgCompType::ReplyToSender ||
                     type == nsIMsgCompType::ReplyToGroup || type == nsIMsgCompType::ReplyToSenderAndGroup) && mQuoteOriginal)
    {
      nsCOMPtr<nsIMsgCompFields> compFields;
      compose->GetCompFields(getter_AddRefs(compFields));
      if (compFields)
      {
        aCharset.AssignLiteral("UTF-8");
        nsAutoString recipient;
        nsAutoString cc;
        nsAutoString bcc;
        nsAutoString replyTo;
        nsAutoString mailReplyTo;
        nsAutoString mailFollowupTo;
        nsAutoString newgroups;
        nsAutoString followUpTo;
        nsAutoString messageId;
        nsAutoString references;
        nsXPIDLCString outCString;
        PRBool needToRemoveDup = PR_FALSE;
        if (!mMimeConverter)
        {
          mMimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
        }
        nsXPIDLCString charset;
        compFields->GetCharacterSet(getter_Copies(charset));

        if (type == nsIMsgCompType::ReplyAll)
        {
          mHeaders->ExtractHeader(HEADER_TO, PR_TRUE, getter_Copies(outCString));
          if (outCString)
          {
            mMimeConverter->DecodeMimeHeader(outCString, recipient, charset);
          }

          mHeaders->ExtractHeader(HEADER_CC, PR_TRUE, getter_Copies(outCString));
          if (outCString)
          {
            mMimeConverter->DecodeMimeHeader(outCString, cc, charset);
          }

          // preserve BCC for the reply-to-self case
          mHeaders->ExtractHeader(HEADER_BCC, PR_TRUE, getter_Copies(outCString));
          if (outCString)
          {
            mMimeConverter->DecodeMimeHeader(outCString, bcc, charset);
            if (bcc.Length() > 0)
              compFields->SetBcc(bcc);
          }

          mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, PR_TRUE, getter_Copies(outCString));
          if (outCString)
          {
            mMimeConverter->DecodeMimeHeader(outCString, mailFollowupTo, charset);
          }

          if (! mailFollowupTo.IsEmpty())
          { // handle Mail-Followup-To (http://cr.yp.to/proto/replyto.html)
            compFields->SetTo(mailFollowupTo);
          }
          else
          { // default behaviour for messages without Mail-Followup-To
            if (recipient.Length() > 0 && cc.Length() > 0)
              recipient.AppendLiteral(", ");
            recipient += cc;
            compFields->SetCc(recipient);
          }

          needToRemoveDup = PR_TRUE;
        }
              
        mHeaders->ExtractHeader(HEADER_REPLY_TO, PR_FALSE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, replyTo, charset);
        }

        mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, PR_TRUE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, mailReplyTo, charset);
        }

        mHeaders->ExtractHeader(HEADER_NEWSGROUPS, PR_FALSE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, newgroups, charset);
        }

        mHeaders->ExtractHeader(HEADER_FOLLOWUP_TO, PR_FALSE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, followUpTo, charset);
        }

        mHeaders->ExtractHeader(HEADER_MESSAGE_ID, PR_FALSE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, messageId, charset);
        }

        mHeaders->ExtractHeader(HEADER_REFERENCES, PR_FALSE, getter_Copies(outCString));
        if (outCString)
        {
          mMimeConverter->DecodeMimeHeader(outCString, references, charset);
        }

        if (! ((type == nsIMsgCompType::ReplyAll) && ! mailFollowupTo.IsEmpty()))
        {
          if (! mailReplyTo.IsEmpty())
          { // handle Mail-Reply-To (http://cr.yp.to/proto/replyto.html)
            compFields->SetTo(mailReplyTo);
            needToRemoveDup = PR_TRUE;
          }
          else if (! replyTo.IsEmpty())
          { // default behaviour for messages without Mail-Reply-To
            compFields->SetTo(replyTo);
            needToRemoveDup = PR_TRUE;
          }
        }

        if (! newgroups.IsEmpty())
        {
          if ((type != nsIMsgCompType::Reply) && (type != nsIMsgCompType::ReplyToSender))
            compFields->SetNewsgroups(newgroups);
          if (type == nsIMsgCompType::ReplyToGroup)
            compFields->SetTo(EmptyString());
        }

        if (! followUpTo.IsEmpty())
        {
          // Handle "followup-to: poster" magic keyword here
          if (followUpTo.EqualsLiteral("poster"))
          {
            nsCOMPtr<nsIDOMWindowInternal> composeWindow;
            nsCOMPtr<nsIPrompt> prompt;
            compose->GetDomWindow(getter_AddRefs(composeWindow));
            if (composeWindow)
              composeWindow->GetPrompter(getter_AddRefs(prompt));
            nsMsgDisplayMessageByID(prompt, NS_MSG_FOLLOWUPTO_ALERT);

            // If reply-to is empty, use the from header to fetch
            // the original sender's email
            if (!replyTo.IsEmpty())
              compFields->SetTo(replyTo);
            else
            {
              mHeaders->ExtractHeader(HEADER_FROM, PR_FALSE, getter_Copies(outCString));
              if (outCString)
              {
                nsAutoString from;
                mMimeConverter->DecodeMimeHeader(outCString, from, charset);
                compFields->SetTo(from);
              }
            }

            // Clear the newsgroup: header field, because followup-to: poster
            // only follows up to the original sender
            if (! newgroups.IsEmpty())
              compFields->SetNewsgroups(EmptyString());
          }
          else // Process "followup-to: newsgroup-content" here
          {
            if (type != nsIMsgCompType::ReplyToSender)
              compFields->SetNewsgroups(followUpTo);
            if (type == nsIMsgCompType::Reply)
              compFields->SetTo(EmptyString());
          }
        }

        if (! references.IsEmpty())
          references.Append(PRUnichar(' '));
        references += messageId;
        compFields->SetReferences(NS_LossyConvertUCS2toASCII(references).get());

        if (needToRemoveDup)
        {
          //Remove duplicate addresses between TO && CC
          char * resultStr;
          nsMsgCompFields* _compFields = (nsMsgCompFields*)compFields.get();  // XXX what is this?
          if (NS_SUCCEEDED(rv))
          {
            nsCString addressToBeRemoved(_compFields->GetTo());
            if (mIdentity)
            {
              nsXPIDLCString email;
              mIdentity->GetEmail(getter_Copies(email));
              addressToBeRemoved += ", ";
              addressToBeRemoved += email;
            }

            rv= RemoveDuplicateAddresses(_compFields->GetCc(), addressToBeRemoved.get(), PR_TRUE, &resultStr);
            if (NS_SUCCEEDED(rv))
            {
              _compFields->SetCc(resultStr);
              PR_Free(resultStr);
            }
          }
        }    
      }
    }

#ifdef MSGCOMP_TRACE_PERFORMANCE
    nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID));
    composeService->TimeStamp("Done with MIME. Now we're updating the UI elements", PR_FALSE);
#endif

    if (mQuoteOriginal)
      compose->NotifyStateListeners(eComposeFieldsReady, NS_OK);

#ifdef MSGCOMP_TRACE_PERFORMANCE
    composeService->TimeStamp("Addressing widget, window title and focus are now set, time to insert the body", PR_FALSE);
#endif

    if (! mHeadersOnly)
      mMsgBody.AppendLiteral("</html>");

    // Now we have an HTML representation of the quoted message.
    // If we are in plain text mode, we need to convert this to plain
    // text before we try to insert it into the editor. If we don't, we
    // just get lots of HTML text in the message...not good.
    //
    // XXX not m_composeHTML? /BenB
    PRBool composeHTML = PR_TRUE;
    compose->GetComposeHTML(&composeHTML);
    if (!composeHTML)
    {
      // Downsampling. The charset should only consist of ascii.
      char *target_charset = ToNewCString(aCharset);
      PRBool formatflowed = UseFormatFlowed(target_charset);
      ConvertToPlainText(formatflowed);
      Recycle(target_charset);
    }

    compose->ProcessSignature(mIdentity, PR_TRUE, &mSignature);

    nsCOMPtr<nsIEditor> editor;
    if (NS_SUCCEEDED(compose->GetEditor(getter_AddRefs(editor))) && editor)
    {
      if (mQuoteOriginal)
        compose->ConvertAndLoadComposeWindow(mCitePrefix,
                                             mMsgBody, mSignature,
                                             PR_TRUE, composeHTML);
      else
        InsertToCompose(editor, composeHTML);
    }

    if (mQuoteOriginal)
      compose->NotifyStateListeners(eComposeBodyReady, NS_OK);
  }
  return rv;
}

NS_IMETHODIMP QuotingOutputStreamListener::OnDataAvailable(nsIRequest *request,
                              nsISupports *ctxt, nsIInputStream *inStr, 
                              PRUint32 sourceOffset, PRUint32 count)
{
  nsresult rv = NS_OK;
  NS_ENSURE_ARG(inStr);

  if (mHeadersOnly)
    return rv;

  char *newBuf = (char *)PR_Malloc(count + 1);
  if (!newBuf)
    return NS_ERROR_FAILURE;

  PRUint32 numWritten = 0; 
  rv = inStr->Read(newBuf, count, &numWritten);
  if (rv == NS_BASE_STREAM_WOULD_BLOCK)
    rv = NS_OK;
  newBuf[numWritten] = '\0';
  if (NS_SUCCEEDED(rv) && numWritten > 0)
  {
    // Create unicode decoder.
    if (!mUnicodeDecoder)
    {
      nsCOMPtr<nsICharsetConverterManager> ccm = 
               do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); 
      if (NS_SUCCEEDED(rv))
      {
        rv = ccm->GetUnicodeDecoderRaw("UTF-8",
                                       getter_AddRefs(mUnicodeDecoder));
      }
    }

    if (NS_SUCCEEDED(rv))
    {
      PRInt32 unicharLength;
      PRInt32 inputLength = (PRInt32) numWritten;
      rv = mUnicodeDecoder->GetMaxLength(newBuf, numWritten, &unicharLength);
      if (NS_SUCCEEDED(rv))
      {
        // Use this local buffer if possible.
        const PRInt32 kLocalBufSize = 4096;
        PRUnichar localBuf[kLocalBufSize];
        PRUnichar *unichars = localBuf;

        if (unicharLength > kLocalBufSize)
        {
          // Otherwise, use the buffer of the class.
          if (!mUnicodeConversionBuffer ||
              unicharLength > mUnicodeBufferCharacterLength)
          {
            if (mUnicodeConversionBuffer)
              nsMemory::Free(mUnicodeConversionBuffer);
            mUnicodeConversionBuffer = (PRUnichar *) nsMemory::Alloc(unicharLength * sizeof(PRUnichar));
            if (!mUnicodeConversionBuffer)
            {
              mUnicodeBufferCharacterLength = 0;
              PR_Free(newBuf);
              return NS_ERROR_OUT_OF_MEMORY;
            }
            mUnicodeBufferCharacterLength = unicharLength;
          }
          unichars = mUnicodeConversionBuffer;
        }

        PRInt32 consumedInputLength = 0;
        PRInt32 originalInputLength = inputLength;
        char *inputBuffer = newBuf;
        PRInt32 convertedOutputLength = 0;
        PRInt32 outputBufferLength = unicharLength;
        PRUnichar *originalOutputBuffer = unichars;
        do 
        {
          rv = mUnicodeDecoder->Convert(inputBuffer, &inputLength, unichars, &unicharLength);
          if (NS_SUCCEEDED(rv)) 
          {
            convertedOutputLength += unicharLength;
            break;
          }

          // if we failed, we consume one byte, replace it with a question mark
          // and try the conversion again.
          unichars += unicharLength;
          *unichars = (PRUnichar)'?';
          unichars++;
          unicharLength++;

          mUnicodeDecoder->Reset();

          inputBuffer += ++inputLength;
          consumedInputLength += inputLength;
          inputLength = originalInputLength - consumedInputLength;  // update input length to convert
          convertedOutputLength += unicharLength;
          unicharLength = outputBufferLength - unicharLength;       // update output length

        } while (NS_FAILED(rv) &&
                 (originalInputLength > consumedInputLength) && 
                 (outputBufferLength > convertedOutputLength));

        if (convertedOutputLength > 0)
          mMsgBody.Append(originalOutputBuffer, convertedOutputLength);
      }
    }
  }

  PR_FREEIF(newBuf);
  return rv;
}

nsresult
QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose *obj)
{
  mWeakComposeObj = do_GetWeakReference(obj);
  return NS_OK;
}

nsresult
QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders * headers)
{
  mHeaders = headers;
  return NS_OK;
}

NS_IMETHODIMP
QuotingOutputStreamListener::InsertToCompose(nsIEditor *aEditor,
                                             PRBool aHTMLEditor)
{
  // First, get the nsIEditor interface for future use
  nsCOMPtr<nsIDOMNode> nodeInserted;

  TranslateLineEnding(mMsgBody);

  // Now, insert it into the editor...
  if (aEditor)
    aEditor->EnableUndo(PR_TRUE);

  nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
  if (!mMsgBody.IsEmpty() && compose)
  {
    compose->SetInsertingQuotedContent(PR_TRUE);
    if (!mCitePrefix.IsEmpty())
    {
      if (!aHTMLEditor)
        mCitePrefix.AppendLiteral("\n");
      nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(aEditor));
      if (textEditor)
        textEditor->InsertText(mCitePrefix);
    }

    nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(aEditor));
    if (mailEditor)
    {
      // XXX see bug #206793
      nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
      nsCOMPtr<nsIDOMWindowInternal> domWindow;
      if (compose)
        compose->GetDomWindow(getter_AddRefs(domWindow));
      nsIDocShell *docshell = nsnull;
      nsCOMPtr<nsIScriptGlobalObject> globalObj = do_QueryInterface(domWindow);
      if (globalObj)
        docshell = globalObj->GetDocShell();
      if (docshell)
        docshell->SetAppType(nsIDocShell::APP_TYPE_EDITOR);
      
      if (aHTMLEditor)
        mailEditor->InsertAsCitedQuotation(mMsgBody, EmptyString(), PR_TRUE,
                                           getter_AddRefs(nodeInserted));
      else
        mailEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted));
    }
    compose->SetInsertingQuotedContent(PR_FALSE);
  }

  if (aEditor)
  {
    nsCOMPtr<nsIPlaintextEditor> textEditor = do_QueryInterface(aEditor);
    if (textEditor)
    {
      nsCOMPtr<nsISelection> selection;
      nsCOMPtr<nsIDOMNode>   parent;
      PRInt32                offset;
      nsresult               rv;

      // get parent and offset of mailcite
      rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
      NS_ENSURE_SUCCESS(rv, rv);

      // get selection
      aEditor->GetSelection(getter_AddRefs(selection));
      if (selection)
      {
        // place selection after mailcite
        selection->Collapse(parent, offset+1);
        // insert a break at current selection
        textEditor->InsertLineBreak();
        selection->Collapse(parent, offset+1);
      }
      nsCOMPtr<nsISelectionController> selCon;
      aEditor->GetSelectionController(getter_AddRefs(selCon));

      if (selCon)
        selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_ANCHOR_REGION, PR_TRUE);
    }
  }

  return NS_OK;
}

NS_IMPL_ISUPPORTS2(QuotingOutputStreamListener, nsIMsgQuotingOutputStreamListener, nsIStreamListener)
////////////////////////////////////////////////////////////////////////////////////
// END OF QUOTING LISTENER
////////////////////////////////////////////////////////////////////////////////////

/* attribute MSG_ComposeType type; */
NS_IMETHODIMP nsMsgCompose::SetType(MSG_ComposeType aType)
{

  mType = aType;
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompose::GetType(MSG_ComposeType *aType)
{
  NS_ENSURE_ARG_POINTER(aType);

  *aType = mType;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompose::QuoteMessage(const char *msgURI)
{
  nsresult    rv;

  mQuotingToFollow = PR_FALSE;

  // Create a mime parser (nsIStreamConverter)!
  mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr <nsIMsgDBHdr> msgHdr;
  rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgHdr));

  // Create the consumer output stream.. this will receive all the HTML from libmime
  mQuoteStreamListener =
    new QuotingOutputStreamListener(msgURI, msgHdr, PR_FALSE, PR_FALSE, m_identity,
                                    m_compFields->GetCharacterSet(), mCharsetOverride, PR_FALSE);

  if (!mQuoteStreamListener)
  {
#ifdef NS_DEBUG
    printf("Failed to create mQuoteStreamListener\n");
#endif
    return NS_ERROR_FAILURE;
  }
  NS_ADDREF(mQuoteStreamListener);

  mQuoteStreamListener->SetComposeObj(this);

  rv = mQuote->QuoteMessage(msgURI, PR_FALSE, mQuoteStreamListener, 
                            mCharsetOverride ? m_compFields->GetCharacterSet() : "", PR_FALSE);
  return rv;
}

nsresult
nsMsgCompose::QuoteOriginalMessage(const char *originalMsgURI, PRInt32 what) // New template
{
  nsresult    rv;

  mQuotingToFollow = PR_FALSE;

  // Create a mime parser (nsIStreamConverter)!
  mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv);
  if (NS_FAILED(rv) || !mQuote)
    return NS_ERROR_FAILURE;

  PRBool bAutoQuote = PR_TRUE;
  m_identity->GetAutoQuote(&bAutoQuote);

  nsCOMPtr <nsIMsgDBHdr> originalMsgHdr = mOrigMsgHdr;
  if (!originalMsgHdr)
  {
    rv = GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Create the consumer output stream.. this will receive all the HTML from libmime
  mQuoteStreamListener =
    new QuotingOutputStreamListener(originalMsgURI, originalMsgHdr, what != 1, !bAutoQuote, m_identity,
                                    mQuoteCharset.get(), mCharsetOverride, PR_TRUE);

  if (!mQuoteStreamListener)
  {
#ifdef NS_DEBUG
    printf("Failed to create mQuoteStreamListener\n");
#endif
    return NS_ERROR_FAILURE;
  }
  NS_ADDREF(mQuoteStreamListener);

  mQuoteStreamListener->SetComposeObj(this);

  rv = mQuote->QuoteMessage(originalMsgURI, what != 1, mQuoteStreamListener, 
                            mCharsetOverride ? mQuoteCharset.get() : "", !bAutoQuote);
  return rv;
}

//CleanUpRecipient will remove un-necesary "<>" when a recipient as an address without name
void nsMsgCompose::CleanUpRecipients(nsString& recipients)
{
  PRUint16 i;
  PRBool startANewRecipient = PR_TRUE;
  PRBool removeBracket = PR_FALSE;
  nsAutoString newRecipient;
  PRUnichar aChar;

  for (i = 0; i < recipients.Length(); i ++)
  {
    aChar = recipients[i];
    switch (aChar)
    {
      case '<'  :
        if (startANewRecipient)
          removeBracket = PR_TRUE;
        else
          newRecipient += aChar;
        startANewRecipient = PR_FALSE;
        break;

      case '>'  :
        if (removeBracket)
          removeBracket = PR_FALSE;
        else
          newRecipient += aChar;
        break;

      case ' '  :
        newRecipient += aChar;
        break;

      case ','  :
        newRecipient += aChar;
        startANewRecipient = PR_TRUE;
        removeBracket = PR_FALSE;
        break;

      default   :
        newRecipient += aChar;
        startANewRecipient = PR_FALSE;
        break;
    } 
  }
  recipients = newRecipient;
}

NS_IMETHODIMP nsMsgCompose::RememberQueuedDisposition()
{
  // need to find the msg hdr in the saved folder and then set a property on 
  // the header that we then look at when we actually send the message.
  if (mType == nsIMsgCompType::Reply || 
    mType == nsIMsgCompType::ReplyAll ||
    mType == nsIMsgCompType::ReplyToGroup ||
    mType == nsIMsgCompType::ReplyToSender ||
    mType == nsIMsgCompType::ReplyToSenderAndGroup ||
    mType == nsIMsgCompType::ForwardAsAttachment ||              
    mType == nsIMsgCompType::ForwardInline)
  {
    if (!mOriginalMsgURI.IsEmpty())
    {
      nsMsgKey msgKey;
      if (mMsgSend)
      {
        mMsgSend->GetMessageKey(&msgKey);
        const char *dispositionSetting = "replied";
        if (mType == nsIMsgCompType::ForwardAsAttachment ||              
          mType == nsIMsgCompType::ForwardInline)
          dispositionSetting = "forwarded";
        nsCAutoString msgUri(m_folderName);
        msgUri.Insert("-message", 7); // "mailbox: -> "mailbox-message:"
        msgUri.Append('#');
        msgUri.AppendInt(msgKey);
        nsCOMPtr <nsIMsgDBHdr> msgHdr;
        nsresult rv = GetMsgDBHdrFromURI(msgUri.get(), getter_AddRefs(msgHdr));
        NS_ENSURE_SUCCESS(rv, rv);
        msgHdr->SetStringProperty(ORIG_URI_PROPERTY, mOriginalMsgURI.get());
        msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY, dispositionSetting);
      }
    }
  }
  return NS_OK;
}

nsresult nsMsgCompose::ProcessReplyFlags()
{
  nsresult rv;
  // check to see if we were doing a reply or a forward, if we were, set the answered field flag on the message folder
  // for this URI.
  if (mType == nsIMsgCompType::Reply || 
      mType == nsIMsgCompType::ReplyAll ||
      mType == nsIMsgCompType::ReplyToGroup ||
      mType == nsIMsgCompType::ReplyToSender ||
      mType == nsIMsgCompType::ReplyToSenderAndGroup ||
      mType == nsIMsgCompType::ForwardAsAttachment ||              
      mType == nsIMsgCompType::ForwardInline ||
      mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None)
  {
    if (!mOriginalMsgURI.IsEmpty())
    {
      char *uriList = PL_strdup(mOriginalMsgURI.get());
      if (!uriList)
        return NS_ERROR_OUT_OF_MEMORY;
      char *newStr = uriList;
      char *uri;
      while (nsnull != (uri = nsCRT::strtok(newStr, ",", &newStr)))
      {
        nsCOMPtr <nsIMsgDBHdr> msgHdr;
        rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr));
        NS_ENSURE_SUCCESS(rv,rv);
        if (msgHdr)
        {
          // get the folder for the message resource
          nsCOMPtr<nsIMsgFolder> msgFolder;
          msgHdr->GetFolder(getter_AddRefs(msgFolder));
          if (msgFolder)
          {
            // assume reply. If a draft with disposition, use that, otherwise,
            // check if it's a forward.
            nsMsgDispositionState dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied;
            if (mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None)
              dispositionSetting = mDraftDisposition;
            else if (mType == nsIMsgCompType::ForwardAsAttachment ||              
                mType == nsIMsgCompType::ForwardInline)
              dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded;

            msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting);
            if (mType != nsIMsgCompType::ForwardAsAttachment)
              break;         // just safeguard
          }
        }
      }
      PR_Free(uriList);
    }
  }

  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////////
// This is the listener class for both the send operation and the copy operation. 
// We have to create this class to listen for message send completion and deal with
// failures in both send and copy operations
////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_ADDREF(nsMsgComposeSendListener)
NS_IMPL_RELEASE(nsMsgComposeSendListener)

/*
NS_IMPL_QUERY_INTERFACE4(nsMsgComposeSendListener,
                         nsIMsgComposeSendListener,
                         nsIMsgSendListener,
                         nsIMsgCopyServiceListener,
                         nsIWebProgressListener)
*/
NS_INTERFACE_MAP_BEGIN(nsMsgComposeSendListener)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgComposeSendListener)
  NS_INTERFACE_MAP_ENTRY(nsIMsgComposeSendListener)
  NS_INTERFACE_MAP_ENTRY(nsIMsgSendListener)
  NS_INTERFACE_MAP_ENTRY(nsIMsgCopyServiceListener)
  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
NS_INTERFACE_MAP_END


nsMsgComposeSendListener::nsMsgComposeSendListener(void) 
{ 
#if defined(DEBUG_ducarroz)
  printf("CREATE nsMsgComposeSendListener: %x\n", this);
#endif
  mDeliverMode = 0;
}

nsMsgComposeSendListener::~nsMsgComposeSendListener(void) 
{
#if defined(DEBUG_ducarroz)
  printf("DISPOSE nsMsgComposeSendListener: %x\n", this);
#endif
}

NS_IMETHODIMP nsMsgComposeSendListener::SetMsgCompose(nsIMsgCompose *obj)
{
  mWeakComposeObj = do_GetWeakReference(obj);
  return NS_OK;
}

NS_IMETHODIMP nsMsgComposeSendListener::SetDeliverMode(MSG_DeliverMode deliverMode)
{
  mDeliverMode = deliverMode;
  return NS_OK;
}

nsresult
nsMsgComposeSendListener::OnStartSending(const char *aMsgID, PRUint32 aMsgSize)
{
#ifdef NS_DEBUG
  printf("nsMsgComposeSendListener::OnStartSending()\n");
#endif

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnStartSending(aMsgID, aMsgSize);
  }

  return NS_OK;
}

nsresult
nsMsgComposeSendListener::OnProgress(const char *aMsgID, PRUint32 aProgress, PRUint32 aProgressMax)
{
#ifdef NS_DEBUG
  printf("nsMsgComposeSendListener::OnProgress()\n");
#endif

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnProgress(aMsgID, aProgress, aProgressMax);
  }

  return NS_OK;
}

nsresult
nsMsgComposeSendListener::OnStatus(const char *aMsgID, const PRUnichar *aMsg)
{
#ifdef NS_DEBUG
  printf("nsMsgComposeSendListener::OnStatus()\n");
#endif

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnStatus(aMsgID, aMsg);
  }

  return NS_OK;
}

nsresult nsMsgComposeSendListener::OnSendNotPerformed(const char *aMsgID, nsresult aStatus)
{
 // since OnSendNotPerformed is called in the case where the user aborts the operation
 // by closing the compose window, we need not do the stuff required 
 // for closing the windows. However we would need to do the other operations as below.

  nsresult rv = NS_OK;

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    compose->NotifyStateListeners(eComposeProcessDone,aStatus);

    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnSendNotPerformed(aMsgID, aStatus) ;
  }

  return rv ;
}


nsresult nsMsgComposeSendListener::OnStopSending(const char *aMsgID, nsresult aStatus, const PRUnichar *aMsg, 
                                     nsIFileSpec *returnFileSpec)
{
  nsresult rv = NS_OK;

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    nsCOMPtr<nsIMsgProgress> progress;
    compose->GetProgress(getter_AddRefs(progress));

    if (NS_SUCCEEDED(aStatus))
    {
#ifdef NS_DEBUG
      printf("nsMsgComposeSendListener: Success on the message send operation!\n");
#endif
      nsCOMPtr<nsIMsgCompFields> compFields;
      compose->GetCompFields(getter_AddRefs(compFields));

      // only process the reply flags if we successfully sent the message
      compose->ProcessReplyFlags();

      // Close the window ONLY if we are not going to do a save operation
      nsAutoString fieldsFCC;
      if (NS_SUCCEEDED(compFields->GetFcc(fieldsFCC)))
      {
        if (!fieldsFCC.IsEmpty())
        {
          if (fieldsFCC.LowerCaseEqualsLiteral("nocopy://"))
          {
            compose->NotifyStateListeners(eComposeProcessDone, NS_OK);
            if (progress)
            {
              progress->UnregisterListener(this);
              progress->CloseProgressDialog(PR_FALSE);
            }
            compose->CloseWindow(PR_TRUE);
          }
        }
      }
      else
      {
        compose->NotifyStateListeners(eComposeProcessDone, NS_OK);
        if (progress)
        {
          progress->UnregisterListener(this);
          progress->CloseProgressDialog(PR_FALSE);
        }
        compose->CloseWindow(PR_TRUE);  // if we fail on the simple GetFcc call, close the window to be safe and avoid
                                        // windows hanging around to prevent the app from exiting.
      }

      // Remove the current draft msg when sending draft is done.
      PRBool deleteDraft;
      compose->GetDeleteDraft(&deleteDraft);
      if (deleteDraft)
        RemoveCurrentDraftMessage(compose, PR_FALSE);
    }
    else
    {
#ifdef NS_DEBUG
      printf("nsMsgComposeSendListener: the message send operation failed!\n");
#endif
      compose->NotifyStateListeners(eComposeProcessDone,aStatus);
      if (progress)
      {
        progress->CloseProgressDialog(PR_TRUE);
        progress->UnregisterListener(this);
      }
    }

    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnStopSending(aMsgID, aStatus, aMsg, returnFileSpec);
  }

  return rv;
}

nsresult
nsMsgComposeSendListener::OnGetDraftFolderURI(const char *aFolderURI)
{
  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    compose->SetSavedFolderURI(aFolderURI);

    nsCOMPtr<nsIMsgSendListener> externalListener;
    compose->GetExternalSendListener(getter_AddRefs(externalListener));
    if (externalListener)
      externalListener->OnGetDraftFolderURI(aFolderURI);
  }

  return NS_OK;
}


nsresult
nsMsgComposeSendListener::OnStartCopy()
{
#ifdef NS_DEBUG
  printf("nsMsgComposeSendListener::OnStartCopy()\n");
#endif

  return NS_OK;
}

nsresult
nsMsgComposeSendListener::OnProgress(PRUint32 aProgress, PRUint32 aProgressMax)
{
#ifdef NS_DEBUG
  printf("nsMsgComposeSendListener::OnProgress() - COPY\n");
#endif
  return NS_OK;
}

nsresult
nsMsgComposeSendListener::OnStopCopy(nsresult aStatus)
{
  nsresult rv = NS_OK;

  nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
  if (compose)
  {
    if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || 
          mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft)
      compose->RememberQueuedDisposition();

    // Ok, if we are here, we are done with the send/copy operation so
    // we have to do something with the window....SHOW if failed, Close
    // if succeeded

    nsCOMPtr<nsIMsgProgress> progress;
    compose->GetProgress(getter_AddRefs(progress));
    if (progress)
    {
    //Unregister ourself from msg compose progress
      progress->UnregisterListener(this);
      progress->CloseProgressDialog(NS_FAILED(aStatus));
    }

    compose->NotifyStateListeners(eComposeProcessDone,aStatus);

    if (NS_SUCCEEDED(aStatus))
    {
#ifdef NS_DEBUG
      printf("nsMsgComposeSendListener: Success on the message copy operation!\n");
#endif
      // We should only close the window if we are done. Things like templates
      // and drafts aren't done so their windows should stay open
      if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft ||
          mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate)
      {
        compose->NotifyStateListeners(eSaveInFolderDone,aStatus);
        // Remove the current draft msg when saving as draft/template is done.
        compose->SetDeleteDraft(PR_TRUE);
        RemoveCurrentDraftMessage(compose, PR_TRUE);
      }
      else
      {
        // Remove (possible) draft if we're in send later mode
        if(mDeliverMode == nsIMsgSend::nsMsgQueueForLater) {
          compose->SetDeleteDraft(PR_TRUE);
          RemoveCurrentDraftMessage(compose, PR_TRUE);
        }
        compose->CloseWindow(PR_TRUE);
      }
    }
#ifdef NS_DEBUG
    else
      printf("nsMsgComposeSendListener: the message copy operation failed!\n");
#endif
  }

  return rv;
}

nsresult
nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder)
{
  nsresult    rv;
  nsCOMPtr<nsIMsgFolder> aMsgFolder;
  nsXPIDLCString folderUri;

  rv = compObj->GetSavedFolderURI(getter_Copies(folderUri));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIRDFService> rdfService (do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
  NS_ENSURE_SUCCESS(rv, rv); 

  nsCOMPtr <nsIRDFResource> resource;
  rv = rdfService->GetResource(folderUri, getter_AddRefs(resource));
  NS_ENSURE_SUCCESS(rv, rv); 

  aMsgFolder = do_QueryInterface(resource, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  *msgFolder = aMsgFolder;
  NS_IF_ADDREF(*msgFolder);
  return rv;
}

nsresult
nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, PRBool calledByCopy)
{
  nsresult    rv;
  nsCOMPtr <nsIMsgCompFields> compFields = nsnull;

  rv = compObj->GetCompFields(getter_AddRefs(compFields));
  NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get compose fields");
  if (NS_FAILED(rv) || !compFields)
    return rv;

  nsXPIDLCString curDraftIdURL;
  nsMsgKey newUid = 0;
  nsXPIDLCString newDraftIdURL;
  nsCOMPtr<nsIMsgFolder> msgFolder;

  rv = compFields->GetDraftId(getter_Copies(curDraftIdURL));
  NS_ASSERTION((NS_SUCCEEDED(rv) && (curDraftIdURL)), "RemoveCurrentDraftMessage can't get draft id");

  // Skip if no draft id (probably a new draft msg).
  if (NS_SUCCEEDED(rv) && curDraftIdURL.get() && strlen(curDraftIdURL.get()))
  { 
    nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
    rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr));
    NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
    if (NS_SUCCEEDED(rv) && msgDBHdr)
    { // get the folder for the message resource
      msgDBHdr->GetFolder(getter_AddRefs(msgFolder));
      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg folder interface pointer.");
      if (NS_SUCCEEDED(rv) && msgFolder)
      {
        PRUint32 folderFlags;
        msgFolder->GetFlags(&folderFlags);
        // only do this if it's a drafts folder.
        if (folderFlags & MSG_FOLDER_FLAG_DRAFTS)
        {  // build the msg arrary
          nsCOMPtr<nsISupportsArray> messageArray;
          rv = NS_NewISupportsArray(getter_AddRefs(messageArray));
          NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't allocate support array.");

          //nsCOMPtr<nsISupports> msgSupport = do_QueryInterface(msgDBHdr, &rv);
          //NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header interface pointer.");
          if (NS_SUCCEEDED(rv) && messageArray)
          {   // ready to delete the msg
            rv = messageArray->AppendElement(msgDBHdr);
            NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't append msg header to array.");
            if (NS_SUCCEEDED(rv))
            rv = msgFolder->DeleteMessages(messageArray, nsnull, PR_TRUE, PR_FALSE, nsnull, PR_FALSE /*allowUndo*/);
            NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't delete message.");
          }
        }
      }
    }
    else
    {
      // If we get here we have the case where the draft folder 
                  // is on the server and
      // it's not currently open (in thread pane), so draft 
                  // msgs are saved to the server
      // but they're not in our local DB. In this case, 
      // GetMsgDBHdrFromURI() will never
      // find the msg. If the draft folder is a local one 
      // then we'll not get here because
      // the draft msgs are saved to the local folder and 
      // are in local DB. Make sure the
      // msg folder is imap.  Even if we get here due to 
      // DB errors (worst case), we should
      // still try to delete msg on the server because 
      // that's where the master copy of the
      // msgs are stored, if draft folder is on the server.  
      // For local case, since DB is bad
      // we can't do anything with it anyway so it'll be 
      // noop in this case.
      rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder));
      if (NS_SUCCEEDED(rv) && msgFolder)
      {
        nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
        NS_ASSERTION(imapFolder, "The draft folder MUST be an imap folder in order to mark the msg delete!");
        if (NS_SUCCEEDED(rv) && imapFolder)
        {
          const char * str = PL_strstr(curDraftIdURL.get(), "#");
          NS_ASSERTION(str, "Failed to get current draft id url");
          if (str)
          {
            nsMsgKeyArray messageID;
            nsCAutoString srcStr(str+1);
            PRInt32 num=0, err;
            num = srcStr.ToInteger(&err);
            if (num != PRInt32(nsMsgKey_None))
            {
              messageID.Add(num);
              rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, PR_TRUE, messageID.GetArray(), 
                                                messageID.GetSize(), nsnull);
            }
          }
        }
      }
    }
  }

  // Now get the new uid so that next save will remove the right msg
  // regardless whether or not the exiting msg can be deleted.
  if (calledByCopy)
  {
    nsCOMPtr<nsIMsgFolder> savedToFolder;
    nsCOMPtr<nsIMsgSend> msgSend;
    rv = compObj->GetMessageSend(getter_AddRefs(msgSend));
    NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null.");
    if (NS_FAILED(rv) || !msgSend)
      return rv;

    rv = msgSend->GetMessageKey(&newUid);
    NS_ENSURE_SUCCESS(rv, rv);

    // Make sure we have a folder interface pointer
    rv = GetMsgFolder(compObj, getter_AddRefs(savedToFolder));

    // Reset draft (uid) url with the new uid.
    if (savedToFolder && newUid != nsMsgKey_None)
    {
      PRUint32 folderFlags;
      savedToFolder->GetFlags(&folderFlags);
      if (folderFlags & MSG_FOLDER_FLAG_DRAFTS)
      {
        rv = savedToFolder->GenerateMessageURI(newUid, getter_Copies(newDraftIdURL));
        NS_ENSURE_SUCCESS(rv, rv);
        compFields->SetDraftId(newDraftIdURL.get());
      }
    }
  }
  return rv;
}

nsresult
nsMsgComposeSendListener::SetMessageKey(PRUint32 aMessageKey)
{
  return NS_OK;
}

nsresult
nsMsgComposeSendListener::GetMessageId(nsCString* aMessageId)
{
  return NS_OK;
}

/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */
NS_IMETHODIMP nsMsgComposeSendListener::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 aStateFlags, nsresult aStatus)
{
  if (aStateFlags == nsIWebProgressListener::STATE_STOP)
  {
    nsCOMPtr<nsIMsgCompose>compose = do_QueryReferent(mWeakComposeObj);
    if (compose)
    {
      nsCOMPtr<nsIMsgProgress> progress;
      compose->GetProgress(getter_AddRefs(progress));

      //Time to stop any pending operation...
      if (progress)
      {
        //Unregister ourself from msg compose progress
        progress->UnregisterListener(this);

        PRBool  bCanceled = PR_FALSE;
        progress->GetProcessCanceledByUser(&bCanceled);
        if (bCanceled)
        {
          nsXPIDLString msg; 
          nsCOMPtr<nsIMsgStringService> strBundle = do_GetService(NS_MSG_COMPOSESTRINGSERVICE_CONTRACTID);
          strBundle->GetStringByID(NS_MSG_CANCELLING, getter_Copies(msg));
          progress->OnStatusChange(nsnull, nsnull, 0, msg);
        }
      }

      nsCOMPtr<nsIMsgSend> msgSend;
      compose->GetMessageSend(getter_AddRefs(msgSend));
      if (msgSend)
          msgSend->Abort();
    }
  }
  return NS_OK;
}

/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
NS_IMETHODIMP nsMsgComposeSendListener::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress)
{
  /* Ignore this call */
  return NS_OK;
}

/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */
NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location)
{
  /* Ignore this call */
  return NS_OK;
}

/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const PRUnichar *aMessage)
{
  /* Ignore this call */
  return NS_OK;
}

/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 state)
{
  /* Ignore this call */
  return NS_OK;
}

nsresult
nsMsgCompose::ConvertHTMLToText(nsFileSpec& aSigFile, nsString &aSigData)
{
  nsresult    rv;
  nsAutoString    origBuf;

  rv = LoadDataFromFile(aSigFile, origBuf);
  if (NS_FAILED(rv))
    return rv;

  ConvertBufToPlainText(origBuf,PR_FALSE);
  aSigData = origBuf;
  return NS_OK;
}

nsresult
nsMsgCompose::ConvertTextToHTML(nsFileSpec& aSigFile, nsString &aSigData)
{
  nsresult    rv;
  nsAutoString    origBuf;

  rv = LoadDataFromFile(aSigFile, origBuf);
  if (NS_FAILED(rv))
    return rv;

  // Ok, once we are here, we need to escape the data to make sure that
  // we don't do HTML stuff with plain text sigs.
  //
  PRUnichar *escaped = nsEscapeHTML2(origBuf.get());
  if (escaped) 
  {
    aSigData.Append(escaped);
    nsCRT::free(escaped);
  }
  else
  {
    aSigData.Append(origBuf);
  }

  return NS_OK;
}

nsresult
nsMsgCompose::LoadDataFromFile(nsFileSpec& fSpec, nsString &sigData,
                               PRBool aAllowUTF8, PRBool aAllowUTF16)
{
  PRInt32       readSize;
  PRInt32       nGot;
  char          *readBuf;
  char          *ptr;

  if (fSpec.IsDirectory()) {
    NS_ASSERTION(0,"file is a directory");
    return NS_MSG_ERROR_READING_FILE;  
  }

  nsInputFileStream tempFile(fSpec);
  if (!tempFile.is_open())
    return NS_MSG_ERROR_READING_FILE;  

  readSize = fSpec.GetFileSize();
  ptr = readBuf = (char *)PR_Malloc(readSize + 1);  if (!readBuf)
    return NS_ERROR_OUT_OF_MEMORY;
  memset(readBuf, 0, readSize + 1);

  while (readSize) {
    nGot = tempFile.read(ptr, readSize);
    if (nGot > 0) {
      readSize -= nGot;
      ptr += nGot;
    }
    else {
      readSize = 0;
    }
  }
  tempFile.close();

  nsCAutoString sigEncoding(nsMsgI18NParseMetaCharset(&fSpec));
  PRBool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML;

  if (sigEncoding.IsEmpty()) {
    if (aAllowUTF8 && IsUTF8(nsDependentCString(readBuf))) {
      sigEncoding.Assign("UTF-8");
    }
    else if (sigEncoding.IsEmpty() && aAllowUTF16 &&
             fSpec.GetFileSize() % 2 == 0 && fSpec.GetFileSize() >= 2 &&
             ((readBuf[0] == char(0xFE) && readBuf[1] == char(0xFF)) ||
              (readBuf[0] == char(0xFF) && readBuf[1] == char(0xFE)))) {
      sigEncoding.Assign("UTF-16");
    }
    else {
      //default to platform encoding for plain text files w/o meta charset
      nsCAutoString textFileCharset;
      nsMsgI18NTextFileCharset(textFileCharset);
      sigEncoding.Assign(textFileCharset);
    }
  }

  nsCAutoString readStr(readBuf, fSpec.GetFileSize());
  PR_FREEIF(readBuf);

  if (NS_FAILED(ConvertToUnicode(sigEncoding.get(), readStr, sigData)))
    CopyASCIItoUTF16(readStr, sigData);

  //remove sig meta charset to allow user charset override during composition
  if (removeSigCharset)
  {
    nsAutoString metaCharset(NS_LITERAL_STRING("charset="));
    AppendASCIItoUTF16(sigEncoding, metaCharset);
    nsAString::const_iterator realstart, start, end;
    sigData.BeginReading(start);
    sigData.EndReading(end);
    realstart = start;
    if (FindInReadable(metaCharset, start, end,
                       nsCaseInsensitiveStringComparator()))
      sigData.Cut(Distance(realstart, start), Distance(start, end));
  }

  return NS_OK;
}

nsresult
nsMsgCompose::BuildQuotedMessageAndSignature(void)
{
  // 
  // This should never happen...if it does, just bail out...
  //
  NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!\n");
  if (!m_editor)
    return NS_ERROR_FAILURE;

  // We will fire off the quote operation and wait for it to
  // finish before we actually do anything with Ender...
  return QuoteOriginalMessage(mOriginalMsgURI.get(), mWhatHolder);
}

//
// This will process the signature file for the user. This method
// will always append the results to the mMsgBody member variable.
//
nsresult
nsMsgCompose::ProcessSignature(nsIMsgIdentity *identity, PRBool aQuoted, nsString *aMsgBody)
{
  nsresult    rv = NS_OK;

  // Now, we can get sort of fancy. This is the time we need to check
  // for all sorts of user defined stuff, like signatures and editor
  // types and the like!
  //
  //    user_pref(".....sig_file", "y:\\sig.html");
  //    user_pref(".....attach_signature", true);
  //
  // Note: We will have intelligent signature behavior in that we
  // look at the signature file first...if the extension is .htm or 
  // .html, we assume its HTML, otherwise, we assume it is plain text
  //
  // ...and that's not all! What we will also do now is look and see if
  // the file is an image file. If it is an image file, then we should 
  // insert the correct HTML into the composer to have it work, but if we
  // are doing plain text compose, we should insert some sort of message
  // saying "Image Signature Omitted" or something.
  //
  nsCAutoString sigNativePath;
  PRBool        useSigFile = PR_FALSE;
  PRBool        htmlSig = PR_FALSE;
  PRBool        imageSig = PR_FALSE;
  nsAutoString  sigData;
  nsAutoString sigOutput;
  PRInt32      reply_on_top = 0;
  PRBool       sig_bottom = PR_TRUE;

  if (identity)
  {
    identity->GetReplyOnTop(&reply_on_top);
    identity->GetSigBottom(&sig_bottom);
    rv = identity->GetAttachSignature(&useSigFile);
    if (NS_SUCCEEDED(rv) && useSigFile) 
    {
      useSigFile = PR_FALSE;  // by default, assume no signature file!

      nsCOMPtr<nsILocalFile> sigFile;
      rv = identity->GetSignature(getter_AddRefs(sigFile));
      if (NS_SUCCEEDED(rv) && sigFile) {
        rv = sigFile->GetNativePath(sigNativePath);
        if (NS_SUCCEEDED(rv) && !sigNativePath.IsEmpty())
          useSigFile = PR_TRUE; // ok, there's a signature file

        // Now, most importantly, we need to figure out what the content type is for
        // this signature...if we can't, we assume text
        nsCAutoString sigContentType;
        nsresult rv2; // don't want to clobber the other rv
        nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv2));
        if (NS_SUCCEEDED(rv2)) {
          rv2 = mimeFinder->GetTypeFromFile(sigFile, sigContentType);
          if (NS_SUCCEEDED(rv2)) {
            if (StringBeginsWith(sigContentType, NS_LITERAL_CSTRING("image/"), nsCaseInsensitiveCStringComparator()))
              imageSig = PR_TRUE;
            else if (sigContentType.Equals(TEXT_HTML, nsCaseInsensitiveCStringComparator()))
              htmlSig = PR_TRUE;
          }
        }
      }
    }
  }

  // Now, if they didn't even want to use a signature, we should
  // just return nicely.
  //
  if (!useSigFile || NS_FAILED(rv))
    return NS_OK;

  nsFileSpec    testSpec(sigNativePath.get());

  // If this file doesn't really exist, just bail!
  if (!testSpec.Exists())
    return NS_OK;

  static const char      htmlBreak[] = "<BR>";
  static const char      dashes[] = "-- ";
  static const char      htmlsigopen[] = "<div class=\"moz-signature\">";
  static const char      htmlsigclose[] = "</div>";    /* XXX: Due to a bug in
                             4.x' HTML editor, it will not be able to
                             break this HTML sig, if quoted (for the user to
                             interleave a comment). */
  static const char      _preopen[] = "<pre class=\"moz-signature\" cols=%d>";
  char*                  preopen;
  static const char      preclose[] = "</pre>";

  PRInt32 wrapLength = 72; // setup default value in case GetWrapLength failed
  GetWrapLength(&wrapLength);
  preopen = PR_smprintf(_preopen, wrapLength);
  if (!preopen)
    return NS_ERROR_OUT_OF_MEMORY;

  if (imageSig)
  {
    // We have an image signature. If we're using the in HTML composer, we
    // should put in the appropriate HTML for inclusion, otherwise, do nothing.
    if (m_composeHTML)
    {
      sigOutput.AppendLiteral(htmlBreak);
      sigOutput.AppendLiteral(htmlsigopen);
      if (reply_on_top != 1 || sig_bottom || !aQuoted)
        sigOutput.AppendLiteral(dashes);
      sigOutput.AppendLiteral(htmlBreak);
      sigOutput.AppendLiteral("<img src=\"file:///");
           /* XXX pp This gives me 4 slashes on Unix, that's at least one to
              much. Better construct the URL with some service. */
      sigOutput.AppendWithConversion(testSpec);
      sigOutput.AppendLiteral("\" border=0>");
      sigOutput.AppendLiteral(htmlsigclose);
    }
  }
  else
  {
    // is this a text sig with an HTML editor?
    if ( (m_composeHTML) && (!htmlSig) )
      ConvertTextToHTML(testSpec, sigData);
    // is this a HTML sig with a text window?
    else if ( (!m_composeHTML) && (htmlSig) )
      ConvertHTMLToText(testSpec, sigData);
    else // We have a match...
      LoadDataFromFile(testSpec, sigData);  // Get the data!
  }

  // Now that sigData holds data...if any, append it to the body in a nice
  // looking manner
  if (!sigData.IsEmpty())
  {
    if (m_composeHTML)
    {
      sigOutput.AppendLiteral(htmlBreak);
      if (htmlSig)
        sigOutput.AppendLiteral(htmlsigopen);
      else
        sigOutput.AppendASCII(preopen);
    }
    else
      sigOutput.AppendLiteral(CRLF);

    if ((reply_on_top != 1 || sig_bottom || !aQuoted) &&
        sigData.Find("\r-- \r", PR_TRUE) < 0 &&
        sigData.Find("\n-- \n", PR_TRUE) < 0 &&
        sigData.Find("\n-- \r", PR_TRUE) < 0)
    {
      nsDependentSubstring firstFourChars(sigData, 0, 4);

      if (!(firstFourChars.EqualsLiteral("-- \n") ||
            firstFourChars.EqualsLiteral("-- \r")))
      {
        sigOutput.AppendLiteral(dashes);

        if (!m_composeHTML || !htmlSig)
          sigOutput.AppendLiteral(CRLF);
        else if (m_composeHTML)
          sigOutput.AppendLiteral(htmlBreak);
      }
    }

    sigOutput.Append(sigData);

    if (m_composeHTML)
    {
      if (htmlSig)
        sigOutput.AppendLiteral(htmlsigclose);
      else
        sigOutput.AppendLiteral(preclose);
    }
  }

  aMsgBody->Append(sigOutput);
  PR_Free(preopen);
  return NS_OK;
}

nsresult
nsMsgCompose::BuildBodyMessageAndSignature()
{
  nsresult    rv = NS_OK;

  // 
  // This should never happen...if it does, just bail out...
  //
  if (!m_editor)
    return NS_ERROR_FAILURE;

  // 
  // Now, we have the body so we can just blast it into the
  // composition editor window.
  //
  nsAutoString   body;
  m_compFields->GetBody(body);

  /* Some time we want to add a signature and sometime we wont. Let's figure that now...*/
  PRBool addSignature;
  switch (mType)
  {
    case nsIMsgCompType::New :
    case nsIMsgCompType::MailToUrl :    /* same as New */
    case nsIMsgCompType::Reply :        /* should not happen! but just in case */
    case nsIMsgCompType::ReplyAll :       /* should not happen! but just in case */
    case nsIMsgCompType::ForwardAsAttachment :  /* should not happen! but just in case */
    case nsIMsgCompType::ForwardInline :
    case nsIMsgCompType::NewsPost :
    case nsIMsgCompType::ReplyToGroup :
    case nsIMsgCompType::ReplyToSender : 
    case nsIMsgCompType::ReplyToSenderAndGroup :
      addSignature = PR_TRUE;
      break;

    case nsIMsgCompType::Draft :
    case nsIMsgCompType::Template :
      addSignature = PR_FALSE;
      break;

    default :
      addSignature = PR_FALSE;
      break;
  }

  nsAutoString tSignature;

  if (addSignature)
    ProcessSignature(m_identity, PR_FALSE, &tSignature);

  // if type is new, but we have body, this is probably a mapi send, so we need to 
  // replace '\n' with <br> so that the line breaks won't be lost by html.
  // if mailtourl, do the same.
  if (m_composeHTML && (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl))
    body.ReplaceSubstring(NS_LITERAL_STRING("\n").get(), NS_LITERAL_STRING("<br>").get());

  nsString empty;
  rv = ConvertAndLoadComposeWindow(empty, body, tSignature,
                                   PR_FALSE, m_composeHTML);

  return rv;
}

nsresult nsMsgCompose::NotifyStateListeners(TStateListenerNotification aNotificationType, nsresult aResult)
{

  if (aNotificationType == eSaveInFolderDone)
    ResetUrisForEmbeddedObjects();

  if (!mStateListeners)
    return NS_OK;    // maybe there just aren't any.

  PRUint32 numListeners;
  nsresult rv = mStateListeners->Count(&numListeners);
  if (NS_FAILED(rv)) return rv;

  PRUint32 i;
  for (i = 0; i < numListeners;i++)
  {
    nsCOMPtr<nsIMsgComposeStateListener> thisListener =
      do_QueryElementAt(mStateListeners, i);
    if (thisListener)
    {
      switch (aNotificationType)
      {
        case eComposeFieldsReady:
          thisListener->NotifyComposeFieldsReady();
          break;

        case eComposeProcessDone:
          thisListener->ComposeProcessDone(aResult);
          break;

        case eSaveInFolderDone:
          thisListener->SaveInFolderDone(m_folderName.get());
          break;

        case eComposeBodyReady:
          thisListener->NotifyComposeBodyReady();
          break;

        default:
          NS_NOTREACHED("Unknown notification");
          break;
      }
    }
  }

  return NS_OK;
}

nsresult nsMsgCompose::AttachmentPrettyName(const char* scheme, const char* charset, nsACString& _retval)
{
  nsresult rv;

  nsCOMPtr<nsIUTF8ConverterService> utf8Cvt =
    do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID);
  NS_ENSURE_TRUE(utf8Cvt, NS_ERROR_UNEXPECTED);

  nsCAutoString utf8Scheme;

  if (PL_strncasestr(scheme, "file:", 5)) 
  {
    nsCOMPtr<nsIFile> file;
    rv = NS_GetFileFromURLSpec(nsDependentCString(scheme),
                               getter_AddRefs(file));
    NS_ENSURE_SUCCESS(rv, rv);
    nsAutoString leafName;
    rv = file->GetLeafName(leafName);
    NS_ENSURE_SUCCESS(rv, rv);
    CopyUTF16toUTF8(leafName, _retval);
    return rv;
  }

  // To work around a mysterious bug in VC++ 6.
  const char* cset = (!charset || !*charset) ? "UTF-8" : charset;
  rv = utf8Cvt->ConvertURISpecToUTF8(nsDependentCString(scheme), 
                                     cset, utf8Scheme);

  if (NS_SUCCEEDED(rv)) {
    // Some ASCII characters still need to be escaped.
    NS_UnescapeURL(utf8Scheme.get(), utf8Scheme.Length(),
                   esc_SkipControl | esc_AlwaysCopy, _retval);
  } else {
    _retval.Assign(scheme);
  }
  if (PL_strncasestr(scheme, "http:", 5)) 
    _retval.Cut(0, 7);

  return NS_OK;
}

static nsresult OpenAddressBook(const char * dbUri, nsIAddrDatabase** aDatabase)
{
  NS_ENSURE_ARG_POINTER(aDatabase);

  nsresult rv;
  nsCOMPtr<nsIAddressBook> addressBook = do_GetService(NS_ADDRESSBOOK_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  rv = addressBook->GetAbDatabaseFromURI(dbUri, aDatabase);
  NS_ENSURE_SUCCESS(rv,rv);
  return rv;
}

nsresult nsMsgCompose::GetABDirectories(const nsACString& dirUri, nsISupportsArray* directoriesArray, PRBool searchSubDirectory)
{
  static PRBool collectedAddressbookFound;
  if (dirUri.EqualsLiteral(kMDBDirectoryRoot))
    collectedAddressbookFound = PR_FALSE;

  nsresult rv = NS_OK;
  nsCOMPtr<nsIRDFService> rdfService (do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr <nsIRDFResource> resource;
  rv = rdfService->GetResource(dirUri, getter_AddRefs(resource));
  if (NS_FAILED(rv)) return rv;

  // query interface 
  nsCOMPtr<nsIAbDirectory> directory(do_QueryInterface(resource, &rv));
  if (NS_FAILED(rv)) return rv;

  if (!searchSubDirectory)
      return rv;

  nsCOMPtr<nsISimpleEnumerator> subDirectories;
  if (NS_SUCCEEDED(directory->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories)
  {
    nsCOMPtr<nsISupports> item;
    PRBool hasMore;
    while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore)
    {
      if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item))))
      {
        directory = do_QueryInterface(item, &rv);
        if (NS_SUCCEEDED(rv))
        {
          PRBool bIsMailList;

          if (NS_SUCCEEDED(directory->GetIsMailList(&bIsMailList)) && bIsMailList)
            continue;

          nsCOMPtr<nsIRDFResource> source(do_QueryInterface(directory));

          nsXPIDLCString uri;
          // rv = directory->GetDirUri(getter_Copies(uri));
          rv = source->GetValue(getter_Copies(uri));
          NS_ENSURE_SUCCESS(rv, rv);

          PRInt32 pos;
          if (nsCRT::strcmp((const char *)uri, kPersonalAddressbookUri) == 0)
            pos = 0;
          else
          {
            PRUint32 count = 0;
            directoriesArray->Count(&count);

            if (PL_strcmp((const char *)uri, kCollectedAddressbookUri) == 0)
            {
              collectedAddressbookFound = PR_TRUE;
              pos = count;
            }
            else
            {
              if (collectedAddressbookFound && count > 1)
                pos = count - 1;
              else
                pos = count;
            }
          }

          directoriesArray->InsertElementAt(directory, pos);
          rv = GetABDirectories(uri, directoriesArray, PR_TRUE);
        }
      }
    }
  }
  return rv;
}

nsresult nsMsgCompose::BuildMailListArray(nsIAddrDatabase* database, nsIAbDirectory* parentDir, nsISupportsArray* array)
{
  nsresult rv;

  nsCOMPtr<nsIAbDirectory> directory;
  nsCOMPtr<nsISimpleEnumerator> subDirectories;

  if (NS_SUCCEEDED(parentDir->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories)
  {
    nsCOMPtr<nsISupports> item;
    PRBool hasMore;
    while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore)
    {
      if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item))))
      {
        directory = do_QueryInterface(item, &rv);
        if (NS_SUCCEEDED(rv))
        {
          PRBool bIsMailList;

          if (NS_SUCCEEDED(directory->GetIsMailList(&bIsMailList)) && bIsMailList)
          {
            nsXPIDLString listName;
            nsXPIDLString listDescription;

            directory->GetDirName(getter_Copies(listName));
            directory->GetDescription(getter_Copies(listDescription));

            nsMsgMailList* mailList = new nsMsgMailList(nsAutoString((const PRUnichar*)listName),
                  nsAutoString((const PRUnichar*)listDescription), directory);
            if (!mailList)
              return NS_ERROR_OUT_OF_MEMORY;
            NS_ADDREF(mailList);

            rv = array->AppendElement(mailList);
            if (NS_FAILED(rv))
              return rv;

            NS_RELEASE(mailList);
          }
        }
      }
    }
  }
  return rv;
}


nsresult nsMsgCompose::GetMailListAddresses(nsString& name, nsISupportsArray* mailListArray, nsISupportsArray** addressesArray)
{
  nsresult rv;
  nsCOMPtr<nsIEnumerator> enumerator;

  rv = mailListArray->Enumerate(getter_AddRefs(enumerator));
  if (NS_SUCCEEDED(rv))
  {
    for (rv = enumerator->First(); NS_SUCCEEDED(rv); rv = enumerator->Next())
    {
      nsMsgMailList* mailList;
      rv = enumerator->CurrentItem((nsISupports**)&mailList);
      if (NS_SUCCEEDED(rv) && mailList)
      {
        if (name.Equals(mailList->mFullName, nsCaseInsensitiveStringComparator()))
        {
          if (!mailList->mDirectory)
            return NS_ERROR_FAILURE;

          mailList->mDirectory->GetAddressLists(addressesArray);
          NS_RELEASE(mailList);
          return NS_OK;
        }
        NS_RELEASE(mailList);
      }
    }
  }

  return NS_ERROR_FAILURE;
}


// 3 = To, Cc, Bcc
#define MAX_OF_RECIPIENT_ARRAY    3

NS_IMETHODIMP nsMsgCompose::CheckAndPopulateRecipients(PRBool populateMailList, PRBool returnNonHTMLRecipients, PRUnichar **nonHTMLRecipients, PRUint32 *_retval)
{
  if (returnNonHTMLRecipients && !nonHTMLRecipients || !_retval)
    return NS_ERROR_INVALID_ARG;

  nsresult rv = NS_OK;
  PRInt32 i;
  PRInt32 j;
  PRInt32 k;

  if (nonHTMLRecipients)
    *nonHTMLRecipients = nsnull;
  if (_retval)
    *_retval = nsIAbPreferMailFormat::unknown;

  /* First, build an array with original recipients */
  nsCOMArray<nsMsgRecipient> recipientsList[MAX_OF_RECIPIENT_ARRAY];

  nsAutoString originalRecipients[MAX_OF_RECIPIENT_ARRAY];
  m_compFields->GetTo(originalRecipients[0]);
  m_compFields->GetCc(originalRecipients[1]);
  m_compFields->GetBcc(originalRecipients[2]);

  nsCOMPtr<nsIMsgRecipientArray> addressArray;
  nsCOMPtr<nsIMsgRecipientArray> emailArray;
  for (i = 0; i < MAX_OF_RECIPIENT_ARRAY; i ++)
  {
    if (originalRecipients[i].IsEmpty())
      continue;
    rv = m_compFields->SplitRecipientsEx(originalRecipients[i].get(),
                                         getter_AddRefs(addressArray), getter_AddRefs(emailArray));
    if (NS_SUCCEEDED(rv))
    {
      PRInt32 nbrRecipients;
      nsXPIDLString emailAddr;
      nsXPIDLString addr;
      addressArray->GetCount(&nbrRecipients);

      for (j = 0; j < nbrRecipients; j ++)
      {
        rv = addressArray->StringAt(j, getter_Copies(addr));
        if (NS_FAILED(rv))
          return rv;

        rv = emailArray->StringAt(j, getter_Copies(emailAddr));
        if (NS_FAILED(rv))
          return rv;

        nsMsgRecipient* recipient = new nsMsgRecipient(nsAutoString(addr), nsAutoString(emailAddr));
        if (!recipient)
           return NS_ERROR_OUT_OF_MEMORY;
        NS_ADDREF(recipient);
        rv = recipientsList[i].AppendObject(recipient) ? NS_OK : NS_ERROR_FAILURE;
        NS_RELEASE(recipient);
        if (NS_FAILED(rv))
          return rv;
      }
    }
    else
      return rv;
  }

  /* Then look them up in the Addressbooks*/

  PRBool stillNeedToSearch = PR_TRUE;
  nsCOMPtr<nsIAddrDatabase> abDataBase;
  nsCOMPtr<nsIAbDirectory> abDirectory;   
  nsCOMPtr <nsIAbCard> existingCard;
  nsCOMPtr <nsISupportsArray> mailListAddresses;
  nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));
  nsCOMPtr<nsISupportsArray> mailListArray (do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv));
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsISupportsArray> addrbookDirArray (do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv));
  if (NS_SUCCEEDED(rv) && addrbookDirArray)
  {
    nsString dirPath;
    GetABDirectories(NS_LITERAL_CSTRING(kAllDirectoryRoot), addrbookDirArray, PR_TRUE);
    PRInt32 nbrRecipients;

    PRBool dirtyABDatabase; 
    PRUint32 nbrAddressbook;
    addrbookDirArray->Count(&nbrAddressbook);
    for (k = 0; k < (PRInt32)nbrAddressbook && stillNeedToSearch; k ++)
    {
      nsCOMPtr<nsISupports> item;
      addrbookDirArray->GetElementAt(k, getter_AddRefs(item));

      // Avoid recursive mailing lists
      if (abDirectory && (item == abDirectory))
      {
        stillNeedToSearch = PR_FALSE;
        break;
      }

      abDirectory = do_QueryInterface(item, &rv);
      if (NS_FAILED(rv))
        return rv;

      nsCOMPtr<nsIRDFResource> source(do_QueryInterface(abDirectory));

      nsXPIDLCString uri;
      rv = source->GetValue(getter_Copies(uri));
      NS_ENSURE_SUCCESS(rv, rv);

      PRBool supportsMailingLists;
      rv = abDirectory->GetSupportsMailingLists(&supportsMailingLists);
      if (NS_FAILED(rv) || !supportsMailingLists)
        continue;

      dirtyABDatabase = PR_FALSE; 
      rv = OpenAddressBook(uri.get(), getter_AddRefs(abDataBase));
      if (NS_FAILED(rv) || !abDataBase)
        continue;

      /* Collect all mailing list defined in this address book */
      rv = BuildMailListArray(abDataBase, abDirectory, mailListArray);
      if (NS_FAILED(rv))
        return rv;

      stillNeedToSearch = PR_FALSE;
      for (i = 0; i < MAX_OF_RECIPIENT_ARRAY; i ++)
      {
        nbrRecipients = recipientsList[i].Count();
        if (nbrRecipients == 0)
          continue;
        for (j = 0; j < nbrRecipients; j++, nbrRecipients = recipientsList[i].Count())
        {
          nsMsgRecipient* recipient = recipientsList[i][j];
          if (recipient && !recipient->mProcessed)
          {
            /* First check if it's a mailing list */
            if (NS_SUCCEEDED(GetMailListAddresses(recipient->mAddress, mailListArray, getter_AddRefs(mailListAddresses))))
            {
              if (populateMailList)
              {
                  PRUint32 nbrAddresses = 0;
                  for (mailListAddresses->Count(&nbrAddresses); nbrAddresses > 0; nbrAddresses --)
                  {
                    existingCard = do_QueryElementAt(mailListAddresses,
                                                     nbrAddresses - 1, &rv);
                    if (NS_FAILED(rv))
                      return rv;

                    nsXPIDLString pDisplayName;
                    nsXPIDLString pEmail;
                    nsAutoString fullNameStr;

                    PRBool bIsMailList;
                    rv = existingCard->GetIsMailList(&bIsMailList);
                    if (NS_FAILED(rv))
                      return rv;

                    rv = existingCard->GetDisplayName(getter_Copies(pDisplayName));
                    if (NS_FAILED(rv))
                      return rv;

                    if (bIsMailList)
                      rv = existingCard->GetNotes(getter_Copies(pEmail));
                    else
                      rv = existingCard->GetPrimaryEmail(getter_Copies(pEmail));
                    if (NS_FAILED(rv))
                      return rv;

                    if (parser)
                    {
                      nsXPIDLCString fullAddress;

                      parser->MakeFullAddress(nsnull, NS_ConvertUCS2toUTF8(pDisplayName).get(),
                                              NS_ConvertUCS2toUTF8(pEmail).get(), getter_Copies(fullAddress));
                      if (!fullAddress.IsEmpty())
                      {
                        /* We need to convert back the result from UTF-8 to Unicode */
                        CopyUTF8toUTF16(fullAddress, fullNameStr);
                      }
                    }
                    if (fullNameStr.IsEmpty())
                    {
                      //oops, parser problem! I will try to do my best...
                      fullNameStr = pDisplayName;
                      fullNameStr.AppendLiteral(" <");
                      if (bIsMailList)
                      {
                        if (pEmail && pEmail[0] != 0)
                          fullNameStr += pEmail;
                        else
                          fullNameStr += pDisplayName;
                      }
                      else
                        fullNameStr += pEmail;
                      fullNameStr.Append(PRUnichar('>'));
                    }

                    if (fullNameStr.IsEmpty())
                      continue;

                    /* Now we need to insert the new address into the list of recipient */
                    nsMsgRecipient* newRecipient = new nsMsgRecipient(fullNameStr, nsAutoString(pEmail));
                    if (!recipient)
                       return  NS_ERROR_OUT_OF_MEMORY;
                    NS_ADDREF(newRecipient);

                    if (bIsMailList)
                    {
                      stillNeedToSearch = PR_TRUE;
                    }
                    else
                    {
                      newRecipient->mPreferFormat = nsIAbPreferMailFormat::unknown;
                      rv = existingCard->GetPreferMailFormat(&newRecipient->mPreferFormat);
                      if (NS_SUCCEEDED(rv))
                        recipient->mProcessed = PR_TRUE;
                    }
                    rv = recipientsList[i].InsertObjectAt(newRecipient,
                                                          j + 1) ? NS_OK : NS_ERROR_FAILURE;
                    NS_RELEASE(newRecipient);
                    if (NS_FAILED(rv))
                      return rv;
                  }
                  rv = recipientsList[i].RemoveObjectAt(j) ? NS_OK : NS_ERROR_FAILURE;
                 j --;
              }
              else
                recipient->mProcessed = PR_TRUE;

              continue;
            }

            // Then if we have a card for this email address
            // Please DO NOT change the 4th param of GetCardFromAttribute() call to 
            // PR_TRUE (ie, case insensitive) without reading bugs #128535 and #121478.
            rv = abDataBase->GetCardFromAttribute(abDirectory, kPriEmailColumn, NS_LossyConvertUCS2toASCII(recipient->mEmail).get(), PR_FALSE /* case insensitive */, getter_AddRefs(existingCard));
            if (NS_SUCCEEDED(rv) && existingCard)
            {
              recipient->mPreferFormat = nsIAbPreferMailFormat::unknown;
              rv = existingCard->GetPreferMailFormat(&recipient->mPreferFormat);
              if (NS_SUCCEEDED(rv))
                recipient->mProcessed = PR_TRUE;

              // bump the popularity index for this card since we are about to send e-mail to it
              PRUint32 popularityIndex = 0;
              if (NS_SUCCEEDED(existingCard->GetPopularityIndex(&popularityIndex)))
              {
                existingCard->SetPopularityIndex(++popularityIndex);
                abDataBase->EditCard(existingCard, PR_FALSE);
                dirtyABDatabase = PR_TRUE;
              }
            }
            else
              stillNeedToSearch = PR_TRUE;
          }
        }
      }

        abDataBase->Close(dirtyABDatabase); // commit the database changes if we updated the popularity count.
    }
  }

  /* Finally return the list of non HTML recipient if requested and/or rebuilt the recipient field.
     Also, check for domain preference when preferFormat is unknown
  */
    nsAutoString recipientsStr;
    nsAutoString nonHtmlRecipientsStr;
    nsXPIDLString plaintextDomains;
    nsXPIDLString htmlDomains;
    nsAutoString domain;

    nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch)
    {
      NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.plaintext_domains", EmptyString(),
                                                  plaintextDomains);
      NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.html_domains", EmptyString(),
                                                  htmlDomains);
    }

    PRBool atLeastOneRecipientPrefersUnknown = PR_FALSE;
    PRBool atLeastOneRecipientPrefersPlainText = PR_FALSE;
    PRBool atLeastOneRecipientPrefersHTML = PR_FALSE;

    for (i = 0; i < MAX_OF_RECIPIENT_ARRAY; i ++)
    {
      PRInt32 nbrRecipients = recipientsList[i].Count();
      if (nbrRecipients == 0)
        continue;
      recipientsStr.SetLength(0);

      for (j = 0; j < nbrRecipients; j ++)
      {
        nsMsgRecipient* recipient = recipientsList[i][j];
        if (recipient)
        {          
          /* if we don't have a prefer format for a recipient, check the domain in case we have a format defined for it */
          if (recipient->mPreferFormat == nsIAbPreferMailFormat::unknown &&
              (plaintextDomains.Length() || htmlDomains.Length()))
          {
            PRInt32 atPos = recipient->mEmail.FindChar('@');
            if (atPos >= 0)
            {
              recipient->mEmail.Right(domain, recipient->mEmail.Length() - atPos - 1);
              if (FindInReadable(domain, plaintextDomains, nsCaseInsensitiveStringComparator()))
                recipient->mPreferFormat = nsIAbPreferMailFormat::plaintext;
              else
                if (FindInReadable(domain, htmlDomains, nsCaseInsensitiveStringComparator()))
                  recipient->mPreferFormat = nsIAbPreferMailFormat::html;
            }
          }

          switch (recipient->mPreferFormat)
          {
            case nsIAbPreferMailFormat::html:
              atLeastOneRecipientPrefersHTML = PR_TRUE;
              break;

            case nsIAbPreferMailFormat::plaintext:
              atLeastOneRecipientPrefersPlainText = PR_TRUE;
              break;

            default: /* nsIAbPreferMailFormat::unknown */
              atLeastOneRecipientPrefersUnknown = PR_TRUE;
              break;
          }

          if (populateMailList)
          {
            if (! recipientsStr.IsEmpty())
              recipientsStr.Append(PRUnichar(','));
            recipientsStr.Append(recipient->mAddress);
          }

          if (returnNonHTMLRecipients && recipient->mPreferFormat != nsIAbPreferMailFormat::html)
          {
            if (! nonHtmlRecipientsStr.IsEmpty())
              nonHtmlRecipientsStr.Append(PRUnichar(','));
            nonHtmlRecipientsStr.Append(recipient->mEmail);
          }

        }
      }

      if (populateMailList)
      {
        switch (i)
        {
          case 0 : m_compFields->SetTo(recipientsStr);  break;
          case 1 : m_compFields->SetCc(recipientsStr);  break;
          case 2 : m_compFields->SetBcc(recipientsStr); break;
        }
      }
  }

  if (returnNonHTMLRecipients)
    *nonHTMLRecipients = ToNewUnicode(nonHtmlRecipientsStr);

  if (atLeastOneRecipientPrefersUnknown)
    *_retval = nsIAbPreferMailFormat::unknown;
  else if (atLeastOneRecipientPrefersHTML)
  {
    // if we have at least one recipient that prefers html
    // and at least one that recipients that prefers plain text
    // we need to return unknown, so that we can prompt the user
    if (atLeastOneRecipientPrefersPlainText)
      *_retval = nsIAbPreferMailFormat::unknown;
    else
      *_retval = nsIAbPreferMailFormat::html;
  }
  else 
  {
    NS_ASSERTION(atLeastOneRecipientPrefersPlainText, "at least one should prefer plain text");
    *_retval = nsIAbPreferMailFormat::plaintext;
  }      

  return rv;
}

/* Decides which tags trigger which convertible mode, i.e. here is the logic
   for BodyConvertible */
// Helper function. Parameters are not checked.
nsresult nsMsgCompose::TagConvertible(nsIDOMNode *node,  PRInt32 *_retval)
{
    nsresult rv;

    *_retval = nsIMsgCompConvertible::No;

    PRUint16 nodeType;
    rv = node->GetNodeType(&nodeType);
    if (NS_FAILED(rv))
      return rv;

    nsAutoString element;
    rv = node->GetNodeName(element);
    if (NS_FAILED(rv))
      return rv;

    nsCOMPtr<nsIDOMNode> pItem;
    if      (
              nodeType == nsIDOMNode::TEXT_NODE ||
              element.LowerCaseEqualsLiteral("br") ||
              element.LowerCaseEqualsLiteral("p") ||
              element.LowerCaseEqualsLiteral("pre") ||
              element.LowerCaseEqualsLiteral("tt") ||
              element.LowerCaseEqualsLiteral("html") ||
              element.LowerCaseEqualsLiteral("head") ||
              element.LowerCaseEqualsLiteral("title")
            )
    {
      *_retval = nsIMsgCompConvertible::Plain;
    }
    else if (
              //element.LowerCaseEqualsLiteral("blockquote") || // see below
              element.LowerCaseEqualsLiteral("ul") ||
              element.LowerCaseEqualsLiteral("ol") ||
              element.LowerCaseEqualsLiteral("li") ||
              element.LowerCaseEqualsLiteral("dl") ||
              element.LowerCaseEqualsLiteral("dt") ||
              element.LowerCaseEqualsLiteral("dd")
            )
    {
      *_retval = nsIMsgCompConvertible::Yes;
    }
    else if (
              //element.LowerCaseEqualsLiteral("a") || // see below
              element.LowerCaseEqualsLiteral("h1") ||
              element.LowerCaseEqualsLiteral("h2") ||
              element.LowerCaseEqualsLiteral("h3") ||
              element.LowerCaseEqualsLiteral("h4") ||
              element.LowerCaseEqualsLiteral("h5") ||
              element.LowerCaseEqualsLiteral("h6") ||
              element.LowerCaseEqualsLiteral("hr") ||
              (
                mConvertStructs
                &&
                (
                  element.LowerCaseEqualsLiteral("em") ||
                  element.LowerCaseEqualsLiteral("strong") ||
                  element.LowerCaseEqualsLiteral("code") ||
                  element.LowerCaseEqualsLiteral("b") ||
                  element.LowerCaseEqualsLiteral("i") ||
                  element.LowerCaseEqualsLiteral("u")
                )
              )
            )
    {
      *_retval = nsIMsgCompConvertible::Altering;
    }
    else if (element.LowerCaseEqualsLiteral("body"))
    {
      *_retval = nsIMsgCompConvertible::Plain;

      nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node);
      if (domElement)
      {
        PRBool hasAttribute;
        nsAutoString color;
        if (NS_SUCCEEDED(domElement->HasAttribute(NS_LITERAL_STRING("background"), &hasAttribute))
            && hasAttribute)  // There is a background image
          *_retval = nsIMsgCompConvertible::No; 
        else if (NS_SUCCEEDED(domElement->HasAttribute(NS_LITERAL_STRING("text"), &hasAttribute)) &&
                 hasAttribute &&
                 NS_SUCCEEDED(domElement->GetAttribute(NS_LITERAL_STRING("text"), color)) &&
                 !color.EqualsLiteral("#000000")) {
          *_retval = nsIMsgCompConvertible::Altering;
        }
        else if (NS_SUCCEEDED(domElement->HasAttribute(NS_LITERAL_STRING("bgcolor"), &hasAttribute)) &&
                 hasAttribute &&
                 NS_SUCCEEDED(domElement->GetAttribute(NS_LITERAL_STRING("bgcolor"), color)) &&
                 !color.LowerCaseEqualsLiteral("#ffffff")) {
          *_retval = nsIMsgCompConvertible::Altering;
        }
		else if (NS_SUCCEEDED(domElement->HasAttribute(NS_LITERAL_STRING("dir"), &hasAttribute))
            && hasAttribute)  // dir=rtl attributes should not downconvert
          *_retval = nsIMsgCompConvertible::No; 

        //ignore special color setting for link, vlink and alink at this point.
      }

    }
    else if (element.LowerCaseEqualsLiteral("blockquote"))
    {
      // Skip <blockquote type="cite">
      *_retval = nsIMsgCompConvertible::Yes;

      nsCOMPtr<nsIDOMNamedNodeMap> pAttributes;
      if (NS_SUCCEEDED(node->GetAttributes(getter_AddRefs(pAttributes)))
          && pAttributes)
      {
        nsAutoString typeName; typeName.AssignLiteral("type");
        if (NS_SUCCEEDED(pAttributes->GetNamedItem(typeName,
                                                   getter_AddRefs(pItem)))
            && pItem)
        {
          nsAutoString typeValue;
          if (NS_SUCCEEDED(pItem->GetNodeValue(typeValue)))
          {
            typeValue.StripChars("\"");
            if (typeValue.LowerCaseEqualsLiteral("cite"))
              *_retval = nsIMsgCompConvertible::Plain;
          }
        }
      }
    }
    else if (
              element.LowerCaseEqualsLiteral("div") ||
              element.LowerCaseEqualsLiteral("span") ||
              element.LowerCaseEqualsLiteral("a")
            )
    {
      /* Do some special checks for these tags. They are inside this |else if|
         for performance reasons */
      nsCOMPtr<nsIDOMNamedNodeMap> pAttributes;

      /* First, test, if the <a>, <div> or <span> is inserted by our
         [TXT|HTML]->HTML converter */
      /* This is for an edge case: A Mozilla user replies to plaintext per HTML
         and the recipient of that HTML msg, also a Mozilla user, replies to
         that again. Then we'll have to recognize the stuff inserted by our
         TXT->HTML converter. */
      if (NS_SUCCEEDED(node->GetAttributes(getter_AddRefs(pAttributes)))
          && pAttributes)
      {
        nsAutoString className;
        className.AssignLiteral("class");
        if (NS_SUCCEEDED(pAttributes->GetNamedItem(className,
                                                   getter_AddRefs(pItem)))
            && pItem)
        {
          nsAutoString classValue;
          if (NS_SUCCEEDED(pItem->GetNodeValue(classValue))
              && (classValue.EqualsIgnoreCase("moz-txt", 7) ||
                  classValue.EqualsIgnoreCase("\"moz-txt", 8)))
          {
            *_retval = nsIMsgCompConvertible::Plain;
            return rv;  // Inconsistent :-(
          }
        }
      }

      // Maybe, it's an <a> element inserted by another recognizer (e.g. 4.x')
      if (element.LowerCaseEqualsLiteral("a"))
      {
        /* Ignore anchor tag, if the URI is the same as the text
           (as inserted by recognizers) */
        *_retval = nsIMsgCompConvertible::Altering;

        if (NS_SUCCEEDED(node->GetAttributes(getter_AddRefs(pAttributes)))
            && pAttributes)
        {
          nsAutoString hrefName; hrefName.AssignLiteral("href");
          if (NS_SUCCEEDED(pAttributes->GetNamedItem(hrefName,
                                                     getter_AddRefs(pItem)))
              && pItem)
          {
            nsAutoString hrefValue;
            PRBool hasChild;
            if (NS_SUCCEEDED(pItem->GetNodeValue(hrefValue))
                && NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
            {
              nsCOMPtr<nsIDOMNodeList> children;
              if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children)))
                  && children
                  && NS_SUCCEEDED(children->Item(0, getter_AddRefs(pItem)))
                  && pItem)
              {
                nsAutoString textValue;
                if (NS_SUCCEEDED(pItem->GetNodeValue(textValue))
                    && textValue == hrefValue)
                  *_retval = nsIMsgCompConvertible::Plain;
              }
            }
          }
        }
      }

      // Lastly, test, if it is just a "simple" <div> or <span>
      else if (
                element.LowerCaseEqualsLiteral("div") ||
                element.LowerCaseEqualsLiteral("span")
              )
      {
        /* skip only if no style attribute */
        *_retval = nsIMsgCompConvertible::Plain;

        if (NS_SUCCEEDED(node->GetAttributes(getter_AddRefs(pAttributes)))
            && pAttributes)
        {
          nsAutoString styleName;
          styleName.AssignLiteral("style");
          if (NS_SUCCEEDED(pAttributes->GetNamedItem(styleName,
                                                     getter_AddRefs(pItem)))
              && pItem)
          {
            nsAutoString styleValue;
            if (NS_SUCCEEDED(pItem->GetNodeValue(styleValue))
                && !styleValue.IsEmpty())
              *_retval = nsIMsgCompConvertible::No;
          }
        }
      }
    }

    return rv;
}

nsresult nsMsgCompose::_BodyConvertible(nsIDOMNode *node, PRInt32 *_retval)
{
    NS_ENSURE_TRUE(node && _retval, NS_ERROR_NULL_POINTER);

    nsresult rv;
    PRInt32 result;

    // Check this node
    rv = TagConvertible(node, &result);
    if (NS_FAILED(rv))
        return rv;

    // Walk tree recursively to check the children
    PRBool hasChild;
    if (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
    {
      nsCOMPtr<nsIDOMNodeList> children;
      if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children)))
          && children)
      {
        PRUint32 nbrOfElements;
        rv = children->GetLength(&nbrOfElements);
        for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < nbrOfElements; i++)
        {
          nsCOMPtr<nsIDOMNode> pItem;
          if (NS_SUCCEEDED(children->Item(i, getter_AddRefs(pItem)))
              && pItem)
          {
            PRInt32 curresult;
            rv = _BodyConvertible(pItem, &curresult);
            if (NS_SUCCEEDED(rv) && curresult > result)
              result = curresult;
          }
        }
      }
    }

    *_retval = result;
    return rv;
}

nsresult nsMsgCompose::BodyConvertible(PRInt32 *_retval)
{
    NS_ENSURE_TRUE(_retval, NS_ERROR_NULL_POINTER);

    nsresult rv;

    if (!m_editor)
      return NS_ERROR_FAILURE;

    nsCOMPtr<nsIDOMElement> rootElement;
    rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
    if (NS_FAILED(rv) || nsnull == rootElement)
      return rv;

    nsCOMPtr<nsIDOMNode> node = do_QueryInterface(rootElement);
    if (nsnull == node)
      return NS_ERROR_FAILURE;

    return _BodyConvertible(node, _retval);
}

nsresult nsMsgCompose::SetSignature(nsIMsgIdentity *identity)
{
  nsresult rv;

  if (! m_editor)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIDOMElement> rootElement;
  rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
  if (NS_FAILED(rv) || nsnull == rootElement)
    return rv;

  //First look for the current signature, if we have one
  nsCOMPtr<nsIDOMNode> lastNode;
  nsCOMPtr<nsIDOMNode> node;
  nsCOMPtr<nsIDOMNode> tempNode;
  nsAutoString tagLocalName;

  rv = rootElement->GetLastChild(getter_AddRefs(lastNode));
  if (NS_SUCCEEDED(rv) && nsnull != lastNode)
  {
    node = lastNode;
    if (m_composeHTML)
    {
      /* In html, the signature is inside an element with
         class="moz-signature", it's must be the last node */
      nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
      if (element)
      {
        nsAutoString attributeName;
        nsAutoString attributeValue;
        attributeName.AssignLiteral("class");

        rv = element->GetAttribute(attributeName, attributeValue);
        if (NS_SUCCEEDED(rv))
        {
          if (attributeValue.Find("moz-signature", PR_TRUE) != kNotFound)
          {
            //Now, I am sure I get the right node!
            m_editor->BeginTransaction();
            node->GetPreviousSibling(getter_AddRefs(tempNode));
            rv = m_editor->DeleteNode(node);
            if (NS_FAILED(rv))
            {
              m_editor->EndTransaction();
              return rv;
            }

            //Also, remove the <br> right before the signature.
            if (tempNode)
            {
              tempNode->GetLocalName(tagLocalName);
              if (tagLocalName.EqualsLiteral("BR"))
                m_editor->DeleteNode(tempNode);
            }
            m_editor->EndTransaction();
          }
        }
      }
    }
    else
    {
      //In plain text, we have to walk back the dom look for the pattern <br>-- <br>
      PRUint16 nodeType;
      PRInt32 searchState = 0; //0=nothing, 1=br 2='-- '+br, 3=br+'-- '+br

      do
      {
        node->GetNodeType(&nodeType);
        node->GetLocalName(tagLocalName);
        switch (searchState)
        {
          case 0: 
            if (nodeType == nsIDOMNode::ELEMENT_NODE && tagLocalName.EqualsLiteral("BR"))
              searchState = 1;
            break;

          case 1:
            searchState = 0;
            if (nodeType == nsIDOMNode::TEXT_NODE)
            {
              nsString nodeValue;
              node->GetNodeValue(nodeValue);
              if (nodeValue.EqualsLiteral("-- "))
                searchState = 2;
            }
            else
              if (nodeType == nsIDOMNode::ELEMENT_NODE && tagLocalName.EqualsLiteral("BR"))
              {
                searchState = 1;
                break;
              }
            break;

          case 2:
            if (nodeType == nsIDOMNode::ELEMENT_NODE && tagLocalName.EqualsLiteral("BR"))
              searchState = 3;
            else
              searchState = 0;               
            break;
        }

        tempNode = node;
      } while (searchState != 3 && NS_SUCCEEDED(tempNode->GetPreviousSibling(getter_AddRefs(node))) && node);

      if (searchState == 3)
      {
        //Now, I am sure I get the right node!
        m_editor->BeginTransaction();

        tempNode = lastNode;
        lastNode = node;
        do
        {
          node = tempNode;
          node->GetPreviousSibling(getter_AddRefs(tempNode));
          rv = m_editor->DeleteNode(node);
          if (NS_FAILED(rv))
          {
            m_editor->EndTransaction();
            return rv;
          }

        } while (node != lastNode && tempNode);
        m_editor->EndTransaction();
      }
    }
  }

  //Then add the new one if needed
  nsAutoString aSignature;

  // No delimiter needed if not a compose window
  PRBool noDelimiter;
  switch (mType)
  {
    case nsIMsgCompType::New :
    case nsIMsgCompType::NewsPost :
    case nsIMsgCompType::MailToUrl :
    case nsIMsgCompType::ForwardAsAttachment :
      noDelimiter = PR_FALSE;
      break;
    default :
      noDelimiter = PR_TRUE;
      break;
  }

  ProcessSignature(identity, noDelimiter, &aSignature);

  if (!aSignature.IsEmpty())
  {
    TranslateLineEnding(aSignature);

    m_editor->BeginTransaction();
    PRInt32 reply_on_top = 0;
    PRBool sig_bottom = PR_TRUE;
    identity->GetReplyOnTop(&reply_on_top);
    identity->GetSigBottom(&sig_bottom);
    PRBool sigOnTop = (reply_on_top == 1 && !sig_bottom);
    if (sigOnTop && noDelimiter)
      m_editor->BeginningOfDocument();
    else
      m_editor->EndOfDocument();
    if (m_composeHTML)
    {
      nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
      rv = htmlEditor->InsertHTML(aSignature);
    }
    else
    {
      nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
      rv = textEditor->InsertText(aSignature);
    }
    if (sigOnTop && noDelimiter)
      m_editor->EndOfDocument();
    m_editor->EndTransaction();
  }

  return rv;
}

NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity *identity, char **fallbackCharset, PRBool *_retval)
{
  NS_ENSURE_ARG_POINTER(identity);
  NS_ENSURE_ARG_POINTER(_retval);

  nsresult rv = m_compFields->CheckCharsetConversion(fallbackCharset, _retval);
  NS_ENSURE_SUCCESS(rv, rv);

  if (*_retval) 
  {
    nsXPIDLString fullName;
    nsXPIDLString organization;
    nsAutoString identityStrings;

    rv = identity->GetFullName(getter_Copies(fullName));
    NS_ENSURE_SUCCESS(rv, rv);
    if (fullName)
      identityStrings.Append(fullName.get());

    rv = identity->GetOrganization(getter_Copies(organization));
    NS_ENSURE_SUCCESS(rv, rv);
    if (organization)
      identityStrings.Append(organization.get());

    if (!identityStrings.IsEmpty())
    {
      // use fallback charset if that's already set
      const char *charset = (fallbackCharset && *fallbackCharset) ? *fallbackCharset : m_compFields->GetCharacterSet();
      *_retval = nsMsgI18Ncheck_data_in_charset_range(charset, identityStrings.get(),
                                                      fallbackCharset);
    }
  }

  return NS_OK;
}

NS_IMPL_ADDREF(nsMsgRecipient)
NS_IMPL_RELEASE(nsMsgRecipient)

NS_INTERFACE_MAP_BEGIN(nsMsgRecipient)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISupports)
NS_INTERFACE_MAP_END

nsMsgRecipient::nsMsgRecipient() :
  mPreferFormat(nsIAbPreferMailFormat::unknown),
  mProcessed(PR_FALSE)
{
}

nsMsgRecipient::nsMsgRecipient(nsString fullAddress, nsString email, PRUint32 preferFormat, PRBool processed) :
  mAddress(fullAddress),
  mEmail(email),
  mPreferFormat(preferFormat),
  mProcessed(processed)
{
}

nsMsgRecipient::~nsMsgRecipient()
{
}

NS_IMPL_ADDREF(nsMsgMailList)
NS_IMPL_RELEASE(nsMsgMailList)

NS_INTERFACE_MAP_BEGIN(nsMsgMailList)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISupports)
NS_INTERFACE_MAP_END


nsMsgMailList::nsMsgMailList()
{
}

nsMsgMailList::nsMsgMailList(nsString listName, nsString listDescription, nsIAbDirectory* directory) :
  mDirectory(directory)
{
  nsCOMPtr<nsIMsgHeaderParser> parser (do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID));

  if (parser)
  {
    nsXPIDLCString utf8Email;
    if (listDescription.IsEmpty())
      CopyUTF16toUTF8(listName, utf8Email);
    else
      CopyUTF16toUTF8(listDescription, utf8Email);

    nsXPIDLCString fullAddress;
    parser->MakeFullAddress(nsnull, NS_ConvertUTF16toUTF8(listName).get(),
                            utf8Email, getter_Copies(fullAddress));
    if (!fullAddress.IsEmpty())
    {
      /* We need to convert back the result from UTF-8 to Unicode */
      CopyUTF8toUTF16(fullAddress, mFullName);
    }
  }

  if (mFullName.IsEmpty())
  {
      //oops, parser problem! I will try to do my best...
      mFullName = listName;
      mFullName.AppendLiteral(" <");
      if (listDescription.IsEmpty())
        mFullName += listName;
      else
        mFullName += listDescription;
      mFullName.Append(PRUnichar('>'));
  }

  mDirectory = directory;
}

nsMsgMailList::~nsMsgMailList()
{
}
