/* -*- 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.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
#include "nsTablePart.h"
#include "nsTableOuterFrame.h"
#include "nsITableContent.h"
#include "nsTableCol.h"
#include "nsTableColGroup.h"
#include "nsTableRowGroup.h"
#include "nsTableCaption.h"
#include "nsTableRow.h"
#include "nsTableCell.h"
#include "nsCellMap.h"
#include "nsHTMLParts.h"
#include "nsIPresContext.h"
#include "nsContainerFrame.h"
#include "nsIRenderingContext.h"
#include "nsHTMLIIDs.h"
#include "nsIAtom.h"

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kITableContentIID, NS_ITABLECONTENT_IID);

#ifdef NS_DEBUG
static PRBool gsDebug = PR_FALSE;
static PRBool gsNoisyRefs = PR_FALSE;
#else
static const PRBool gsDebug = PR_FALSE;
static const PRBool gsNoisyRefs = PR_FALSE;
#endif

const char *nsTablePart::kCaptionTagString="CAPTION";
const char *nsTablePart::kRowGroupBodyTagString="TBODY";
const char *nsTablePart::kRowGroupHeadTagString="THEAD";
const char *nsTablePart::kRowGroupFootTagString="TFOOT";
const char *nsTablePart::kRowTagString="TR";
const char *nsTablePart::kColGroupTagString="COLGROUP";
const char *nsTablePart::kColTagString="COL";
const char *nsTablePart::kDataCellTagString="TD";
const char *nsTablePart::kHeaderCellTagString="TH";

CellData::CellData()
{
  mCell = nsnull;
  mRealCell = nsnull;
  mOverlap = nsnull;
}

CellData::~CellData()
{}


/*---------- nsTablePart implementation -----------*/

/**
 *
 * BUGS:
 * <ul>
 * <li>Enabling css1 margins on tables, rows, cells causes things to not
 * fit. why?
 * <li>centering (etc.) is leaking into table cell's from the outer environment
 * </ul>
 *
 * TODO:
 * <ul>
 * <li>as table border gets wider, the table gets narrower (somebody is over
 * subtracting the table border.
 * <li>colspan creating empty cells on rows w/o matching cells
 * <li>draw table borders ala navigator
 * <li>border sizing
 * <li>bordercolor
 * <li>draw cell borders ala navigator
 * <li>cellspacing
 * <li>colspan not the last cell
 * <li>rowspan
 * <li>floaters in table cells
 * <ul>
 * <li>rowspan truncation (rowspan sticking out past the bottom of the table)
 * <li>effect on subsequent row structure
 * </ul>
 * <li>eliminate TableRow and TableRowLayoutDelegate's
 * <li>inherit table border color ala nav4 (use inherited text color) when
 * the border is enabled and the bordercolor value nsnull.
 * </ul>
 *
 * HTML4:
 * <ul>
 * <li>borders
 * <li>table frame
 * <li>colgroup
 * <li>col
 * <li>thead, tfoot, tbody
 * </ul>
 *
 * CSS1:
 * <ul>
 * <li>table margin
 * <li>table border-width
 * <li>table padding
 * <li>row margin
 * <li>row border-width
 * <li>row padding
 * <li>cell margin
 * <li>cell border-width
 * <li>cell padding
 * <li>what about colgroup, thead, tbody, tfoot margin/border-width/padding?
 * <li>map css1 style (borders, padding, margins) in some sensible manner
 * </ul>
 *
 * CSS2:
 * <ul>
 * <li>watch it
 * </ul>
 *
 * Eventually:
 * <ul>
 * <li>splitting tables across pages
 * </ul>
 *
 * And the hardest of all:
 * <ul>
 * <li>column sizing
 * </ul>
 */

/** constructor
  * I do not addref aTag because my superclass does that for me
  */
nsTablePart::nsTablePart(nsIAtom* aTag)
  : nsHTMLContainer(aTag),
    mColCount(0),
    mSpecifiedColCount(0),
    mCellMap(0)
{ 
}

/** constructor
  * I do not addref aTag because my superclass does that for me
  */
nsTablePart::nsTablePart (nsIAtom* aTag, int aColumnCount)
  : nsHTMLContainer(aTag),
    mColCount(aColumnCount),
    mSpecifiedColCount(0),
    mCellMap(0)
{
}

/**
  */
nsTablePart::~nsTablePart()
{
  if (nsnull!=mCellMap)
  {
    delete mCellMap;
  }
}

/**
  */
/*
nsTablePart::void compact() {
  compact();
}
*/

