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

/*
 * class that manages regions of 2-D space, originally designed
 * generally but actually specific to space occupied by floats
 */

#include "nsSpaceManager.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsSize.h"
#include <stdlib.h>
#include "nsVoidArray.h"
#include "nsIFrame.h"
#include "nsString.h"
#include "nsIPresShell.h"
#include "nsMemory.h"
#include "nsHTMLReflowState.h"
#include "nsHashSets.h"
#ifdef DEBUG
#include "nsIFrameDebug.h"
#endif

/////////////////////////////////////////////////////////////////////////////
// BandList

PRInt32 nsSpaceManager::sCachedSpaceManagerCount = 0;
void* nsSpaceManager::sCachedSpaceManagers[NS_SPACE_MANAGER_CACHE_SIZE];

#define NSCOORD_MIN (-2147483647 - 1) /* minimum signed value */

nsSpaceManager::BandList::BandList()
  : nsSpaceManager::BandRect(NSCOORD_MIN, NSCOORD_MIN, NSCOORD_MIN, NSCOORD_MIN, (nsIFrame*)nsnull)
{
  PR_INIT_CLIST(this);
}

void
nsSpaceManager::BandList::Clear()
{
  if (!IsEmpty()) {
    BandRect* bandRect = Head();
  
    while (bandRect != this) {
      BandRect* nxt = bandRect->Next();
  
      delete bandRect;
      bandRect = nxt;
    }
  
    PR_INIT_CLIST(this);
  }
}

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

// PresShell Arena allocate callback (for nsIntervalSet use below)
static void*
PSArenaAllocCB(size_t aSize, void* aClosure)
{
  return static_cast<nsIPresShell*>(aClosure)->AllocateFrame(aSize);
}

// PresShell Arena free callback (for nsIntervalSet use below)
static void
PSArenaFreeCB(size_t aSize, void* aPtr, void* aClosure)
{
  static_cast<nsIPresShell*>(aClosure)->FreeFrame(aSize, aPtr);
}

/////////////////////////////////////////////////////////////////////////////
// nsSpaceManager

nsSpaceManager::nsSpaceManager(nsIPresShell* aPresShell)
  : mLowestTop(NSCOORD_MIN),
    mFloatDamage(PSArenaAllocCB, PSArenaFreeCB, aPresShell),
    mHaveCachedLeftYMost(PR_TRUE),
    mHaveCachedRightYMost(PR_TRUE),
    mMaximalLeftYMost(nscoord_MIN),
    mMaximalRightYMost(nscoord_MIN),
    mCachedBandPosition(nsnull)
{
  MOZ_COUNT_CTOR(nsSpaceManager);
  mX = mY = 0;
  mFrameInfoMap = nsnull;
}

void
nsSpaceManager::ClearFrameInfo()
{
  while (mFrameInfoMap) {
    FrameInfo*  next = mFrameInfoMap->mNext;
    delete mFrameInfoMap;
    mFrameInfoMap = next;
  }
}

nsSpaceManager::~nsSpaceManager()
{
  MOZ_COUNT_DTOR(nsSpaceManager);
  mBandList.Clear();
  ClearFrameInfo();
}

// static
void* nsSpaceManager::operator new(size_t aSize) CPP_THROW_NEW
{
  if (sCachedSpaceManagerCount > 0) {
    // We have cached unused instances of this class, return a cached
    // instance in stead of always creating a new one.
    return sCachedSpaceManagers[--sCachedSpaceManagerCount];
  }

  // The cache is empty, this means we haveto create a new instance using
  // the global |operator new|.
  return nsMemory::Alloc(aSize);
}

void
nsSpaceManager::operator delete(void* aPtr, size_t aSize)
{
  if (!aPtr)
    return;
  // This space manager is no longer used, if there's still room in
  // the cache we'll cache this space manager, unless the layout
  // module was already shut down.

  if (sCachedSpaceManagerCount < NS_SPACE_MANAGER_CACHE_SIZE &&
      sCachedSpaceManagerCount >= 0) {
    // There's still space in the cache for more instances, put this
    // instance in the cache in stead of deleting it.

    sCachedSpaceManagers[sCachedSpaceManagerCount++] = aPtr;
    return;
  }

  // The cache is full, or the layout module has been shut down,
  // delete this space manager.
  nsMemory::Free(aPtr);
}


/* static */
void nsSpaceManager::Shutdown()
{
  // The layout module is being shut down, clean up the cache and
  // disable further caching.

  PRInt32 i;

  for (i = 0; i < sCachedSpaceManagerCount; i++) {
    void* spaceManager = sCachedSpaceManagers[i];
    if (spaceManager)
      nsMemory::Free(spaceManager);
  }

  // Disable further caching.
  sCachedSpaceManagerCount = -1;
}

PRBool
nsSpaceManager::YMost(nscoord& aYMost) const
{
  PRBool result;

  if (mBandList.IsEmpty()) {
    aYMost = 0;
    result = PR_FALSE;

  } else {
    BandRect* lastRect = mBandList.Tail();

    aYMost = lastRect->mBottom;
    result = PR_TRUE;
  }

  return result;
}

/**
 * Internal function that returns the list of available and unavailable space
 * within the band
 *
 * Note: If the clip rectangle has 0 width and is aligned exactly at
 * aBand->mLeft or aBand->mRight, we count it as intersecting the band, and we
 * return an unavailable-space trapezoid for the band.  (The alternative would
 * be to return a zero-width available-space trapezoid, which would make no
 * sense.  See bug 403129)
 *
 * @param aBand the first rect in the band
 * @param aY the y-offset in world coordinates
 * @param aMaxSize the size to use to constrain the band data
 * @param aBandData the object to populate with available and unavailable space
 */
