/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape 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/NPL/
 *
 * 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 Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *   Pierre Phaneuf <pp@ludusdesign.com>
 */

#include "nsHTMLEditRules.h"

#include "nsEditor.h"
#include "nsHTMLEditor.h"

#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsIDOMNode.h"
#include "nsIDOMText.h"
#include "nsIDOMElement.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMSelection.h"
#include "nsIDOMRange.h"
#include "nsIDOMCharacterData.h"
#include "nsIEnumerator.h"
#include "nsIStyleContext.h"
#include "nsIPresShell.h"
#include "nsLayoutCID.h"

#include "nsEditorUtils.h"

#include "InsertTextTxn.h"
#include "DeleteTextTxn.h"

//const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
//const static char* kMOZEditorBogusNodeValue="TRUE";
const static PRUnichar nbsp = 160;

static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID);

enum
{
  kLonely = 0,
  kPrevSib = 1,
  kNextSib = 2,
  kBothSibs = 3
};

/********************************************************
 *  Constructor/Destructor 
 ********************************************************/

nsHTMLEditRules::nsHTMLEditRules()
: nsTextEditRules()
{
}

nsHTMLEditRules::~nsHTMLEditRules()
{
  // remove ourselves as a listener to edit actions
  mEditor->RemoveEditActionListener(mListener);
}


/********************************************************
 *  Public methods 
 ********************************************************/

NS_IMETHODIMP
nsHTMLEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags)
{
  // call through to base class Init first
  nsresult res = nsTextEditRules::Init(aEditor, aFlags);
  if (NS_FAILED(res)) return res;

  // pass over document and add any needed mozBRs
  // first turn off undo
  mEditor->EnableUndo(PR_FALSE);
  // set up mDocChangeRange to be whole doc
  nsCOMPtr<nsIDOMElement> bodyElem;
  nsCOMPtr<nsIDOMNode> bodyNode;
  mEditor->GetBodyElement(getter_AddRefs(bodyElem));
  bodyNode = do_QueryInterface(bodyElem);
  if (bodyNode)
  {
    // temporarily turn off rules sniffing
    nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
    res = nsComponentManager::CreateInstance(kRangeCID, nsnull, NS_GET_IID(nsIDOMRange),
                                                    getter_AddRefs(mDocChangeRange));
    if (NS_FAILED(res)) return res;
    if (!mDocChangeRange) return NS_ERROR_NULL_POINTER;
    mDocChangeRange->SelectNode(bodyNode);
    res = ReplaceNewlines(mDocChangeRange);
    if (NS_FAILED(res)) return res;
    res = AdjustSpecialBreaks();
    if (NS_FAILED(res)) return res;
  }
  // turn on undo
  mEditor->EnableUndo(PR_TRUE);
  
  // make a listener
  res = NS_NewEditListener(getter_AddRefs(mListener), mEditor, this);
  if (NS_FAILED(res)) return res;
  
  // add it as a listener to edit actions
  res = mEditor->AddEditActionListener(mListener);

  return res;
}


NS_IMETHODIMP
nsHTMLEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection)
{
  if (mLockRulesSniffing) return NS_OK;
  
  nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
  
  if (!mActionNesting)
  {
    mDocChangeRange = nsnull;  // clear out our accounting of what changed
    // turn off caret
    nsCOMPtr<nsIPresShell> pres;
    mEditor->GetPresShell(getter_AddRefs(pres));
    if (pres) pres->SetCaretEnabled(PR_FALSE);
    // check that selection is in subtree defined by body node
    ConfirmSelectionInBody();
  }
  mActionNesting++;
  return NS_OK;
}


NS_IMETHODIMP
nsHTMLEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
{
  if (mLockRulesSniffing) return NS_OK;

  nsAutoLockRulesSniffing lockIt(this);

  NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
  nsresult res = NS_OK;
  if (!--mActionNesting)
  {
    ConfirmSelectionInBody();
    if (action == nsEditor::kOpIgnore) return NS_OK;
    
    nsCOMPtr<nsIDOMSelection>selection;
    res = mEditor->GetSelection(getter_AddRefs(selection));
    if (NS_FAILED(res)) return res;
    
    if (mDocChangeRange && !((action == nsEditor::kOpUndo) || (action == nsEditor::kOpRedo)))
    {
      // dont let any txns in here move the selection around behind our back.
      // Note that this won't prevent explicit selection setting from working.
      nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
     
      // expand the "changed doc range" as needed
      res = PromoteRange(mDocChangeRange, action);
      if (NS_FAILED(res)) return res;

      // add in any needed <br>s, and remove any unneeded ones.
      res = AdjustSpecialBreaks();
      if (NS_FAILED(res)) return res;
      // adjust whitespace for insert text and delete actions
      if ((action == nsEditor::kOpInsertText) || 
          (action == nsEditor::kOpInsertIMEText) ||
          (action == nsEditor::kOpDeleteSelection))
      {
        res = AdjustWhitespace(selection);
        if (NS_FAILED(res)) return res;
      }
      // replace newlines that are preformatted
      if ((action == nsEditor::kOpInsertText) || 
          (action == nsEditor::kOpInsertIMEText) ||
          (action == nsEditor::kOpInsertNode))
      {
        res = ReplaceNewlines(mDocChangeRange);
      }
      // clean up any empty nodes in the selection
      res = RemoveEmptyNodes();
      if (NS_FAILED(res)) return res;
    }
    
    // adjust selection
    res = AdjustSelection(selection, aDirection);
    if (NS_FAILED(res)) return res;
    // detect empty doc
    res = CreateBogusNodeIfNeeded(selection);

    // turn on caret
    nsCOMPtr<nsIPresShell> pres;
    mEditor->GetPresShell(getter_AddRefs(pres));
    if (pres) pres->SetCaretEnabled(PR_TRUE);
  }

  return res;
}


NS_IMETHODIMP 
nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection, 
                              nsRulesInfo *aInfo, 
                              PRBool *aCancel, 
                              PRBool *aHandled)
{
  if (!aInfo || !aCancel || !aHandled) 
    return NS_ERROR_NULL_POINTER;
#if defined(DEBUG_ftang)
  printf("nsHTMLEditRules::WillDoAction action = %d\n", aInfo->action);
#endif

  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;
    
  // my kingdom for dynamic cast
  nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
    
  switch (info->action)
  {
    case kInsertText:
    case kInsertTextIME:
      return WillInsertText(info->action,
                            aSelection, 
                            aCancel, 
                            aHandled,
                            info->inString,
                            info->outString,
                            info->typeInState,
                            info->maxLength);
    case kInsertBreak:
      return WillInsertBreak(aSelection, aCancel, aHandled);
    case kDeleteSelection:
      return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
    case kMakeList:
      return WillMakeList(aSelection, info->bOrdered, aCancel, aHandled);
    case kIndent:
      return WillIndent(aSelection, aCancel, aHandled);
    case kOutdent:
      return WillOutdent(aSelection, aCancel, aHandled);
    case kAlign:
      return WillAlign(aSelection, info->alignType, aCancel, aHandled);
    case kMakeBasicBlock:
      return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
    case kRemoveList:
      return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
    case kInsertElement:
      return WillInsert(aSelection, aCancel);
  }
  return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
}
  
  
NS_IMETHODIMP 
nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection,
                             nsRulesInfo *aInfo, nsresult aResult)
{
  // pass thru to nsTextEditRules:
  nsresult res = nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
  return res;
}
  

/********************************************************
 *  Protected rules methods 
 ********************************************************/


nsresult
nsHTMLEditRules::WillInsertText(PRInt32          aAction,
                                nsIDOMSelection *aSelection, 
                                PRBool          *aCancel,
                                PRBool          *aHandled,
                                const nsString  *inString,
                                nsString        *outString,
                                TypeInState      typeInState,
                                PRInt32          aMaxLength)
{  
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }

  if (inString->IsEmpty() && (aAction != kInsertTextIME))
  {
    // HACK: this is a fix for bug 19395
    // I can't outlaw all empty insertions
    // because IME transaction depend on them
    // There is more work to do to make the 
    // world safe for IME.
    *aCancel = PR_TRUE;
    *aHandled = PR_FALSE;
    return NS_OK;
  }
  
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_TRUE;
  nsresult res;
  nsCOMPtr<nsIDOMNode> selNode;
  PRInt32 selOffset;

  char specialChars[] = {'\t','\n',0};
  
  // if the selection isn't collapsed, delete it.
  PRBool bCollapsed;
  res = aSelection->GetIsCollapsed(&bCollapsed);
  if (NS_FAILED(res)) return res;
  if (!bCollapsed)
  {
    res = mEditor->DeleteSelection(nsIEditor::eNone);
    if (NS_FAILED(res)) return res;
  }

  res = WillInsert(aSelection, aCancel);
  if (NS_FAILED(res)) return res;
  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = PR_FALSE;
  
  // get the (collapsed) selection location
  res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;

  // dont put text in places that cant have it
  nsAutoString textTag = "__moz_text";
  if (!mEditor->IsTextNode(selNode) && !mEditor->CanContainTag(selNode, textTag))
    return NS_ERROR_FAILURE;

  // split any mailcites in the way
  if (mFlags & nsIHTMLEditor::eEditorMailMask)
  {
    nsCOMPtr<nsIDOMNode> citeNode;
    PRInt32 newOffset;
    res = GetTopEnclosingMailCite(selNode, &citeNode);
    if (NS_FAILED(res)) return res;
    
    if (citeNode)
    {
      res = mEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset);
      if (NS_FAILED(res)) return res;
      res = citeNode->GetParentNode(getter_AddRefs(selNode));
      if (NS_FAILED(res)) return res;
      res = aSelection->Collapse(selNode, newOffset);
      if (NS_FAILED(res)) return res;
    }
  }
  
  // identify the block
  nsCOMPtr<nsIDOMNode> blockParent;
  
  if (nsEditor::IsBlockNode(selNode)) 
    blockParent = selNode;
  else 
    blockParent = mEditor->GetBlockNodeParent(selNode);
  if (!blockParent) return NS_ERROR_FAILURE;

  // are we not in a textnode?
  if (!mEditor->IsTextNode(selNode))  
  {
    // find a nearby text node if possible
    nsCOMPtr<nsIDOMNode> priorNode, nextNode;
    res = GetPriorHTMLNode(selNode, selOffset, &priorNode);
    if (NS_SUCCEEDED(res) && priorNode && mEditor->IsTextNode(priorNode) 
         && (blockParent == mEditor->GetBlockNodeParent(priorNode)))
    {
      // put selection at end of prior node
      PRUint32 strLength;
      nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(priorNode);
      res = textNode->GetLength(&strLength);
      if (NS_FAILED(res)) return res;
      res = aSelection->Collapse(priorNode, strLength);
      if (NS_FAILED(res)) return res;
    }
    else
    {
      res = GetNextHTMLNode(selNode, selOffset, &nextNode);
      if (NS_SUCCEEDED(res) && nextNode && mEditor->IsTextNode(nextNode)
           && (blockParent == mEditor->GetBlockNodeParent(nextNode)))
      {
        // put selection at begining of next node
        res = aSelection->Collapse(nextNode, 0);
        if (NS_FAILED(res)) return res;
      }
    }
  }
  
  PRBool bCancel;  
  nsString theString(*inString);  // copy instring for now
  if(aAction == kInsertTextIME) 
  { 
     // special case for IME. We need this to :
     // a) handle null strings, which are meaningful for IME
     // b) prevent the string from being broken into substrings,
     //    which can happen in non-IME processing below.
     // I should probably convert runs of spaces and tabs here as well
     res = DoTextInsertion(aSelection, &bCancel, &theString, typeInState);
  }
  else // aAction == kInsertText
  {
    while (theString.Length())
    {
      nsString partialString;
      PRInt32 pos = theString.FindCharInSet(specialChars);
      // if first char is special, then use just it
      if (pos == 0) pos = 1;
      if (pos == -1) pos = theString.Length();
      theString.Left(partialString, pos);
      theString.Cut(0, pos);
      // is it a tab?
      if (partialString == "\t" )
      {
        res = InsertTab(aSelection,outString);
        if (NS_FAILED(res)) return res;
        res = DoTextInsertion(aSelection, &bCancel, outString, typeInState);
      }
      // is it a return?
      else if (partialString == "\n")
      {
        res = mEditor->InsertBreak();
      }
      else
      {
        res = DoTextInsertion(aSelection, &bCancel, &partialString, typeInState);
      }
      if (NS_FAILED(res)) return res;
      pos = theString.FindCharInSet(specialChars);
    }
  }
  return res;
}

nsresult
nsHTMLEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;
  

  // if the selection isn't collapsed, delete it.
  PRBool bCollapsed;
  nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
  if (NS_FAILED(res)) return res;
  if (!bCollapsed)
  {
    res = mEditor->DeleteSelection(nsIEditor::eNone);
    if (NS_FAILED(res)) return res;
  }
  
  res = WillInsert(aSelection, aCancel);
  if (NS_FAILED(res)) return res;
  
  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = PR_FALSE;


  // split any mailcites in the way
  if (mFlags & nsIHTMLEditor::eEditorMailMask)
  {
    nsCOMPtr<nsIDOMNode> citeNode, selNode;
    PRInt32 selOffset, newOffset;
    res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset);
    if (NS_FAILED(res)) return res;
    res = GetTopEnclosingMailCite(selNode, &citeNode);
    if (NS_FAILED(res)) return res;
    
    if (citeNode)
    {
      res = mEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset);
      if (NS_FAILED(res)) return res;
      res = citeNode->GetParentNode(getter_AddRefs(selNode));
      if (NS_FAILED(res)) return res;
      res = aSelection->Collapse(selNode, newOffset);
      if (NS_FAILED(res)) return res;
    }
  }

  // smart splitting rules
  nsCOMPtr<nsIDOMNode> node;
  PRInt32 offset;
  
  res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset);
  if (NS_FAILED(res)) return res;
  if (!node) return NS_ERROR_FAILURE;
    
  // identify the block
  nsCOMPtr<nsIDOMNode> blockParent;
  
  if (nsEditor::IsBlockNode(node)) 
    blockParent = node;
  else 
    blockParent = mEditor->GetBlockNodeParent(node);
    
  if (!blockParent) return NS_ERROR_FAILURE;
  
  // headers: close (or split) header
  else if (IsHeader(blockParent))
  {
    res = ReturnInHeader(aSelection, blockParent, node, offset);
    *aHandled = PR_TRUE;
    return NS_OK;
  }
  
  // paragraphs: special rules to look for <br>s
  else if (IsParagraph(blockParent))
  {
    res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel, aHandled);
    return NS_OK;
  }
  
  // list items: special rules to make new list items
  else if (IsListItem(blockParent))
  {
    res = ReturnInListItem(aSelection, blockParent, node, offset);
    *aHandled = PR_TRUE;
    return NS_OK;
  }
  // its something else (body, div, td, ...): insert a normal br
  else
  {
    nsCOMPtr<nsIDOMNode> brNode;
    res = mEditor->CreateBR(node, offset, &brNode);  
    if (NS_FAILED(res)) return res;
    res = nsEditor::GetNodeLocation(brNode, &node, &offset);
    if (NS_FAILED(res)) return res;
    res = aSelection->Collapse(node, offset+1);
    if (NS_FAILED(res)) return res;
    *aHandled = PR_TRUE;
  }
  
  return res;
}