nsrefcnt nsTablePart::AddRef(void)
{
  if (gsNoisyRefs==PR_TRUE) printf("Add Ref: nsTablePart cnt = %d \n",mRefCnt+1);
  return ++mRefCnt;
}

nsrefcnt nsTablePart::Release(void)
{
  if (gsNoisyRefs==PR_TRUE) printf("Release: nsTablePart cnt = %d \n",mRefCnt-1);
  if (--mRefCnt == 0) {
    if (gsNoisyRefs==PR_TRUE) printf("Delete: nsTablePart \n");
    delete this;
    return 0;
  }
  return mRefCnt;
}

/**
  */
int nsTablePart::GetMaxColumns ()
{
  if (nsnull == mCellMap)
  {
    BuildCellMap ();
  }
  return mColCount;
}

  // XXX what do rows with no cells turn into?
/**
  */
int nsTablePart::GetRowCount ()
{
  if (nsnull != mCellMap)
    return mCellMap->GetRowCount();

  int rowCount = 0;
  int index = ChildCount ();
  while (0 < index)
  {
    nsIContent *child = ChildAt (--index);  // child: REFCNT++
    nsTableContent *tableContent = (nsTableContent *)child;
    const int contentType = tableContent->GetType();
    if (contentType == nsITableContent::kTableRowGroupType)
      rowCount += ((nsTableRowGroup *)tableContent)->GetRowCount ();
    NS_RELEASE(child);                      // child: REFCNT--
  }
  return rowCount;
}
  
/** counts columns in column groups
  */
int nsTablePart::GetSpecifiedColumnCount ()
{
  if (mSpecifiedColCount < 0)
  {
    mSpecifiedColCount = 0;
    int count = ChildCount ();
    for (int index = 0; index < count; index++)
    {
      nsIContent *child = ChildAt (index);  // child: REFCNT++
      nsTableContent *tableContent = (nsTableContent *)child;
      const int contentType = tableContent->GetType();
      if (contentType == nsITableContent::kTableColGroupType)
      {
        ((nsTableColGroup *)tableContent)->SetStartColumnIndex (mSpecifiedColCount);
        mSpecifiedColCount += ((nsTableColGroup *)tableContent)->GetColumnCount ();
      }
      NS_RELEASE(child);                    // child: REFCNT--
    }
  }
  return mSpecifiedColCount;
}


nsCellMap*  nsTablePart::GetCellMap() const
{
  return mCellMap;
}


/**
  */
void nsTablePart::ResetCellMap ()
{
  if (mCellMap)
    delete mCellMap;
  mCellMap = nsnull; // for now, will rebuild when needed
}

/**
  */
void nsTablePart::ResetColumns ()
{
  mSpecifiedColCount = -1;
  if (nsnull != mCellMap)
  {
    int colCount = GetSpecifiedColumnCount ();
    GrowCellMap (colCount); // make sure we're at least as big as specified columns
  }
  else
    mColCount = 0;  // we'll compute later (as part of building cell map)
}

/** sum the columns represented by all nsTableColGroup objects
  * if the cell map says there are more columns than this, 
  * add extra implicit columns to the content tree.
  */
void nsTablePart::EnsureColumns()
{
  if (nsnull!=mCellMap)
  {
    PRInt32 actualColumns = 0;
    PRInt32 numColGroups = ChildCount();
    nsTableColGroup *lastColGroup = nsnull;
    for (PRInt32 colGroupIndex = 0; colGroupIndex < numColGroups; colGroupIndex++)
    {
      nsTableContent *colGroup = (nsTableContent*)ChildAt(colGroupIndex); // colGroup: REFCNT++
      const int contentType = colGroup->GetType();
      if (contentType==nsTableContent::kTableColGroupType)
      {
        PRInt32 numCols = ((nsTableColGroup *)colGroup)->GetColumnCount();
        actualColumns += numCols;
        lastColGroup = (nsTableColGroup *)colGroup;
        NS_RELEASE(colGroup);
      }
      else if (contentType==nsTableContent::kTableRowGroupType)
      {
        NS_RELEASE(colGroup);
        break;
      }
    }    
    if (actualColumns < mCellMap->GetColCount())
    {
      if (nsnull==lastColGroup)
      {
        lastColGroup = new nsTableColGroup (PR_TRUE);
        AppendColGroup(lastColGroup);
      }
      PRInt32 excessColumns = mCellMap->GetColCount() - actualColumns;
      for ( ; excessColumns > 0; excessColumns--)
      {
        nsTableCol *col = new nsTableCol(PR_TRUE);
        lastColGroup->AppendChild (col);
      }
    }
  }
}

