/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Mozilla Communicator client code.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Original Author: David W. Hyatt (hyatt@netscape.com)
 *
 * Contributor(s): 
 */

#include "nsCOMPtr.h"
#include "nsTreeFrame.h"
#include "nsTreeRowFrame.h"
#include "nsIStyleContext.h"
#include "nsIContent.h"
#include "nsCSSRendering.h"
#include "nsTreeCellFrame.h"
#include "nsCellMap.h"
#include "nsIDOMXULTreeElement.h"
#include "nsINameSpaceManager.h"
#include "nsTreeRowGroupFrame.h"
#include "nsXULAtoms.h"
#include "nsHTMLAtoms.h"
#include "nsTableColFrame.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMXULTreeElement.h"
#include "nsIViewManager.h"
#include "nsIView.h"
#include "nsIPresContext.h"

//
// NS_NewTreeFrame
//
// Creates a new tree frame
//
nsresult
NS_NewTreeRowFrame (nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  if (nsnull == aNewFrame) {
    return NS_ERROR_NULL_POINTER;
  }
  nsTreeRowFrame* it = new (aPresShell) nsTreeRowFrame;
  if (!it)
    return NS_ERROR_OUT_OF_MEMORY;

  *aNewFrame = it;
  return NS_OK;
  
} // NS_NewTreeRowFrame


// Constructor
nsTreeRowFrame::nsTreeRowFrame()
:nsTableRowFrame(), mIsHeader(PR_FALSE), mGeneration(0), mDraggingHeader(PR_FALSE), 
 mHitFrame(nsnull), mFlexingCol(nsnull), mHeaderPosition(0)
{ }

// Destructor
nsTreeRowFrame::~nsTreeRowFrame()
{
}

NS_METHOD nsTreeRowFrame::IR_TargetIsChild(nsIPresContext*      aPresContext,
                                            nsHTMLReflowMetrics& aDesiredSize,
                                            RowReflowState&      aReflowState,
                                            nsReflowStatus&      aStatus,
                                            nsIFrame *           aNextFrame)
{
  nsRect rect;
  GetRect(rect);
  nsresult rv = nsTableRowFrame::IR_TargetIsChild(aPresContext, aDesiredSize, aReflowState, aStatus, aNextFrame);
  if (rv == NS_OK) {
    // Find out if our height changed.  With the tree widget, changing the height of a row is a
    // big deal, since it may force us to dynamically isntantiate newly exposed frames.
    if (rect.height != aDesiredSize.height) {
      // Retrieve the table frame and invalidate the cell map.
      nsTableFrame* tableFrame = nsnull;
      nsTableFrame::GetTableFrame(this, tableFrame);
  
      tableFrame->InvalidateColumnWidths(); 
    }
  }
  return rv;
}


NS_IMETHODIMP
nsTreeRowFrame::Init(nsIPresContext*  aPresContext,
                      nsIContent*      aContent,
                      nsIFrame*        aParent,
                      nsIStyleContext* aContext,
                      nsIFrame*        aPrevInFlow)
{
  nsresult rv = nsTableRowFrame::Init(aPresContext, aContent, aParent, aContext,
                                aPrevInFlow);
  
  // Determine if we're a column header or not.
  // Get row group frame
  if (aParent != nsnull)
  {
		// Get the display type of the row group frame and see if it's a header or body
		nsCOMPtr<nsIStyleContext> parentContext;
		aParent->GetStyleContext(getter_AddRefs(parentContext));
		if (parentContext)
		{
			const nsStyleDisplay* display = (const nsStyleDisplay*)
				parentContext->GetStyleData(eStyleStruct_Display);
			if (display->mDisplay == NS_STYLE_DISPLAY_TABLE_HEADER_GROUP)
			{
				mIsHeader = PR_TRUE;

        // headers get their own views, so that they can capture events
        CreateViewForFrame(aPresContext,this,aContext,PR_TRUE);
        nsIView* view;
        GetView(aPresContext, &view);
        view->SetContentTransparency(PR_TRUE);
			}
			else 
      {
        mIsHeader = PR_FALSE;
        
        // Determine the row's generation.
        nsTableFrame* tableFrame;
        nsTableFrame::GetTableFrame(aParent, tableFrame);
        nsTreeFrame* treeFrame = (nsTreeFrame*)tableFrame;
        mGeneration = treeFrame->GetCurrentGeneration();
      }
		}
  }

  return rv;
}

NS_IMETHODIMP
nsTreeRowFrame::HeaderDrag(nsIPresContext* aPresContext, PRBool aGrabMouseEvents)
{
    // get its view
  nsIView* view = nsnull;
  GetView(aPresContext, &view);
  nsCOMPtr<nsIViewManager> viewMan;
  PRBool result;

  if (view) {
    view->GetViewManager(*getter_AddRefs(viewMan));

    if (viewMan) {
      if (aGrabMouseEvents) {
        viewMan->GrabMouseEvents(view,result);
        mDraggingHeader = PR_TRUE;
      } else {
        viewMan->GrabMouseEvents(nsnull,result);
        mDraggingHeader = PR_FALSE;
      }
    }
  }

  return NS_OK;
}