nsresult
nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, 
                                     nsIEditor::EDirection aAction, 
                                     PRBool *aCancel,
                                     PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;
  
  // if there is only bogus content, cancel the operation
  if (mBogusNode) 
  {
    *aCancel = PR_TRUE;
    return NS_OK;
  }

  nsresult res = NS_OK;
  
  PRBool bCollapsed;
  res = aSelection->GetIsCollapsed(&bCollapsed);
  if (NS_FAILED(res)) return res;
  
  nsCOMPtr<nsIDOMNode> node, selNode;
  PRInt32 offset, selOffset;
  
  res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset);
  if (NS_FAILED(res)) return res;
  if (!node) return NS_ERROR_FAILURE;
    
  if (bCollapsed)
  {
    // easy case, in a text node:
    if (mEditor->IsTextNode(node))
    {
      nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(node);
      PRUint32 strLength;
      res = textNode->GetLength(&strLength);
      if (NS_FAILED(res)) return res;
    
      // at beginning of text node and backspaced?
      if (!offset && (aAction == nsIEditor::ePrevious))
      {
        nsCOMPtr<nsIDOMNode> priorNode;
        res = GetPriorHTMLNode(node, &priorNode);
        if (NS_FAILED(res)) return res;
        
        // if there is no prior node then cancel the deletion
        if (!priorNode)
        {
          *aCancel = PR_TRUE;
          return res;
        }
        
        // block parents the same?  
        if (mEditor->HasSameBlockNodeParent(node, priorNode)) 
        {
          // is prior node not a container?  (ie, a br, hr, image...)
          if (!mEditor->IsContainer(priorNode))   // MOOSE: anchors not handled
          {
            // delete the break, and join like nodes if appropriate
            res = mEditor->DeleteNode(priorNode);
            if (NS_FAILED(res)) return res;
            // we did something, so lets say so.
            *aHandled = PR_TRUE;
            // get new prior node
            res = GetPriorHTMLNode(node, &priorNode);
            if (NS_FAILED(res)) return res;
            // are they in same block?
            if (mEditor->HasSameBlockNodeParent(node, priorNode)) 
            {
              // are they same type?
              if (mEditor->NodesSameType(node, priorNode))
              {
                // if so, join them!
                nsCOMPtr<nsIDOMNode> topParent;
                priorNode->GetParentNode(getter_AddRefs(topParent));
                res = JoinNodesSmart(priorNode,node,&selNode,&selOffset);
                if (NS_FAILED(res)) return res;
                // fix up selection
                res = aSelection->Collapse(selNode,selOffset);
                return res;
              }
            }
          }
          // is prior node inline and same type?  (This shouldn't happen)
          if ( mEditor->HasSameBlockNodeParent(node, priorNode) && 
                 ( mEditor->IsInlineNode(node) || mEditor->IsTextNode(node) ) &&
                 mEditor->NodesSameType(node, priorNode) )
          {
            // if so, join them!
            nsCOMPtr<nsIDOMNode> topParent;
            priorNode->GetParentNode(getter_AddRefs(topParent));
            *aHandled = PR_TRUE;
            res = JoinNodesSmart(priorNode,node,&selNode,&selOffset);
            if (NS_FAILED(res)) return res;
            // fix up selection
            res = aSelection->Collapse(selNode,selOffset);
            return res;
          }
          else return NS_OK; // punt to default
        }
        
        // deleting across blocks
        nsCOMPtr<nsIDOMNode> leftParent = mEditor->GetBlockNodeParent(priorNode);
        nsCOMPtr<nsIDOMNode> rightParent = mEditor->GetBlockNodeParent(node);
        
        // if leftParent or rightParent is null, it's because the
        // corresponding selection endpoint is in the body node.
        if (!leftParent || !rightParent)
          return NS_OK;  // bail to default
          
        // do not delete across table structures
        if (mEditor->IsTableElement(leftParent) || mEditor->IsTableElement(rightParent))
        {
          *aCancel = PR_TRUE;
          return NS_OK;
        }
                
        // are the blocks of same type?
        if (mEditor->NodesSameType(leftParent, rightParent))
        {
          nsCOMPtr<nsIDOMNode> topParent;
          leftParent->GetParentNode(getter_AddRefs(topParent));
          
          *aHandled = PR_TRUE;
          res = JoinNodesSmart(leftParent,rightParent,&selNode,&selOffset);
          if (NS_FAILED(res)) return res;
          // fix up selection
          res = aSelection->Collapse(selNode,selOffset);
          return res;
        }
        
        // else blocks not same type, bail to default
        return NS_OK;
        
      }
    
      // at end of text node and deleted?
      if ((offset == (PRInt32)strLength)
          && (aAction == nsIEditor::eNext))
      {
        nsCOMPtr<nsIDOMNode> nextNode;
        res = GetNextHTMLNode(node, &nextNode);
        if (NS_FAILED(res)) return res;
         
        // if there is no next node, or it's not in the body, then cancel the deletion
        if (!nextNode || !InBody(nextNode))
        {
          *aCancel = PR_TRUE;
          return res;
        }
       
        // block parents the same?  
        if (mEditor->HasSameBlockNodeParent(node, nextNode)) 
        {
          // is next node not a container?  (ie, a br, hr, image...)
          if (!mEditor->IsContainer(nextNode))  // MOOSE: anchors not handled
          {
            // delete the break, and join like nodes if appropriate
            res = mEditor->DeleteNode(nextNode);
            if (NS_FAILED(res)) return res;
            // we did something, so lets say so.
            *aHandled = PR_TRUE;
            // get new next node
            res = GetNextHTMLNode(node, &nextNode);
            if (NS_FAILED(res)) return res;
            // are they in same block?
            if (mEditor->HasSameBlockNodeParent(node, nextNode)) 
            {
              // are they same type?
              if (mEditor->NodesSameType(node, nextNode))
              {
                // if so, join them!
                nsCOMPtr<nsIDOMNode> topParent;
                nextNode->GetParentNode(getter_AddRefs(topParent));
                res = JoinNodesSmart(node,nextNode,&selNode,&selOffset);
                if (NS_FAILED(res)) return res;
                // fix up selection
                res = aSelection->Collapse(selNode,selOffset);
                return res;
              }
            }
          }
          // is next node inline and same type?  (This shouldn't happen)
          if ( mEditor->HasSameBlockNodeParent(node, nextNode) && 
                 ( mEditor->IsInlineNode(node) || mEditor->IsTextNode(node) ) &&
                 mEditor->NodesSameType(node, nextNode) )
          {
            // if so, join them!
            nsCOMPtr<nsIDOMNode> topParent;
            nextNode->GetParentNode(getter_AddRefs(topParent));
            *aHandled = PR_TRUE;
            res = JoinNodesSmart(node,nextNode,&selNode,&selOffset);
            if (NS_FAILED(res)) return res;
            // fix up selection
            res = aSelection->Collapse(selNode,selOffset);
            return res;
          }
          else return NS_OK; // punt to default
        }
                
        // deleting across blocks
        nsCOMPtr<nsIDOMNode> leftParent = mEditor->GetBlockNodeParent(node);
        nsCOMPtr<nsIDOMNode> rightParent = mEditor->GetBlockNodeParent(nextNode);

        // if leftParent or rightParent is null, it's because the
        // corresponding selection endpoint is in the body node.
        if (!leftParent || !rightParent)
          return NS_OK;  // bail to default
          
        // do not delete across table structures
        if (mEditor->IsTableElement(leftParent) || mEditor->IsTableElement(rightParent))
        {
          *aCancel = PR_TRUE;
          return NS_OK;
        }
                
        // are the blocks of same type?
        if (mEditor->NodesSameType(leftParent, rightParent))
        {
          nsCOMPtr<nsIDOMNode> topParent;
          leftParent->GetParentNode(getter_AddRefs(topParent));
          
          *aHandled = PR_TRUE;
          res = JoinNodesSmart(leftParent,rightParent,&selNode,&selOffset);
          if (NS_FAILED(res)) return res;
          // fix up selection
          res = aSelection->Collapse(selNode,selOffset);
          return res;
        }
        
        // else blocks not same type, bail to default
        return NS_OK;
        
      }
    }
    // else not in text node; we need to find right place to act on
    else
    {
      nsCOMPtr<nsIDOMNode> nodeToDelete;
      
      // first note that the right node to delete might be the one we
      // are in.  For example, if a list item is deleted one character at a time,
      // eventually it will be empty (except for a moz-br).  If the user hits 
      // backspace again, they expect the item itself to go away.  Check to
      // see if we are in an "empty" node.
      // Note: do NOT delete table elements this way.
      PRBool bIsEmptyNode;
      res = IsEmptyNode(node, &bIsEmptyNode, PR_TRUE, PR_FALSE);
      if (bIsEmptyNode && !mEditor->IsTableElement(node))
        nodeToDelete = node;
      else if (aAction == nsIEditor::ePrevious)
        res = GetPriorHTMLNode(node, offset, &nodeToDelete);
      else if (aAction == nsIEditor::eNext)
        res = GetNextHTMLNode(node, offset, &nodeToDelete);
      else
        return NS_OK;
        
      if (NS_FAILED(res)) return res;
      if (!nodeToDelete) return NS_ERROR_NULL_POINTER;
        
      // if this node is text node, adjust selection
      if (nsEditor::IsTextNode(nodeToDelete))
      {
        PRUint32 selPoint = 0;
        nsCOMPtr<nsIDOMCharacterData>nodeAsText;
        nodeAsText = do_QueryInterface(nodeToDelete);
        if (aAction == nsIEditor::ePrevious)
          nodeAsText->GetLength(&selPoint);
        res = aSelection->Collapse(nodeToDelete,selPoint);
        return res;
      }
      else
      {
        // editable leaf node is not text; delete it.
        // that's the default behavior
        res = nsEditor::GetNodeLocation(nodeToDelete, &node, &offset);
        if (NS_FAILED(res)) return res;
        // EXCEPTION: if it's a mozBR, we have to check and see if
        // there is a br in front of it. If so, we must delete both.
        // else you get this: deletion code deletes mozBR, then selection
        // adjusting code puts it back in.  doh
        if (IsMozBR(nodeToDelete))
        {
          nsCOMPtr<nsIDOMNode> brNode;
          res = GetPriorHTMLNode(nodeToDelete, &brNode);
          if (IsBreak(brNode))
          {
            // is brNode also a descendant of same block?
            nsCOMPtr<nsIDOMNode> block, brBlock;
            block = mEditor->GetBlockNodeParent(nodeToDelete);
            brBlock = mEditor->GetBlockNodeParent(brNode);
            if (block == brBlock)
            {
              // delete both breaks
              res = mEditor->DeleteNode(brNode);
              if (NS_FAILED(res)) return res;
              res = mEditor->DeleteNode(nodeToDelete);
              *aHandled = PR_TRUE;
              return res;
            }
            // else fall through
          }
          // else fall through
        }
        // adjust selection to be right after it
        res = aSelection->Collapse(node, offset+1);
        if (NS_FAILED(res)) return res;
        res = mEditor->DeleteNode(nodeToDelete);
        *aHandled = PR_TRUE;
        return res;
      }
    }
    
    return NS_OK;
  }
  
  // else we have a non collapsed selection
  // figure out if the enpoints are in nodes that can be merged
  nsCOMPtr<nsIDOMNode> endNode;
  PRInt32 endOffset;
  res = mEditor->GetEndNodeAndOffset(aSelection, &endNode, &endOffset);
  if (NS_FAILED(res)) 
  { 
    return res; 
  }
  if (endNode.get() != node.get())
  {
    // block parents the same?  use default deletion
    if (mEditor->HasSameBlockNodeParent(node, endNode)) return NS_OK;
    
    // deleting across blocks
    // are the blocks of same type?
    nsCOMPtr<nsIDOMNode> leftParent;
    nsCOMPtr<nsIDOMNode> rightParent;

    // XXX: Fix for bug #10815: Crash deleting selected text and table.
    //      Make sure leftParent and rightParent are never NULL. This
    //      can happen if we call GetBlockNodeParent() and the node we
    //      pass in is a body node.
    //
    //      Should we be calling IsBlockNode() instead of IsBody() here?

    if (IsBody(node))
      leftParent = node;
    else
      leftParent = mEditor->GetBlockNodeParent(node);

    if (IsBody(endNode))
      rightParent = endNode;
    else
      rightParent = mEditor->GetBlockNodeParent(endNode);
    
    // do not delete across table structures
    if (mEditor->IsTableElement(leftParent) || mEditor->IsTableElement(rightParent))
    {
      *aCancel = PR_TRUE;
      return NS_OK;
    }
                
    // are the blocks siblings?
    nsCOMPtr<nsIDOMNode> leftBlockParent;
    nsCOMPtr<nsIDOMNode> rightBlockParent;
    leftParent->GetParentNode(getter_AddRefs(leftBlockParent));
    rightParent->GetParentNode(getter_AddRefs(rightBlockParent));
    // bail to default if blocks aren't siblings
    if (leftBlockParent.get() != rightBlockParent.get()) return NS_OK;

    if (mEditor->NodesSameType(leftParent, rightParent))
    {
      nsCOMPtr<nsIDOMNode> topParent;
      leftParent->GetParentNode(getter_AddRefs(topParent));
      
      if (IsParagraph(leftParent))
      {
        // first delete the selection
        *aHandled = PR_TRUE;
        res = mEditor->DeleteSelectionImpl(aAction);
        if (NS_FAILED(res)) return res;
        // then join para's, insert break
        res = mEditor->JoinNodeDeep(leftParent,rightParent,&selNode,&selOffset);
        if (NS_FAILED(res)) return res;
        // fix up selection
        res = aSelection->Collapse(selNode,selOffset);
        return res;
      }
      if (IsListItem(leftParent) || IsHeader(leftParent))
      {
        // first delete the selection
        *aHandled = PR_TRUE;
        res = mEditor->DeleteSelectionImpl(aAction);
        if (NS_FAILED(res)) return res;
        // join blocks
        res = mEditor->JoinNodeDeep(leftParent,rightParent,&selNode,&selOffset);
        if (NS_FAILED(res)) return res;
        // fix up selection
        res = aSelection->Collapse(selNode,selOffset);
        return res;
      }
    }
    
    // else blocks not same type, bail to default
    return NS_OK;
  }

  return res;
}  


nsresult
nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, 
                              PRBool aOrdered, 
                              PRBool *aCancel,
                              PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }

  nsresult res = WillInsert(aSelection, aCancel);
  if (NS_FAILED(res)) return res;

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;

  nsAutoString blockType("ul");
  if (aOrdered) blockType = "ol";
  
  nsAutoSelectionReset selectionResetter(aSelection);
  
  PRBool outMakeEmpty;
  res = ShouldMakeEmptyBlock(aSelection, &blockType, &outMakeEmpty);
  if (NS_FAILED(res)) return res;
  if (outMakeEmpty) return NS_OK;
  
  // ok, we aren't creating a new empty list.  Instead we are converting
  // the set of blocks implied by the selection into a list.
  
  // convert the selection ranges into "promoted" selection ranges:
  // this basically just expands the range to include the immediate
  // block parent, and then further expands to include any ancestors
  // whose children are all in the range
  
  *aHandled = PR_TRUE;

  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeList);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeList);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Remove all non-editable nodes.  Leave them be.
  
  PRUint32 listCount;
  PRInt32 i;
  arrayOfNodes->Count(&listCount);
  for (i=(PRInt32)listCount-1; i>=0; i--)
  {
    nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> testNode( do_QueryInterface(isupports ) );
    if (!mEditor->IsEditable(testNode))
    {
      arrayOfNodes->RemoveElementAt(i);
    }
  }
  
  // if there is only one node in the array, and it is a list, div, or blockquote,
  // then look inside of it until we find what we want to make a list out of.
  arrayOfNodes->Count(&listCount);
  if (listCount == 1)
  {
    nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
    nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
    
    while (IsDiv(curNode) || IsOrderedList(curNode) || IsUnorderedList(curNode) || IsBlockquote(curNode))
    {
      // dive as long as there is only one child, and it is a list, div, or blockquote
      PRUint32 numChildren;
      res = mEditor->CountEditableChildren(curNode, numChildren);
      if (NS_FAILED(res)) return res;
      
      if (numChildren == 1)
      {
        // keep diving
        nsCOMPtr <nsIDOMNode> tmpNode = nsEditor::GetChildAt(curNode, 0);
        if (IsDiv(tmpNode) || IsOrderedList(tmpNode) || IsUnorderedList(tmpNode) || IsBlockquote(tmpNode))
        {
          // check editablility XXX floppy moose
          curNode = tmpNode;
        }
        else break;
      }
      else break;
    }
    // we've found innermost list/blockquote/div: 
    // replace the one node in the array with this node
    isupports = do_QueryInterface(curNode);
    arrayOfNodes->ReplaceElementAt(isupports, 0);
  }

  // Next we detect all the transitions in the array, where a transition
  // means that adjacent nodes in the array don't have the same parent.
  
  nsVoidArray transitionList;
  res = MakeTransitionList(arrayOfNodes, &transitionList);
  if (NS_FAILED(res)) return res;                                 

  // Ok, now go through all the nodes and put then in the list, 
  // or whatever is approriate.  Wohoo!

  arrayOfNodes->Count(&listCount);
  nsCOMPtr<nsIDOMNode> curParent;
  nsCOMPtr<nsIDOMNode> curList;
  nsCOMPtr<nsIDOMNode> prevListItem;
    
    for (i=0; i<(PRInt32)listCount; i++)
    {
      // here's where we actually figure out what to do
      nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
      nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
      PRInt32 offset;
      res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
      if (NS_FAILED(res)) return res;
    
      if (transitionList[i] &&                                      // transition node
          ((((i+1)<(PRInt32)listCount) && transitionList[i+1]) ||   // and next node is transistion node
          ( i+1 >= (PRInt32)listCount)))                            // or there is no next node
      {
        // the parent of this node has no other children on the 
        // list of nodes to make a list out of.  So if this node
        // is a list, change it's list type if needed instead of 
        // reparenting it 
        nsCOMPtr<nsIDOMNode> newBlock;
        if (IsUnorderedList(curNode))
        {
          if (aOrdered)
          {
            // make a new ordered list, insert it where the current unordered list is,
            // and move all the children to the new list, and remove the old list
            res = ReplaceContainer(curNode,&newBlock,blockType);
            if (NS_FAILED(res)) return res;
            curList = newBlock;
            continue;
          }
          else
          {
            // do nothing, we are already the right kind of list
            curList = newBlock;
            continue;
          }
        }
        else if (IsOrderedList(curNode))
        {
          if (!aOrdered)
          {
            // make a new unordered list, insert it where the current ordered list is,
            // and move all the children to the new list, and remove the old list
            ReplaceContainer(curNode,&newBlock,blockType);
            if (NS_FAILED(res)) return res;
            curList = newBlock;
            continue;
          }
          else
          {
            // do nothing, we are already the right kind of list
            curList = newBlock;
            continue;
          }
        }
        else if (IsDiv(curNode) || IsBlockquote(curNode))
        {
          // XXX floppy moose
        }
      }  // lonely node
    
      // need to make a list to put things in if we haven't already,
      // or if this node doesn't go in list we used earlier.
      if (!curList || transitionList[i])
      {
        nsAutoString listType;
        if (aOrdered) listType = "ol";
        else listType = "ul";
        res = mEditor->CreateNode(listType, curParent, offset, getter_AddRefs(curList));
        if (NS_FAILED(res)) return res;
        // curList is now the correct thing to put curNode in
        prevListItem = 0;
      }
    
      // if curNode is a Break, delete it, and quit remembering prev list item
      if (IsBreak(curNode)) 
      {
        res = mEditor->DeleteNode(curNode);
        if (NS_FAILED(res)) return res;
        prevListItem = 0;
        continue;
      }
      
      // if curNode isn't a list item, we must wrap it in one
      nsCOMPtr<nsIDOMNode> listItem;
      if (!IsListItem(curNode))
      {
        if (nsEditor::IsInlineNode(curNode) && prevListItem)
        {
          // this is a continuation of some inline nodes that belong together in
          // the same list item.  use prevListItem
          PRUint32 listItemLen;
          res = mEditor->GetLengthOfDOMNode(prevListItem, listItemLen);
          if (NS_FAILED(res)) return res;
          res = mEditor->DeleteNode(curNode);
          if (NS_FAILED(res)) return res;
          res = mEditor->InsertNode(curNode, prevListItem, listItemLen);
          if (NS_FAILED(res)) return res;
        }
        else
        {
          // don't wrap li around a paragraph.  instead replace paragraph with li
          if (IsParagraph(curNode))
          {
            res = ReplaceContainer(curNode, &listItem, "li");
          }
          else
          {
            res = InsertContainerAbove(curNode, &listItem, "li");
          }
          if (NS_FAILED(res)) return res;
          if (nsEditor::IsInlineNode(curNode)) 
            prevListItem = listItem;
        }
      }
      else
      {
        listItem = curNode;
      }
    
      if (listItem)  // if we made a new list item, deal with it
      {
        // tuck the listItem into the end of the active list
        PRUint32 listLen;
        res = mEditor->GetLengthOfDOMNode(curList, listLen);
        if (NS_FAILED(res)) return res;
        res = mEditor->DeleteNode(listItem);
        if (NS_FAILED(res)) return res;
        res = mEditor->InsertNode(listItem, curList, listLen);
        if (NS_FAILED(res)) return res;
      }
    }

  return res;
}