nsresult
nsSpaceManager::GetBandAvailableSpace(const BandRect* aBand,
                                      nscoord         aY,
                                      const nsSize&   aMaxSize,
                                      nsBandData&     aBandData) const
{
  nscoord          topOfBand = aBand->mTop;
  nscoord          localY = aY - mY;
  nscoord          height = PR_MIN(aBand->mBottom - aY, aMaxSize.height);
  nsBandTrapezoid* trapezoid = aBandData.mTrapezoids;
  nscoord          rightEdge = mX + aMaxSize.width;

  // Initialize the band data
  aBandData.mCount = 0;

  // Skip any rectangles that are to the left of the local coordinate space
  while (aBand->mTop == topOfBand) {
    if (aBand->mRight > mX ||
        (aMaxSize.width == 0 && aBand->mRight == mX)) {
      break;
    }

    // Get the next rect in the band
    aBand = aBand->Next();
  }

  // This is used to track the current x-location within the band. This is in
  // world coordinates
  nscoord   left = mX;

  // Process the remaining rectangles that are within the clip width
  while ((aBand->mTop == topOfBand) && 
         (aBand->mLeft < rightEdge || 
          (aMaxSize.width == 0 && aBand->mLeft == mX))) {
    // Compare the left edge of the occupied space with the current left
    // coordinate
    if (aBand->mLeft > left) {
      // The rect is to the right of our current left coordinate, so we've
      // found some available space
      if (aBandData.mCount >= aBandData.mSize) {
        // Not enough space in the array of trapezoids
        aBandData.mCount += 2 * aBand->Length() + 2;  // estimate the number needed
        return NS_ERROR_FAILURE;
      }
      trapezoid->mFrames = nsnull;

      // Assign the trapezoid a rectangular shape. The trapezoid must be in the
      // local coordinate space, so convert the current left coordinate
      *trapezoid = nsRect(left - mX, localY, aBand->mLeft - left, height);

      // Move to the next output rect
      trapezoid++;
      aBandData.mCount++;
    }

    // The rect represents unavailable space, so add another trapezoid
    if (aBandData.mCount >= aBandData.mSize) {
      // Not enough space in the array of trapezoids
      aBandData.mCount += 2 * aBand->Length() + 1;  // estimate the number needed
      return NS_ERROR_FAILURE;
    }
    NS_ASSERTION(aBand->mFrames.Count() > 0, "unexpected frame count");
    trapezoid->mFrames = &aBand->mFrames;

    nscoord x = aBand->mLeft;
    // The first band can straddle the clip rect
    if (x < mX) {
      // Clip the left edge
      x = mX;
    }

    // Assign the trapezoid a rectangular shape. The trapezoid must be in the
    // local coordinate space, so convert the rects's left coordinate
    *trapezoid = nsRect(x - mX, localY, aBand->mRight - x, height);

    // Move to the next output rect
    trapezoid++;
    aBandData.mCount++;

    // Adjust our current x-location within the band
    left = aBand->mRight;

    // Move to the next rect within the band
    aBand = aBand->Next();
  }

  // No more rects left in the band. If we haven't yet reached the right edge,
  // then all the remaining space is available
  if (left < rightEdge || aBandData.mCount == 0) {
    if (aBandData.mCount >= aBandData.mSize) {
      // Not enough space in the array of trapezoids
      aBandData.mCount++;
      return NS_ERROR_FAILURE;
    }
    trapezoid->mFrames = nsnull;

    // Assign the trapezoid a rectangular shape. The trapezoid must be in the
    // local coordinate space, so convert the current left coordinate
    *trapezoid = nsRect(left - mX, localY, rightEdge - left, height);
    aBandData.mCount++;
  }

  return NS_OK;
}

nsresult
nsSpaceManager::GetBandData(nscoord       aYOffset,
                            const nsSize& aMaxSize,
                            nsBandData&   aBandData) const
{
  NS_PRECONDITION(aBandData.mSize >= 1, "bad band data");
  nsresult  result = NS_OK;

  // Convert the y-offset to world coordinates
  nscoord   y = mY + aYOffset;

  // If there are no unavailable rects or the offset is below the bottommost
  // band, then all the space is available
  nscoord yMost;
  nscoord maxHeight = aMaxSize.height == NS_UNCONSTRAINEDSIZE ? NS_UNCONSTRAINEDSIZE 
    : PR_MAX(0, aMaxSize.height - aYOffset);

  if (!YMost(yMost) || (y >= yMost)) {
    // All the requested space is available
    aBandData.mCount = 1;
    aBandData.mTrapezoids[0] = nsRect(0, aYOffset, aMaxSize.width, maxHeight);
    aBandData.mTrapezoids[0].mFrames = nsnull;
  } else {
    // Find the first band that contains the y-offset or is below the y-offset
    BandRect* band = GuessBandWithTopAbove(y);

    aBandData.mCount = 0;
    while (nsnull != band) {
      if (band->mTop > y) {
        // The band is below the y-offset. The area between the y-offset and
        // the top of the band is available
        aBandData.mCount = 1;
        aBandData.mTrapezoids[0] =
          nsRect(0, aYOffset, aMaxSize.width, PR_MIN(band->mTop - y, maxHeight));
        aBandData.mTrapezoids[0].mFrames = nsnull;
        break;
      } else if (y < band->mBottom) {
        // The band contains the y-offset. Return a list of available and
        // unavailable rects within the band
        return GetBandAvailableSpace(band, y, nsSize(aMaxSize.width, maxHeight), aBandData);
      } else {
        // Skip to the next band
        band = GetNextBand(band);
      }
    }
  }

  NS_POSTCONDITION(aBandData.mCount > 0, "unexpected band data count");
  return result;
}

/**
 * Skips to the start of the next band.
 *
 * @param aBandRect A rect within the band
 * @returns The start of the next band, or nsnull of this is the last band.
 */
nsSpaceManager::BandRect*
nsSpaceManager::GetNextBand(const BandRect* aBandRect) const
{
  nscoord topOfBand = aBandRect->mTop;

  aBandRect = aBandRect->Next();
  while (aBandRect != &mBandList) {
    // Check whether this rect is part of the same band
    if (aBandRect->mTop != topOfBand) {
      // We found the start of the next band
      return (BandRect*)aBandRect;
    }

    aBandRect = aBandRect->Next();
  }

  // No bands left
  return nsnull;
}