/**
  */
void nsTablePart::ReorderChildren()
{
  NS_ASSERTION(PR_FALSE, "not yet implemented.");
}

void nsTablePart::NotifyContentComplete()
{
  // set the children in order
  ReorderChildren();
}

/** add a child to the table content.
  * tables are special because they require the content to be normalized, in order.
  * so this function doesn't really "append" the content, but adds it in the proper place,
  * possibly nested inside of implicit parents.
  * order is:
  *   Captions (optional)
  *   Column Groups (1 or more, possibly implicit)
  *   Row Groups 
  *     THEADs (optional)
  *     TFOOTs (optional)
  *     TBODY (at least 1, possibly implicit)
  * 
  * should be broken out into separate functions!
  */
PRBool nsTablePart::AppendChild (nsIContent * aContent)
{
  PRBool result = PR_FALSE;
  PRBool newCells = PR_FALSE;
  PRBool contentHandled = PR_FALSE;

  // wait, stop!  need to check to see if this is really tableContent or not!
  nsITableContent *tableContentInterface = nsnull;
  nsresult rv = aContent->QueryInterface(kITableContentIID, 
                                         (void **)&tableContentInterface);  // tableContentInterface: REFCNT++
  if (NS_FAILED(rv))
  { // create an implicit cell and return the result of adding it to the table
    if (gsDebug==PR_TRUE)
      printf ("*** bad HTML in table, not yet implemented.\n");
    return PR_FALSE;
  }
  else
  {
    nsTableContent *tableContent = (nsTableContent *)tableContentInterface;
    const int contentType = tableContent->GetType();
    if ((contentType == nsITableContent::kTableCellType) ||
        (contentType==nsITableContent::kTableRowType))
    {
      if (gsDebug==PR_TRUE)
      {
        if (contentType == nsITableContent::kTableRowType)
          printf ("nsTablePart::AppendChild -- adding a row.\n");
        else
          printf ("nsTablePart::AppendChild -- adding a cell.\n");
      }
      // find last row group, if ! implicit, make one, append there
      nsTableRowGroup *group = nsnull;
      int index = ChildCount ();
      while ((0 < index) && (nsnull==group))
      {
        nsIContent *child = ChildAt (--index);    // child: REFCNT++
        if (nsnull != child)
        {
          nsTableContent * content = (nsTableContent *)child;
          if (content->GetType()==nsITableContent::kTableRowGroupType)
          {  
            group = (nsTableRowGroup *)content;
            NS_ADDREF(group);                     // group: REFCNT++
          }
        }
        NS_RELEASE(child);                        // child: REFCNT--
      }
      if ((nsnull == group) || (! group->IsImplicit ()))
      {
        if (gsDebug==PR_TRUE) printf ("nsTablePart::AppendChild -- creating an implicit row group.\n");
        nsIAtom * rowGroupTag = NS_NewAtom(kRowGroupBodyTagString); // rowGroupTag: REFCNT++
        group = new nsTableRowGroup (rowGroupTag, PR_TRUE);
        NS_ADDREF(group);                         // group: REFCNT++
        AppendChild (group);
        NS_RELEASE(rowGroupTag);                                  // rowGroupTag: REFCNT--
      }
      // group is guaranteed to be allocated at this point
      result = group->AppendChild(aContent);
      newCells = result;
      contentHandled = PR_TRUE;
      NS_RELEASE(group);                          // group: REFCNT--
    }
    else if (contentType == nsITableContent::kTableColType)
    {
      result = AppendColumn((nsTableCol *)aContent);
      newCells = result;
      contentHandled = PR_TRUE;
    }
    else if (contentType == nsITableContent::kTableCaptionType)
    {
      result = AppendCaption((nsTableCaption *)aContent);
      contentHandled = PR_TRUE; // whether we succeeded or not, we've "handled" this request
    }
    else if (contentType == nsITableContent::kTableRowGroupType)
    {
      result = AppendRowGroup((nsTableRowGroup *)aContent);
      if (PR_TRUE==result)
      {
        newCells = PR_TRUE;
      }
      contentHandled = PR_TRUE; // whether we succeeded or not, we've "handled" this request
    }
    else if (contentType == nsITableContent::kTableColGroupType)
    {
      result = AppendColGroup((nsTableColGroup *)aContent);
      if (PR_TRUE==result)
      {
        newCells = PR_TRUE;
      }
      contentHandled = PR_TRUE; // whether we succeeded or not, we've "handled" this request
    }
    /* if aContent is not a known content type, make a capion out of it */
    // SEC the logic here is very suspicious!!!!
    if (PR_FALSE==contentHandled)
    {
      if (gsDebug==PR_TRUE) printf ("nsTablePart::AppendChild -- content not handled!!!\n");
      nsTableCaption *caption = nsnull;
      nsIContent *lastChild = ChildAt (ChildCount() - 1); // lastChild: REFCNT++
      if (nsnull != lastChild)
      {
        nsTableContent * content = (nsTableContent *)lastChild;
        if (content->GetType()==nsITableContent::kTableCaptionType)
          caption = (nsTableCaption *)content;
        NS_RELEASE(lastChild);                            // lastChild: REFCNT--
      }
      if ((nsnull == caption) || (! caption->IsImplicit ()))
      {
        if (gsDebug==PR_TRUE) printf ("nsTablePart::AppendChild -- adding an implicit caption.\n");
        caption = new nsTableCaption (PR_TRUE);
        AppendCaption (caption);
      }
      result = caption->AppendChild (aContent);
    }
    /* if we added new cells, we need to fix up the cell map */
    if (newCells)
    {
      ResetCellMap ();
    }
  }
  NS_RELEASE(tableContentInterface);                                        // tableContentInterface: REFCNT--
  return result;
}

