/* -*- 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 "nsFrameSetFrame.h"
#include "nsIHTMLContent.h"
#include "nsLeafFrame.h"
#include "nsHTMLContainerFrame.h"
#include "nsIWebShell.h"
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsHTMLIIDs.h"
#include "nsRepository.h"
#include "nsIStreamListener.h"
#include "nsIURL.h"
#include "nsIDocument.h"
#include "nsIView.h"
#include "nsIViewManager.h"
#include "nsWidgetsCID.h"
#include "nsViewsCID.h"
#include "nsHTMLAtoms.h"
#include "nsIScrollableView.h"
#include "nsStyleCoord.h"
#include "nsStyleConsts.h"
#include "nsIStyleContext.h"
#include "nsIDocumentLoader.h"
#include "nsGenericHTMLElement.h"
#include "nsHTMLParts.h"
// masks for mEdgeVisibility
#define LEFT_VIS   0x0001
#define RIGHT_VIS  0x0002
#define TOP_VIS    0x0004
#define BOTTOM_VIS 0x0008
#define ALL_VIS    0x000F
#define NONE_VIS   0x0000
static nsPoint NULL_POINT(-1000000,1000000);
static PRInt32 LEFT_EDGE  = -1;
static PRInt32 RIGHT_EDGE = 1000000;
static NS_DEFINE_IID(kIFramesetFrameIID, NS_IFRAMESETFRAME_IID);
/*******************************************************************************
 * nsHTMLFramesetBorderFrame
 ******************************************************************************/
class nsHTMLFramesetBorderFrame : public nsLeafFrame {
public:
  NS_IMETHOD GetFrameName(nsString& aResult) const;
  NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, 
                         nsGUIEvent* aEvent,
                         nsEventStatus& aEventStatus);
  NS_IMETHOD GetFrameForPoint(const nsPoint& aPoint, 
                              nsIFrame**     aFrame);
  NS_IMETHOD GetCursor(nsIPresContext& aPresContext,
                             nsPoint&        aPoint,
                             PRInt32&        aCursor);
  
  NS_IMETHOD Paint(nsIPresContext& aPresContext,
                   nsIRenderingContext& aRenderingContext,
                   const nsRect& aDirtyRect);
  NS_IMETHOD Reflow(nsIPresContext&          aPresContext,
                    nsHTMLReflowMetrics&     aDesiredSize,
                    const nsHTMLReflowState& aReflowState,
                    nsReflowStatus&          aStatus);
  PRBool GetVisibility() { return mVisibility; }
  void SetVisibility(PRBool aVisibility);
  void SetColor(nscolor aColor);
protected:
  nsHTMLFramesetBorderFrame(nsIContent* aContent, nsIFrame* aParentFrame, 
                            PRInt32 aWidth, PRBool aVertical, PRBool aVisible);
  virtual ~nsHTMLFramesetBorderFrame();
  virtual void GetDesiredSize(nsIPresContext* aPresContext,
                              const nsHTMLReflowState& aReflowState,
                              nsHTMLReflowMetrics& aDesiredSize);
  PRInt32 mWidth;
  PRBool mVertical;
  PRBool mVisibility;
  nscolor mColor;
  // the prev and next neighbors are indexes into the row (for a horizontal border) or col (for
  // a vertical border) of nsHTMLFramesetFrames or nsHTMLFrames
  PRInt32 mPrevNeighbor; 
  PRInt32 mNextNeighbor;
  PRBool mCanResize;
  friend class nsHTMLFramesetFrame;
};
/*******************************************************************************
 * nsHTMLFramesetBlankFrame
 ******************************************************************************/