/**
 * Skips to the start of the previous band.
 *
 * @param aBandRect The first rect within a band
 * @returns The start of the previous band, or nsnull of this is the first band.
 */
nsSpaceManager::BandRect*
nsSpaceManager::GetPrevBand(const BandRect* aBandRect) const
{
  NS_ASSERTION(aBandRect->Prev() == &mBandList ||
               aBandRect->Prev()->mBottom <= aBandRect->mTop,
               "aBandRect should be first rect within its band");

  BandRect* prev = aBandRect->Prev();
  nscoord topOfBand = prev->mTop;

  while (prev != &mBandList) {
    // Check whether the prev rect is part of the same band
    if (prev->mTop != topOfBand) {
      // We found the beginning of this band
      return (BandRect*)aBandRect;
    }

    aBandRect = prev;
    prev = aBandRect->Prev();
  }

  // No bands left
  return nsnull;
}

/**
 * Divides the current band into two vertically
 *
 * @param aBandRect the first rect in the band
 * @param aBottom where to split the band. This becomes the bottom of the top
 *          part
 */
void
nsSpaceManager::DivideBand(BandRect* aBandRect, nscoord aBottom)
{
  NS_PRECONDITION(aBottom < aBandRect->mBottom, "bad height");
  nscoord   topOfBand = aBandRect->mTop;
  BandRect* nextBand = GetNextBand(aBandRect);

  if (nsnull == nextBand) {
    nextBand = (BandRect*)&mBandList;
  }

  while (topOfBand == aBandRect->mTop) {
    // Split the band rect into two vertically
    BandRect* bottomBandRect = aBandRect->SplitVertically(aBottom);

    // Insert the new bottom part
    nextBand->InsertBefore(bottomBandRect);

    // Move to the next rect in the band
    aBandRect = aBandRect->Next();
  }
}

PRBool
nsSpaceManager::CanJoinBands(BandRect* aBand, BandRect* aPrevBand)
{
  PRBool  result;
  nscoord topOfBand = aBand->mTop;
  nscoord topOfPrevBand = aPrevBand->mTop;

  // The bands can be joined if:
  // - they're adjacent
  // - they have the same number of rects
  // - each rect has the same left and right edge as its corresponding rect, and
  //   the rects are occupied by the same frames
  if (aPrevBand->mBottom == aBand->mTop) {
    // Compare each of the rects in the two bands
    while (PR_TRUE) {
      if ((aBand->mLeft != aPrevBand->mLeft) || (aBand->mRight != aPrevBand->mRight)) {
        // The rects have different edges
        result = PR_FALSE;
        break;
      }

      if (!aBand->HasSameFrameList(aPrevBand)) {
        // The rects are occupied by different frames
        result = PR_FALSE;
        break;
      }

      // Move to the next rects within the bands
      aBand = aBand->Next();
      aPrevBand = aPrevBand->Next();

      // Have we reached the end of either band?
      PRBool  endOfBand = aBand->mTop != topOfBand;
      PRBool  endOfPrevBand = aPrevBand->mTop != topOfPrevBand;

      if (endOfBand || endOfPrevBand) {
        result = endOfBand & endOfPrevBand;
        break;  // all done
      }
    }

  } else {
    // The bands aren't adjacent
    result = PR_FALSE;
  }

  return result;
}

/**
 * Tries to join the two adjacent bands. Returns PR_TRUE if successful and
 * PR_FALSE otherwise
 *
 * If the two bands are joined, the previous band is the band that's deleted
 */
PRBool
nsSpaceManager::JoinBands(BandRect* aBand, BandRect* aPrevBand)
{
  if (CanJoinBands(aBand, aPrevBand)) {
    BandRect* startOfNextBand = aBand;
    // We're going to be removing aPrevBand, so if mCachedBandPosition points
    // to it just advance it to startOfNextBand.
    if (mCachedBandPosition == aPrevBand) {
      SetCachedBandPosition(startOfNextBand);
    }

    while (aPrevBand != startOfNextBand) {
      // Adjust the top of the band we're keeping, and then move to the next
      // rect within the band
      aBand->mTop = aPrevBand->mTop;
      aBand = aBand->Next();

      // Delete the rect from the previous band
      BandRect* next = aPrevBand->Next();

      NS_ASSERTION(mCachedBandPosition != aPrevBand,
                   "Removing mCachedBandPosition BandRect?");
      aPrevBand->Remove();
      delete aPrevBand;
      aPrevBand = next;
    }

    return PR_TRUE;
  }

  return PR_FALSE;
}

/**
 * Adds a new rect to a band.
 *
 * @param aBand the first rect in the band
 * @param aBandRect the band rect to add to the band
 */