/**
  */
  /* SEC: why can we only insertChildAt (captions or groups) ? */
PRBool nsTablePart::InsertChildAt(nsIContent * aContent, int aIndex)
{
  PRBool result = PR_FALSE;
  nsTableContent *tableContent = (nsTableContent *)aContent;
  const int contentType = tableContent->GetType();
  if ((contentType == nsITableContent::kTableCaptionType) ||
      (contentType == nsITableContent::kTableColGroupType) ||
      (contentType == nsITableContent::kTableRowGroupType))
  {
    result = nsHTMLContainer::InsertChildAt (aContent, aIndex);
    if (result)
    {
      tableContent->SetTable (this);
      ResetCellMap ();
    }
  }
  return result;
}

/**
  */
PRBool nsTablePart::ReplaceChildAt (nsIContent *aContent, int aIndex)
{
  PRBool result = PR_FALSE;
  nsTableContent *tableContent = (nsTableColGroup *)aContent;
  const int contentType = tableContent->GetType();
  if ( (contentType == nsITableContent::kTableCaptionType) ||
       (contentType == nsITableContent::kTableColGroupType) ||
       (contentType == nsITableContent::kTableRowGroupType))
  {
    nsIContent *lastChild = ChildAt (aIndex);// lastChild: REFCNT++
    result = nsHTMLContainer::ReplaceChildAt (aContent, aIndex);
    if (result)
    {
      if (nsnull != lastChild)
        tableContent->SetTable (nsnull);
      tableContent->SetTable (this);
      ResetCellMap ();
    }
    NS_IF_RELEASE(lastChild);               // lastChild: REFCNT--
  }
  return result;
}

/**
 * Remove a child at the given position. The method is ignored if
 * the index is invalid (too small or too large).
 */
PRBool nsTablePart::RemoveChildAt (int aIndex)
{
  nsIContent * lastChild = ChildAt (aIndex);    // lastChild: REFCNT++
  PRBool result = nsHTMLContainer::RemoveChildAt (aIndex);
  if (result)
  {
    nsTableContent *tableContent = (nsTableColGroup *)lastChild;
    const int contentType = tableContent->GetType();
    if (contentType == nsITableContent::kTableRowType)
    {
      if (nsnull != lastChild)
        ((nsTableRow *)tableContent)->SetRowGroup (nsnull);
      tableContent->SetTable(nsnull);
      ResetCellMap ();
    }
  }
  NS_IF_RELEASE(lastChild);
  return result;
}

