/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * 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 the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Original Author: David W. Hyatt (hyatt@netscape.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either 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 NPL, 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 NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsCOMPtr.h"
#include "nsXULTreeFrame.h"
#include "nsIScrollableFrame.h"
#include "nsIDOMElement.h"
#include "nsXULTreeOuterGroupFrame.h"
#include "nsXULAtoms.h"
#include "nsINameSpaceManager.h"

//
// NS_NewXULTreeFrame
//
// Creates a new tree frame
//
nsresult
NS_NewXULTreeFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRBool aIsRoot, 
                   nsIBoxLayout* aLayoutManager)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  if (nsnull == aNewFrame) {
    return NS_ERROR_NULL_POINTER;
  }
  nsXULTreeFrame* it = new (aPresShell) nsXULTreeFrame(aPresShell, aIsRoot, aLayoutManager);
  if (!it)
    return NS_ERROR_OUT_OF_MEMORY;

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


// Constructor
nsXULTreeFrame::nsXULTreeFrame(nsIPresShell* aPresShell, PRBool aIsRoot, nsIBoxLayout* aLayoutManager)
:nsBoxFrame(aPresShell, aIsRoot, aLayoutManager) 
{
  mPresShell = aPresShell;
}

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

NS_IMETHODIMP_(nsrefcnt) 
nsXULTreeFrame::AddRef(void)
{
  return NS_OK;
}

NS_IMETHODIMP_(nsrefcnt)
nsXULTreeFrame::Release(void)
{
  return NS_OK;
}

//
// QueryInterface
//
NS_INTERFACE_MAP_BEGIN(nsXULTreeFrame)
  NS_INTERFACE_MAP_ENTRY(nsITreeFrame)
NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame)

static void
GetImmediateChild(nsIContent* aParent, nsIAtom* aTag, nsIContent** aResult) 
{
  *aResult = nsnull;
  PRInt32 childCount;
  aParent->ChildCount(childCount);
  for (PRInt32 i = 0; i < childCount; i++) {
    nsCOMPtr<nsIContent> child;
    aParent->ChildAt(i, *getter_AddRefs(child));
    nsCOMPtr<nsIAtom> tag;
    child->GetTag(*getter_AddRefs(tag));
    if (aTag == tag.get()) {
      *aResult = child;
      NS_ADDREF(*aResult);
      return;
    }
  }

  return;
}

NS_IMETHODIMP
nsXULTreeFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
{
  nsresult rv = nsBoxFrame::DoLayout(aBoxLayoutState);

  return rv;
}

NS_IMETHODIMP
nsXULTreeFrame::EnsureRowIsVisible(PRInt32 aRowIndex)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* XULTreeOuterGroup = nsnull;
  GetTreeBody(&XULTreeOuterGroup);

  if (!XULTreeOuterGroup) return NS_OK;
  
  XULTreeOuterGroup->EnsureRowIsVisible(aRowIndex);
  return NS_OK;
}

/* void scrollToIndex (in long rowIndex); */
NS_IMETHODIMP 
nsXULTreeFrame::ScrollToIndex(PRInt32 aRowIndex)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* XULTreeOuterGroup = nsnull;
  GetTreeBody(&XULTreeOuterGroup);

  if (!XULTreeOuterGroup) return NS_OK;
  
  XULTreeOuterGroup->ScrollToIndex(aRowIndex);
  return NS_OK;
}