void
nsSpaceManager::AddRectToBand(BandRect* aBand, BandRect* aBandRect)
{
  NS_PRECONDITION((aBand->mTop == aBandRect->mTop) &&
                  (aBand->mBottom == aBandRect->mBottom), "bad band");
  NS_PRECONDITION(1 == aBandRect->mFrames.Count(), "shared band rect");
  nscoord topOfBand = aBand->mTop;

  // Figure out where in the band horizontally to insert the rect
  do {
    // Compare the left edge of the new rect with the left edge of the existing
    // rect
    if (aBandRect->mLeft < aBand->mLeft) {
      // The new rect's left edge is to the left of the existing rect's left edge.
      // Could be any of these cases (N is new rect, E is existing rect):
      //
      //   Case 1: left-of      Case 2: overlaps     Case 3: N.contains(E)
      //   ---------------      ----------------     ---------------------
      //   +-----+ +-----+      +-----+              +---------+
      //   |  N  | |  E  |      |  N  |              |    N    |
      //   +-----+ +-----+      +-----+              +---------+
      //                           +-----+              +---+
      //                           |  E  |              | E |
      //                           +-----+              +---+
      //
      // Do the two rectangles overlap?
      if (aBandRect->mRight <= aBand->mLeft) {
        // No, the new rect is completely to the left of the existing rect
        // (case #1). Insert a new rect
        aBand->InsertBefore(aBandRect);
        if (mCachedBandPosition == aBand) {
          SetCachedBandPosition(aBandRect);
        }
        return;
      }

      // Yes, they overlap. Compare the right edges.
      if (aBandRect->mRight > aBand->mRight) {
        // The new rect's right edge is to the right of the existing rect's
        // right edge (case #3). Split the new rect
        BandRect* r1 = aBandRect->SplitHorizontally(aBand->mLeft);

        // Insert the part of the new rect that's to the left of the existing
        // rect as a new band rect
        aBand->InsertBefore(aBandRect);
        if (mCachedBandPosition == aBand) {
          SetCachedBandPosition(aBandRect);
        }

        // Continue below with the part that overlaps the existing rect
        aBandRect = r1;

      } else {
        if (aBand->mRight > aBandRect->mRight) {
          // The existing rect extends past the new rect (case #2). Split the
          // existing rect
          BandRect* r1 = aBand->SplitHorizontally(aBandRect->mRight);

          // Insert the new right half of the existing rect
          aBand->InsertAfter(r1);
        }

        // Insert the part of the new rect that's to the left of the existing
        // rect
        aBandRect->mRight = aBand->mLeft;
        aBand->InsertBefore(aBandRect);

        if (mCachedBandPosition == aBand) {
          SetCachedBandPosition(aBandRect);
        }

        // Mark the existing rect as shared
        aBand->AddFrame(aBandRect->FrameAt(0));
        return;
      }
    }
      
    if (aBandRect->mLeft > aBand->mLeft) {
      // The new rect's left edge is to the right of the existing rect's left
      // edge. Could be any one of these cases:
      //
      //   Case 4: right-of    Case 5: overlaps     Case 6: E.Contains(N)
      //   ---------------    ----------------     ---------------------
      //   +-----+ +-----+    +-----+              +------------+
      //   |  E  | |  N  |    |  E  |              |      E     |
      //   +-----+ +-----+    +-----+              +------------+
      //                         +-----+              +-----+
      //                         |  N  |              |  N  |
      //                         +-----+              +-----+
      //
      if (aBandRect->mLeft >= aBand->mRight) {
        // The new rect is to the right of the existing rect (case #4), so move
        // to the next rect in the band
        aBand = aBand->Next();
        continue;
      }

      // The rects overlap, so divide the existing rect into two rects: the
      // part to the left of the new rect, and the part that overlaps
      BandRect* r1 = aBand->SplitHorizontally(aBandRect->mLeft);

      // Insert the new right half of the existing rect, and make it the current
      // rect
      aBand->InsertAfter(r1);
      aBand = r1;
    }

    // At this point the left edge of the new rect is the same as the left edge
    // of the existing rect
    NS_ASSERTION(aBandRect->mLeft == aBand->mLeft, "unexpected rect");

    // Compare which rect is wider, the new rect or the existing rect
    if (aBand->mRight > aBandRect->mRight) {
      // The existing rect is wider (case #6). Divide the existing rect into
      // two rects: the part that overlaps, and the part to the right of the
      // new rect
      BandRect* r1 = aBand->SplitHorizontally(aBandRect->mRight);

      // Insert the new right half of the existing rect
      aBand->InsertAfter(r1);

      // Mark the overlap as being shared
      aBand->AddFrame(aBandRect->FrameAt(0));

      // We no longer need aBandRect, since the area it covers is covered by
      // the part of aBand that SplitHorizontally left in place.  Just delete
      // it.
      delete aBandRect;
      return;

    } else {
      // Indicate the frames share the existing rect
      aBand->AddFrame(aBandRect->FrameAt(0));

      if (aBand->mRight == aBandRect->mRight) {
        // The new and existing rect have the same right edge. We're all done,
        // and the new band rect is no longer needed
        delete aBandRect;
        return;
      } else {
        // The new rect is wider than the existing rect (cases #5). Set the
        // new rect to be the overhang, and move to the next rect within the band
        aBandRect->mLeft = aBand->mRight;
        aBand = aBand->Next();
        continue;
      }
    }
  } while (aBand->mTop == topOfBand);

  // Insert a new rect.  This is an insertion at the _end_ of the band, so we
  // absolutely do not want to set mCachedBandPosition to aBandRect here.
  aBand->InsertBefore(aBandRect);
}