/** protected method for appending a column group to this table */
PRBool nsTablePart::AppendRowGroup (nsTableRowGroup *aContent)
{
  NS_PRECONDITION(nsnull!=aContent, "null arg.");
  PRBool result = PR_TRUE;
  if (gsDebug==PR_TRUE)
    printf ("nsTablePart::AppendRowGroup -- adding a row group.\n");
  // find the last row group of this kind and insert this row group after it
  // if there is no row group of this type already in the table, 
  // find the appropriate slot depending on this row group tag
  nsIAtom * rowGroupTag = aContent->GetTag();
  int childCount = ChildCount ();
  nsIAtom * tHeadTag = NS_NewAtom(kRowGroupHeadTagString); // tHeadTag: REFCNT++
  nsIAtom * tFootTag = NS_NewAtom(kRowGroupFootTagString); // tFootTag: REFCNT++
  nsIAtom * tBodyTag = NS_NewAtom(kRowGroupBodyTagString); // tBodyTag: REFCNT++
  for (PRInt32 childIndex = 0; childIndex < childCount; childIndex++)
  {
    nsTableContent *tableChild = (nsTableContent *)ChildAt(childIndex); // tableChild: REFCNT++
    const int tableChildType = tableChild->GetType();
    if ((tableChildType == nsITableContent::kTableCaptionType) ||
        (tableChildType == nsITableContent::kTableColGroupType))
    {
      NS_RELEASE(tableChild);                                             // tableChild: REFCNT-- (a)
      continue;
    }
    else if (tableChildType == nsITableContent::kTableRowGroupType)
    {
      nsIAtom * tableChildTag = tableChild->GetTag();
      NS_RELEASE(tableChild);                                             // tableChild: REFCNT-- (b)
      if (tHeadTag==rowGroupTag && tHeadTag==tableChildTag)
        continue;
      else if (tFootTag==rowGroupTag &&
               tHeadTag==tableChildTag || 
               tFootTag==tableChildTag)
        continue;
      else if (tBodyTag==tableChildTag)
        continue;
      else
        break;
    }
    else
      break;
  }
  NS_RELEASE(tFootTag);
  NS_RELEASE(tHeadTag);
  NS_RELEASE(tBodyTag);
  result = nsHTMLContainer::InsertChildAt(aContent, childIndex);
  if (result)
  {
    ((nsTableContent *)aContent)->SetTable (this);
  }
  return result;
}

/** protected method for appending a column group to this table */
PRBool nsTablePart::AppendColGroup(nsTableColGroup *aContent)
{
  NS_PRECONDITION(nsnull!=aContent, "null arg.");
  if (gsDebug==PR_TRUE)
    printf ("nsTablePart::AppendColGroup -- adding a column group.\n");
  // find the last column group and insert this column group after it.
  // if there is no column group already in the table, make this the first child
  // after any caption
  int childCount = ChildCount ();
  for (PRInt32 childIndex = 0; childIndex < childCount; childIndex++)
  {
    nsTableContent *tableChild = (nsTableContent *)ChildAt(childIndex); // tableChild: REFCNT++
    const int tableChildType = tableChild->GetType();
    NS_RELEASE(tableChild);                                             // tableChild: REFCNT--
    if (!((tableChildType == nsITableContent::kTableCaptionType) ||
         (tableChildType == nsITableContent::kTableColGroupType)))
      break;
  }
  PRBool result = nsHTMLContainer::InsertChildAt(aContent, childIndex);
  if (result)
  {
    ((nsTableContent *)aContent)->SetTable (this);
  }
  // if col group has a SPAN attribute, create implicit columns for the value of SPAN
  // what sucks is if we then get a COL for this COLGROUP, we have to delete all the
  // COLs we created for SPAN, and just contain the explicit COLs.
  PRInt32 span = 0; // SEC: TODO find a way to really get this
  for (PRInt32 i=0; i<span; i++)
  {
    nsTableCol *col = new nsTableCol(PR_TRUE);
    aContent->AppendChild (col);
  }

  return result;
}

/** protected method for appending a column group to this table */
PRBool nsTablePart::AppendColumn(nsTableCol *aContent)
{
  NS_PRECONDITION(nsnull!=aContent, "null arg.");
  if (gsDebug==PR_TRUE)
    printf ("nsTablePart::AppendColumn -- adding a column.\n");
  // find last col group, if ! implicit, make one, append there
  nsTableColGroup *group = nsnull;
  PRBool foundColGroup = PR_FALSE;
  int index = ChildCount ();
  while ((0 < index) && (PR_FALSE==foundColGroup))
  {
    nsIContent *child = ChildAt (--index);    // child: REFCNT++
    if (nsnull != child)
    {
      group = (nsTableColGroup *)child;
      foundColGroup = (PRBool)
        (group->GetType()==nsITableContent::kTableColGroupType);
      NS_RELEASE(child);                      // child: REFCNT--
    }
  }
  if ((PR_FALSE == foundColGroup) || (! group->IsImplicit ()))
  {
    if (gsDebug==PR_TRUE) 
      printf ("nsTablePart::AppendChild -- creating an implicit column group.\n");
    group = new nsTableColGroup (PR_TRUE);
    AppendChild (group);
  }
  PRBool result = group->AppendChild (aContent);
  return result;
}