nsresult
nsHTMLEditRules::WillRemoveList(nsIDOMSelection *aSelection, 
                                PRBool aOrdered, 
                                PRBool *aCancel,
                                PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_TRUE;

  nsAutoString blockType("ul");
  if (aOrdered) blockType = "ol";
  
  nsAutoSelectionReset selectionResetter(aSelection);
  
  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  nsresult res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeList);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeList);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Remove all non-editable nodes.  Leave them be.
  
  PRUint32 listCount;
  PRInt32 i;
  arrayOfNodes->Count(&listCount);
  for (i=listCount-1; i>=0; i--)
  {
    nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> testNode( do_QueryInterface(isupports ) );
    if (!mEditor->IsEditable(testNode))
    {
      arrayOfNodes->RemoveElementAt(i);
    }
  }
  
  // Only act on lists or list items in the array
  nsCOMPtr<nsIDOMNode> curParent;
  for (i=0; i<(PRInt32)listCount; i++)
  {
    // here's where we actually figure out what to do
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
    PRInt32 offset;
    res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
    if (NS_FAILED(res)) return res;
    
    if (IsListItem(curNode))  // unlist this listitem
    {
      PRBool bOutOfList;
      do
      {
        res = PopListItem(curNode, &bOutOfList);
        if (NS_FAILED(res)) return res;
      } while (!bOutOfList); // keep popping it out until it's not in a list anymore
    }
    else if (IsList(curNode)) // node is a list, move list items out
    {
      nsCOMPtr<nsIDOMNode> child;
      curNode->GetLastChild(getter_AddRefs(child));

      while (child)
      {
        if (IsListItem(child))
        {
          PRBool bOutOfList;
          do
          {
            res = PopListItem(child, &bOutOfList);
            if (NS_FAILED(res)) return res;
          } while (!bOutOfList);   // keep popping it out until it's not in a list anymore
        }
        else
        {
          // delete any non- list items for now
          res = mEditor->DeleteNode(child);
          if (NS_FAILED(res)) return res;
        }
        curNode->GetLastChild(getter_AddRefs(child));
      }
      // delete the now-empty list
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}


nsresult
nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, 
                                    const nsString *aBlockType, 
                                    PRBool *aCancel,
                                    PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;
  
  PRBool makeEmpty;
  nsresult res = ShouldMakeEmptyBlock(aSelection, aBlockType, &makeEmpty);
  if (NS_FAILED(res)) return res;
  
  if (makeEmpty) return res;  // just insert a new empty block
  
  // else it's not that easy...
  nsAutoSelectionReset selectionResetter(aSelection);
  *aHandled = PR_TRUE;

  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeBasicBlock);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeBasicBlock);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Ok, now go through all the nodes and make the right kind of blocks, 
  // or whatever is approriate.  Wohoo!
  res = ApplyBlockStyle(arrayOfNodes, aBlockType);
  return res;
}


nsresult
nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool * aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  
  nsresult res = WillInsert(aSelection, aCancel);
  if (NS_FAILED(res)) return res;

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = PR_FALSE;
  *aHandled = PR_TRUE;

  nsAutoSelectionReset selectionResetter(aSelection);
  
  // convert the selection ranges into "promoted" selection ranges:
  // this basically just expands the range to include the immediate
  // block parent, and then further expands to include any ancestors
  // whose children are all in the range
  
  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  res = GetPromotedRanges(aSelection, &arrayOfRanges, kIndent);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kIndent);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Next we detect all the transitions in the array, where a transition
  // means that adjacent nodes in the array don't have the same parent.
  
  nsVoidArray transitionList;
  res = MakeTransitionList(arrayOfNodes, &transitionList);
  if (NS_FAILED(res)) return res;                                 
  
  // Ok, now go through all the nodes and put them in a blockquote, 
  // or whatever is appropriate.  Wohoo!

  PRUint32 listCount;
  PRInt32 i;
  arrayOfNodes->Count(&listCount);
  nsCOMPtr<nsIDOMNode> curParent;
  nsCOMPtr<nsIDOMNode> curQuote;
  nsCOMPtr<nsIDOMNode> curList;
  for (i=0; i<(PRInt32)listCount; i++)
  {
    // here's where we actually figure out what to do
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
    PRInt32 offset;
    res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
    if (NS_FAILED(res)) return res;
    
    // some logic for putting list items into nested lists...
    if (IsList(curParent))
    {
      if (!curList || transitionList[i])
      {
        nsAutoString listTag;
        nsEditor::GetTagString(curParent,listTag);
        // create a new nested list of correct type
        res = mEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
        if (NS_FAILED(res)) return res;
        // curList is now the correct thing to put curNode in
      }
      // tuck the node into the end of the active list
      PRUint32 listLen;
      res = mEditor->GetLengthOfDOMNode(curList, listLen);
      if (NS_FAILED(res)) return res;
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
      res = mEditor->InsertNode(curNode, curList, listLen);
      if (NS_FAILED(res)) return res;
    }
    
    else // not a list item, use blockquote
    {
      // need to make a blockquote to put things in if we haven't already,
      // or if this node doesn't go in blockquote we used earlier.
      if (!curQuote || transitionList[i])
      {
        nsAutoString quoteType("blockquote");
        if (mEditor->CanContainTag(curParent,quoteType))
        {
          res = mEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote));
          if (NS_FAILED(res)) return res;
          // curQuote is now the correct thing to put curNode in
        }
        else
        {
          printf("trying to put a blockquote in a bad place\n");     
        }
      }
        
      // tuck the node into the end of the active blockquote
      PRUint32 quoteLen;
      res = mEditor->GetLengthOfDOMNode(curQuote, quoteLen);
      if (NS_FAILED(res)) return res;
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
      res = mEditor->InsertNode(curNode, curQuote, quoteLen);
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}


nsresult
nsHTMLEditRules::WillOutdent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  // initialize out param
  *aCancel = PR_FALSE;
  *aHandled = PR_TRUE;
  
  nsAutoSelectionReset selectionResetter(aSelection);
  nsresult res = NS_OK;
  
  // convert the selection ranges into "promoted" selection ranges:
  // this basically just expands the range to include the immediate
  // block parent, and then further expands to include any ancestors
  // whose children are all in the range
  
  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  res = GetPromotedRanges(aSelection, &arrayOfRanges, kOutdent);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kOutdent);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Next we detect all the transitions in the array, where a transition
  // means that adjacent nodes in the array don't have the same parent.
  
  nsVoidArray transitionList;
  res = MakeTransitionList(arrayOfNodes, &transitionList);
  if (NS_FAILED(res)) return res;                                 
  
  // Ok, now go through all the nodes and remove a level of blockquoting, 
  // or whatever is appropriate.  Wohoo!

  PRUint32 listCount;
  PRInt32 i;
  arrayOfNodes->Count(&listCount);
  nsCOMPtr<nsIDOMNode> curParent;
  for (i=0; i<(PRInt32)listCount; i++)
  {
    // here's where we actually figure out what to do
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
    PRInt32 offset;
    res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
    if (NS_FAILED(res)) return res;
    
    if (IsList(curParent))  // move node out of list
    {
      if (IsList(curNode))  // just unwrap this sublist
      {
        res = RemoveContainer(curNode);
        if (NS_FAILED(res)) return res;
      }
      else  // we are moving a list item, but not whole list
      {
        PRBool bOutOfList;
        res = PopListItem(curNode, &bOutOfList);
        if (NS_FAILED(res)) return res;
      }
    }
    else if (IsList(curNode)) // node is a list, but parent is non-list: move list items out
    {
      nsCOMPtr<nsIDOMNode> child;
      curNode->GetLastChild(getter_AddRefs(child));
      while (child)
      {
        if (IsListItem(child))
        {
          PRBool bOutOfList;
          res = PopListItem(child, &bOutOfList);
          if (NS_FAILED(res)) return res;
        }
        else
        {
          // delete any non- list items for now
          res = mEditor->DeleteNode(child);
          if (NS_FAILED(res)) return res;
        }
        curNode->GetLastChild(getter_AddRefs(child));
      }
      // delete the now-empty list
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
    }
    else if (transitionList[i])  // not list related - look for enclosing blockquotes and remove
    {
      // look for a blockquote somewhere above us and remove it.
      // this is a hack until i think about outdent for real.
      nsCOMPtr<nsIDOMNode> n = curNode;
      nsCOMPtr<nsIDOMNode> tmp;
      while (!IsBody(n))
      {
        if (IsBlockquote(n))
        {
          RemoveContainer(n);
          break;
        }
        n->GetParentNode(getter_AddRefs(tmp));
        n = tmp;
      }
    }
  }

  return res;
}


nsresult
nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, 
                           const nsString *alignType, 
                           PRBool *aCancel,
                           PRBool *aHandled)
{
  if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
  
  nsresult res = WillInsert(aSelection, aCancel);
  if (NS_FAILED(res)) return res;

  // initialize out param
  // we want to ignore result of WillInsert()
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;
  

  PRBool outMakeEmpty;
  res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty);
  if (NS_FAILED(res)) return res;
  if (outMakeEmpty) 
  {
    PRInt32 offset;
    nsCOMPtr<nsIDOMNode> brNode, parent, theDiv;
    nsAutoString divType("div");
    res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset);
    if (NS_FAILED(res)) return res;
    res = mEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv));
    if (NS_FAILED(res)) return res;
    // set up the alignment on the div
    nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(theDiv);
    nsAutoString attr("align");
    res = mEditor->SetAttribute(divElem, attr, *alignType);
    if (NS_FAILED(res)) return res;
    *aHandled = PR_TRUE;
    // put in a moz-br so that it won't get deleted
    res = CreateMozBR(theDiv, 0, &brNode);
    if (NS_FAILED(res)) return res;
    res = aSelection->Collapse(theDiv, 0);
    return res;
  }

  nsAutoSelectionReset selectionResetter(aSelection);

  // convert the selection ranges into "promoted" selection ranges:
  // this basically just expands the range to include the immediate
  // block parent, and then further expands to include any ancestors
  // whose children are all in the range
  *aHandled = PR_TRUE;
  
  nsCOMPtr<nsISupportsArray> arrayOfRanges;
  res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign);
  if (NS_FAILED(res)) return res;
  
  // use these ranges to contruct a list of nodes to act on.
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign);
  if (NS_FAILED(res)) return res;                                 
                                     
  // Next we detect all the transitions in the array, where a transition
  // means that adjacent nodes in the array don't have the same parent.
  
  nsVoidArray transitionList;
  res = MakeTransitionList(arrayOfNodes, &transitionList);
  if (NS_FAILED(res)) return res;                                 

  // Ok, now go through all the nodes and give them an align attrib or put them in a div, 
  // or whatever is appropriate.  Wohoo!
  
  PRUint32 listCount;
  PRInt32 i;
  arrayOfNodes->Count(&listCount);
  nsCOMPtr<nsIDOMNode> curParent;
  nsCOMPtr<nsIDOMNode> curDiv;
  for (i=0; i<(PRInt32)listCount; i++)
  {
    // here's where we actually figure out what to do
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
    PRInt32 offset;
    res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
    if (NS_FAILED(res)) return res;
    
    // if it's a div, don't nest it, just set the alignment
    if (IsDiv(curNode))
    {
      nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curNode);
      nsAutoString attr("align");
      res = mEditor->SetAttribute(divElem, attr, *alignType);
      if (NS_FAILED(res)) return res;
      // clear out curDiv so that we don't put nodes after this one into it
      curDiv = 0;
      continue;
    }
    
    // need to make a div to put things in if we haven't already,
    // or if this node doesn't go in div we used earlier.
    if (!curDiv || transitionList[i])
    {
      nsAutoString divType("div");
      res = mEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv));
      if (NS_FAILED(res)) return res;
      // set up the alignment on the div
      nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv);
      nsAutoString attr("align");
      res = mEditor->SetAttribute(divElem, attr, *alignType);
      if (NS_FAILED(res)) return res;
      // curDiv is now the correct thing to put curNode in
    }
        
    // tuck the node into the end of the active div
    PRUint32 listLen;
    res = mEditor->GetLengthOfDOMNode(curDiv, listLen);
    if (NS_FAILED(res)) return res;
    res = mEditor->DeleteNode(curNode);
    if (NS_FAILED(res)) return res;
    res = mEditor->InsertNode(curNode, curDiv, listLen);
    if (NS_FAILED(res)) return res;
  }

  return res;
}



/********************************************************
 *  helper methods 
 ********************************************************/
 
