/* -*- 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 "nsInput.h"
#include "nsInputFrame.h"
#include "nsHTMLParts.h"
#include "nsHTMLContainer.h"
#include "nsIRenderingContext.h"
#include "nsIPresShell.h"
#include "nsIPresContext.h"
#include "nsIStyleContext.h"
#include "nsLeafFrame.h"
#include "nsCSSRendering.h"
#include "nsHTMLIIDs.h"
#include "nsIView.h"
#include "nsIViewManager.h"
#include "nsCoord.h"
#include "nsWidgetsCID.h"
#include "nsViewsCID.h"
#include "nsRepository.h"
#include "nsGUIEvent.h"
#include "nsIFontCache.h"
#include "nsIFontMetrics.h"
#include "nsIFormManager.h"
#include "nsIDeviceContext.h"
#include "nsHTMLAtoms.h"
#include "nsIButton.h"  // remove this when GetCID is pure virtual
#include "nsICheckButton.h"  //remove this
#include "nsITextWidget.h"  //remove this
#include "nsISupports.h"
#include "nsHTMLForms.h"

static NS_DEFINE_IID(kStyleFontSID, NS_STYLEFONT_SID);
static NS_DEFINE_IID(kStylePositionSID, NS_STYLEPOSITION_SID);

struct nsInputCallbackData
{
  nsIPresContext* mPresContext;
  nsInputFrame*   mFrame;
  nsInputCallbackData(nsIPresContext* aPresContext, nsInputFrame* aFrame)
    :mPresContext(aPresContext), mFrame(aFrame)
  {
  }
};

nsInputFrame::nsInputFrame(nsIContent* aContent,
                           PRInt32 aIndexInParent,
                           nsIFrame* aParentFrame)
  : nsLeafFrame(aContent, aIndexInParent, aParentFrame)
{
  mCacheBounds.width  = 0;
  mCacheBounds.height = 0;
  mLastMouseState = eMouseNone;
}

nsInputFrame::~nsInputFrame()
{
}

// XXX it would be cool if form element used our rendering sw, then
// they could be blended, and bordered, and so on...
NS_METHOD
nsInputFrame::Paint(nsIPresContext& aPresContext,
                    nsIRenderingContext& aRenderingContext,
                    const nsRect& aDirtyRect)
{
  static NS_DEFINE_IID(kIWidgetIID, NS_IWIDGET_IID);

  nsPoint offset;
  nsIView *parent;
  GetOffsetFromView(offset, parent);
  if (nsnull == parent) {  // a problem
	  NS_ASSERTION(0, "parent view was null\n");
  } else {
	  nsIView* view;
	  GetView(view);
	  float t2p = aPresContext.GetTwipsToPixels();
	  //nsIWidget *widget = view->GetWindow();
	  nsIWidget* widget;
	  nsresult result = GetWidget(view, &widget);
	  if (NS_OK == result) {
      nsRect  vBounds;
      view->GetBounds(vBounds);

      if ((vBounds.x != offset.x) || (vBounds.y != offset.y))
        view->SetPosition(offset.x, offset.y);

	    // initially the widget was created as hidden
	    if (nsViewVisibility_kHide == view->GetVisibility()) {
        nsInput* content;
        GetContent((nsIContent *&) content);
        content->GetFormManager()->Init(PR_FALSE); // this only inits the 1st time
        NS_RELEASE(content);

	      view->SetVisibility(nsViewVisibility_kShow);
        PostCreateWidget(&aPresContext, view);
      }
	  } else {
	    NS_ASSERTION(0, "could not get widget");
	  }
	  NS_IF_RELEASE(widget);
	  NS_RELEASE(view);
    NS_RELEASE(parent);
  }
  return NS_OK;
}

PRBool 
nsInputFrame::BoundsAreSet()
{
  if ((0 != mCacheBounds.width) || (0 != mCacheBounds.height)) {
    return PR_TRUE;
  } else {
	return PR_FALSE;
  }
}

PRBool
nsInputFrame::IsHidden()
{
  nsInput* content = (nsInput *)mContent; 
  return content->IsHidden();
}

void 
nsInputFrame::GetDesiredSize(nsIPresContext* aPresContext,
                             const nsSize& aMaxSize,
                             nsReflowMetrics& aDesiredLayoutSize,
                             nsSize& aDesiredWidgetSize)
{
  // get the css size and let the frame use or override it
  nsSize styleSize;
  GetStyleSize(*aPresContext, aMaxSize, styleSize);

  // subclasses should always override this method, but if not and no css, make it small
  aDesiredLayoutSize.width  = (styleSize.width  > CSS_NOTSET) ? styleSize.width  : 144;
  aDesiredLayoutSize.height = (styleSize.height > CSS_NOTSET) ? styleSize.height : 144;
  aDesiredWidgetSize.width  = aDesiredLayoutSize.width;
  aDesiredWidgetSize.height = aDesiredLayoutSize.height;
}

void 
nsInputFrame::GetDesiredSize(nsIPresContext* aPresContext,
                             nsReflowMetrics& aDesiredSize,
                             const nsSize& aMaxSize)
{
  nsSize ignore;
  GetDesiredSize(aPresContext, aMaxSize, aDesiredSize, ignore);
}

NS_METHOD
nsInputFrame::ResizeReflow(nsIPresContext* aPresContext,
                          nsReflowMetrics& aDesiredSize,
                          const nsSize& aMaxSize,
                          nsSize* aMaxElementSize,
                          ReflowStatus& aStatus)
{
  nsIView* view;
  GetView(view);

  if (nsnull == view) {

    static NS_DEFINE_IID(kViewCID, NS_VIEW_CID);
    static NS_DEFINE_IID(kIViewIID, NS_IVIEW_IID);

    // make sure the style context is set
    if (nsnull == mStyleContext) {
      GetStyleContext(aPresContext, mStyleContext);
    }
    nsresult result = 
	    NSRepository::CreateInstance(kViewCID, nsnull, kIViewIID, (void **)&view);// need to release
	  if (NS_OK != result) {
	    NS_ASSERTION(0, "Could not create view for button"); 
      aStatus = frNotComplete;
      return NS_OK;
	  }
	  nsIPresShell   *presShell = aPresContext->GetShell();     // need to release
	  nsIViewManager *viewMan   = presShell->GetViewManager();  // need to release

    nsReflowMetrics layoutSize;
    nsSize widgetSize;
    GetDesiredSize(aPresContext, aMaxSize, layoutSize, widgetSize);
    mCacheBounds.width  = widgetSize.width; // YYY what about caching widget size?
    mCacheBounds.height = widgetSize.height;

    nsRect boundBox(0, 0, widgetSize.width, widgetSize.height); 

    nsIFrame* parWithView;
	  nsIView *parView;

    GetParentWithView(parWithView);
	  parWithView->GetView(parView);

	  const nsIID& id = GetCID();
    nsInputWidgetData* initData = GetWidgetInitData(); // needs to be deleted
	  // initialize the view as hidden since we don't know the (x,y) until Paint
    result = view->Init(viewMan, boundBox, parView, &id, initData, nsnull, 0, nsnull,
		                1.0f, nsViewVisibility_kHide);
    if (nsnull != initData) {
      delete(initData);
    }
    if (NS_OK != result) {
	    NS_ASSERTION(0, "widget initialization failed"); 
      aStatus = frNotComplete;
      return NS_OK;
	  }

	  // set the content's widget, so it can get content modified by the widget
	  nsIWidget *widget;
	  result = GetWidget(view, &widget);
	  if (NS_OK == result) {
      nsInput* content = (nsInput *)mContent; // change this cast to QueryInterface 
      content->SetWidget(widget);
	    NS_IF_RELEASE(widget);
	  } else {
	    NS_ASSERTION(0, "could not get widget");
	  }

    viewMan->InsertChild(parView, view, 0);
	  SetView(view);
	  //PostCreateWidget(aPresContext, view);
	  
    NS_IF_RELEASE(parView);
    NS_IF_RELEASE(view);
	  NS_IF_RELEASE(viewMan);  
	  NS_IF_RELEASE(presShell); 
  }
  aDesiredSize.width   = mCacheBounds.width;
  aDesiredSize.height  = mCacheBounds.height;
  aDesiredSize.ascent  = mCacheBounds.height;
  aDesiredSize.descent = 0;

  if (nsnull != aMaxElementSize) {
    aMaxElementSize->width = aDesiredSize.width;
	  aMaxElementSize->height = aDesiredSize.height;
  }
    
  aStatus = frComplete;
  return NS_OK;
}

nsInputWidgetData* 
nsInputFrame::GetWidgetInitData()
{
  return nsnull;
}

void 
nsInputFrame::PostCreateWidget(nsIPresContext* aPresContext, nsIView *aView)
{
}

nsresult
nsInputFrame::GetWidget(nsIView* aView, nsIWidget** aWidget)
{
  const nsIID id = GetIID();
  if (NS_OK == aView->QueryInterface(id, (void**)aWidget)) {
    return NS_OK;
  } else {
	NS_ASSERTION(0, "The widget interface is invalid");  // need to print out more details of the widget
	return NS_NOINTERFACE;
  }
}

const nsIID&
nsInputFrame::GetIID()
{
  static NS_DEFINE_IID(kButtonIID, NS_IBUTTON_IID);
  return kButtonIID;
}
  
const nsIID&
nsInputFrame::GetCID()
{
  static NS_DEFINE_IID(kButtonCID, NS_BUTTON_CID);
  return kButtonCID;
}

NS_METHOD nsInputFrame::HandleEvent(nsIPresContext& aPresContext, 
                                    nsGUIEvent* aEvent,
                                    nsEventStatus& aEventStatus)
{
	// make sure that the widget in the event is this
  static NS_DEFINE_IID(kSupportsIID, NS_ISUPPORTS_IID);
  nsIWidget* thisWidget;

  nsIView*   view;
  GetView(view);
  if (view == nsnull) {
    return nsEventStatus_eIgnore;
  }

  nsresult result = GetWidget(view, &thisWidget);
  nsISupports* thisWidgetSup;
  result = thisWidget->QueryInterface(kSupportsIID, (void **)&thisWidgetSup);
  if (thisWidget == nsnull) {
    return nsEventStatus_eIgnore;
  }
  nsISupports* eventWidgetSup;
  result = aEvent->widget->QueryInterface(kSupportsIID, (void **)&eventWidgetSup);

  PRBool isOurEvent = (thisWidgetSup == eventWidgetSup) ? PR_TRUE : PR_FALSE;
    
  NS_RELEASE(eventWidgetSup);
  NS_RELEASE(thisWidgetSup);
  NS_IF_RELEASE(view);
  NS_IF_RELEASE(thisWidget);

	if (!isOurEvent) {
		aEventStatus = nsEventStatus_eIgnore;
    return NS_OK;
	}

  switch (aEvent->message) {
    case NS_MOUSE_ENTER:
	    mLastMouseState = eMouseEnter;
	    break;
    case NS_MOUSE_LEFT_BUTTON_DOWN:
	    mLastMouseState = 
	      (eMouseEnter == mLastMouseState) ? eMouseDown : eMouseNone;
	    break;
    case NS_MOUSE_LEFT_BUTTON_UP:
	    if (eMouseDown == mLastMouseState) {
	      /*nsIView* view = GetView();
	      nsIWidget *widget = view->GetWindow();
		  widget->SetFocus();
		  NS_RELEASE(widget);
	      NS_RELEASE(view); */
		  MouseClicked(&aPresContext);
		  //return PR_FALSE;
	    } 
	    mLastMouseState = eMouseEnter;
	    break;
    case NS_MOUSE_EXIT:
	    mLastMouseState = eMouseNone;
	    break;
  }
  return nsEventStatus_eConsumeDoDefault;
}