/** protected method for appending a column group to this table */
PRBool nsTablePart::AppendCaption(nsTableCaption *aContent)
{
  NS_PRECONDITION(nsnull!=aContent, "null arg.");
  if (gsDebug==PR_TRUE)
    printf ("nsTablePart::AppendCaption -- adding a caption.\n");
  // find the last caption and insert this caption after it.
  // if there is no caption already in the table, make this the first child
  int childCount = ChildCount ();
  for (PRInt32 childIndex = 0; childIndex < childCount; childIndex++)
  {
    nsTableContent *tableChild = (nsTableContent *)ChildAt(childIndex);
    const int tableChildType = tableChild->GetType();
    NS_RELEASE(tableChild);
    if (tableChildType != nsITableContent::kTableCaptionType)
      break;
  }
  PRBool result = nsHTMLContainer::InsertChildAt(aContent, childIndex);
  if (PR_TRUE==result)
  {
    ((nsTableContent *)aContent)->SetTable (this);
  }
  return result;
}

/**
  */
int nsTablePart::NextRowGroup (int aStartIndex)
{
  int index = aStartIndex;
  int count = ChildCount ();

  while (++index < count)
  {
    nsIContent * child = ChildAt (index); // child: REFCNT++
    nsTableContent *tableContent = (nsTableColGroup *)child;
    const int contentType = tableContent->GetType();
    NS_RELEASE(child);                    // child: REFCNT--
    if (contentType == nsITableContent::kTableRowGroupType)
      return index;
  }
  return count;
}

// XXX This should be computed incrementally and updated
// automatically when rows are added/deleted. To do this, however,
// we need to change the way the ContentSink works and somehow
// notify this table when a row is modified. We can give the rows
// parent pointers, but that would bite.

// XXX nuke this; instead pretend the content sink is working
// incrementally like we want it to and build up the data a row at a
// time.

void nsTablePart::BuildCellMap ()
{
  if (gsDebug==PR_TRUE) printf("Build Cell Map...\n");
  int rowCount = GetRowCount ();
  if (0 == rowCount)
  {
    if (gsDebug==PR_TRUE) printf("0 row count.  Returning.\n");
    mColCount = GetSpecifiedColumnCount ();  // at least set known column count
    EnsureColumns();
    return;
  }

  // Make an educated guess as to how many columns we have. It's
  // only a guess because we can't know exactly until we have
  // processed the last row.

  int childCount = ChildCount ();
  int groupIndex = NextRowGroup (-1);
  if (0 == mColCount)
    mColCount = GetSpecifiedColumnCount ();
  if (0 == mColCount) // no column parts
  {
    nsTableRowGroup *rowGroup = (nsTableRowGroup*)(ChildAt (groupIndex)); // rowGroup: REFCNT++
    nsTableRow *row = (nsTableRow *)(rowGroup->ChildAt (0));              // row: REFCNT++
    mColCount = row->GetMaxColumns ();
    if (gsDebug==PR_TRUE) printf("mColCount=0 at start.  Guessing col count to be %d from a row.\n", mColCount);
    NS_RELEASE(rowGroup);                                                 // rowGroup: REFCNT--
    NS_RELEASE(row);                                                      // row: REFCNT--
  }
  if (nsnull==mCellMap)
    mCellMap = new nsCellMap(rowCount, mColCount);
  else
    mCellMap->Reset(rowCount, mColCount);
  if (gsDebug==PR_TRUE) printf("mCellMap set to (%d, %d)\n", rowCount, mColCount);
  int rowStart = 0;
  if (gsDebug==PR_TRUE) printf("childCount is %d\n", childCount);
  while (groupIndex < childCount)
  {
    if (gsDebug==PR_TRUE) printf("  groupIndex is %d\n", groupIndex);
    if (gsDebug==PR_TRUE) printf("  rowStart is %d\n", rowStart);
    nsTableRowGroup *rowGroup = (nsTableRowGroup *)ChildAt (groupIndex);  // rowGroup: REFCNT++
    int groupRowCount = rowGroup->ChildCount ();
    if (gsDebug==PR_TRUE) printf("  groupRowCount is %d\n", groupRowCount);
    for (int rowIndex = 0; rowIndex < groupRowCount; rowIndex++)
    {
      nsTableRow *row = (nsTableRow *)(rowGroup->ChildAt (rowIndex));     // row: REFCNT++
      int cellCount = row->ChildCount ();
      int cellIndex = 0;
      int colIndex = 0;
      if (gsDebug==PR_TRUE) 
        DumpCellMap();
      if (gsDebug==PR_TRUE) printf("    rowIndex is %d, row->SetRowIndex(%d)\n", rowIndex, rowIndex + rowStart);
      row->SetRowIndex (rowIndex + rowStart);
      while (colIndex < mColCount)
      {
        if (gsDebug==PR_TRUE) printf("      colIndex = %d, with mColCount = %d\n", colIndex, mColCount);
        CellData *data =mCellMap->GetCellAt(rowIndex + rowStart, colIndex);
        if (nsnull == data)
        {
          if (gsDebug==PR_TRUE) printf("      null data from GetCellAt(%d,%d)\n", rowIndex+rowStart, colIndex);
          if (gsDebug==PR_TRUE) printf("      cellIndex=%d, cellCount=%d)\n", cellIndex, cellCount);
          if (cellIndex < cellCount)
          {
            nsTableCell* cell = (nsTableCell *) row->ChildAt (cellIndex); // cell: REFCNT++
            if (gsDebug==PR_TRUE) printf("      calling BuildCellIntoMap(cell, %d, %d), and incrementing cellIndex\n", rowIndex + rowStart, colIndex);
            BuildCellIntoMap (cell, rowIndex + rowStart, colIndex);
            NS_RELEASE(cell);                                             // cell: REFCNT--
            cellIndex++;
          }
        }
        colIndex++;
      }

      if (cellIndex < cellCount)  
      {
        // We didn't use all the cells in this row up. Grow the cell
        // data because we now know that we have more columns than we
        // originally thought we had.
        if (gsDebug==PR_TRUE) printf("   calling GrowCellMap because cellIndex < %d\n", cellIndex, cellCount);
        GrowCellMap (cellCount);
        while (cellIndex < cellCount)
        {
          if (gsDebug==PR_TRUE) printf("     calling GrowCellMap again because cellIndex < %d\n", cellIndex, cellCount);
          GrowCellMap (colIndex + 1); // ensure enough cols in map, may be low due to colspans
          CellData *data =mCellMap->GetCellAt(rowIndex + rowStart, colIndex);
          if (data == nsnull)
          {
            nsTableCell* cell = (nsTableCell *) row->ChildAt (cellIndex); // cell: REFCNT++
            BuildCellIntoMap (cell, rowIndex + rowStart, colIndex);
            cellIndex++;
            NS_RELEASE(cell);                                             // cell: REFCNT--
          }
          colIndex++;
        }
      }
      NS_RELEASE(row);      // row: REFCNT--
    }
    NS_RELEASE(rowGroup);   // rowGroup: REFCNT--
    rowStart += groupRowCount;
    groupIndex = NextRowGroup (groupIndex);
  }
  if (gsDebug==PR_TRUE)
    DumpCellMap ();
  EnsureColumns();
}