NS_IMETHODIMP
nsTreeRowFrame::Reflow(nsIPresContext*          aPresContext,
							         nsHTMLReflowMetrics&     aDesiredSize,
							         const nsHTMLReflowState& aReflowState,
							         nsReflowStatus&          aStatus)
{
  DO_GLOBAL_REFLOW_COUNT("nsTreeRowFrame", aReflowState.reason);
/*
  if (aReflowState.reason != eReflowReason_Incremental) {
    // Determine the row's generation.
    nsTableFrame* tableFrame;
    nsTableFrame::GetTableFrame(this, tableFrame);
    nsTreeFrame* treeFrame = (nsTreeFrame*)tableFrame;
    if (treeFrame->UseGeneration()) {
      PRInt32 currGeneration = treeFrame->GetCurrentGeneration();
      if (currGeneration > mGeneration) {
        nsRect rect;
        GetRect(rect);
        aDesiredSize.width = rect.width;
        aDesiredSize.height = rect.height;
        aStatus = NS_FRAME_COMPLETE;
        return NS_OK;
      }
    }
  }
*/
 /* static int i = 0;
  i++;
  printf("Full row reflow! Number %d\n", i);
*/

  // Debugging info.
  NS_ASSERTION(aReflowState.mComputedWidth != NS_UNCONSTRAINEDSIZE, 
               "Reflowing tree row frame with unconstrained width!!!!");
  
  //printf("Tree Row Width: %d, Tree Row Height: %d\n", aReflowState.mComputedWidth, aReflowState.mComputedHeight);

  if (mState & NS_FRAME_FIRST_REFLOW) {
    // Newly inserted frame
    ((nsHTMLReflowState&)aReflowState).reason = eReflowReason_Initial;
  }

  return nsTableRowFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
}

nsresult
nsTreeRowFrame::HandleMouseUpEvent(nsIPresContext* aPresContext, 
									                  nsGUIEvent*     aEvent,
									                  nsEventStatus*  aEventStatus)
{
  if (DraggingHeader()) {
    HeaderDrag(aPresContext, PR_FALSE);
  }
  return NS_OK;
}

NS_IMETHODIMP 
nsTreeRowFrame::HandleEvent(nsIPresContext* aPresContext, 
                             nsGUIEvent*     aEvent,
                             nsEventStatus*  aEventStatus)
{
  NS_ENSURE_ARG_POINTER(aEventStatus);
  *aEventStatus = nsEventStatus_eConsumeDoDefault;
	if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP)
    HandleMouseUpEvent(aPresContext, aEvent, aEventStatus);
  else if (aEvent->message == NS_MOUSE_MOVE && mDraggingHeader && mHitFrame)
    HandleHeaderDragEvent(aPresContext, aEvent, aEventStatus);
  return NS_OK;
}

NS_IMETHODIMP
nsTreeRowFrame::GetFrameForPoint(nsIPresContext* aPresContext,
                                 const nsPoint& aPoint, // Overridden to capture events
                                 nsFramePaintLayer aWhichLayer,
                                 nsIFrame**     aFrame)
{
  nsresult rv = nsTableRowFrame::GetFrameForPoint(aPresContext, aPoint, aWhichLayer, aFrame);
  if (mDraggingHeader) {
    mHitFrame = *aFrame;
    *aFrame = this;
    nsRect rect;
    GetRect(rect);
    if (rect.x > aPoint.x || (rect.x+rect.width < aPoint.x)) {
      mHitFrame = nsnull;
    }
  }

  return rv;
}

NS_IMETHODIMP
nsTreeRowFrame::GetCursor(nsIPresContext* aPresContext,
                                     nsPoint&        aPoint,
                                     PRInt32&        aCursor)
{
  if (mDraggingHeader) {
    nsRect rect;
    GetRect(rect);
    if (rect.x > aPoint.x || (rect.x+rect.width < aPoint.x)) {
      aCursor = NS_STYLE_CURSOR_DEFAULT;
    }
    else {
      aCursor = NS_STYLE_CURSOR_W_RESIZE;
    }
  }
  else aCursor = NS_STYLE_CURSOR_DEFAULT;
  return NS_OK;
}