///////////////////////////////////////////////////////////////////////////
// IsHeader: true if node an html header
//                  
PRBool 
nsHTMLEditRules::IsHeader(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsHeader");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if ( (tag == "h1") ||
       (tag == "h2") ||
       (tag == "h3") ||
       (tag == "h4") ||
       (tag == "h5") ||
       (tag == "h6") )
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsParagraph: true if node an html paragraph
//                  
PRBool 
nsHTMLEditRules::IsParagraph(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsParagraph");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "p")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsListItem: true if node an html list item
//                  
PRBool 
nsHTMLEditRules::IsListItem(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsListItem");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "li")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsTableCell: true if node an html td or th
//                  
PRBool 
nsHTMLEditRules::IsTableCell(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsTableCell");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "td" || tag == "th")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsList: true if node an html list
//                  
PRBool 
nsHTMLEditRules::IsList(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsList");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if ( (tag == "ol") ||
       (tag == "ul") )
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsOrderedList: true if node an html orderd list
//                  
PRBool 
nsHTMLEditRules::IsOrderedList(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsOrderedList");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "ol")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsUnorderedList: true if node an html orderd list
//                  
PRBool 
nsHTMLEditRules::IsUnorderedList(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsUnorderedList");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "ul")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsBlockquote: true if node an html blockquote node
//                  
PRBool 
nsHTMLEditRules::IsBlockquote(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBlockquote");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "blockquote")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsAnchor: true if node an html anchor node
//                  
PRBool 
nsHTMLEditRules::IsAnchor(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsAnchor");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "a")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsImage: true if node an html image node
//                  
PRBool 
nsHTMLEditRules::IsImage(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null node passed to nsHTMLEditRules::IsImage");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "img")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsDiv: true if node an html div node
//                  
PRBool 
nsHTMLEditRules::IsDiv(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsDiv");
  nsAutoString tag;
  nsEditor::GetTagString(node,tag);
  tag.ToLowerCase();
  if (tag == "div")
  {
    return PR_TRUE;
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsNormalDiv: true if node an html div node, without type = _moz
//                  
PRBool 
nsHTMLEditRules::IsNormalDiv(nsIDOMNode *node)
{
  if (IsDiv(node) && !HasMozAttr(node)) return PR_TRUE;
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsMozDiv: true if node an html div node with type = _moz
//                  
PRBool 
nsHTMLEditRules::IsMozDiv(nsIDOMNode *node)
{
  if (IsDiv(node) && HasMozAttr(node)) return PR_TRUE;
  return PR_FALSE;
}



///////////////////////////////////////////////////////////////////////////
// IsMailCite: true if node an html blockquote with type=cite
//                  
PRBool 
nsHTMLEditRules::IsMailCite(nsIDOMNode *node)
{
  NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsMailCite");
  if (IsBlockquote(node))
  {
    nsCOMPtr<nsIDOMElement> bqElem = do_QueryInterface(node);
    nsAutoString typeAttrName("type");
    nsAutoString typeAttrVal;
    nsresult res = bqElem->GetAttribute(typeAttrName, typeAttrVal);
    typeAttrVal.ToLowerCase();
    if (NS_SUCCEEDED(res))
    {
      if (typeAttrVal.Equals("cite", PR_TRUE, 4))
        return PR_TRUE;
    }
  }
  return PR_FALSE;
}


///////////////////////////////////////////////////////////////////////////
// IsEmptyBlock: figure out if aNode is (or is inside) an empty block.
//               A block can have children and still be considered empty,
//               if the children are empty or non-editable.
//                  
nsresult 
nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, 
                              PRBool *outIsEmptyBlock, 
                              PRBool aMozBRDoesntCount,
                              PRBool aListItemsNotEmpty) 
{
  if (!aNode || !outIsEmptyBlock) return NS_ERROR_NULL_POINTER;
  *outIsEmptyBlock = PR_TRUE;
  
//  nsresult res = NS_OK;
  nsCOMPtr<nsIDOMNode> nodeToTest;
  if (nsEditor::IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode);
//  else nsCOMPtr<nsIDOMElement> block;
//  looks like I forgot to finish this.  Wonder what I was going to do?

  if (!nodeToTest) return NS_ERROR_NULL_POINTER;
  return IsEmptyNode(nodeToTest, outIsEmptyBlock, aMozBRDoesntCount, aListItemsNotEmpty);
}


///////////////////////////////////////////////////////////////////////////
// IsEmptyNode: figure out if aNode is an empty node.
//               A block can have children and still be considered empty,
//               if the children are empty or non-editable.
//                  
nsresult 
nsHTMLEditRules::IsEmptyNode( nsIDOMNode *aNode, 
                              PRBool *outIsEmptyNode, 
                              PRBool aMozBRDoesntCount,
                              PRBool aListItemsNotEmpty)
{
  if (!aNode || !outIsEmptyNode) return NS_ERROR_NULL_POINTER;
  *outIsEmptyNode = PR_TRUE;
  
  // effeciency hack - special case if it's a text node
  if (nsEditor::IsTextNode(aNode))
  {
    PRUint32 length = 0;
    nsCOMPtr<nsIDOMCharacterData>nodeAsText;
    nodeAsText = do_QueryInterface(aNode);
    nodeAsText->GetLength(&length);
    if (length) *outIsEmptyNode = PR_FALSE;
    return NS_OK;
  }

  // if it's not a text node (handled above) and it's not a container,
  // then we dont call it empty (it's an <hr>, or <br>, etc).
  // Also, if it's an anchor then dont treat it as empty - even though
  // anchors are containers, named anchors are "empty" but we don't
  // want to treat them as such.  Also, don't call ListItems or table
  // cells empty if caller desires.
  if (!mEditor->IsContainer(aNode) || IsAnchor(aNode) || 
       (aListItemsNotEmpty &&IsListItem(aNode)) ||
       (aListItemsNotEmpty &&IsTableCell(aNode)) ) 
  {
    *outIsEmptyNode = PR_FALSE;
    return NS_OK;
  }
  
  // iterate over node. if no children, or all children are either 
  // empty text nodes or non-editable, then node qualifies as empty
  nsCOMPtr<nsIContentIterator> iter;
  nsCOMPtr<nsIContent> nodeAsContent = do_QueryInterface(aNode);
  if (!nodeAsContent) return NS_ERROR_FAILURE;
  nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID,
                                        nsnull,
                                        NS_GET_IID(nsIContentIterator), 
                                        getter_AddRefs(iter));
  if (NS_FAILED(res)) return res;
  res = iter->Init(nodeAsContent);
  if (NS_FAILED(res)) return res;
    
  while (NS_ENUMERATOR_FALSE == iter->IsDone())
  {
    nsCOMPtr<nsIDOMNode> node;
    nsCOMPtr<nsIContent> content;
    res = iter->CurrentNode(getter_AddRefs(content));
    if (NS_FAILED(res)) return res;
    node = do_QueryInterface(content);
    if (!node) return NS_ERROR_FAILURE;
 
    // is the node editable and non-empty?  if so, return false
    if (mEditor->IsEditable(node))
    {
      if (nsEditor::IsTextNode(node))
      {
        PRUint32 length = 0;
        nsCOMPtr<nsIDOMCharacterData>nodeAsText;
        nodeAsText = do_QueryInterface(node);
        nodeAsText->GetLength(&length);
        if (length) *outIsEmptyNode = PR_FALSE;
      }
      else  // an editable, non-text node. we aren't an empty block 
      {
        // is it the node we are iterating over?
        if (node.get() == aNode) break;
        // is it a moz-BR and did the caller ask us not to consider those relevant?
        if (!(aMozBRDoesntCount && IsMozBR(node)))
        {
          // otherwise it ain't empty
          *outIsEmptyNode = PR_FALSE;
          break;
        }
      }
    }
    res = iter->Next();
    if (NS_FAILED(res)) return res;
  }
  
  return NS_OK;
}


PRBool 
nsHTMLEditRules::IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent) 
{
  if (!aNode && !aParent) return PR_FALSE;
  if (aNode == aParent) return PR_FALSE;
  
  nsCOMPtr<nsIDOMNode> parent, node = do_QueryInterface(aNode);
  nsresult res;
  
  do
  {
    res = node->GetParentNode(getter_AddRefs(parent));
    if (NS_FAILED(res)) return PR_FALSE;
    if (parent.get() == aParent) return PR_TRUE;
    node = parent;
  } while (parent);
  
  return PR_FALSE;
}


// not needed at moment - leaving around in case we go back to it.
#if 0
///////////////////////////////////////////////////////////////////////////
// CreateMozDiv: makes a div with type = _moz
//                       
nsresult
nsHTMLEditRules::CreateMozDiv(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outDiv)
{
  if (!inParent || !outDiv) return NS_ERROR_NULL_POINTER;
  nsAutoString divType= "div";
  *outDiv = nsnull;
  nsresult res = mEditor->CreateNode(divType, inParent, inOffset, getter_AddRefs(*outDiv));
  if (NS_FAILED(res)) return res;
  // give it special moz attr
  nsCOMPtr<nsIDOMElement> mozDivElem = do_QueryInterface(*outDiv);
  res = mEditor->SetAttribute(mozDivElem, "type", "_moz");
  if (NS_FAILED(res)) return res;
  res = AddTrailerBR(*outDiv);
  return res;
}
#endif    

///////////////////////////////////////////////////////////////////////////
// IsFirstNode: Are we the first edittable node in our parent?
//                  
PRBool
nsHTMLEditRules::IsFirstNode(nsIDOMNode *aNode)
{
  nsCOMPtr<nsIDOMNode> parent;
  PRInt32 offset, j=0;
  nsresult res = nsEditor::GetNodeLocation(aNode, &parent, &offset);
  if (NS_FAILED(res)) 
  {
    NS_NOTREACHED("failure in nsHTMLEditRules::IsFirstNode");
    return PR_FALSE;
  }
  if (!offset)  // easy case, we are first dom child
    return PR_TRUE;
  if (!parent)  
    return PR_TRUE;
  
  // ok, so there are earlier children.  But are they editable???
  nsCOMPtr<nsIDOMNodeList>childList;
  nsCOMPtr<nsIDOMNode> child;

  res = parent->GetChildNodes(getter_AddRefs(childList));
  if (NS_FAILED(res) || !childList) 
  {
    NS_NOTREACHED("failure in nsHTMLEditRules::IsFirstNode");
    return PR_TRUE;
  }
  while (j < offset)
  {
    childList->Item(j, getter_AddRefs(child));
    if (mEditor->IsEditable(child)) 
      return PR_FALSE;
    j++;
  }
  return PR_TRUE;
}


///////////////////////////////////////////////////////////////////////////
// IsLastNode: Are we the first edittable node in our parent?
//                  
PRBool
nsHTMLEditRules::IsLastNode(nsIDOMNode *aNode)
{
  nsCOMPtr<nsIDOMNode> parent;
  PRInt32 offset, j;
  PRUint32 numChildren;
  nsresult res = nsEditor::GetNodeLocation(aNode, &parent, &offset);
  if (NS_FAILED(res)) 
  {
    NS_NOTREACHED("failure in nsHTMLEditRules::IsLastNode");
    return PR_FALSE;
  }
  nsEditor::GetLengthOfDOMNode(parent, numChildren); 
  if (offset+1 == (PRInt32)numChildren) // easy case, we are last dom child
    return PR_TRUE;
  if (!parent)
    return PR_TRUE;
    
  // ok, so there are later children.  But are they editable???
  j = offset+1;
  nsCOMPtr<nsIDOMNodeList>childList;
  nsCOMPtr<nsIDOMNode> child;
  res = parent->GetChildNodes(getter_AddRefs(childList));
  if (NS_FAILED(res) || !childList) 
  {
    NS_NOTREACHED("failure in nsHTMLEditRules::IsLastNode");
    return PR_TRUE;
  }
  while (j < (PRInt32)numChildren)
  {
    childList->Item(j, getter_AddRefs(child));
    if (mEditor->IsEditable(child)) 
      return PR_FALSE;
    j++;
  }
  return PR_TRUE;
}


///////////////////////////////////////////////////////////////////////////
// AtStartOfBlock: is node/offset at the start of the editable material in this block?
//                  
PRBool
nsHTMLEditRules::AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
{
  nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
  if (nodeAsText && aOffset) return PR_FALSE;  // there are chars in front of us
  
  nsCOMPtr<nsIDOMNode> priorNode;
  nsresult  res = GetPriorHTMLNode(aNode, aOffset, &priorNode);
  if (NS_FAILED(res)) return PR_TRUE;
  if (!priorNode) return PR_TRUE;
  nsCOMPtr<nsIDOMNode> blockParent = mEditor->GetBlockNodeParent(priorNode);
  if (blockParent && (blockParent.get() == aBlock)) return PR_FALSE;
  return PR_TRUE;
}


///////////////////////////////////////////////////////////////////////////
// AtEndOfBlock: is node/offset at the end of the editable material in this block?
//                  
PRBool
nsHTMLEditRules::AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
{
  nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
  if (nodeAsText)   
  {
    PRUint32 strLength;
    nodeAsText->GetLength(&strLength);
    if ((PRInt32)strLength > aOffset) return PR_FALSE;  // there are chars in after us
  }
  nsCOMPtr<nsIDOMNode> nextNode;
  nsresult  res = GetNextHTMLNode(aNode, aOffset, &nextNode);
  if (NS_FAILED(res)) return PR_TRUE;
  if (!nextNode) return PR_TRUE;
  nsCOMPtr<nsIDOMNode> blockParent = mEditor->GetBlockNodeParent(nextNode);
  if (blockParent && (blockParent.get() == aBlock)) return PR_FALSE;
  return PR_TRUE;
}


///////////////////////////////////////////////////////////////////////////
// GetPromotedPoint: figure out where a start or end point for a block
//                   operation really is
nsresult
nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt32 aOffset, 
                                  PRInt32 actionID, nsCOMPtr<nsIDOMNode> *outNode, PRInt32 *outOffset)
{
  nsresult res = NS_OK;
  nsCOMPtr<nsIDOMNode> node = aNode;
  nsCOMPtr<nsIDOMNode> parent = aNode;
  PRInt32 offset = aOffset;
  
  // we do one thing for InsertText actions, something else entirely for other actions
  if (actionID == kInsertText)
  {
    PRBool isSpace, isNBSP; 
    nsCOMPtr<nsIDOMNode> temp;   
    // for insert text or delete actions, we want to look backwards (or forwards, as appropriate)
    // for additional whitespace or nbsp's.  We may have to act on these later even though
    // they are outside of the initial selection.  Even if they are in another node!
    if (aWhere == kStart)
    {
      do
      {
        res = mEditor->IsPrevCharWhitespace(node, offset, &isSpace, &isNBSP, &temp, &offset);
        if (NS_FAILED(res)) return res;
        if (isSpace || isNBSP) node = temp;
        else break;
      } while (node);
  
      *outNode = node;
      *outOffset = offset;
    }
    else if (aWhere == kEnd)
    {
      do
      {
        res = mEditor->IsNextCharWhitespace(node, offset, &isSpace, &isNBSP, &temp, &offset);
        if (NS_FAILED(res)) return res;
        if (isSpace || isNBSP) node = temp;
        else break;
      } while (node);
  
      *outNode = node;
      *outOffset = offset;
    }
    return res;
  }
  
  // else not kInsertText.  In this case we want to see if we should
  // grab any adjacent inlline nodes and/or parents and other ancestors
  if (IsBody(aNode))
  {
    // we cant go any higher
    *outNode = do_QueryInterface(aNode);
    *outOffset = aOffset;
    return res;
  }
  
  if (aWhere == kStart)
  {
    // some special casing for text nodes
    if (nsEditor::IsTextNode(aNode))  
    {
      res = nsEditor::GetNodeLocation(aNode, &parent, &offset);
      if (NS_FAILED(res)) return res;
    }
    else
    {
      node = nsEditor::GetChildAt(parent,offset);
      if (!node) node = parent;  
    }
    
    // if this is an inline node who's block parent is the body,
    // back up through any prior inline nodes that
    // aren't across a <br> from us.
    
    if (!nsEditor::IsBlockNode(node))
    {
      nsCOMPtr<nsIDOMNode> block = nsEditor::GetBlockNodeParent(node);
//    if (IsBody(block))
//    {
        nsCOMPtr<nsIDOMNode> prevNode;
        prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward);
        while (prevNode)
        {
          if (IsBreak(prevNode))
            break;
          if (nsEditor::IsBlockNode(prevNode))
            break;
          node = prevNode;
          prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward);
        }
//    }
//    else
//    {
        // just grap the whole block
//      node = block;
//    }
    }
    
    // finding the real start for this point.  look up the tree for as long as we are the 
    // first node in the container, and as long as we haven't hit the body node.
    res = nsEditor::GetNodeLocation(node, &parent, &offset);
    if (NS_FAILED(res)) return res;
    while ((IsFirstNode(node)) && (!IsBody(parent)))
    {
      node = parent;
      res = nsEditor::GetNodeLocation(node, &parent, &offset);
      if (NS_FAILED(res)) return res;
    } 
    *outNode = parent;
    *outOffset = offset;
    return res;
  }
  
  if (aWhere == kEnd)
  {
    // some special casing for text nodes
    if (nsEditor::IsTextNode(aNode))  
    {
      res = nsEditor::GetNodeLocation(aNode, &parent, &offset);
      if (NS_FAILED(res)) return res;
    }
    else
    {
      node = nsEditor::GetChildAt(parent,offset);
      if (!node) node = parent;  
    }

    if (!node)
      node = parent;
    
    // if this is an inline node who's block parent is the body, 
    // look ahead through any further inline nodes that
    // aren't across a <br> from us, and that are enclosed in the same block.
    
    if (!nsEditor::IsBlockNode(node))
    {
      nsCOMPtr<nsIDOMNode> block = nsEditor::GetBlockNodeParent(node);
//    if (IsBody(block))
//    {
        nsCOMPtr<nsIDOMNode> nextNode;
        nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward);
        while (nextNode)
        {
          if (IsBreak(nextNode))
            break;
          if (nsEditor::IsBlockNode(nextNode))
            break;
          node = nextNode;
          nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward);
        }
//    }
//    else
//    {
        // just grap the whole block
//      node = block;
//    }
    }
    
    // finding the real end for this point.  look up the tree for as long as we are the 
    // last node in the container, and as long as we haven't hit the body node.
    res = nsEditor::GetNodeLocation(node, &parent, &offset);
    if (NS_FAILED(res)) return res;
    while ((IsLastNode(node)) && (!IsBody(parent)))
    {
      node = parent;
      res = nsEditor::GetNodeLocation(node, &parent, &offset);
      if (NS_FAILED(res)) return res;
    } 
    *outNode = parent;
    offset++;  // add one since this in an endpoint - want to be AFTER node.
    *outOffset = offset;
    return res;
  }
  
  return res;
}


///////////////////////////////////////////////////////////////////////////
// GetPromotedRanges: run all the selection range endpoint through 
//                    GetPromotedPoint()
//                       
nsresult 
nsHTMLEditRules::GetPromotedRanges(nsIDOMSelection *inSelection, 
                                   nsCOMPtr<nsISupportsArray> *outArrayOfRanges, 
                                   PRInt32 inOperationType)
{
  if (!inSelection || !outArrayOfRanges) return NS_ERROR_NULL_POINTER;

  nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfRanges));
  if (NS_FAILED(res)) return res;
  
  PRInt32 rangeCount;
  res = inSelection->GetRangeCount(&rangeCount);
  if (NS_FAILED(res)) return res;
  
  PRInt32 i;
  nsCOMPtr<nsIDOMRange> selectionRange;
  nsCOMPtr<nsIDOMRange> opRange;

  for (i = 0; i < rangeCount; i++)
  {
    res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange));
    if (NS_FAILED(res)) return res;

    // clone range so we dont muck with actual selection ranges
    res = selectionRange->Clone(getter_AddRefs(opRange));
    if (NS_FAILED(res)) return res;

    // make a new adjusted range to represent the appropriate block content.
    // The basic idea is to push out the range endpoints
    // to truly enclose the blocks that we will affect.
    // This call alters opRange.
    res = PromoteRange(opRange, inOperationType);
    if (NS_FAILED(res)) return res;
      
    // stuff new opRange into nsISupportsArray
    nsCOMPtr<nsISupports> isupports = do_QueryInterface(opRange);
    (*outArrayOfRanges)->AppendElement(isupports);
  }
  return res;
}