/**
  */
void nsTablePart::DumpCellMap () const
{
  printf("dumping CellMap:\n");
  if (nsnull != mCellMap)
  {
    int rowCount = mCellMap->GetRowCount();
    int cols = mCellMap->GetColCount();
    for (int r = 0; r < rowCount; r++)
    {
      if (gsDebug==PR_TRUE)
      { printf("row %d", r);
        printf(": ");
      }
      for (int c = 0; c < cols; c++)
      {
        CellData *cd =mCellMap->GetCellAt(r, c);
        if (cd != nsnull)
        {
          if (cd->mCell != nsnull)
          {
            printf("C%d,%d ", r, c);
            printf("     ");
          }
          else
          {
            nsTableCell *cell = cd->mRealCell->mCell;
            nsTableRow *row = cell->GetRow();
            int rr = row->GetRowIndex ();
            int cc = cell->GetColIndex ();
            printf("S%d,%d ", rr, cc);
            if (cd->mOverlap != nsnull)
            {
              cell = cd->mOverlap->mCell;
              nsTableRow* row2 = cell->GetRow();
              rr = row2->GetRowIndex ();
              cc = cell->GetColIndex ();
              printf("O%d,%c ", rr, cc);
              NS_RELEASE(row2);
            }
            else
              printf("     ");
            NS_RELEASE(row);
          }
        }
        else
          printf("----      ");
      }
      printf("\n");
    }
  }
  else
    printf ("[nsnull]");
}