NS_IMETHODIMP
nsXULTreeFrame::ScrollByLines(nsIPresContext* aPresContext, PRInt32 aNumLines)
{
  PRInt32 scrollIndex, visibleRows;
  GetIndexOfFirstVisibleRow(&scrollIndex);
  GetNumberOfVisibleRows(&visibleRows);

  scrollIndex += aNumLines;
  
  if (scrollIndex < 0)
    scrollIndex = 0;
  else {
    PRInt32 numRows;
    GetRowCount(&numRows);
    PRInt32 lastPageTopRow = numRows - visibleRows;
    if (scrollIndex > lastPageTopRow)
      scrollIndex = lastPageTopRow;
  }
  
  ScrollToIndex(scrollIndex);

  // we have to do a sync update for mac because if we scroll too quickly
  // w/out going back to the main event loop we can easily scroll the wrong
  // bits and it looks like garbage (bug 63465).
  nsIFrame* frame = nsnull;
  if ( NS_SUCCEEDED(QueryInterface(NS_GET_IID(nsIFrame), (void **)&frame)) ) {
    nsIView* treeView = nsnull;
    frame->GetView(aPresContext, &treeView);
    if (!treeView) {
      nsIFrame* frameWithView;
      frame->GetParentWithView(aPresContext, &frameWithView);
      if (frameWithView)
        frameWithView->GetView(aPresContext, &treeView);
      else
        return NS_ERROR_FAILURE;
    }
    if (treeView) {
      nsCOMPtr<nsIViewManager> vm;
      if (treeView->GetViewManager(*getter_AddRefs(vm)) && nsnull != vm) {
        // I'd use Composite here, but it doesn't always work.
        // vm->Composite();
        vm->ForceUpdate();
    
      }      
    }
  }
  else
    return NS_ERROR_FAILURE;
     
  return NS_OK;
}

/* nsIDOMElement getNextItem (in nsIDOMElement startItem, in long delta); */
NS_IMETHODIMP 
nsXULTreeFrame::GetNextItem(nsIDOMElement *aStartItem, PRInt32 aDelta, nsIDOMElement **aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  nsCOMPtr<nsIContent> start(do_QueryInterface(aStartItem));
  nsCOMPtr<nsIContent> row;
  GetImmediateChild(start, nsXULAtoms::treerow, getter_AddRefs(row));

  nsCOMPtr<nsIContent> result;
  treeOuterGroup->FindNextRowContent(aDelta, row, nsnull, getter_AddRefs(result));
  if (!result)
    return NS_OK;

  nsCOMPtr<nsIContent> parent;
  result->GetParent(*getter_AddRefs(parent));
  if (!parent)
    return NS_OK;

  nsCOMPtr<nsIDOMElement> item(do_QueryInterface(parent));
  *aResult = item;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

/* nsIDOMElement getPreviousItem (in nsIDOMElement startItem, in long delta); */
NS_IMETHODIMP 
nsXULTreeFrame::GetPreviousItem(nsIDOMElement* aStartItem, PRInt32 aDelta, nsIDOMElement** aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  nsCOMPtr<nsIContent> start(do_QueryInterface(aStartItem));
  nsCOMPtr<nsIContent> row;
  GetImmediateChild(start, nsXULAtoms::treerow, getter_AddRefs(row));

  nsCOMPtr<nsIContent> result;
  treeOuterGroup->FindPreviousRowContent(aDelta, row, nsnull, getter_AddRefs(result));
  if (!result)
    return NS_OK;

  nsCOMPtr<nsIContent> parent;
  result->GetParent(*getter_AddRefs(parent));
  if (!parent)
    return NS_OK;

  nsCOMPtr<nsIDOMElement> item(do_QueryInterface(parent));
  *aResult = item;
  NS_IF_ADDREF(*aResult);

  return NS_OK;
}

/* nsIDOMElement getItemAtIndex (in long index); */
NS_IMETHODIMP 
nsXULTreeFrame::GetItemAtIndex(PRInt32 aIndex, nsIDOMElement **aResult)
{
  *aResult = nsnull;

  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  nsCOMPtr<nsIContent> result;
  treeOuterGroup->FindRowContentAtIndex(aIndex, nsnull, getter_AddRefs(result));
  if (!result)
    return NS_OK;

  nsCOMPtr<nsIContent> parent;
  result->GetParent(*getter_AddRefs(parent));
  if (!parent)
    return NS_OK;

  nsCOMPtr<nsIDOMElement> item(do_QueryInterface(parent));
  *aResult = item;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

/* long getIndexOfItem (in nsIDOMElement item); */
NS_IMETHODIMP 
nsXULTreeFrame::GetIndexOfItem(nsIPresContext* aPresContext, nsIDOMElement* aElement, PRInt32* aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  nsCOMPtr<nsIContent> content(do_QueryInterface(aElement));
  nsCOMPtr<nsIContent> root;
  treeOuterGroup->GetContent(getter_AddRefs(root));
  return treeOuterGroup->IndexOfItem(root, content, PR_FALSE, PR_TRUE, aResult);
}

/* void getNumberOfVisibleRows (); */
NS_IMETHODIMP 
nsXULTreeFrame::GetNumberOfVisibleRows(PRInt32 *aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* XULTreeOuterGroup = nsnull;
  GetTreeBody(&XULTreeOuterGroup);

  if (!XULTreeOuterGroup) return NS_OK;
  
  return XULTreeOuterGroup->GetNumberOfVisibleRows(aResult);
}

/* void getIndexOfFirstVisibleRow (); */
NS_IMETHODIMP 
nsXULTreeFrame::GetIndexOfFirstVisibleRow(PRInt32 *aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* XULTreeOuterGroup = nsnull;
  GetTreeBody(&XULTreeOuterGroup);

  if (!XULTreeOuterGroup) return NS_OK;
  
  return XULTreeOuterGroup->GetIndexOfFirstVisibleRow(aResult);
}

NS_IMETHODIMP 
nsXULTreeFrame::GetRowCount(PRInt32 *aResult)
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* XULTreeOuterGroup = nsnull;
  GetTreeBody(&XULTreeOuterGroup);

  // initialize out param
  *aResult = 0;

  if (!XULTreeOuterGroup) return NS_OK;
  
  return XULTreeOuterGroup->GetRowCount(aResult);
}

NS_IMETHODIMP
nsXULTreeFrame::BeginBatch()
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  return treeOuterGroup->BeginBatch();
}