///////////////////////////////////////////////////////////////////////////
// PromoteRange: expand a range to include any parentsfor which all
//               editable children are already in range. 
//                       
nsresult 
nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, 
                              PRInt32 inOperationType)
{
  if (!inRange) return NS_ERROR_NULL_POINTER;
  nsresult res;
  nsCOMPtr<nsIDOMNode> startNode, endNode;
  PRInt32 startOffset, endOffset;
  
  res = inRange->GetStartParent(getter_AddRefs(startNode));
  if (NS_FAILED(res)) return res;
  res = inRange->GetStartOffset(&startOffset);
  if (NS_FAILED(res)) return res;
  res = inRange->GetEndParent(getter_AddRefs(endNode));
  if (NS_FAILED(res)) return res;
  res = inRange->GetEndOffset(&endOffset);
  if (NS_FAILED(res)) return res;
  
  // make a new adjusted range to represent the appropriate block content
  // this is tricky.  the basic idea is to push out the range endpoints
  // to truly enclose the blocks that we will affect
  
  nsCOMPtr<nsIDOMNode> opStartNode;
  nsCOMPtr<nsIDOMNode> opEndNode;
  PRInt32 opStartOffset, opEndOffset;
  nsCOMPtr<nsIDOMRange> opRange;
  
  res = GetPromotedPoint( kStart, startNode, startOffset, inOperationType, &opStartNode, &opStartOffset);
  if (NS_FAILED(res)) return res;
  res = GetPromotedPoint( kEnd, endNode, endOffset, inOperationType, &opEndNode, &opEndOffset);
  if (NS_FAILED(res)) return res;
  inRange->SetStart(opStartNode, opStartOffset);
  if (NS_FAILED(res)) return res;
  inRange->SetEnd(opEndNode, opEndOffset);
  return res;
} 

///////////////////////////////////////////////////////////////////////////
// GetNodesForOperation: run through the ranges in the array and construct 
//                       a new array of nodes to be acted on.
//                       
nsresult 
nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, 
                                   nsCOMPtr<nsISupportsArray> *outArrayOfNodes, 
                                   PRInt32 inOperationType)
{
  if (!inArrayOfRanges || !outArrayOfNodes) return NS_ERROR_NULL_POINTER;

  nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes));
  if (NS_FAILED(res)) return res;
  
  PRUint32 rangeCount;
  res = inArrayOfRanges->Count(&rangeCount);
  if (NS_FAILED(res)) return res;
  
  PRInt32 i;
  nsCOMPtr<nsIDOMRange> opRange;
  nsCOMPtr<nsISupports> isupports;
  nsCOMPtr<nsIContentIterator> iter;

  for (i = 0; i < (PRInt32)rangeCount; i++)
  {
    isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i));
    opRange = do_QueryInterface(isupports);
    res = nsComponentManager::CreateInstance(kSubtreeIteratorCID,
                                        nsnull,
                                        NS_GET_IID(nsIContentIterator), 
                                        getter_AddRefs(iter));
    if (NS_FAILED(res)) return res;
    res = iter->Init(opRange);
    if (NS_FAILED(res)) return res;
    
    while (NS_ENUMERATOR_FALSE == iter->IsDone())
    {
      nsCOMPtr<nsIDOMNode> node;
      nsCOMPtr<nsIContent> content;
      res = iter->CurrentNode(getter_AddRefs(content));
      if (NS_FAILED(res)) return res;
      node = do_QueryInterface(content);
      if (!node) return NS_ERROR_FAILURE;
      isupports = do_QueryInterface(node);
      (*outArrayOfNodes)->AppendElement(isupports);
      res = iter->Next();
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}



///////////////////////////////////////////////////////////////////////////
// GetChildNodesForOperation: 
//                       
nsresult 
nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, 
                                   nsCOMPtr<nsISupportsArray> *outArrayOfNodes)
{
  if (!inNode || !outArrayOfNodes) return NS_ERROR_NULL_POINTER;

  nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes));
  if (NS_FAILED(res)) return res;
  
  nsCOMPtr<nsIDOMNodeList> childNodes;
  res = inNode->GetChildNodes(getter_AddRefs(childNodes));
  if (NS_FAILED(res)) return res;
  if (!childNodes) return NS_ERROR_NULL_POINTER;
  PRUint32 childCount;
  res = childNodes->GetLength(&childCount);
  if (NS_FAILED(res)) return res;
  
  PRUint32 i;
  nsCOMPtr<nsIDOMNode> node;
  nsCOMPtr<nsISupports> isupports;
  for (i = 0; i < childCount; i++)
  {
    res = childNodes->Item( i, getter_AddRefs(node));
    if (!node) return NS_ERROR_FAILURE;
    isupports = do_QueryInterface(node);
    (*outArrayOfNodes)->AppendElement(isupports);
    if (NS_FAILED(res)) return res;
  }
  return res;
}



///////////////////////////////////////////////////////////////////////////
// MakeTransitionList: detect all the transitions in the array, where a 
//                     transition means that adjacent nodes in the array 
//                     don't have the same parent.
//                       
nsresult 
nsHTMLEditRules::MakeTransitionList(nsISupportsArray *inArrayOfNodes, 
                                   nsVoidArray *inTransitionArray)
{
  if (!inArrayOfNodes || !inTransitionArray) return NS_ERROR_NULL_POINTER;

  PRUint32 listCount;
  PRInt32 i;
  inArrayOfNodes->Count(&listCount);
  nsVoidArray transitionList;
  nsCOMPtr<nsIDOMNode> prevElementParent;
  nsCOMPtr<nsIDOMNode> curElementParent;
  
  for (i=0; i<(PRInt32)listCount; i++)
  {
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(inArrayOfNodes->ElementAt(i));
    nsCOMPtr<nsIDOMNode> transNode( do_QueryInterface(isupports ) );
    transNode->GetParentNode(getter_AddRefs(curElementParent));
    if (curElementParent != prevElementParent)
    {
      // different parents, or seperated by <br>: transition point
      inTransitionArray->InsertElementAt((void*)PR_TRUE,i);  
    }
    else
    {
      // same parents: these nodes grew up together
      inTransitionArray->InsertElementAt((void*)PR_FALSE,i); 
    }
    prevElementParent = curElementParent;
  }
  return NS_OK;
}


///////////////////////////////////////////////////////////////////////////
// ReplaceContainer: replace inNode with a new node (outNode) which is contructed 
//                   to be of type aNodeType.  Put inNodes children into outNode.
//                   Callers responsibility to make sure inNode's children can 
//                   go in outNode.
nsresult
nsHTMLEditRules::ReplaceContainer(nsIDOMNode *inNode, 
                                  nsCOMPtr<nsIDOMNode> *outNode, 
                                  const nsString &aNodeType)
{
  if (!inNode || !outNode)
    return NS_ERROR_NULL_POINTER;
  nsresult res;
  nsCOMPtr<nsIDOMNode> parent;
  PRInt32 offset;
  res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
  if (NS_FAILED(res)) return res;
  res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode));
  if (NS_FAILED(res)) return res;
  PRBool bHasMoreChildren;
  inNode->HasChildNodes(&bHasMoreChildren);
  nsCOMPtr<nsIDOMNode> child;
  offset = 0;
  while (bHasMoreChildren)
  {
    inNode->GetLastChild(getter_AddRefs(child));
    res = mEditor->DeleteNode(child);
    if (NS_FAILED(res)) return res;
    res = mEditor->InsertNode(child, *outNode, 0);
    if (NS_FAILED(res)) return res;
    inNode->HasChildNodes(&bHasMoreChildren);
  }
  res = mEditor->DeleteNode(inNode);
  return res;
}


///////////////////////////////////////////////////////////////////////////
// RemoveContainer: remove inNode, reparenting it's children into their
//                  the parent of inNode
//
nsresult
nsHTMLEditRules::RemoveContainer(nsIDOMNode *inNode, PRBool aAddBRIfNeeded)
{
  if (!inNode)
    return NS_ERROR_NULL_POINTER;
  if (IsBody(inNode))
    return NS_ERROR_UNEXPECTED;
  nsresult res;
  nsCOMPtr<nsIDOMNode> parent;
  PRInt32 offset;
  res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
  if (NS_FAILED(res)) return res;
  
  // add BR's before and/or after the inNode
  // if aAddBRIfNeeded is set and if the content
  // before/after the node is inline.  
  // Only do this if inNode is a block node.
  if (aAddBRIfNeeded && mEditor->IsBlockNode(inNode))
  {
    nsCOMPtr<nsIDOMNode> nearNode, brNode;
    res = GetPriorHTMLSibling(inNode, &nearNode);
    if (NS_FAILED(res)) return res;
    if (nearNode && mEditor->IsInlineNode(nearNode))
    {
      res = mEditor->CreateBR(parent, offset, &brNode);
      if (NS_FAILED(res)) return res;
      // refresh location info
      res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
      if (NS_FAILED(res)) return res;
    }
    res = GetNextHTMLSibling(inNode, &nearNode);
    if (NS_FAILED(res)) return res;
    if (nearNode && mEditor->IsInlineNode(nearNode))
    {
      res = mEditor->CreateBR(parent, offset+1, &brNode);
      if (NS_FAILED(res)) return res;
      // refresh location info
      res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
      if (NS_FAILED(res)) return res;
    }
  }
  
  // loop through the child nodes of inNode and promote them
  // into inNode's parent.
  PRBool bHasMoreChildren;
  inNode->HasChildNodes(&bHasMoreChildren);
  nsCOMPtr<nsIDOMNode> child;
  while (bHasMoreChildren)
  {
    inNode->GetLastChild(getter_AddRefs(child));
    res = mEditor->DeleteNode(child);
    if (NS_FAILED(res)) return res;
    res = mEditor->InsertNode(child, parent, offset);
    if (NS_FAILED(res)) return res;
    inNode->HasChildNodes(&bHasMoreChildren);
  }
  res = mEditor->DeleteNode(inNode);
  return res;
}


///////////////////////////////////////////////////////////////////////////
// InsertContainerAbove:  insert a new parent for inNode, returned in outNode,
//                   which is contructed to be of type aNodeType.  outNode becomes
//                   a child of inNode's earlier parent.
//                   Callers responsibility to make sure inNode's can be child
//                   of outNode, and outNode can be child of old parent.
nsresult
nsHTMLEditRules::InsertContainerAbove(nsIDOMNode *inNode, 
                                  nsCOMPtr<nsIDOMNode> *outNode, 
                                  const nsString &aNodeType)
{
  if (!inNode || !outNode)
    return NS_ERROR_NULL_POINTER;
  nsresult res;
  nsCOMPtr<nsIDOMNode> parent;
  PRInt32 offset;
  res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
  if (NS_FAILED(res)) return res;
  
  // make new parent, outNode
  res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode));
  if (NS_FAILED(res)) return res;

  // put inNode in new parent, outNode
  res = mEditor->DeleteNode(inNode);
  if (NS_FAILED(res)) return res;
  res = mEditor->InsertNode(inNode, *outNode, 0);
  if (NS_FAILED(res)) return res;

  return NS_OK;
}




/********************************************************
 *  main implementation methods 
 ********************************************************/
 
///////////////////////////////////////////////////////////////////////////
// InsertTab: top level logic for determining how to insert a tab
//                       
nsresult 
nsHTMLEditRules::InsertTab(nsIDOMSelection *aSelection, 
                           nsString *outString)
{
  nsCOMPtr<nsIDOMNode> parentNode;
  PRInt32 offset;
  PRBool isPRE;
  
  nsresult res = mEditor->GetStartNodeAndOffset(aSelection, &parentNode, &offset);
  if (NS_FAILED(res)) return res;
  
  if (!parentNode) return NS_ERROR_FAILURE;
    
  res = mEditor->IsPreformatted(parentNode, &isPRE);
  if (NS_FAILED(res)) return res;
    
  if (isPRE)
  {
    *outString = '\t';
  }
  else
  {
    // number of spaces should be a pref?
    // note that we dont play around with nbsps here anymore.  
    // let the AfterEdit whitespace cleanup code handle it.
    *outString = "    ";
  }
  
  return NS_OK;
}


///////////////////////////////////////////////////////////////////////////
// ReturnInHeader: do the right thing for returns pressed in headers
//                       
nsresult 
nsHTMLEditRules::ReturnInHeader(nsIDOMSelection *aSelection, 
                                nsIDOMNode *aHeader, 
                                nsIDOMNode *aNode, 
                                PRInt32 aOffset)
{
  if (!aSelection || !aHeader || !aNode) return NS_ERROR_NULL_POINTER;  
  
  // remeber where the header is
  nsCOMPtr<nsIDOMNode> headerParent;
  PRInt32 offset;
  nsresult res = nsEditor::GetNodeLocation(aHeader, &headerParent, &offset);
  if (NS_FAILED(res)) return res;

  // split the header
  PRInt32 newOffset;
  res = mEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset);
  if (NS_FAILED(res)) return res;
  
  // if the new (righthand) header node is empty, delete it
  PRBool isEmpty;
  res = IsEmptyBlock(aHeader, &isEmpty, PR_TRUE);
  if (NS_FAILED(res)) return res;
  if (isEmpty)
  {
    res = mEditor->DeleteNode(aHeader);
    if (NS_FAILED(res)) return res;
    res = aSelection->Collapse(headerParent,offset+1);
    return res;
  }
  // else unwrap it 
  nsCOMPtr<nsIDOMNode> newBlock;
  res = RemoveContainer(aHeader);
  return res;
}