// When comparing a rect to a band there are seven cases to consider.
// 'R' is the rect and 'B' is the band.
//
//      Case 1              Case 2              Case 3              Case 4
//      ------              ------              ------              ------
// +-----+             +-----+                      +-----+             +-----+
// |  R  |             |  R  |  +-----+    +-----+  |     |             |     |
// +-----+             +-----+  |     |    |  R  |  |  B  |             |  B  |
//          +-----+             |  B  |    +-----+  |     |    +-----+  |     |
//          |     |             |     |             +-----+    |  R  |  +-----+
//          |  B  |             +-----+                        +-----+
//          |     |
//          +-----+
//
//
//
//      Case 5              Case 6              Case 7
//      ------              ------              ------
//          +-----+    +-----+  +-----+    +-----+
//          |     |    |  R  |  |  B  |    |     |  +-----+
//          |  B  |    +-----+  +-----+    |  R  |  |  B  |
//          |     |                        |     |  +-----+
//          +-----+                        +-----+
// +-----+
// |  R  |
// +-----+
//
void
nsSpaceManager::InsertBandRect(BandRect* aBandRect)
{
  // If there are no existing bands or this rect is below the bottommost
  // band, then add a new band
  nscoord yMost;
  if (!YMost(yMost) || (aBandRect->mTop >= yMost)) {
    mBandList.Append(aBandRect);
    SetCachedBandPosition(aBandRect);
    return;
  }

  // Examine each band looking for a band that intersects this rect
  // First guess a band whose top is above aBandRect->mTop.  We know
  // aBandRect won't overlap any bands before that one.
  BandRect* band = GuessBandWithTopAbove(aBandRect->mTop);

  while (nsnull != band) {
    // Compare the top edge of this rect with the top edge of the band
    if (aBandRect->mTop < band->mTop) {
      // The top edge of the rect is above the top edge of the band.
      // Is there any overlap?
      if (aBandRect->mBottom <= band->mTop) {
        // Case #1. This rect is completely above the band, so insert a
        // new band before the current band
        band->InsertBefore(aBandRect);
        SetCachedBandPosition(aBandRect);
        break;  // we're all done
      }

      // Case #2 and case #7. Divide this rect, creating a new rect for
      // the part that's above the band
      BandRect* bandRect1 = new BandRect(aBandRect->mLeft, aBandRect->mTop,
                                         aBandRect->mRight, band->mTop,
                                         aBandRect->mFrames);

      // Insert bandRect1 as a new band
      band->InsertBefore(bandRect1);

      // Modify this rect to exclude the part above the band
      aBandRect->mTop = band->mTop;

    } else if (aBandRect->mTop > band->mTop) {
      // The top edge of the rect is below the top edge of the band. Is there
      // any overlap?
      if (aBandRect->mTop >= band->mBottom) {
        // Case #5. This rect is below the current band. Skip to the next band
        band = GetNextBand(band);
        continue;
      }

      // Case #3 and case #4. Divide the current band into two bands with the
      // top band being the part that's above the rect
      DivideBand(band, aBandRect->mTop);

      // Skip to the bottom band that we just created
      band = GetNextBand(band);
    }

    // At this point the rect and the band should have the same y-offset
    NS_ASSERTION(aBandRect->mTop == band->mTop, "unexpected band");

    // Is the band higher than the rect?
    if (band->mBottom > aBandRect->mBottom) {
      // Divide the band into two bands with the top band the same height
      // as the rect
      DivideBand(band, aBandRect->mBottom);
    }

    if (aBandRect->mBottom == band->mBottom) {
      // Add the rect to the band
      SetCachedBandPosition(band);  // Do this before AddRectToBand
      AddRectToBand(band, aBandRect);
      break;

    } else {
      // Case #4 and case #7. The rect contains the band vertically. Divide
      // the rect, creating a new rect for the part that overlaps the band
      BandRect* bandRect1 = new BandRect(aBandRect->mLeft, aBandRect->mTop,
                                         aBandRect->mRight, band->mBottom,
                                         aBandRect->mFrames);

      // Add bandRect1 to the band
      AddRectToBand(band, bandRect1);

      // Modify aBandRect to be the part below the band
      aBandRect->mTop = band->mBottom;

      // Continue with the next band
      band = GetNextBand(band);
      if (nsnull == band) {
        // Append a new bottommost band
        mBandList.Append(aBandRect);
        SetCachedBandPosition(aBandRect);
        break;
      }
    }
  }
}

nsresult
nsSpaceManager::AddRectRegion(nsIFrame* aFrame, const nsRect& aUnavailableSpace)
{
  NS_PRECONDITION(nsnull != aFrame, "null frame");

  // Convert the frame to world coordinates
  nsRect  rect(aUnavailableSpace.x + mX, aUnavailableSpace.y + mY,
               aUnavailableSpace.width, aUnavailableSpace.height);

  if (rect.y > mLowestTop)
    mLowestTop = rect.y;

  // Create a frame info structure
  FrameInfo* frameInfo = CreateFrameInfo(aFrame, rect);
  if (nsnull == frameInfo) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (aUnavailableSpace.IsEmpty())
    return NS_OK;

  // Allocate a band rect
  BandRect* bandRect = new BandRect(rect.x, rect.y, 
                                    PR_MIN(rect.XMost(), nscoord_MAX),
                                    PR_MIN(rect.YMost(), nscoord_MAX),
                                    aFrame);
  if (nsnull == bandRect) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // Insert the band rect
  InsertBandRect(bandRect);
  return NS_OK;
}

nsresult
nsSpaceManager::RemoveTrailingRegions(nsIFrame* aFrameList) {
  nsVoidHashSet frameSet;

  frameSet.Init(1);
  for (nsIFrame* f = aFrameList; f; f = f->GetNextSibling()) {
    frameSet.Put(f);
  }

  // Pop frame regions off as long as they're in the set of frames to
  // remove
  while (mFrameInfoMap && frameSet.Contains(mFrameInfoMap->mFrame)) {
    RemoveRegion(mFrameInfoMap->mFrame);
  }

#ifdef DEBUG
  for (FrameInfo* frameInfo = mFrameInfoMap; frameInfo;
       frameInfo = frameInfo->mNext) {
    NS_ASSERTION(!frameSet.Contains(frameInfo->mFrame),
                 "Frame region deletion was requested but we couldn't delete it");
  }
#endif

  return NS_OK;
}