nsresult
nsTreeRowFrame::HandleHeaderDragEvent(nsIPresContext* aPresContext, 
									                    nsGUIEvent*     aEvent,
									                    nsEventStatus*  aEventStatus)
{
  // Grab our tree frame.
  nsTableFrame* tableFrame = nsnull;
  nsTableFrame::GetTableFrame(this, tableFrame);
  nsTreeFrame* treeFrame = (nsTreeFrame*)tableFrame;
  
  // Until we finish all of our batched operations, suppress all reflow.
  // XXX How to suppress?

  PRInt32 columnCount = treeFrame->GetColCount();
  PRInt32* colWidths = new PRInt32[columnCount];
  nsCRT::memset(colWidths, 0, columnCount*sizeof(PRInt32));

  // Retrieve our column widths.
  PRInt32 i;
  for (i = 0; i < columnCount; i++) {
    nsTableColFrame* result = treeFrame->GetColFrame(i);
    nsCOMPtr<nsIContent> colContent;
    result->GetContent(getter_AddRefs(colContent));
    nsCOMPtr<nsIAtom> fixedAtom = dont_AddRef(NS_NewAtom("fixed"));
    if (colContent) {
      nsAutoString fixedValue;
      colContent->GetAttribute(kNameSpaceID_None, fixedAtom, fixedValue);
      if (!fixedValue.EqualsWithConversion("true")) {
        // We are a proportional column and should be annotated with our current
        // width.
        PRInt32 colWidth = treeFrame->GetColumnWidth(i);
        colWidths[i] = colWidth;
      }
    }
  }

  // Annotate with the current proportions
  for (i = 0; i < columnCount; i++) {
    if (colWidths[i] > 0) {
      nsTableColFrame* result = treeFrame->GetColFrame(i);
      nsCOMPtr<nsIContent> colContent;
      result->GetContent(getter_AddRefs(colContent));
      if (colContent) {
        PRInt32 colWidth = colWidths[i];
        char ch[100];
        sprintf(ch,"%d*", colWidth);
        nsAutoString propColWidth; propColWidth.AssignWithConversion(ch);
        colContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::width, propColWidth,
                                 PR_TRUE);
      }
    }
  }

  // Figure out how much we shifted the mouse.
  char ch[100];
  nsPoint point = ((nsMouseEvent*)aEvent)->point;
  PRInt32 delta = mHeaderPosition - point.x;
  mHeaderPosition = point.x;

  // The proportional columns to the right will gain or lose space
  // according to the percentages they currently consume.
  nscoord propTotal = 0;

  // Find our flexing col and note its index and width.
  PRInt32 colX;
  PRInt32 flexWidth = 0;
  PRInt32 flexIndex = 0;
  for (colX = 0; colX < columnCount; colX++) { 
    // Get column information
    nsTableColFrame* colFrame = tableFrame->GetColFrame(colX);
    if (colFrame == mFlexingCol) {
      flexWidth = colWidths[colX];
      flexIndex = colX;
      break;
    }
  }
  
  for (colX = flexIndex+1; colX < columnCount; colX++) {
    // Retrieve the current widths for these columns and compute
    // the total amount of space they occupy.
    propTotal += colWidths[colX];
  }

  // Iterate over the columns to the right of the flexing column, 
  // and give them a percentage of the delta based off their proportions.
  nsCOMPtr<nsIContent> colContent;
  nsTableColFrame* colFrame;
  PRInt32 colWidth = 0;
  PRInt32 remaining = delta;
  for (colX = flexIndex+1; colX < columnCount; colX++) {
    if (colWidths[colX] > 0) {
      colFrame = tableFrame->GetColFrame(colX);
      float percentage = ((float)colWidths[colX])/((float)propTotal);
      PRInt32 mod = (PRInt32)(percentage * (float)delta);
      colWidth = colWidths[colX] + mod;
     
      sprintf(ch,"%d*", colWidth);
      nsAutoString propColWidth; propColWidth.AssignWithConversion(ch);
      
      colFrame->GetContent(getter_AddRefs(colContent));
      if (colContent) {
        colContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::width, propColWidth,
                                 PR_TRUE);
      }

      remaining -= mod;
    }
  }

  // Fix the spillover. We'll probably be off by a little.
  if (remaining != 0 && colContent) {
    colWidth += remaining;
    sprintf(ch,"%d*", colWidth);
    nsAutoString propColWidth; propColWidth.AssignWithConversion(ch);
    colContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::width, propColWidth,
                             PR_TRUE);

  }

  // Delete the colWidths array.
  delete []colWidths;
  
  // Modify the flexing column by the delta.
  nsCOMPtr<nsIContent> flexContent;
  mFlexingCol->GetContent(getter_AddRefs(flexContent));
  if (flexContent) {
    treeFrame->SetUseGeneration(PR_FALSE); // Cached rows have to reflow.
    
    colWidth = flexWidth - delta;
    sprintf(ch,"%d*", colWidth);
    nsAutoString propColWidth; propColWidth.AssignWithConversion(ch);
    flexContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::width, propColWidth,
                              PR_TRUE); // NOW we send the notification that causes the reflow.
  }
  return NS_OK;
}