///////////////////////////////////////////////////////////////////////////
// ReturnInParagraph: do the right thing for returns pressed in paragraphs
//                       
nsresult 
nsHTMLEditRules::ReturnInParagraph(nsIDOMSelection *aSelection, 
                                   nsIDOMNode *aPara, 
                                   nsIDOMNode *aNode, 
                                   PRInt32 aOffset,
                                   PRBool *aCancel,
                                   PRBool *aHandled)
{
  if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) 
    { return NS_ERROR_NULL_POINTER; }
  *aCancel = PR_FALSE;
  *aHandled = PR_FALSE;

  nsCOMPtr<nsIDOMNode> sibling;
  nsresult res = NS_OK;

  // easy case, in a text node:
  if (mEditor->IsTextNode(aNode))
  {
    nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode);
    PRUint32 strLength;
    res = textNode->GetLength(&strLength);
    if (NS_FAILED(res)) return res;
    
    // at beginning of text node?
    if (!aOffset)
    {
      // is there a BR prior to it?
      GetPriorHTMLSibling(aNode, &sibling);
      if (!sibling) 
      {
        // no previous sib, so
        // just fall out to default of inserting a BR
        return res;
      }
      if (IsBreak(sibling) && !HasMozAttr(sibling))
      {
        PRInt32 newOffset;
        *aCancel = PR_TRUE;
        // split the paragraph
        res = mEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
        if (NS_FAILED(res)) return res;
        // get rid of the break
        res = mEditor->DeleteNode(sibling);  
        if (NS_FAILED(res)) return res;
        // position selection inside right hand para
        res = aSelection->Collapse(aPara,0);
      }
      // else just fall out to default of inserting a BR
      return res;
    }
    // at end of text node?
    if (aOffset == (PRInt32)strLength)
    {
      // is there a BR after to it?
      res = GetNextHTMLSibling(aNode, &sibling);
      if (!sibling) 
      {
        // no next sib, so
        // just fall out to default of inserting a BR
        return res;
      }
      if (IsBreak(sibling) && !HasMozAttr(sibling))
      {
        PRInt32 newOffset;
        *aCancel = PR_TRUE;
        // split the paragraph
        res = mEditor->SplitNodeDeep(aPara, aNode, aOffset, &newOffset);
        if (NS_FAILED(res)) return res;
        // get rid of the break
        res = mEditor->DeleteNode(sibling);  
        if (NS_FAILED(res)) return res;
        // position selection inside right hand para
        res = aSelection->Collapse(aPara,0);
      }
      // else just fall out to default of inserting a BR
      return res;
    }
    // inside text node
    // just fall out to default of inserting a BR
    return res;
  }
  else
  {
    // not in a text node.  
    // is there a BR prior to it?
    nsCOMPtr<nsIDOMNode> nearNode;
    res = GetPriorHTMLNode(aNode, aOffset, &nearNode);
    if (NS_FAILED(res)) return res;
    if (!nearNode || !IsBreak(nearNode) || HasMozAttr(nearNode)) 
    {
      // is there a BR after to it?
      res = GetNextHTMLNode(aNode, aOffset, &nearNode);
      if (NS_FAILED(res)) return res;
      if (!nearNode || !IsBreak(nearNode) || HasMozAttr(nearNode)) 
      {
        // just fall out to default of inserting a BR
        return res;
      }
    }
    // else remove sibling br and split para
    PRInt32 newOffset;
    *aCancel = PR_TRUE;
    // split the paragraph
    res = mEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
    if (NS_FAILED(res)) return res;
    // get rid of the break
    res = mEditor->DeleteNode(nearNode);  
    if (NS_FAILED(res)) return res;
    // selection to beginning of right hand para
    aSelection->Collapse(aPara,0);
  }
  
  return res;
}



///////////////////////////////////////////////////////////////////////////
// ReturnInListItem: do the right thing for returns pressed in list items
//                       
nsresult 
nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, 
                                  nsIDOMNode *aListItem, 
                                  nsIDOMNode *aNode, 
                                  PRInt32 aOffset)
{
  if (!aSelection || !aListItem || !aNode) return NS_ERROR_NULL_POINTER;
  nsresult res = NS_OK;
  
  nsCOMPtr<nsIDOMNode> listitem;
  
  // sanity check
  NS_PRECONDITION(PR_TRUE == IsListItem(aListItem), "expected a list item and didnt get one");
  
  // if we are in an empty listitem, then we want to pop up out of the list
  PRBool isEmpty;
  res = IsEmptyBlock(aListItem, &isEmpty, PR_TRUE, PR_FALSE);
  if (NS_FAILED(res)) return res;
  if (isEmpty)
  {
    nsCOMPtr<nsIDOMNode> list, listparent;
    PRInt32 offset, itemOffset;
    res = nsEditor::GetNodeLocation(aListItem, &list, &itemOffset);
    if (NS_FAILED(res)) return res;
    res = nsEditor::GetNodeLocation(list, &listparent, &offset);
    if (NS_FAILED(res)) return res;
    
    // are we the last list item in the list?
    PRBool bIsLast;
    res = IsLastEditableChild(aListItem, &bIsLast);
    if (NS_FAILED(res)) return res;
    if (!bIsLast)
    {
      // we need to split the list!
      nsCOMPtr<nsIDOMNode> tempNode;
      res = mEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode));
      if (NS_FAILED(res)) return res;
    }
    // are we in a sublist?
    if (IsList(listparent))  //in a sublist
    {
      // if so, move this list item out of this list and into the grandparent list
      res = mEditor->DeleteNode(aListItem);
      if (NS_FAILED(res)) return res;
      res = mEditor->InsertNode(aListItem,listparent,offset+1);
      if (NS_FAILED(res)) return res;
      res = aSelection->Collapse(aListItem,0);
    }
    else
    {
      // otherwise kill this listitem and set the selection to after the parent list
      res = mEditor->DeleteNode(aListItem);
      if (NS_FAILED(res)) return res;
      res = aSelection->Collapse(listparent,offset+1);
      if (NS_FAILED(res)) return res;
      
      // now attempt to adjust selection to a text node.
      // if we fail, then that means we need toinsert a break
      res = AdjustSelection(aSelection, nsIEditor::eNext);
      if (NS_FAILED(res)) return res;
      // get the selection location
      nsCOMPtr<nsIDOMNode> selNode;
      PRInt32 selOffset;
      res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset);
      if (NS_FAILED(res)) return res;

      // are we in a text node? 
      nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode);
      if (!textNode) 
      {
        // time to insert a break
        nsCOMPtr<nsIDOMNode> brNode;
        res = CreateMozBR(selNode, selOffset, &brNode);
        if (NS_FAILED(res)) return res;
        res = nsEditor::GetNodeLocation(brNode, &selNode, &selOffset);
        if (NS_FAILED(res)) return res;
        res = aSelection->Collapse(selNode,selOffset+1);
        if (NS_FAILED(res)) return res;
      }
    }
    return res;
  }
  
  // else we want a new list item at the same list level
  PRInt32 newOffset;
  res = mEditor->SplitNodeDeep( aListItem, aNode, aOffset, &newOffset);
  if (NS_FAILED(res)) return res;
  res = aSelection->Collapse(aListItem,0);
  return res;
}


///////////////////////////////////////////////////////////////////////////
// ShouldMakeEmptyBlock: determine if a block transformation should make 
//                       a new empty block, or instead transform a block
//                       
nsresult 
nsHTMLEditRules::ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, 
                                      const nsString *blockTag, 
                                      PRBool *outMakeEmpty)
{
  // a note about strategy:
  // this routine will be called by the rules code to figure out
  // if it should do something, or let the nsHTMLEditor default
  // action happen.  The default action is to insert a new block.
  // Note that if _nothing_ should happen, ie, the selection is
  // already entireyl inside a block (or blocks) or the correct type,
  // then you don't want to return true in outMakeEmpty, since the
  // defualt code will insert a new empty block anyway, rather than
  // doing nothing.  So we have to detect that case and return false.
  
  if (!aSelection || !outMakeEmpty) return NS_ERROR_NULL_POINTER;
  nsresult res = NS_OK;
  
  // if the selection is collapsed, and
  // if we in the body, or after a <br> with
  // no more inline content before the next block, then we want
  // a new block.  Otherwise we want to trasform a block
  
  // xxx possible bug: selection could be not callapsed, but
  // still empty.  it would be nice to have a call for this: IsEmptySelection()
  
  PRBool isCollapsed;
  res = aSelection->GetIsCollapsed(&isCollapsed);
  if (NS_FAILED(res)) return res;
  if (isCollapsed) 
  {
    nsCOMPtr<nsIDOMNode> parent;
    PRInt32 offset;
    res = nsEditor::GetStartNodeAndOffset(aSelection, &parent, &offset);
    if (NS_FAILED(res)) return res;
    
    // is selection point in the body?
    if (IsBody(parent))
    {
      *outMakeEmpty = PR_TRUE;
      return res;
    }
    
    // see if block parent is already right kind of block.  
    // See strategy comment above.
    nsCOMPtr<nsIDOMNode> block;
    if (!nsEditor::IsBlockNode(parent))
      block = nsEditor::GetBlockNodeParent(parent);
    else
      block = parent;
    if (block)
    {
      nsAutoString tag;
      nsEditor::GetTagString(block,tag);
      if (tag == *blockTag)
      {
        *outMakeEmpty = PR_FALSE;
        return res;
      }
    }
    
    // are we in a textnode or inline node?
    if (!nsEditor::IsBlockNode(parent))
    {
      // we must be in a text or inline node - convert existing block
      *outMakeEmpty = PR_FALSE;
      return res;
    }
    
    // is it after a <br> with no inline nodes after it, or a <br> after it??
    if (offset)
    {
      nsCOMPtr<nsIDOMNode> prevChild, nextChild, tmp;
      prevChild = nsEditor::GetChildAt(parent, offset-1);
      while (prevChild && !mEditor->IsEditable(prevChild))
      {
        // search back until we either find an editable node, 
        // or hit the beginning of the block
        tmp = nsEditor::NextNodeInBlock(prevChild, nsEditor::kIterBackward);
        prevChild = tmp;
      }
      
      if (prevChild && IsBreak(prevChild)) 
      {
        nextChild = nsEditor::GetChildAt(parent, offset);
        while (nextChild && !mEditor->IsEditable(nextChild))
        {
          // search back until we either find an editable node, 
          // or hit the beginning of the block
          tmp = nsEditor::NextNodeInBlock(nextChild, nsEditor::kIterForward);
          nextChild = tmp;
        }
        if (!nextChild || IsBreak(nextChild) || nsEditor::IsBlockNode(nextChild))
        {
          // we are after a <br> and not before inline content,
          // or we are between <br>s.
          // make an empty block
          *outMakeEmpty = PR_FALSE;
          return res;
        }
      }
    }
  }    
  // otherwise transform an existing block
  *outMakeEmpty = PR_FALSE;
  return res;
}


///////////////////////////////////////////////////////////////////////////
// ApplyBlockStyle:  do whatever it takes to make the list of nodes into 
//                   one or more blocks of type blockTag.  
//                       
nsresult 
nsHTMLEditRules::ApplyBlockStyle(nsISupportsArray *arrayOfNodes, const nsString *aBlockTag)
{
  // intent of this routine is to be used for converting to/from
  // headers, paragraphs, pre, and address.  Those blocks
  // that pretty much just contain inline things...
  
  if (!arrayOfNodes || !aBlockTag) return NS_ERROR_NULL_POINTER;
  nsresult res = NS_OK;
  
  nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
  PRInt32 offset;
  PRUint32 listCount;
  PRBool bNoParent = PR_FALSE;
  
  // we special case an empty tag name to mean "remove block parents".
  // This is used for the "normal" paragraph style in mail-compose
  if (aBlockTag->IsEmpty() || *aBlockTag=="normal") bNoParent = PR_TRUE;
  
  arrayOfNodes->Count(&listCount);
  
  PRUint32 i;
  for (i=0; i<listCount; i++)
  {
    // get the node to act on, and it's location
    nsCOMPtr<nsISupports> isupports  = (dont_AddRef)(arrayOfNodes->ElementAt(i));
    curNode = do_QueryInterface(isupports);
    res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
    if (NS_FAILED(res)) return res;
    nsAutoString curNodeTag;
    nsEditor::GetTagString(curNode, curNodeTag);
        
 
    // is it already the right kind of block?
    if (!bNoParent && curNodeTag == *aBlockTag)
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      continue;  // do nothing to this block
    }
        
    // if curNode is a mozdiv, p, header, address, or pre, replace 
    // it with a new block of correct type.
    // xxx floppy moose: pre cant hold everything the others can
    if (IsMozDiv(curNode)     ||
        (curNodeTag == "pre") || 
        (curNodeTag == "p")   ||
        (curNodeTag == "h1")  ||
        (curNodeTag == "h2")  ||
        (curNodeTag == "h3")  ||
        (curNodeTag == "h4")  ||
        (curNodeTag == "h5")  ||
        (curNodeTag == "h6")  ||
        (curNodeTag == "address"))
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      if (bNoParent)
      {
//        nsCOMPtr<nsIDOMNode> brNode;
//        res = mEditor->CreateBR(curParent, offset+1, &brNode);
//        if (NS_FAILED(res)) return res;
        res = RemoveContainer(curNode, PR_TRUE); 
      }
      else
      {
        res = ReplaceContainer(curNode, &newBlock, *aBlockTag);
      }
      if (NS_FAILED(res)) return res;
    }
    else if ((curNodeTag == "table")      || 
             (curNodeTag == "tbody")      ||
             (curNodeTag == "tr")         ||
             (curNodeTag == "td")         ||
             (curNodeTag == "ol")         ||
             (curNodeTag == "ul")         ||
             (curNodeTag == "li")         ||
             (curNodeTag == "blockquote") ||
             (curNodeTag == "div"))  // div's other than mozdivs
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      // recursion time
      nsCOMPtr<nsISupportsArray> childArray;
      res = GetChildNodesForOperation(curNode, &childArray);
      if (NS_FAILED(res)) return res;
      res = ApplyBlockStyle(childArray, aBlockTag);
      if (NS_FAILED(res)) return res;
    }
    
    // if the node is a break, we honor it by putting further nodes in a new parent
    else if (curNodeTag == "br")
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      if (!bNoParent)
      {
        res = mEditor->DeleteNode(curNode);
        if (NS_FAILED(res)) return res;
      }
    }
        
    
    // if curNode is inline, pull it into curBlock
    // note: it's assumed that consecutive inline nodes in the 
    // arrayOfNodes are actually members of the same block parent.
    // this happens to be true now as a side effect of how
    // arrayOfNodes is contructed, but some additional logic should
    // be added here if that should change
    
    else if (nsEditor::IsInlineNode(curNode) && !bNoParent)
    {
      // if curNode is a non editable, drop it if we are going to <pre>
      if ((*aBlockTag == "pre") && (!mEditor->IsEditable(curNode)))
        continue; // do nothing to this block
      
      // if no curBlock, make one
      if (!curBlock)
      {
        res = mEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
        if (NS_FAILED(res)) return res;
      }
      
      // if curNode is a Break, replace it with a return if we are going to <pre>
      // xxx floppy moose
 
      // this is a continuation of some inline nodes that belong together in
      // the same block item.  use curBlock
      PRUint32 blockLen;
      res = mEditor->GetLengthOfDOMNode(curBlock, blockLen);
      if (NS_FAILED(res)) return res;
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
      res = mEditor->InsertNode(curNode, curBlock, blockLen);
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}




///////////////////////////////////////////////////////////////////////////
// JoinNodesSmart:  join two nodes, doing whatever makes sense for their  
//                  children (which often means joining them, too).
//                  aNodeLeft & aNodeRight must be same type of node.
nsresult 
nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, 
                                 nsIDOMNode *aNodeRight, 
                                 nsCOMPtr<nsIDOMNode> *aOutMergeParent, 
                                 PRInt32 *aOutMergeOffset)
{
  // check parms
  if (!aNodeLeft ||  
      !aNodeRight || 
      !aOutMergeParent ||
      !aOutMergeOffset) 
    return NS_ERROR_NULL_POINTER;
  
  nsresult res = NS_OK;
  // caller responsible for:
  //   left & right node are same type
  PRInt32 parOffset;
  nsCOMPtr<nsIDOMNode> parent, rightParent;
  res = nsEditor::GetNodeLocation(aNodeLeft, &parent, &parOffset);
  if (NS_FAILED(res)) return res;
  aNodeRight->GetParentNode(getter_AddRefs(rightParent));

  // if they don't have the same parent, first move the 'right' node 
  // to after the 'left' one
  if (parent != rightParent)
  {
    res = mEditor->DeleteNode(aNodeRight);
    if (NS_FAILED(res)) return res;
    res = mEditor->InsertNode(aNodeRight, parent, parOffset);
    if (NS_FAILED(res)) return res;
  }
  
  // defaults for outParams
  *aOutMergeParent = aNodeRight;
  res = mEditor->GetLengthOfDOMNode(aNodeLeft, *((PRUint32*)aOutMergeOffset));
  if (NS_FAILED(res)) return res;

  // seperate join rules for differing blocks
  if (IsParagraph(aNodeLeft))
  {
    // for para's, merge deep & add a <br> after merging
    res = mEditor->JoinNodeDeep(aNodeLeft, aNodeRight, aOutMergeParent, aOutMergeOffset);
    if (NS_FAILED(res)) return res;
    // now we need to insert a br.  
    nsCOMPtr<nsIDOMNode> brNode;
    res = mEditor->CreateBR(*aOutMergeParent, *aOutMergeOffset, &brNode);
    if (NS_FAILED(res)) return res;
    res = nsEditor::GetNodeLocation(brNode, aOutMergeParent, aOutMergeOffset);
    if (NS_FAILED(res)) return res;
    (*aOutMergeOffset)++;
    return res;
  }
  else if (IsList(aNodeLeft) || mEditor->IsTextNode(aNodeLeft))
  {
    // for list's, merge shallow (wouldn't want to combine list items)
    res = mEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
    if (NS_FAILED(res)) return res;
    return res;
  }
  else
  {
    // remember the last left child, and firt right child
    nsCOMPtr<nsIDOMNode> lastLeft, firstRight;
    res = GetLastEditableChild(aNodeLeft, &lastLeft);
    if (NS_FAILED(res)) return res;
    res = GetFirstEditableChild(aNodeRight, &firstRight);
    if (NS_FAILED(res)) return res;

    // for list items, divs, etc, merge smart
    res = mEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
    if (NS_FAILED(res)) return res;

    if (lastLeft && firstRight && mEditor->NodesSameType(lastLeft, firstRight))
    {
      return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
    }
  }
  return res;
}