nsresult
nsSpaceManager::RemoveRegion(nsIFrame* aFrame)
{
  // Get the frame info associated with aFrame
  FrameInfo*  frameInfo = GetFrameInfoFor(aFrame);

  if (nsnull == frameInfo) {
    NS_WARNING("no region associated with aFrame");
    return NS_ERROR_INVALID_ARG;
  }

  if (!frameInfo->mRect.IsEmpty()) {
    NS_ASSERTION(!mBandList.IsEmpty(), "no bands");
    BandRect* band = mBandList.Head();
    BandRect* prevBand = nsnull;
    PRBool    prevFoundMatchingRect = PR_FALSE;

    // Iterate each band looking for rects tagged with aFrame
    while (nsnull != band) {
      BandRect* rect = band;
      BandRect* prevRect = nsnull;
      nscoord   topOfBand = band->mTop;
      PRBool    foundMatchingRect = PR_FALSE;
      PRBool    prevIsSharedRect = PR_FALSE;

      // Iterate each rect in the band
      do {
        PRBool  isSharedRect = PR_FALSE;

        if (rect->IsOccupiedBy(aFrame)) {
          // Remember that we found a matching rect in this band
          foundMatchingRect = PR_TRUE;

          if (rect->mFrames.Count() > 1) {
            // The band rect is occupied by more than one frame
            rect->mFrames.RemoveElement(aFrame);

            // Remember that this rect was being shared by more than one frame
            // including aFrame
            isSharedRect = PR_TRUE;
          } else {
            // The rect isn't shared so just delete it
            BandRect* next = rect->Next();
            rect->Remove();
            if (rect == band) {
              // The rect we're deleting is the start of the band
              if (topOfBand == next->mTop) {
                band = next;
              } else {
                band = nsnull;
              }
              if (mCachedBandPosition == rect) {
                SetCachedBandPosition(band);
              }                
            }
            delete rect;
            rect = next;

            // We don't need to try and coalesce adjacent rects in this case
            prevRect = nsnull;
            prevIsSharedRect = PR_FALSE;
            continue;
          }
        }
           
        // If we found a shared rect occupied by aFrame, then we need to try
        // and coalesce adjacent rects
        if (prevIsSharedRect || (isSharedRect && (nsnull != prevRect))) {
          NS_ASSERTION(nsnull != prevRect, "no previous rect");
          if ((prevRect->mRight == rect->mLeft) && (prevRect->HasSameFrameList(rect))) {
            // Modify the current rect's left edge, and delete the previous rect
            rect->mLeft = prevRect->mLeft;
            prevRect->Remove();
            if (prevRect == band) {
              // the rect we're deleting is the start of the band
              band = rect;
              if (mCachedBandPosition == prevRect) {
                SetCachedBandPosition(band);
              }
            }
            delete prevRect;
          }
        }

        // Get the next rect in the band
        prevRect = rect;
        prevIsSharedRect = isSharedRect;
        rect = rect->Next();
      } while (rect->mTop == topOfBand);

      if (nsnull != band) {
        // If we found a rect occupied by aFrame in this band or the previous band
        // then try to join the two bands
        if ((nsnull != prevBand) && (foundMatchingRect || prevFoundMatchingRect)) {
          // Try and join this band with the previous band
          JoinBands(band, prevBand);
        }
      }

      // Move to the next band
      prevFoundMatchingRect = foundMatchingRect;
      prevBand = band;
      band = (rect == &mBandList) ? nsnull : rect;
      if (!mCachedBandPosition) {
        SetCachedBandPosition(band);
      }
    }
  }

  DestroyFrameInfo(frameInfo);
  return NS_OK;
}

void
nsSpaceManager::PushState(SavedState* aState)
{
  NS_PRECONDITION(aState, "Need a place to save state");

  // This is a cheap push implementation, which
  // only saves the (x,y) and last frame in the mFrameInfoMap
  // which is enough info to get us back to where we should be
  // when pop is called.
  //
  // This push/pop mechanism is used to undo any
  // floats that were added during the unconstrained reflow
  // in nsBlockReflowContext::DoReflowBlock(). (See bug 96736)
  //
  // It should also be noted that the state for mFloatDamage is
  // intentionally not saved or restored in PushState() and PopState(),
  // since that could lead to bugs where damage is missed/dropped when
  // we move from position A to B (during the intermediate incremental
  // reflow mentioned above) and then from B to C during the subsequent
  // reflow. In the typical case A and C will be the same, but not always.
  // Allowing mFloatDamage to accumulate the damage incurred during both
  // reflows ensures that nothing gets missed.
  aState->mX = mX;
  aState->mY = mY;
  aState->mLowestTop = mLowestTop;
  aState->mHaveCachedLeftYMost = mHaveCachedLeftYMost;
  aState->mHaveCachedRightYMost = mHaveCachedRightYMost;
  aState->mMaximalLeftYMost = mMaximalLeftYMost;
  aState->mMaximalRightYMost = mMaximalRightYMost;

  if (mFrameInfoMap) {
    aState->mLastFrame = mFrameInfoMap->mFrame;
  } else {
    aState->mLastFrame = nsnull;
  }
}

void
nsSpaceManager::PopState(SavedState* aState)
{
  NS_PRECONDITION(aState, "No state to restore?");

  // This is a quick and dirty pop implementation, to
  // match the current implementation of PushState(). The
  // idea here is to remove any frames that have been added
  // to the mFrameInfoMap since the last call to PushState().

  // Say we don't have cached left- and right-YMost, so that we don't
  // try to check for it in RemoveRegion.  We'll restore these from
  // the state anyway.
  mHaveCachedLeftYMost = mHaveCachedRightYMost = PR_FALSE;

  // mFrameInfoMap is LIFO so keep removing what it points
  // to until we hit mLastFrame.
  while (mFrameInfoMap && mFrameInfoMap->mFrame != aState->mLastFrame) {
    RemoveRegion(mFrameInfoMap->mFrame);
  }

  // If we trip this assertion it means that someone added
  // PushState()/PopState() calls around code that actually
  // removed mLastFrame from mFrameInfoMap, which means our
  // state is now out of sync with what we thought it should be.

  NS_ASSERTION(((aState->mLastFrame && mFrameInfoMap) ||
               (!aState->mLastFrame && !mFrameInfoMap)),
               "Unexpected outcome!");

  mX = aState->mX;
  mY = aState->mY;
  mLowestTop = aState->mLowestTop;
  mHaveCachedLeftYMost = aState->mHaveCachedLeftYMost;
  mHaveCachedRightYMost = aState->mHaveCachedRightYMost;
  mMaximalLeftYMost = aState->mMaximalLeftYMost;
  mMaximalRightYMost = aState->mMaximalRightYMost;
}

nscoord
nsSpaceManager::GetLowestRegionTop()
{
  if (mLowestTop == NSCOORD_MIN)
    return mLowestTop;
  return mLowestTop - mY;
}

#ifdef DEBUG
void
DebugListSpaceManager(nsSpaceManager *aSpaceManager)
{
  aSpaceManager->List(stdout);
}