class nsHTMLFramesetBlankFrame : public nsLeafFrame {
public:
  NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;
  NS_IMETHOD Paint(nsIPresContext& aPresContext,
                   nsIRenderingContext& aRenderingContext,
                   const nsRect& aDirtyRect);
  NS_IMETHOD Reflow(nsIPresContext&          aPresContext,
                    nsHTMLReflowMetrics&     aDesiredSize,
                    const nsHTMLReflowState& aReflowState,
                    nsReflowStatus&          aStatus);
protected:
  nsHTMLFramesetBlankFrame(nsIContent* aContent, nsIFrame* aParentFrame);
  virtual ~nsHTMLFramesetBlankFrame();
  virtual void GetDesiredSize(nsIPresContext* aPresContext,
                              const nsHTMLReflowState& aReflowState,
                              nsHTMLReflowMetrics& aDesiredSize);
  friend class nsHTMLFramesetFrame;
  friend class nsHTMLFrameset;
};
/*******************************************************************************
 * nsHTMLFramesetFrame
 ******************************************************************************/
PRInt32 nsHTMLFramesetFrame::gMaxNumRowColSpecs = 25;
PRBool  nsHTMLFramesetFrame::gDragInProgress = PR_FALSE;
nsHTMLFramesetFrame::nsHTMLFramesetFrame(nsIContent* aContent, nsIFrame* aParent)
  : nsHTMLContainerFrame(aContent, aParent)
{
  mNumRows  = 0;
  mRowSpecs = nsnull;
  mRowSizes = nsnull;
  mNumCols  = 0;
  mColSpecs = nsnull;
  mColSizes = nsnull;
  mEdgeVisibility = 0;
  mEdgeColors.Set(NO_COLOR);
  mParentFrameborder = eFrameborder_Yes; // default
  mParentBorderWidth = -1; // default not set
  mParentBorderColor = NO_COLOR; // default not set
  mLastDragPoint.x = mLastDragPoint.y = 0;
  mMinDrag = 0;
}
nsHTMLFramesetFrame::~nsHTMLFramesetFrame()
{
  printf("nsFramesetFrame destructor %X \n", this);
  if (mRowSizes) delete [] mRowSizes;
  if (mRowSpecs) delete [] mRowSpecs;
  if (mColSizes) delete [] mColSizes;
  if (mColSpecs) delete [] mColSpecs;
  mRowSizes = mColSizes = nsnull;
  mRowSpecs = mColSpecs = nsnull;
}
nsresult nsHTMLFramesetFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
  if (NULL == aInstancePtr) {
    return NS_ERROR_NULL_POINTER;
  } else if (aIID.Equals(kIFramesetFrameIID)) {
    *aInstancePtr = (void*)this;
    return NS_OK;
  } 
  return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
}
/**
  * Translate the rows/cols specs into an array of integer sizes for
  * each cell in the frameset. The sizes are computed by first
  * rationalizing the spec into a set of percentages that total
  * 100. Then the cell sizes are computed.
  */
  // XXX This doesn't do a good job of translating "*,625,*" specs