nsresult 
nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutCiteNode)
{
  // check parms
  if (!aNode || !aOutCiteNode) 
    return NS_ERROR_NULL_POINTER;
  
  nsresult res = NS_OK;
  nsCOMPtr<nsIDOMNode> node, parentNode;
  node = do_QueryInterface(aNode);
  
  while (node)
  {
    if (IsMailCite(node)) *aOutCiteNode = node;
    if (IsBody(node)) break;
    
    res = node->GetParentNode(getter_AddRefs(parentNode));
    if (NS_FAILED(res)) return res;
    node = parentNode;
  }

  return res;
}


nsresult 
nsHTMLEditRules::AdjustSpecialBreaks()
{
  nsCOMPtr<nsIContentIterator> iter;
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  nsCOMPtr<nsISupports> isupports;
  PRUint32 nodeCount,j;
  
  // make an isupportsArray to hold a list of nodes
  nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
  if (NS_FAILED(res)) return res;

  // need an iterator
  res = nsComponentManager::CreateInstance(kContentIteratorCID,
                                        nsnull,
                                        NS_GET_IID(nsIContentIterator), 
                                        getter_AddRefs(iter));
  if (NS_FAILED(res)) return res;
  
  // loop over iter and create list of empty containers
  res = iter->Init(mDocChangeRange);
  if (NS_FAILED(res)) return res;
  
  // gather up a list of empty nodes
  while (NS_ENUMERATOR_FALSE == iter->IsDone())
  {
    nsCOMPtr<nsIDOMNode> node;
    nsCOMPtr<nsIContent> content;
    res = iter->CurrentNode(getter_AddRefs(content));
    if (NS_FAILED(res)) return res;
    node = do_QueryInterface(content);
    if (!node) return NS_ERROR_FAILURE;
    
    PRBool bIsEmptyNode;
    res = IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_FALSE);
    if (NS_FAILED(res)) return res;
    if (bIsEmptyNode && (IsListItem(node) || mEditor->IsTableCell(node)))
    {
      isupports = do_QueryInterface(node);
      arrayOfNodes->AppendElement(isupports);
    }
    
    res = iter->Next();
    if (NS_FAILED(res)) return res;
  }
  
  // put moz-br's into these empty li's and td's
  res = arrayOfNodes->Count(&nodeCount);
  if (NS_FAILED(res)) return res;
  for (j = 0; j < nodeCount; j++)
  {
    isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
    nsCOMPtr<nsIDOMNode> brNode, theNode( do_QueryInterface(isupports ) );
    arrayOfNodes->RemoveElementAt(0);
    res = CreateMozBR(theNode, 0, &brNode);
    if (NS_FAILED(res)) return res;
  }
  
  return res;
}


nsresult 
nsHTMLEditRules::AdjustWhitespace(nsIDOMSelection *aSelection)
{
  nsCOMPtr<nsIContentIterator> iter;
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  nsCOMPtr<nsISupports> isupports;
  PRUint32 nodeCount,j;
  nsresult res;

  nsAutoSelectionReset selectionResetter(aSelection);
  
  // special case for mDocChangeRange entirely in one text node.
  // This is an efficiency hack for normal typing in the editor.
  nsCOMPtr<nsIDOMNode> startNode, endNode;
  PRInt32 startOffset, endOffset;
  res = mDocChangeRange->GetStartParent(getter_AddRefs(startNode));
  if (NS_FAILED(res)) return res;
  res = mDocChangeRange->GetStartOffset(&startOffset);
  if (NS_FAILED(res)) return res;
  res = mDocChangeRange->GetEndParent(getter_AddRefs(endNode));
  if (NS_FAILED(res)) return res;
  res = mDocChangeRange->GetEndOffset(&endOffset);
  if (NS_FAILED(res)) return res;

  if (startNode == endNode)
  {
    nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
    if (nodeAsText)
    {
      res = DoTextNodeWhitespace(nodeAsText, startOffset, endOffset);
      return res;
    }
  }
  
  // make an isupportsArray to hold a list of nodes
  res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
  if (NS_FAILED(res)) return res;

  // need an iterator
  res = nsComponentManager::CreateInstance(kContentIteratorCID,
                                        nsnull,
                                        NS_GET_IID(nsIContentIterator), 
                                        getter_AddRefs(iter));
  if (NS_FAILED(res)) return res;
  
  // loop over iter and adjust whitespace in any text nodes we find
  res = iter->Init(mDocChangeRange);
  if (NS_FAILED(res)) return res;
  
  // gather up a list of text nodes
  while (NS_ENUMERATOR_FALSE == iter->IsDone())
  {
    nsCOMPtr<nsIDOMNode> node;
    nsCOMPtr<nsIContent> content;
    res = iter->CurrentNode(getter_AddRefs(content));
    if (NS_FAILED(res)) return res;
    node = do_QueryInterface(content);
    if (!node) return NS_ERROR_FAILURE;
    
    if (nsEditor::IsTextNode(node) && mEditor->IsEditable(node)) 
    {
      isupports = do_QueryInterface(node);
      arrayOfNodes->AppendElement(isupports);
    }
    
    res = iter->Next();
    if (NS_FAILED(res)) return res;
  }
  
  // now adjust whitespace on node we found
  res = arrayOfNodes->Count(&nodeCount);
  if (NS_FAILED(res)) return res;
  for (j = 0; j < nodeCount; j++)
  {
    isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
    nsCOMPtr<nsIDOMCharacterData> textNode( do_QueryInterface(isupports ) );
    arrayOfNodes->RemoveElementAt(0);
    res = DoTextNodeWhitespace(textNode, -1, -1);
    if (NS_FAILED(res)) return res;
  }
  
  return res;
}


nsresult 
nsHTMLEditRules::AdjustSelection(nsIDOMSelection *aSelection, nsIEditor::EDirection aAction)
{
  if (!aSelection) return NS_ERROR_NULL_POINTER;
  
  // if the selection isn't collapsed, do nothing.
  // moose: one thing to do instead is check for the case of
  // only a single break selected, and collapse it.  Good thing?  Beats me.
  PRBool bCollapsed;
  nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
  if (NS_FAILED(res)) return res;
  if (!bCollapsed) return res;

  // get the (collapsed) selection location
  nsCOMPtr<nsIDOMNode> selNode, temp;
  PRInt32 selOffset;
  res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;
  temp = selNode;
  
  // are we in an editable node?
  while (!mEditor->IsEditable(selNode))
  {
    // scan up the tree until we find an editable place to be
    res = nsEditor::GetNodeLocation(temp, &selNode, &selOffset);
    if (NS_FAILED(res)) return res;
    if (!selNode) return NS_ERROR_FAILURE;
    temp = selNode;
  }
  
  // are we in a text node? 
  nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode);
  if (textNode) 
    return NS_OK; // we LIKE it when we are in a text node.  that RULZ
  
  // do we need to insert a special mozBR?  We do if we are:
  // 1) in a collapsed selection AND
  // 2) after a normal (non-moz) br AND
  // 3) that br is the last editable node in it's block AND
  // 4) that block is same block where selection is
  nsCOMPtr<nsIDOMNode> nearNode;
  res = GetPriorHTMLNode(selNode, selOffset, &nearNode);
  if (NS_FAILED(res)) return res;
  if (nearNode && IsBreak(nearNode) && !IsMozBR(nearNode))
  {
    PRBool bIsLast;
    res = IsLastEditableChild(nearNode, &bIsLast);
    if (NS_FAILED(res)) return res;
    if (bIsLast)
    {
      // is nearNode also a descendant of same block?
      nsCOMPtr<nsIDOMNode> block, nearBlock;
      if (mEditor->IsBlockNode(selNode)) block = selNode;
      else block = mEditor->GetBlockNodeParent(selNode);
      nearBlock = mEditor->GetBlockNodeParent(nearNode);
      if (block == nearBlock)
      {
        // need to insert special moz BR. Why?  Because if we don't
        // the user will see no new line for the break.  Also, things
        // like table cells won't grow in height.
        nsCOMPtr<nsIDOMNode> brNode;
        res = CreateMozBR(selNode, selOffset, &brNode);
        if (NS_FAILED(res)) return res;
        res = nsEditor::GetNodeLocation(brNode, &selNode, &selOffset);
        if (NS_FAILED(res)) return res;
        res = aSelection->Collapse(selNode,selOffset+1);
        if (NS_FAILED(res)) return res;
      }
    }
    else
    {
      // ok, the br inst the last child.  But it might be second-to-last
      // with a mozBR already exiting after it.  In this case we have to
      // move the selection to after the mozBR so it will show up on the
      // empty line.
      nsCOMPtr<nsIDOMNode> nextNode;
      res = GetNextHTMLNode(nearNode, &nextNode);
      if (NS_FAILED(res)) return res;
      if (nextNode && IsMozBR(nextNode))
      {
        res = nsEditor::GetNodeLocation(nextNode, &selNode, &selOffset);
        if (NS_FAILED(res)) return res;
        res = aSelection->Collapse(selNode,selOffset+1);
        if (NS_FAILED(res)) return res;
      }
      else
      {
        // alright, not that either.  But there's one more squirrel to feed:
        // the br might be right in front of a new block (ie,:
        // <body> text<br> <ol><li>list item</li></ol></body>  )
        // in this case we also need moz-br.
        res = GetNextHTMLSibling(nearNode, &nextNode);
        if (NS_FAILED(res)) return res;
        if (nextNode && mEditor->IsBlockNode(nextNode))
        {
          // need to insert special moz BR. Why?  Because if we don't
          // the user will see no new line for the break.  
          nsCOMPtr<nsIDOMNode> brNode;
          res = CreateMozBR(selNode, selOffset, &brNode);
          if (NS_FAILED(res)) return res;
          res = nsEditor::GetNodeLocation(brNode, &selNode, &selOffset);
          if (NS_FAILED(res)) return res;
          res = aSelection->Collapse(selNode,selOffset+1);
          if (NS_FAILED(res)) return res;
        }
      }
    }
  }
  
  // we aren't in a textnode: are we adjacent to a break or an image?
  res = GetPriorHTMLSibling(selNode, selOffset, &nearNode);
  if (NS_FAILED(res)) return res;
  if (nearNode && (IsBreak(nearNode)  || IsImage(nearNode)))
    return NS_OK; // this is a good place for the caret to be
  res = GetNextHTMLSibling(selNode, selOffset, &nearNode);
  if (NS_FAILED(res)) return res;
  if (nearNode && (IsBreak(nearNode)  || IsImage(nearNode)))
    return NS_OK; // this is a good place for the caret to be

  // look for a nearby text node.
  // prefer the correct direction.
  res = FindNearSelectableNode(selNode, selOffset, aAction, &nearNode);
  if (NS_FAILED(res)) return res;

  if (!nearNode) return NS_OK; // couldn't find a near text node

  // is the nearnode a text node?
  textNode = do_QueryInterface(nearNode);
  if (textNode)
  {
    PRInt32 offset = 0;
    // put selection in right place:
    if (aAction == nsIEditor::ePrevious)
      textNode->GetLength((PRUint32*)&offset);
    res = aSelection->Collapse(nearNode,offset);
  }
  else  // must be break or image
  {
    res = nsEditor::GetNodeLocation(nearNode, &selNode, &selOffset);
    if (NS_FAILED(res)) return res;
    if (aAction == nsIEditor::ePrevious) selOffset++;  // want to be beyond it if we backed up to it
    res = aSelection->Collapse(selNode, selOffset);
  }
  return res;
}

nsresult
nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode, 
                                        PRInt32 aSelOffset, 
                                        nsIEditor::EDirection aDirection,
                                        nsCOMPtr<nsIDOMNode> *outSelectableNode)
{
  if (!aSelNode || !outSelectableNode) return NS_ERROR_NULL_POINTER;
  *outSelectableNode = nsnull;
  nsresult res = NS_OK;
  
  nsCOMPtr<nsIDOMNode> nearNode, curNode;
  if (aDirection == nsIEditor::ePrevious)
    res = GetPriorHTMLNode(aSelNode, aSelOffset, &nearNode);
  else
    res = GetNextHTMLNode(aSelNode, aSelOffset, &nearNode);
  if (NS_FAILED(res)) return res;
  
  // scan in the right direction until we find an eligible text node,
  // but dont cross any breaks, images, or table elements.
  while (nearNode && !(mEditor->IsTextNode(nearNode) || IsBreak(nearNode)  || IsImage(nearNode)))
  {
    // dont cross any table elements
    if (mEditor->IsTableElement(nearNode))
      return NS_OK;  

    curNode = nearNode;
    if (aDirection == nsIEditor::ePrevious)
      res = GetPriorHTMLNode(curNode, &nearNode);
    else
      res = GetNextHTMLNode(curNode, &nearNode);
    if (NS_FAILED(res)) return res;
  }
  
  if (nearNode) *outSelectableNode = do_QueryInterface(nearNode);
  return res;
}


nsresult 
nsHTMLEditRules::RemoveEmptyNodes()
{
  nsCOMPtr<nsIContentIterator> iter;
  nsCOMPtr<nsISupportsArray> arrayOfNodes;
  nsCOMPtr<nsISupports> isupports;
  PRUint32 nodeCount,j;
  
  // make an isupportsArray to hold a list of nodes
  nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
  if (NS_FAILED(res)) return res;

  // need an iterator
  res = nsComponentManager::CreateInstance(kContentIteratorCID,
                                        nsnull,
                                        NS_GET_IID(nsIContentIterator), 
                                        getter_AddRefs(iter));
  if (NS_FAILED(res)) return res;
  
  // loop over iter and create list of empty containers
  do
  {
    res = iter->Init(mDocChangeRange);
    if (NS_FAILED(res)) return res;
    
    // gather up a list of empty nodes
    while (NS_ENUMERATOR_FALSE == iter->IsDone())
    {
      nsCOMPtr<nsIDOMNode> node;
      nsCOMPtr<nsIContent> content;
      res = iter->CurrentNode(getter_AddRefs(content));
      if (NS_FAILED(res)) return res;
      node = do_QueryInterface(content);
      if (!node) return NS_ERROR_FAILURE;
      
      PRBool bIsEmptyNode;
      res = IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_TRUE);
      if (NS_FAILED(res)) return res;
      if (bIsEmptyNode && !IsBody(node))
      {
        isupports = do_QueryInterface(node);
        arrayOfNodes->AppendElement(isupports);
      }
      
      res = iter->Next();
      if (NS_FAILED(res)) return res;
    }
    
    // now delete the empty nodes
    res = arrayOfNodes->Count(&nodeCount);
    if (NS_FAILED(res)) return res;
    for (j = 0; j < nodeCount; j++)
    {
      isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
      nsCOMPtr<nsIDOMNode> delNode( do_QueryInterface(isupports ) );
      arrayOfNodes->RemoveElementAt(0);
      res = mEditor->DeleteNode(delNode);
      if (NS_FAILED(res)) return res;
    }
    
  } while (nodeCount);  // if we deleted any, loop again
                        // deleting some nodes may make some parents now empty
  return res;
}