nsresult
nsSpaceManager::List(FILE* out)
{
  nsAutoString tmp;

  fprintf(out, "SpaceManager@%p", this);
  fprintf(out, " xy=%d,%d <\n", mX, mY);
  if (mBandList.IsEmpty()) {
    fprintf(out, "  no bands\n");
  }
  else {
    BandRect* band = mBandList.Head();
    do {
      PRInt32 const n = band->mFrames.Count();
      fprintf(out, "  left=%d top=%d right=%d bottom=%d count=%d frames=",
              band->mLeft, band->mTop, band->mRight, band->mBottom, n);

      for (PRInt32 i = 0; i < n; i++) {
        nsIFrame* frame = (nsIFrame*)band->mFrames.FastElementAt(i);
        if (frame) {
          nsIFrameDebug*  frameDebug;

		  if (NS_SUCCEEDED(frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
            frameDebug->GetFrameName(tmp);
            fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
            fprintf(out, "@%p ", frame);
          }
        }
      }
      fprintf(out, "\n");
      band = band->Next();
    } while (band != mBandList.Head());
  }
  fprintf(out, ">\n");
  return NS_OK;
}
#endif

nsSpaceManager::FrameInfo*
nsSpaceManager::GetFrameInfoFor(nsIFrame* aFrame)
{
  FrameInfo*  result = nsnull;

  for (result = mFrameInfoMap; result; result = result->mNext) {
    if (result->mFrame == aFrame) {
      break;
    }
  }

  return result;
}

nsSpaceManager::FrameInfo*
nsSpaceManager::CreateFrameInfo(nsIFrame* aFrame, const nsRect& aRect)
{
  FrameInfo*  frameInfo = new FrameInfo(aFrame, aRect);

  if (frameInfo) {
    // Link it into the list
    frameInfo->mNext = mFrameInfoMap;
    mFrameInfoMap = frameInfo;

    // Optimize for the common case case when the frame being added is
    // likely to be near the bottom.
    nscoord ymost = aRect.YMost();
    PRUint8 floatType = aFrame->GetStyleDisplay()->mFloats;
    if (mHaveCachedLeftYMost && ymost > mMaximalLeftYMost &&
        floatType == NS_STYLE_FLOAT_LEFT) {
      mMaximalLeftYMost = ymost;
    }
    else if (mHaveCachedRightYMost && ymost > mMaximalRightYMost &&
             floatType == NS_STYLE_FLOAT_RIGHT) {
      mMaximalRightYMost = ymost;
    }
  }
  return frameInfo;
}

void
nsSpaceManager::DestroyFrameInfo(FrameInfo* aFrameInfo)
{
  // See if it's at the head of the list
  if (mFrameInfoMap == aFrameInfo) {
    mFrameInfoMap = aFrameInfo->mNext;

  } else {
    FrameInfo*  prev;
    
    // Find the previous node in the list
    for (prev = mFrameInfoMap; prev && (prev->mNext != aFrameInfo); prev = prev->mNext) {
      ;
    }

    // Disconnect it from the list
    NS_ASSERTION(prev, "element not in list");
    if (prev) {
      prev->mNext = aFrameInfo->mNext;
    }
  }

  // Optimize for the case when the frame being removed is likely to be near
  // the bottom, but do nothing if we have neither cached value -- that case is
  // likely to be hit from PopState().
  if (mHaveCachedLeftYMost || mHaveCachedRightYMost) {
    PRUint8 floatType = aFrameInfo->mFrame->GetStyleDisplay()->mFloats;
    if (floatType == NS_STYLE_FLOAT_LEFT) {
      mHaveCachedLeftYMost = PR_FALSE;
    }
    else {
      NS_ASSERTION(floatType == NS_STYLE_FLOAT_RIGHT, "Unexpected float type");
      mHaveCachedRightYMost = PR_FALSE;
    }
  }

  delete aFrameInfo;
}

nscoord
nsSpaceManager::ClearFloats(nscoord aY, PRUint8 aBreakType)
{
  nscoord bottom = aY + mY;

  if ((!mHaveCachedLeftYMost && aBreakType != NS_STYLE_CLEAR_RIGHT) ||
      (!mHaveCachedRightYMost && aBreakType != NS_STYLE_CLEAR_LEFT)) {
    // Recover our maximal YMost values.  Might need both if this is a
    // NS_STYLE_CLEAR_LEFT_AND_RIGHT
    nscoord maximalLeftYMost = mHaveCachedLeftYMost ? mMaximalLeftYMost : nscoord_MIN;
    nscoord maximalRightYMost = mHaveCachedRightYMost ? mMaximalRightYMost : nscoord_MIN;

    // Optimize for most floats not being near the bottom
    for (FrameInfo *frame = mFrameInfoMap; frame; frame = frame->mNext) {
      nscoord ymost = frame->mRect.YMost();
      if (ymost > maximalLeftYMost) {
        if (frame->mFrame->GetStyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) {
          NS_ASSERTION(!mHaveCachedLeftYMost, "Shouldn't happen");
          maximalLeftYMost = ymost;
          // No need to compare to the right ymost
          continue;
        }
      }

      if (ymost > maximalRightYMost) {
        if (frame->mFrame->GetStyleDisplay()->mFloats == NS_STYLE_FLOAT_RIGHT) {
          NS_ASSERTION(!mHaveCachedRightYMost, "Shouldn't happen");
          maximalRightYMost = ymost;
        }
      }
    }

    mMaximalLeftYMost = maximalLeftYMost;
    mMaximalRightYMost = maximalRightYMost;
    mHaveCachedRightYMost = mHaveCachedLeftYMost = PR_TRUE;
  }
  
  switch (aBreakType) {
    case NS_STYLE_CLEAR_LEFT_AND_RIGHT:
      NS_ASSERTION(mHaveCachedLeftYMost && mHaveCachedRightYMost,
                   "Need cached values!");
      bottom = PR_MAX(bottom, mMaximalLeftYMost);
      bottom = PR_MAX(bottom, mMaximalRightYMost);
      break;
    case NS_STYLE_CLEAR_LEFT:
      NS_ASSERTION(mHaveCachedLeftYMost, "Need cached value!");
      bottom = PR_MAX(bottom, mMaximalLeftYMost);
      break;
    case NS_STYLE_CLEAR_RIGHT:
      NS_ASSERTION(mHaveCachedRightYMost, "Need cached value!");
      bottom = PR_MAX(bottom, mMaximalRightYMost);
      break;
    default:
      // Do nothing
      break;
  }

  bottom -= mY;

  return bottom;
}

nsSpaceManager::BandRect*
nsSpaceManager::GuessBandWithTopAbove(nscoord aYOffset) const
{
  NS_ASSERTION(!mBandList.IsEmpty(), "no bands");
  BandRect* band = nsnull;
  if (mCachedBandPosition) {
    band = mCachedBandPosition;
    // Now seek backward so that we're guaranteed to be the topmost
    // band which might contain the y-offset or be below it.
    while (band && band->mTop > aYOffset) {
      band = GetPrevBand(band);
    }
  }

  if (band) {
    return band;
  }
  
  return mBandList.Head();
}

/////////////////////////////////////////////////////////////////////////////
// FrameInfo

nsSpaceManager::FrameInfo::FrameInfo(nsIFrame* aFrame, const nsRect& aRect)
  : mFrame(aFrame), mRect(aRect), mNext(0)
{
  MOZ_COUNT_CTOR(nsSpaceManager::FrameInfo);
}

#ifdef NS_BUILD_REFCNT_LOGGING
nsSpaceManager::FrameInfo::~FrameInfo()
{
  MOZ_COUNT_DTOR(nsSpaceManager::FrameInfo);
}
#endif

/////////////////////////////////////////////////////////////////////////////
// BandRect

nsSpaceManager::BandRect::BandRect(nscoord    aLeft,
                                   nscoord    aTop,
                                   nscoord    aRight,
                                   nscoord    aBottom,
                                   nsIFrame*  aFrame)
{
  MOZ_COUNT_CTOR(BandRect);
  mLeft = aLeft;
  mTop = aTop;
  mRight = aRight;
  mBottom = aBottom;
  AddFrame(aFrame);
}

nsSpaceManager::BandRect::BandRect(nscoord      aLeft,
                                   nscoord      aTop,
                                   nscoord      aRight,
                                   nscoord      aBottom,
                                   nsSmallVoidArray& aFrames)
{
  MOZ_COUNT_CTOR(BandRect);
  mLeft = aLeft;
  mTop = aTop;
  mRight = aRight;
  mBottom = aBottom;
  mFrames = aFrames;
}

nsSpaceManager::BandRect::~BandRect()
{
  MOZ_COUNT_DTOR(BandRect);
}

nsSpaceManager::BandRect*
nsSpaceManager::BandRect::SplitVertically(nscoord aBottom)
{
  NS_PRECONDITION((aBottom > mTop) && (aBottom < mBottom), "bad argument");

  // Create a new band rect for the bottom part
  BandRect* bottomBandRect = new BandRect(mLeft, aBottom, mRight, mBottom, mFrames);
                                           
  // This band rect becomes the top part, so adjust the bottom edge
  mBottom = aBottom;
  return bottomBandRect;
}

nsSpaceManager::BandRect*
nsSpaceManager::BandRect::SplitHorizontally(nscoord aRight)
{
  NS_PRECONDITION((aRight > mLeft) && (aRight < mRight), "bad argument");
  
  // Create a new band rect for the right part
  BandRect* rightBandRect = new BandRect(aRight, mTop, mRight, mBottom, mFrames);
                                           
  // This band rect becomes the left part, so adjust the right edge
  mRight = aRight;
  return rightBandRect;
}

PRBool
nsSpaceManager::BandRect::HasSameFrameList(const BandRect* aBandRect) const
{
  const PRInt32 count = mFrames.Count();

  // Check whether they're occupied by the same number of frames
  if (count != aBandRect->mFrames.Count()) {
    return PR_FALSE;
  }
  // For each frame occupying this band rect check whether it also occupies
  // aBandRect
  for (PRInt32 i = 0; i < count; i++) {
    if (-1 == aBandRect->mFrames.IndexOf(mFrames.FastElementAt(i))) {
      return PR_FALSE;
    }
  }

  return PR_TRUE;
}

/**
 * Internal helper function that counts the number of rects in this band
 * including the current band rect
 */
PRInt32
nsSpaceManager::BandRect::Length() const
{
  PRInt32   len = 1;
  BandRect* bandRect = Next();

  // Because there's a header cell we know we'll either find the next band
  // (which has a different y-offset) or the header cell which has an invalid
  // y-offset
  while (bandRect->mTop == mTop) {
    len++;
    bandRect = bandRect->Next();
  }

  return len;
}


//----------------------------------------------------------------------

nsAutoSpaceManager::~nsAutoSpaceManager()
{
  // Restore the old space manager in the reflow state if necessary.
  if (mNew) {
#ifdef NOISY_SPACEMANAGER
    printf("restoring old space manager %p\n", mOld);
#endif

    mReflowState.mSpaceManager = mOld;

#ifdef NOISY_SPACEMANAGER
    if (mOld) {
      static_cast<nsFrame *>(mReflowState.frame)->ListTag(stdout);
      printf(": space-manager %p after reflow\n", mOld);
      mOld->List(stdout);
    }
#endif

#ifdef DEBUG
    if (mOwns)
#endif
      delete mNew;
  }
}

nsresult
nsAutoSpaceManager::CreateSpaceManager(nsPresContext *aPresContext)
{
  // Create a new space manager and install it in the reflow
  // state. `Remember' the old space manager so we can restore it
  // later.
  mNew = new nsSpaceManager(aPresContext->PresShell());
  if (! mNew)
    return NS_ERROR_OUT_OF_MEMORY;

#ifdef NOISY_SPACEMANAGER
  printf("constructed new space manager %p (replacing %p)\n",
         mNew, mReflowState.mSpaceManager);
#endif

  // Set the space manager in the existing reflow state
  mOld = mReflowState.mSpaceManager;
  mReflowState.mSpaceManager = mNew;
  return NS_OK;
}