void nsInputFrame::GetStyleSize(nsIPresContext& aPresContext,
                                const nsSize& aMaxSize, nsSize& aSize)
{
  nsInput* input;
  GetContent((nsIContent *&) input); // this must be an nsInput
  nsStylePosition* position = (nsStylePosition*)
    mStyleContext->GetData(kStylePositionSID);

//printf("\n ** %d %d", mol->fixedWidth, mol->proportionalWidth);
  // set the width, height
#if 0
  aSize.width = mol->fixedWidth;
  if ((CSS_NOTSET == aSize.width) && (CSS_NOTSET != mol->proportionalWidth)) {
    aSize.width = (aMaxSize.width * mol->proportionalWidth) / 100;
  }
  aSize.height = mol->fixedHeight;
  if ((CSS_NOTSET == aSize.height) && (CSS_NOTSET != mol->proportionalHeight)) {
    aSize.height = (aMaxSize.height * mol->proportionalHeight) / 100;
  }
#else
  aSize.width = -1;
  aSize.height = -1;
#endif

  NS_RELEASE(input);
}

PRInt32 
nsInputFrame::GetTextSize(nsIPresContext& aPresContext, nsIFrame* aFrame,
                          const nsString& aString, nsSize& aSize)
{
  //printf("\n GetTextSize %s", aString.ToNewCString());
  nsIStyleContext* styleContext;
  aFrame->GetStyleContext(&aPresContext, styleContext);
  nsStyleFont* styleFont = (nsStyleFont*)styleContext->GetData(kStyleFontSID);
  NS_RELEASE(styleContext);
  nsIDeviceContext* deviceContext = aPresContext.GetDeviceContext();
  nsIFontCache* fontCache = deviceContext->GetFontCache();

  nsIFontMetrics* fontMet = fontCache->GetMetricsFor(styleFont->mFont);
  aSize.width  = fontMet->GetWidth(aString);
  aSize.height = fontMet->GetHeight() + fontMet->GetLeading();

  PRInt32 charWidth  = fontMet->GetWidth("W");

  NS_RELEASE(fontMet);
  NS_RELEASE(fontCache);
  NS_RELEASE(deviceContext);

  return charWidth;
}
  