NS_IMETHODIMP
nsXULTreeFrame::EndBatch()
{
  // Get our treechildren child frame.
  nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
  GetTreeBody(&treeOuterGroup);

  if (!treeOuterGroup)
    return NS_OK; // No tree body. Just bail.

  return treeOuterGroup->EndBatch();
}

void
nsXULTreeFrame::GetTreeBody(nsXULTreeOuterGroupFrame** aResult)
{
  nsCOMPtr<nsIContent> child;
  PRInt32 count;
  mContent->ChildCount(count);
  for (PRInt32 i = 0; i < count; i++) {
    mContent->ChildAt(i, *getter_AddRefs(child));
    if (child) {
      nsCOMPtr<nsIAtom> tag;
      child->GetTag(*getter_AddRefs(tag));
      if (tag && tag.get() == nsXULAtoms::treechildren) {
        // This is our actual treechildren frame.
        nsIFrame* frame;
        mPresShell->GetPrimaryFrameFor(child, &frame);
        if (frame) {
          nsCOMPtr<nsIScrollableFrame> scroll(do_QueryInterface(frame));
          if (scroll) {
            scroll->GetScrolledFrame(nsnull, frame);
          }
          *aResult = (nsXULTreeOuterGroupFrame*)frame;
          return;
        }
      }
    }
  }
  *aResult = nsnull;
}

NS_IMETHODIMP
nsXULTreeFrame::AttributeChanged (nsIPresContext* aPresContext, nsIContent* aChild,
                                  PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType, PRInt32 aHint)
{
  if (aAttribute == nsXULAtoms::rows) {
    nsXULTreeOuterGroupFrame* treeOuterGroup = nsnull;
    GetTreeBody(&treeOuterGroup);

    if (treeOuterGroup) {
      nsCOMPtr<nsIContent>child;
      treeOuterGroup->GetContent(getter_AddRefs(child));
      
      nsAutoString rows;
      mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
      
      if (!rows.IsEmpty()) {
        PRInt32 dummy;
        PRInt32 count = rows.ToInteger(&dummy);
        float t2p;
        aPresContext->GetTwipsToPixels(&t2p);
        PRInt32 rowHeight = treeOuterGroup->GetRowHeightTwips();
        rowHeight = NSTwipsToIntPixels(rowHeight, t2p);
        nsAutoString value;
        value.AppendInt(rowHeight*count);
        child->SetAttr(kNameSpaceID_None, nsXULAtoms::minheight, value, PR_FALSE);

        nsBoxLayoutState state(aPresContext);
        treeOuterGroup->MarkDirty(state);
      }
    }
    return NS_OK;
  }
  else
    return nsBoxFrame::AttributeChanged(aPresContext, aChild, aNameSpaceID, aAttribute, aModType, aHint);
}