nsresult 
nsHTMLEditRules::DoTextNodeWhitespace(nsIDOMCharacterData *aTextNode, PRInt32 aStart, PRInt32 aEnd)
{
  // check parms
  if (!aTextNode) return NS_ERROR_NULL_POINTER;
  if (aStart == -1)  // -1 means do the whole darn node please
  {
    aStart = 0;
    aTextNode->GetLength((PRUint32*)&aEnd);
  }
  if (aStart == aEnd) return NS_OK;
  
  nsresult res = NS_OK;
  PRBool isPRE;
  nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aTextNode);
  
  res = mEditor->IsPreformatted(node,&isPRE);
  if (NS_FAILED(res)) return res;
    
  if (isPRE)
  {
    // text node has a Preformatted style. All we need to do is strip out any nbsp's 
    // we just put in and replace them with spaces.
  
    // moose: write me!
    return res;
  }
  
  // else we are not preformatted.  Need to convert any adjacent spaces to alterating
  // space/nbsp pairs; need to convert stranded nbsp's to spaces; need to convert
  // tabs to whitespace; need to convert returns to whitespace.
  PRInt32 j = 0;
  nsAutoString tempString;

  aTextNode->SubstringData(aStart, aEnd, tempString);
  
  // identify runs of whitespace
  PRInt32 runStart = -1, runEnd = -1;
  do {
    PRUnichar c = tempString[j];
    PRBool isSpace = nsString::IsSpace(c);
    if (isSpace || c==nbsp)
    {
      if (runStart<0) runStart = j;
      runEnd = j+1;
    }
    // translation of below line:
    // if we have a whitespace run, AND
    // either we are at the end of it, or the end of the whole string,
    // THEN process it
    if (runStart>=0 && (!(isSpace || c==nbsp)  ||  (j==aEnd-1)) )
    {
      // current char is non whitespace, but we have identified an earlier
      // run of whitespace.  convert it if needed.
      NS_PRECONDITION(runEnd>runStart, "this is what happens when integers turn bad!");
      // runStart to runEnd is a run of whitespace
      nsAutoString runStr, newStr;
      tempString.Mid(runStr, runStart, runEnd-runStart);
      res = ConvertWhitespace(runStr, newStr);
      if (NS_FAILED(res)) return res;
      
      if (runStr != newStr)
      {
        // delete the original whitespace run
        EditTxn *txn;
        // note 1: we are not telling edit listeners about these because they don't care
        // note 2: we are not wrapping these in a placeholder because we know they already are
        res = mEditor->CreateTxnForDeleteText(aTextNode, aStart+runStart, runEnd-runStart, (DeleteTextTxn**)&txn);
        if (NS_FAILED(res))  return res; 
        if (!txn)  return NS_ERROR_OUT_OF_MEMORY;
        res = mEditor->Do(txn); 
        if (NS_FAILED(res))  return res; 
        // The transaction system (if any) has taken ownwership of txn
        NS_IF_RELEASE(txn);
        
        // insert the new run
        res = mEditor->CreateTxnForInsertText(newStr, aTextNode, aStart+runStart, (InsertTextTxn**)&txn);
        if (NS_FAILED(res))  return res; 
        if (!txn)  return NS_ERROR_OUT_OF_MEMORY;
        res = mEditor->Do(txn);
        // The transaction system (if any) has taken ownwership of txns.
        NS_IF_RELEASE(txn);
      } 
      runStart = -1; // reset our run     
    }
    j++; // next char please!
  } while (j<aEnd);

  return res;
}


nsresult 
nsHTMLEditRules::ConvertWhitespace(const nsString & inString, nsString & outString)
{
  PRUint32 j,len = inString.Length();
  switch (len)
  {
    case 0:
      outString = "";
      return NS_OK;
    case 1:
      outString = " ";
      return NS_OK;
    case 2:
      outString = (PRUnichar)nbsp;
      outString += " ";
      return NS_OK;
    case 3:
      outString = " ";
      outString += (PRUnichar)nbsp;
      outString += " ";
      return NS_OK;
  }
  if (len%2)  // length is odd
  {
    for (j=0;j<len;j++)
    {
      if (!(j%2)) outString += " ";      // even char
      else outString += (PRUnichar)nbsp; // odd char
    }
  }
  else
  {
    outString = " ";
    outString += (PRUnichar)nbsp;
    outString += (PRUnichar)nbsp;
    for (j=0;j<len-3;j++)
    {
      if (!(j%2)) outString += " ";      // even char
      else outString += (PRUnichar)nbsp; // odd char
    }
  }  
  return NS_OK;
}


nsresult 
nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, PRBool *aOutOfList)
{
  // check parms
  if (!aListItem || !aOutOfList) 
    return NS_ERROR_NULL_POINTER;
  
  // init out params
  *aOutOfList = PR_FALSE;
  
  nsCOMPtr<nsIDOMNode> curParent;
  nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(aListItem));
  PRInt32 offset;
  nsresult res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
  if (NS_FAILED(res)) return res;
    
  if (!IsListItem(curNode))
    return NS_ERROR_FAILURE;
    
  // if it's first or last list item, dont need to split the list
  // otherwise we do.
  nsCOMPtr<nsIDOMNode> curParPar;
  PRInt32 parOffset;
  res = nsEditor::GetNodeLocation(curParent, &curParPar, &parOffset);
  if (NS_FAILED(res)) return res;
  
  PRBool bIsFirstListItem;
  res = IsFirstEditableChild(curNode, &bIsFirstListItem);
  if (NS_FAILED(res)) return res;

  PRBool bIsLastListItem;
  res = IsLastEditableChild(curNode, &bIsLastListItem);
  if (NS_FAILED(res)) return res;
    
  if (!bIsFirstListItem && !bIsLastListItem)
  {
    // split the list
    nsCOMPtr<nsIDOMNode> newBlock;
    res = mEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
    if (NS_FAILED(res)) return res;
  }
  
  if (!bIsFirstListItem) parOffset++;
  
  res = mEditor->DeleteNode(curNode);
  if (NS_FAILED(res)) return res;
  res = mEditor->InsertNode(curNode, curParPar, parOffset);
  if (NS_FAILED(res)) return res;
    
  // unwrap list item contents if they are no longer in a list
  if (!IsList(curParPar) && IsListItem(curNode)) 
  {
    nsCOMPtr<nsIDOMNode> lastChild;
    res = GetLastEditableChild(curNode, &lastChild);
    if (NS_FAILED(res)) return res;
    res = RemoveContainer(curNode);
    if (NS_FAILED(res)) return res;
    if (mEditor->IsInlineNode(lastChild))
    {
      // last thing inside of the listitem wasn't a block node.
      // insert a BR to preserve the illusion of block boundaries
      nsCOMPtr<nsIDOMNode> node, brNode;
      PRInt32 theOffset;
      res = nsEditor::GetNodeLocation(lastChild, &node, &theOffset);
      if (NS_FAILED(res)) return res;
      res = mEditor->CreateBR(node, theOffset+1, &brNode);
      if (NS_FAILED(res)) return res;
    }
    *aOutOfList = PR_TRUE;
  }
  return res;
}


nsresult 
nsHTMLEditRules::ConfirmSelectionInBody()
{
  nsresult res = NS_OK;
  nsCOMPtr<nsIDOMElement> bodyElement;   
  nsCOMPtr<nsIDOMNode> bodyNode; 
  
  // get the body  
  res = mEditor->GetBodyElement(getter_AddRefs(bodyElement));
  if (NS_FAILED(res)) return res;
  if (!bodyElement) return NS_ERROR_UNEXPECTED;
  bodyNode = do_QueryInterface(bodyElement);

  // get the selection
  nsCOMPtr<nsIDOMSelection>selection;
  res = mEditor->GetSelection(getter_AddRefs(selection));
  if (NS_FAILED(res)) return res;
  
  // get the selection start location
  nsCOMPtr<nsIDOMNode> selNode, temp, parent;
  PRInt32 selOffset;
  res = mEditor->GetStartNodeAndOffset(selection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;
  temp = selNode;
  
  // check that selNode is inside body
  while (temp && !IsBody(temp))
  {
    res = temp->GetParentNode(getter_AddRefs(parent));
    temp = parent;
  }
  
  // if we aren't in the body, force the issue
  if (!temp) 
  {
//    uncomment this to see when we get bad selections
//    NS_NOTREACHED("selection not in body");
    selection->Collapse(bodyNode,0);
  }
  
  // get the selection end location
  res = mEditor->GetEndNodeAndOffset(selection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;
  temp = selNode;
  
  // check that selNode is inside body
  while (temp && !IsBody(temp))
  {
    res = temp->GetParentNode(getter_AddRefs(parent));
    temp = parent;
  }
  
  // if we aren't in the body, force the issue
  if (!temp) 
  {
//    uncomment this to see when we get bad selections
//    NS_NOTREACHED("selection not in body");
    selection->Collapse(bodyNode,0);
  }
  
  return res;
}


nsresult 
nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange)
{
  nsresult res = NS_OK;
  
  if (!mDocChangeRange)
  {
    mDocChangeRange = do_QueryInterface(aRange);
    return NS_OK;
  }
  else
  {
    PRInt32 result;
    
    // compare starts of ranges
    res = mDocChangeRange->CompareEndPoints(nsIDOMRange::START_TO_START, aRange, &result);
    if (NS_FAILED(res)) return res;
    if (result < 0)  // negative result means aRange start is before mDocChangeRange start
    {
      nsCOMPtr<nsIDOMNode> startNode;
      PRInt32 startOffset;
      res = aRange->GetStartParent(getter_AddRefs(startNode));
      if (NS_FAILED(res)) return res;
      res = aRange->GetStartOffset(&startOffset);
      if (NS_FAILED(res)) return res;
      res = mDocChangeRange->SetStart(startNode, startOffset);
      if (NS_FAILED(res)) return res;
    }
    
    // compare ends of ranges
    res = mDocChangeRange->CompareEndPoints(nsIDOMRange::END_TO_END, aRange, &result);
    if (NS_FAILED(res)) return res;
    if (result > 0)  // positive result means aRange end is after mDocChangeRange end
    {
      nsCOMPtr<nsIDOMNode> endNode;
      PRInt32 endOffset;
      res = aRange->GetEndParent(getter_AddRefs(endNode));
      if (NS_FAILED(res)) return res;
      res = aRange->GetEndOffset(&endOffset);
      if (NS_FAILED(res)) return res;
      res = mDocChangeRange->SetEnd(endNode, endOffset);
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}


#ifdef XP_MAC
#pragma mark -
#pragma mark --- nsIEditActionListener methods ---
#pragma mark -
#endif

/* Factory for edit listener object */
nsresult NS_NewEditListener(nsIEditActionListener **aResult, 
                            nsHTMLEditor *htmlEditor,
                            nsHTMLEditRules *htmlRules)
{
  if (!aResult) return NS_ERROR_NULL_POINTER;

  *aResult = new nsHTMLEditListener(htmlEditor, htmlRules);
  if (!*aResult)  return NS_ERROR_OUT_OF_MEMORY;

  NS_ADDREF(*aResult);
  return NS_OK;
}

NS_IMPL_ADDREF(nsHTMLEditListener)
NS_IMPL_RELEASE(nsHTMLEditListener)
NS_IMPL_QUERY_INTERFACE1(nsHTMLEditListener, nsIEditActionListener)

nsHTMLEditListener::nsHTMLEditListener(nsHTMLEditor *htmlEditor,
                                       nsHTMLEditRules *htmlRules)  :  
                                             mEditor(htmlEditor),
                                             mRules(htmlRules)
{
  NS_INIT_REFCNT();
  
  NS_PRECONDITION(mEditor, "null editor!");
  NS_PRECONDITION(mRules, "null edit rules!");
  
  nsCOMPtr<nsIDOMElement> bodyElement;
  nsresult res = mEditor->GetBodyElement(getter_AddRefs(bodyElement));
  NS_POSTCONDITION(NS_SUCCEEDED(res), "no body element found for edit listener");
  NS_POSTCONDITION(bodyElement, "no body element found for edit listener");
  mBody = do_QueryInterface(bodyElement);
}

nsHTMLEditListener::~nsHTMLEditListener()
{}

NS_IMETHODIMP 
nsHTMLEditListener::WillCreateNode(const nsString& aTag, nsIDOMNode *aParent, PRInt32 aPosition)
{
  return NS_OK;  
}

NS_IMETHODIMP 
nsHTMLEditListener::DidCreateNode(const nsString& aTag, 
                               nsIDOMNode *aNode, 
                               nsIDOMNode *aParent, 
                               PRInt32 aPosition, 
                               nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  // assumption that Join keeps the righthand node
  nsresult res = MakeRangeFromNode(aNode, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidInsertNode(nsIDOMNode *aNode, 
                               nsIDOMNode *aParent, 
                               PRInt32 aPosition, 
                               nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  // assumption that Join keeps the righthand node
  nsresult res = MakeRangeFromNode(aNode, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillDeleteNode(nsIDOMNode *aChild)
{
  nsCOMPtr<nsIDOMRange> range;
  nsresult res = MakeRangeFromNode(aChild, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidSplitNode(nsIDOMNode *aExistingRightNode, 
                              PRInt32 aOffset, 
                              nsIDOMNode *aNewLeftNode, 
                              nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  nsresult res = MakeRangeFromNode(aNewLeftNode, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    // now extend range to include right node
    res = range->SetEndAfter(aExistingRightNode);
    if (NS_FAILED(res)) return res;
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidJoinNodes(nsIDOMNode  *aLeftNode, 
                              nsIDOMNode *aRightNode, 
                              nsIDOMNode *aParent, 
                              nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  // assumption that Join keeps the righthand node
  nsresult res = MakeRangeFromNode(aRightNode, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsString &aString)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidInsertText(nsIDOMCharacterData *aTextNode, 
                                  PRInt32 aOffset, 
                                  const nsString &aString, 
                                  nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  PRInt32 length = aString.Length();
  nsresult res = MakeRangeFromTextOffsets(aTextNode, aOffset, aOffset+length, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}


NS_IMETHODIMP 
nsHTMLEditListener::WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength)
{
  return NS_OK;  
}


NS_IMETHODIMP 
nsHTMLEditListener::DidDeleteText(nsIDOMCharacterData *aTextNode, 
                                  PRInt32 aOffset, 
                                  PRInt32 aLength, 
                                  nsresult aResult)
{
  nsCOMPtr<nsIDOMRange> range;
  nsresult res = MakeRangeFromTextOffsets(aTextNode, aOffset, aOffset, &range);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}

NS_IMETHODIMP
nsHTMLEditListener::WillDeleteSelection(nsIDOMSelection *aSelection)
{
  nsCOMPtr<nsIDOMRange> range;
  // get the (collapsed) selection location
  nsCOMPtr<nsIDOMNode> selNode;
  PRInt32 selOffset;
  // construct a range to represent start and end of inNode
  nsresult res = nsComponentManager::CreateInstance(kRangeCID,
                                                    nsnull,
                                                    NS_GET_IID(nsIDOMRange),
                                                    getter_AddRefs(range));
  if (NS_FAILED(res)) return res;
  res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;
  res = range->SetStart(selNode, selOffset);
  if (NS_FAILED(res)) return res;
  res = mEditor->GetEndNodeAndOffset(aSelection, &selNode, &selOffset);
  if (NS_FAILED(res)) return res;
  res = range->SetEnd(selNode, selOffset);
  if (NS_FAILED(res)) return res;
  if (range)
  {
    res = mRules->UpdateDocChangeRange(range);
  }
  return res;  
}

NS_IMETHODIMP
nsHTMLEditListener::DidDeleteSelection(nsIDOMSelection *aSelection)
{
  return NS_OK;
}

nsresult 
nsHTMLEditListener::MakeRangeFromNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMRange> *outRange)
{
  if (!inNode || !outRange) return NS_ERROR_NULL_POINTER;
  *outRange = nsnull;
  
  // first check that inNode is still a descendant of the body
  if (!IsDescendantOfBody(inNode)) return NS_OK;
  
  // construct a range to represent start and end of inNode
  nsresult res = nsComponentManager::CreateInstance(kRangeCID,
                                                    nsnull,
                                                    NS_GET_IID(nsIDOMRange),
                                                    getter_AddRefs(*outRange));
  if (NS_FAILED(res)) return res;
  res = (*outRange)->SelectNode(inNode);
  return res;  
}


nsresult 
nsHTMLEditListener::MakeRangeFromTextOffsets(nsIDOMCharacterData *inNode, 
                                             PRInt32 inStart, 
                                             PRInt32 inEnd, 
                                             nsCOMPtr<nsIDOMRange> *outRange)
{
  if (!inNode || !outRange) return NS_ERROR_NULL_POINTER;
  *outRange = nsnull;
  nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(inNode);
  
  // first check that inNode is still a descendant of the body
  if (!IsDescendantOfBody(theNode)) return NS_OK;
  
  // construct a range to represent start and end of text run
  nsresult res = nsComponentManager::CreateInstance(kRangeCID,
                                                    nsnull,
                                                    NS_GET_IID(nsIDOMRange),
                                                    getter_AddRefs(*outRange));
  if (NS_FAILED(res)) return res;
  res = (*outRange)->SetStart(theNode, inStart);
  if (NS_FAILED(res)) return res;
  res = (*outRange)->SetEnd(theNode, inEnd);
  return res;  
}


PRBool 
nsHTMLEditListener::IsDescendantOfBody(nsIDOMNode *inNode) 
{
  if (!inNode) return PR_FALSE;
  if (inNode == mBody.get()) return PR_TRUE;
  
  nsCOMPtr<nsIDOMNode> parent, node = do_QueryInterface(inNode);
  nsresult res;
  
  do
  {
    res = node->GetParentNode(getter_AddRefs(parent));
    if (NS_FAILED(res)) return PR_FALSE;
    if (parent == mBody) return PR_TRUE;
    node = parent;
  } while (parent);
  
  return PR_FALSE;
}