void nsHTMLFramesetFrame::CalculateRowCol(nsIPresContext* aPresContext, nscoord aSize, 
                                          PRInt32 aNumSpecs, nsFramesetSpec* aSpecs, 
                                          nscoord* aValues)
{
  // Total up the three different type of grid cell
  // percentages. Transfer the resulting values into result for
  // temporary storage (we don't overwrite the cell specs)
  PRInt32 free    = 0;
  PRInt32 percent = 0;
  PRInt32 pixel   = 0;
  float p2t;
  aPresContext->GetScaledPixelsToTwips(p2t);
  int i; // feeble compiler
  for (i = 0; i < aNumSpecs; i++) {
    switch (aSpecs[i].mUnit) {
      case eFramesetUnit_Pixel:
        // Now that we know the size we are laying out in, turn fixed
        // pixel dimensions into percents.
        // XXX maybe use UnitConverter for proper rounding? MMP
        aValues[i] = NSToCoordRound(100 * p2t * aSpecs[i].mValue / aSize);
        if (aValues[i] < 1) aValues[i] = 1;
        pixel += aValues[i];
        break;
      case eFramesetUnit_Percent:
        aValues[i] = aSpecs[i].mValue;
        percent += aValues[i];
        break;
      case eFramesetUnit_Free:
        aValues[i] = aSpecs[i].mValue;
        free += aValues[i];
        break;
    }
  }
  if (percent + pixel < 100) {
    // The user didn't explicitly use up all the space. Spread the
    // left over space to the free percentage cells first, then to
    // the normal percentage cells second, and finally to the fixed
    // cells as a last resort.
    if (free != 0) {
      // We have some free percentage cells that want to soak up the
      // excess space. Spread out the left over space to those cells.
      int used = 0;
      int last = -1;
      int leftOver = 100 - (percent + pixel);
      for (int i = 0; i < aNumSpecs; i++) {
        if (eFramesetUnit_Free == aSpecs[i].mUnit) {
          aValues[i] = aValues[i] * leftOver / free;
          if (aValues[i] < 1) aValues[i] = 1;
          used += aValues[i];
          last = i;
        }
      }
      int remainder = 100 - percent - pixel - used;
      if ((remainder > 0) && (last >= 0)) {
        // Slop the extra space into the last qualifying cell
        aValues[last] += remainder;
      }
    } else if (percent != 0) {
      // We have no free cells but have some normal percentage
      // cells. Distribute the extra space among them.
      int used = 0;
      int last = -1;
      int leftOver = 100 - pixel;
      for (int i = 0; i < aNumSpecs; i++) {
        if (eFramesetUnit_Percent == aSpecs[i].mUnit) {
          aValues[i] = aValues[i] * leftOver / percent;
          used += aValues[i];
          last = i;
        }
      }
      if ((used < leftOver) && (last >= 0)) {
        // Slop the extra space into the last qualifying cell
        aValues[last] = aValues[last] + (leftOver - used);
      }
    } else {
      // Give the leftover space to the fixed percentage
      // cells. Recall that at the start of this method we converted
      // the fixed pixel values into percentages.
      int used = 0;
      for (int i = 0; i < aNumSpecs; i++) {
        aValues[i] = aValues[i] * 100 / pixel;
        used += aValues[i];
      }
      if ((used < 100) && (aNumSpecs > 0)) {
        // Slop the extra space into the last cell
        aValues[aNumSpecs - 1] += (100 - used);
      }
    }
  } else if (percent + pixel > 100) {
    // The user overallocated the space. We need to shrink something.
    // If there is not too much fixed percentage added, we can
    // just shrink the normal percentage cells to make things fit.
    if (pixel <= 100) {
      int used = 0;
      int last = -1;
      int val = 100 - pixel;
      for (int i = 0; i < aNumSpecs; i++) {
        if (eFramesetUnit_Percent == aSpecs[i].mUnit) {
          aValues[i] = aValues[i] * val / percent;
          used += aValues[i];
          last = i;
        }
      }
      // Since integer division always truncates, we either made
      // it fit exactly, or overcompensated and made it too small.
      if ((used < val) && (last >= 0)) {
        aValues[last] += (val - used);
      }
    } else {
      // There is too much fixed percentage as well. We will just
      // shrink all the cells.
      int used = 0;
      for (int i = 0; i < aNumSpecs; i++) {
        if (eFramesetUnit_Free == aSpecs[i].mUnit) {
          aValues[i] = 0;
        } else {
          aValues[i] = aValues[i] * 100 / (percent + pixel);
        }
        used += aValues[i];
      }
      if ((used < 100) && (aNumSpecs > 0)) {
        aValues[aNumSpecs-1] += (100 - used);
      }
    }
  }
  // Now map the result which contains nothing but percentages into
  // fixed pixel values.
  int sum = 0;
  for (i = 0; i < aNumSpecs; i++) {
    aValues[i] = (aValues[i] * aSize) / 100;
    sum += aValues[i];
  }
  //Assert.Assertion(sum <= aSize);
  if ((sum < aSize) && (aNumSpecs > 0)) {
    // Any remaining pixels (from roundoff) go into the last cell
    aValues[aNumSpecs-1] += aSize - sum;
  }
}
PRInt32 nsHTMLFramesetFrame::GetBorderWidth(nsIPresContext* aPresContext) 
{
  float p2t;
  aPresContext->GetScaledPixelsToTwips(p2t);
  nsHTMLValue htmlVal;
  PRInt32 intVal;
  nsIHTMLContent* content = nsnull;
  mContent->QueryInterface(kIHTMLContentIID, (void**)&content);
  if (nsnull != content) {
    if (NS_CONTENT_ATTR_HAS_VALUE == (content->GetAttribute(nsHTMLAtoms::border, htmlVal))) {
      if (eHTMLUnit_Pixel == htmlVal.GetUnit()) {
        intVal = htmlVal.GetPixelValue();
      } else {
        intVal = htmlVal.GetIntValue();
      }
      if (intVal < 0) {
        intVal = 0;
      }
      NS_RELEASE(content);
      return NSIntPixelsToTwips(intVal, p2t);
    }
    NS_RELEASE(content);
  }
  if (mParentBorderWidth >= 0) {
    return mParentBorderWidth;
  }
  return NSIntPixelsToTwips(6, p2t);
}
PRIntn
nsHTMLFramesetFrame::GetSkipSides() const
{
  return 0;
}
void 
nsHTMLFramesetFrame::GetDesiredSize(nsIPresContext* aPresContext,
                                    const nsHTMLReflowState& aReflowState,
                                    nsHTMLReflowMetrics& aDesiredSize)
{
  nsHTMLFramesetFrame* framesetParent = GetFramesetParent(this);
  if (nsnull == framesetParent) {
    nsRect area;
    aPresContext->GetVisibleArea(area);
    aDesiredSize.width = area.width;
    aDesiredSize.height= area.height;
  } else {
    nsSize size;
    framesetParent->GetSizeOfChild(this, size);
    aDesiredSize.width  = size.width;
    aDesiredSize.height = size.height;
  } 
  aDesiredSize.ascent = aDesiredSize.height;
  aDesiredSize.descent = 0;
}
nsHTMLFramesetFrame* nsHTMLFramesetFrame::GetFramesetParent(nsIFrame* aChild)
{
  nsHTMLFramesetFrame* parent = nsnull;
  nsIContent* content = nsnull;
  aChild->GetContent(content);
  if (nsnull != content) { 
    nsIContent* contentParent = nsnull;
    content->GetParent(contentParent);
    if (nsnull != contentParent) {
      nsIHTMLContent* contentParent2 = nsnull;
      contentParent->QueryInterface(kIHTMLContentIID, (void**)&contentParent2);
      if (nsnull != contentParent2) {
        nsIAtom* tag;
        contentParent2->GetTag(tag);
        if (nsHTMLAtoms::frameset == tag) {
          aChild->GetGeometricParent((nsIFrame*&)parent);
        }
        NS_IF_RELEASE(tag);
        NS_RELEASE(contentParent2);
      }
      NS_RELEASE(contentParent);
    }
    NS_RELEASE(content);
  }
  return parent;
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChildAt(PRInt32 aIndexInParent, nsSize& aSize, nsPoint& aCellIndex)
{
  PRInt32 row = aIndexInParent / mNumCols;
  PRInt32 col = aIndexInParent - (row * mNumCols); // remainder from dividing index by mNumCols
  if ((row < mNumRows) && (col < mNumCols)) {
    aSize.width  = mColSizes[col];
    aSize.height = mRowSizes[row];
    aCellIndex.x = col;
    aCellIndex.y = row;
  } else {
    aSize.width = aSize.height = aCellIndex.x = aCellIndex.y = 0;
  }
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChild(nsIFrame* aChild, 
                                         nsSize& aSize)
{
  // Reflow only creates children frames for