void nsTablePart::BuildCellIntoMap (nsTableCell *aCell, int aRowIndex, int aColIndex)
{
  //SEC Assert.Assertion (aColIndex < mColCount);
  //SEC Assert.Assertion (nsnull!=aCell);

  // Setup CellMap for this cell
  int rowSpan = GetEffectiveRowSpan (aRowIndex, aCell);
  int colSpan = aCell->GetColSpan ();
  if (gsDebug==PR_TRUE) printf("        BuildCellIntoMap. rowSpan = %d, colSpan = %d\n", rowSpan, colSpan);

  // Grow the mCellMap array if we will end up addressing
  // some new columns.
  if (mColCount < (aColIndex + colSpan))
  {
    if (gsDebug==PR_TRUE) printf("        mColCount=%d<aColIndex+colSpan so calling GrowCellMap(%d)\n", mColCount, aColIndex+colSpan);
    GrowCellMap (aColIndex + colSpan);
  }

  // Setup CellMap for this cell in the table
  CellData *data = new CellData ();
  data->mCell = aCell;
  data->mRealCell = data;
  if (gsDebug==PR_TRUE) printf("        calling mCellMap->SetCellAt(data, %d, %d)\n", aRowIndex, aColIndex);
  mCellMap->SetCellAt(data, aRowIndex, aColIndex);
  aCell->SetColIndex (aColIndex);

  // Create CellData objects for the rows that this cell spans. Set
  // their mCell to nsnull and their mRealCell to point to data. If
  // there were no column overlaps then we could use the same
  // CellData object for each row that we span...
  if ((1 < rowSpan) || (1 < colSpan))
  {
    if (gsDebug==PR_TRUE) printf("        spans\n");
    for (int rowIndex = 0; rowIndex < rowSpan; rowIndex++)
    {
      if (gsDebug==PR_TRUE) printf("          rowIndex = %d\n", rowIndex);
      int workRow = aRowIndex + rowIndex;
      if (gsDebug==PR_TRUE) printf("          workRow = %d\n", workRow);
      for (int colIndex = 0; colIndex < colSpan; colIndex++)
      {
        if (gsDebug==PR_TRUE) printf("            colIndex = %d\n", colIndex);
        int workCol = aColIndex + colIndex;
        if (gsDebug==PR_TRUE) printf("            workCol = %d\n", workCol);
        CellData *testData = mCellMap->GetCellAt(workRow, workCol);
        if (nsnull == testData)
        {
          CellData *spanData = new CellData ();
          spanData->mRealCell = data;
          if (gsDebug==PR_TRUE) printf("            null GetCellAt(%d, %d) so setting to spanData\n", workRow, workCol);
          mCellMap->SetCellAt(spanData, workRow, workCol);
        }
        else if ((0 < rowIndex) || (0 < colIndex))
        { // we overlap, replace existing data, it might be shared
          if (gsDebug==PR_TRUE) printf("            overlapping Cell from GetCellAt(%d, %d) so setting to spanData\n", workRow, workCol);
          CellData *overlap = new CellData ();
          overlap->mCell = testData->mCell;
          overlap->mRealCell = testData->mRealCell;
          overlap->mOverlap = data;
          mCellMap->SetCellAt(overlap, workRow, workCol);
        }
      }
    }
  }
}

void nsTablePart::GrowCellMap (int aColCount)
{
  if (nsnull!=mCellMap)
  {
    if (mColCount < aColCount)
    {
      mCellMap->GrowTo(aColCount);
    }
    mColCount = aColCount;
  }
}

int nsTablePart::GetEffectiveRowSpan (int aRowIndex, nsTableCell *aCell)
{
  //SEC Assert.Assertion (nsnull!=aCell)
  int rowSpan = aCell->GetRowSpan ();
  int rowCount = GetRowCount ();
  if (rowCount < (aRowIndex + rowSpan))
    return (rowCount - aRowIndex);
  return rowSpan;
}


/**
 * Create a frame object that will layout this table.
 */
nsIFrame* nsTablePart::CreateFrame(nsIPresContext* aPresContext,
                                   PRInt32 aIndexInParent,
                                  nsIFrame* aParentFrame)
{
  nsIFrame* rv;
  nsresult status = nsTableOuterFrame::NewFrame(&rv, this, aIndexInParent,
                                                aParentFrame);
  return rv;
}

/* ---------- Global Functions ---------- */

/**
  */
nsresult
NS_NewTablePart(nsIHTMLContent** aInstancePtrResult,
               nsIAtom* aTag)
{
  NS_PRECONDITION(nsnull != aInstancePtrResult, "nsnull ptr");
  if (nsnull == aInstancePtrResult) {
    return NS_ERROR_NULL_POINTER;
  }
  nsIHTMLContent* table = new nsTablePart(aTag);
  if (nsnull == table) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return table->QueryInterface(kIHTMLContentIID, (void **) aInstancePtrResult);
}