PRInt32
nsInputFrame::GetTextSize(nsIPresContext& aPresContext, nsIFrame* aFrame,
                          PRInt32 aNumChars, nsSize& aSize)
{
  nsAutoString val;
  for (int i = 0; i < aNumChars; i++) {
    val += 'e';  // use a typical char, what is the avg width character?
  }
  return GetTextSize(aPresContext, aFrame, val, aSize);
}
  
PRInt32
nsInputFrame::CalculateSize (nsIPresContext* aPresContext, nsInputFrame* aFrame,
                             const nsSize& aCSSSize, nsInputDimensionSpec& aSpec, 
                             nsSize& aBounds, PRBool& aWidthExplicit, 
                             PRBool& aHeightExplicit, PRInt32& aRowHeight) 
{
  PRInt32 charWidth   = 0;
  aWidthExplicit      = PR_FALSE;
  aHeightExplicit     = PR_FALSE;

  aBounds.width  = CSS_NOTSET;
  aBounds.height = CSS_NOTSET;
  nsSize textSize(0,0);

  nsInput* content;
  aFrame->GetContent((nsIContent *&) content); 
  nsAutoString valAttr;
  nsContentAttr valStatus = eContentAttr_NotThere;
  if (nsnull != aSpec.mColValueAttr) {
    valStatus = content->GetAttribute(aSpec.mColValueAttr, valAttr);
  }
  PRInt32 colAttr;
  nsContentAttr colStatus = eContentAttr_NotThere;
  if (nsnull != aSpec.mColSizeAttr) {
    colStatus = content->GetAttribute(aSpec.mColSizeAttr, colAttr);
  }

  if (eContentAttr_HasValue == colStatus) {  // col attr will provide width
    if (aSpec.mColSizeAttrInPixels) {
      float p2t = aPresContext->GetPixelsToTwips();
      aBounds.width = (int) (((float)colAttr) * p2t);
    }
    else {
      charWidth = GetTextSize(*aPresContext, aFrame, colAttr, aBounds);
      aRowHeight = aBounds.height;
    }
	  if (aSpec.mColSizeAttrInPixels) {
	    aWidthExplicit = PR_TRUE;
	  }
  }
  else {
    if (CSS_NOTSET != aCSSSize.width) {  // css provides width
      aBounds.width = aCSSSize.width;
	    aWidthExplicit = PR_TRUE;
    } 
    else {                       
      if (eContentAttr_HasValue == valStatus) { // use width of initial value if specified
        charWidth = GetTextSize(*aPresContext, aFrame, valAttr, aBounds);
      }
      else if (aSpec.mColDefaultSizeInPixels) {    // use default width in pixels
        charWidth = GetTextSize(*aPresContext, aFrame, 1, aBounds);
        aBounds.width = aSpec.mColDefaultSize;
      }
      else  {                                    // use default width in num characters
        charWidth = GetTextSize(*aPresContext, aFrame, aSpec.mColDefaultSize, aBounds); 
      }
      aRowHeight = aBounds.height;
    }
  }

  PRInt32 rowAttr = CSS_NOTSET;
  nsContentAttr rowStatus = eContentAttr_NotThere;
  if (nsnull != aSpec.mRowSizeAttr) {
    rowStatus = content->GetAttribute(aSpec.mRowSizeAttr, rowAttr);
  }

  if (eContentAttr_HasValue == rowStatus) { // row attr will provide height
    if (0 == charWidth) {
      charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize);
      aBounds.height = textSize.height * rowAttr;
      aRowHeight = textSize.height;
    }
    else {
      aBounds.height = aBounds.height * rowAttr;
    }
  }
  else if (CSS_NOTSET != aCSSSize.height) {  // css provides height
    aBounds.height = aCSSSize.height;
	  aHeightExplicit = PR_TRUE;
  } 
  else {         // use default height in num lines
    if (0 == charWidth) {  
      charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize);
      aBounds.height = textSize.height * aSpec.mRowDefaultSize;
      aRowHeight = textSize.height;
    } 
    else {
      aBounds.height = aBounds.height * aSpec.mRowDefaultSize;
    }
  }

  if (0 == charWidth) {
    charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize);
    aRowHeight = textSize.height;
  }

  // add padding to width if width wasn't specified either from css or size attr
  if (!aWidthExplicit) {
    aBounds.width += charWidth;
  }

  NS_RELEASE(content);

  // return number of rows that the calcuated size can support
  return (int)(((float)aBounds.height) / ((float)aRowHeight));

}


nsFont& 
nsInputFrame::GetFont(nsIPresContext* aPresContext)
{
  nsStyleFont* styleFont = (nsStyleFont*)mStyleContext->GetData(kStyleFontSID);

  return styleFont->mFont;
}





