/* -*- 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 "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.
 */
#include "nsCOMPtr.h"
#include "nsIForm.h"
#include "nsIFormControl.h"
#include "nsIFormManager.h"
#include "nsIDOMHTMLFormElement.h"
#include "nsIDOMNSHTMLFormElement.h"
#include "nsIDOMHTMLCollection.h"
#include "nsIScriptObjectOwner.h"
#include "nsIScriptContextOwner.h"
#include "nsIDOMEventReceiver.h"
#include "nsIHTMLContent.h"
#include "nsGenericHTMLElement.h"
#include "nsHTMLAtoms.h"
#include "nsHTMLIIDs.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsIPresContext.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"   
#include "nsIFrame.h"

static NS_DEFINE_IID(kIDOMHTMLFormElementIID, NS_IDOMHTMLFORMELEMENT_IID);
static NS_DEFINE_IID(kIFormControlIID, NS_IFORMCONTROL_IID);
static NS_DEFINE_IID(kIFormIID, NS_IFORM_IID);
static NS_DEFINE_IID(kIFormManagerIID, NS_IFORMMANAGER_IID);
static NS_DEFINE_IID(kIDOMNSHTMLFormElementIID, NS_IDOMNSHTMLFORMELEMENT_IID);

class nsFormControlList;

// nsHTMLFormElement

class nsHTMLFormElement : public nsIDOMHTMLFormElement,
                          public nsIDOMNSHTMLFormElement,
                          public nsIScriptObjectOwner,
                          public nsIDOMEventReceiver,
                          public nsIHTMLContent,
                          public nsIForm,
                          public nsIJSScriptObject
{
public:
  nsHTMLFormElement(nsIAtom* aTag);
  virtual ~nsHTMLFormElement();

  // nsISupports
  NS_DECL_ISUPPORTS

  // nsIDOMNode
  NS_IMPL_IDOMNODE_USING_GENERIC(mInner)

  // nsIDOMElement
  NS_IMPL_IDOMELEMENT_USING_GENERIC(mInner)

  // nsIDOMHTMLElement
  NS_IMPL_IDOMHTMLELEMENT_USING_GENERIC(mInner)

  // nsIDOMHTMLFormElement
  NS_IMETHOD GetElements(nsIDOMHTMLCollection** aElements);
  NS_IMETHOD GetName(nsString& aName);
  NS_IMETHOD SetName(const nsString& aName);
  NS_IMETHOD GetAcceptCharset(nsString& aAcceptCharset);
  NS_IMETHOD SetAcceptCharset(const nsString& aAcceptCharset);
  NS_IMETHOD GetAction(nsString& aAction);
  NS_IMETHOD SetAction(const nsString& aAction);
  NS_IMETHOD GetEnctype(nsString& aEnctype);
  NS_IMETHOD SetEnctype(const nsString& aEnctype);
  NS_IMETHOD GetMethod(nsString& aMethod);
  NS_IMETHOD SetMethod(const nsString& aMethod);
  NS_IMETHOD GetTarget(nsString& aTarget);
  NS_IMETHOD SetTarget(const nsString& aTarget);
  NS_IMETHOD GetLength(PRUint32* aLength);
  NS_IMETHOD Reset();
  NS_IMETHOD Submit();

  // nsIDOMNSHTMLFormElement
  NS_IMETHOD    GetEncoding(nsString& aEncoding);
  NS_IMETHOD    NamedItem(const nsString& aName, nsIDOMElement** aReturn);
  
  // nsIScriptObjectOwner
  NS_IMPL_ISCRIPTOBJECTOWNER_USING_GENERIC(mInner)

  // nsIDOMEventReceiver
  NS_IMPL_IDOMEVENTRECEIVER_USING_GENERIC(mInner)

  // nsIContent
  NS_IMPL_ICONTENT_USING_GENERIC(mInner)

  // nsIHTMLContent
  NS_IMPL_IHTMLCONTENT_USING_GENERIC(mInner)

  // nsIJSScriptObject
  virtual PRBool    AddProperty(JSContext *aContext, jsval aID, jsval *aVp);
  virtual PRBool    DeleteProperty(JSContext *aContext, jsval aID, jsval *aVp);
  virtual PRBool    GetProperty(JSContext *aContext, jsval aID, jsval *aVp);
  virtual PRBool    SetProperty(JSContext *aContext, jsval aID, jsval *aVp);
  virtual PRBool    EnumerateProperty(JSContext *aContext);
  virtual PRBool    Resolve(JSContext *aContext, jsval aID);
  virtual PRBool    Convert(JSContext *aContext, jsval aID);
  virtual void      Finalize(JSContext *aContext);

  // nsIForm
  NS_IMETHOD AddElement(nsIFormControl* aElement);
  NS_IMETHOD GetElementAt(PRInt32 aIndex, nsIFormControl** aElement) const;
  NS_IMETHOD GetElementCount(PRUint32* aCount) const;
  NS_IMETHOD RemoveElement(nsIFormControl* aElement, PRBool aChildIsRef = PR_TRUE);

protected:
  nsFormControlList*       mControls;
  nsGenericHTMLLeafElement mInner;
};

// nsFormControlList
class nsFormControlList : public nsIDOMHTMLCollection, public nsIScriptObjectOwner {
public:
  nsFormControlList();
  virtual ~nsFormControlList();

  void Clear();

  NS_DECL_ISUPPORTS

  NS_IMETHOD GetScriptObject(nsIScriptContext *aContext, void** aScriptObject);
  NS_IMETHOD SetScriptObject(void *aScriptObject);
  NS_IMETHOD ResetScriptObject();

  // nsIDOMHTMLCollection interface
  NS_DECL_IDOMHTMLCOLLECTION
  
  // Called to tell us that the form is going away and that we
  // should drop our (non ref-counted) reference to it
  void ReleaseForm();

  void        *mScriptObject;
  nsVoidArray  mElements;
};

// nsHTMLFormElement implementation

// construction, destruction
nsresult
NS_NewHTMLFormElement(nsIHTMLContent** aInstancePtrResult, nsIAtom* aTag)
{
  NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
  if (nsnull == aInstancePtrResult) {
    return NS_ERROR_NULL_POINTER;
  }
  nsIHTMLContent* it = new nsHTMLFormElement(aTag);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return it->QueryInterface(kIHTMLContentIID, (void**) aInstancePtrResult);
}

nsHTMLFormElement::nsHTMLFormElement(nsIAtom* aTag)
{
  NS_INIT_REFCNT();
  mInner.Init(this, aTag);
  mControls = new nsFormControlList();
  NS_ADDREF(mControls);
//nsTraceRefcnt::Create((nsIForm*)this, "nsHTMLFormElement", __FILE__, __LINE__);
}

nsHTMLFormElement::~nsHTMLFormElement()
{
  // set the controls to have no form
  PRUint32 numControls;
  GetElementCount(&numControls);
  for (PRUint32 i = 0; i < numControls; i++) {
    // avoid addref to child
    nsIFormControl* control = (nsIFormControl*)mControls->mElements.ElementAt(i); 
    if (control) {
      // it is assummed that passing in nsnull will not release formControl's previous form
      control->SetForm(nsnull); 
    }
  }

  NS_RELEASE(mControls);

//nsTraceRefcnt::Destroy((nsIForm*)this, __FILE__, __LINE__);
}

// nsISupports
NS_IMETHODIMP
nsHTMLFormElement::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
  // Note that this has to stay above the generic element
  // QI macro, since it overrides the nsIJSScriptObject implementation
  // from the generic element.
  if (aIID.Equals(kIJSScriptObjectIID)) {
    nsIJSScriptObject* tmp = this;
    *aInstancePtr = (void*) tmp;
    AddRef();
    return NS_OK;
  }                                                             
  NS_IMPL_HTML_CONTENT_QUERY_INTERFACE(aIID, aInstancePtr, this)
  if (aIID.Equals(kIFormIID)) {
    *aInstancePtr = (void*)(nsIForm*)this;
    AddRef();
    return NS_OK;
  } 
  else if (aIID.Equals(kIDOMHTMLFormElementIID)) {
    *aInstancePtr = (void*)(nsIDOMHTMLFormElement*)this;
    AddRef();
    return NS_OK;
  } 
  else if (aIID.Equals(kIDOMNSHTMLFormElementIID)) {
    *aInstancePtr = (void*)(nsIDOMNSHTMLFormElement*)this;
    AddRef();
    return NS_OK;
  }
  return NS_NOINTERFACE;
}

NS_IMETHODIMP_(nsrefcnt)
nsHTMLFormElement::AddRef(void)                                
{ 
  //nsTraceRefcnt::AddRef((nsIForm*)this, mRefCnt+1, __FILE__, __LINE__);
  return ++mRefCnt;                                          
}

NS_IMETHODIMP_(nsrefcnt)
nsHTMLFormElement::Release()
{
  //nsTraceRefcnt::Release((nsIForm*)this, mRefCnt-1, __FILE__, __LINE__);
  --mRefCnt;
  PRUint32 numChildren;
  GetElementCount(&numChildren);
  if (mRefCnt == nsrefcnt(numChildren)) {
    mRefCnt = 0;
    delete this; 
    return 0;
  } 
  return mRefCnt;
}

// nsIDOMHTMLFormElement
nsresult
nsHTMLFormElement::CloneNode(PRBool aDeep, nsIDOMNode** aReturn)
{
  nsHTMLFormElement* it = new nsHTMLFormElement(mInner.mTag);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  mInner.CopyInnerTo(this, &it->mInner, aDeep);
  return it->QueryInterface(kIDOMNodeIID, (void**) aReturn);
}

NS_IMETHODIMP
nsHTMLFormElement::GetElements(nsIDOMHTMLCollection** aElements)
{
  *aElements = mControls;
  NS_ADDREF(mControls);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLFormElement::GetName(nsString& aValue)
{
  return mInner.GetAttribute(kNameSpaceID_HTML, nsHTMLAtoms::name, aValue);
}

NS_IMETHODIMP
nsHTMLFormElement::SetName(const nsString& aValue)
{
  return mInner.SetAttribute(kNameSpaceID_HTML, nsHTMLAtoms::name, aValue, PR_TRUE);
}

NS_IMPL_STRING_ATTR(nsHTMLFormElement, AcceptCharset, acceptcharset)
NS_IMPL_STRING_ATTR(nsHTMLFormElement, Action, action)
NS_IMPL_STRING_ATTR(nsHTMLFormElement, Enctype, enctype)
NS_IMPL_STRING_ATTR(nsHTMLFormElement, Method, method)
NS_IMPL_STRING_ATTR(nsHTMLFormElement, Target, target)

NS_IMETHODIMP
nsHTMLFormElement::Submit()
{
  // XXX Need to do something special with mailto: or news: URLs
  nsIDocument* doc = nsnull;
  nsresult result = GetDocument(doc);
  if ((NS_OK == result) && doc) {
    nsIPresShell *shell = doc->GetShellAt(0);
    if (nsnull != shell) {
      nsIFrame* frame;
      shell->GetPrimaryFrameFor(this, &frame);
      if (frame) {
        nsIFormManager* formMan = nsnull;
        nsresult result = frame->QueryInterface(kIFormManagerIID, (void**)&formMan);
        if ((NS_OK == result) && formMan) {
          nsCOMPtr<nsIPresContext> context;
          shell->GetPresContext(getter_AddRefs(context));
          if (context) {
            // XXX We're currently passing in null for the frame.
            // It works for now, but might not always
            // be correct. In the future, we might not need the 
            // frame to be passed to the link handler.
            result = formMan->OnSubmit(context, nsnull);
          }
        }
      }
      NS_RELEASE(shell);
    }
    NS_RELEASE(doc);
  }

  return result;
}

NS_IMETHODIMP
nsHTMLFormElement::Reset()
{
  // XXX Need to do something special with mailto: or news: URLs
  nsIDocument* doc = nsnull;
  nsresult result = GetDocument(doc);
  if ((NS_OK == result) && doc) {
    nsIPresShell *shell = doc->GetShellAt(0);
    if (nsnull != shell) {
      nsIFrame* frame;
      shell->GetPrimaryFrameFor(this, &frame);
      if (frame) {
        nsIFormManager* formMan = nsnull;
        nsresult result = frame->QueryInterface(kIFormManagerIID, (void**)&formMan);
        if ((NS_OK == result) && formMan) {
          result = formMan->OnReset();
        }
      }
      NS_RELEASE(shell);
    }
  }

  return result;
}
static nsGenericHTMLElement::EnumTable kFormMethodTable[] = {
  { "get", NS_FORM_METHOD_GET },
  { "post", NS_FORM_METHOD_POST },
  { 0 }
};
static nsGenericHTMLElement::EnumTable kFormEnctypeTable[] = {
  { "multipart/form-data", NS_FORM_ENCTYPE_MULTIPART },
  { "application/x-www-form-urlencoded", NS_FORM_ENCTYPE_URLENCODED },
  { 0 }
};

NS_IMETHODIMP
nsHTMLFormElement::StringToAttribute(nsIAtom* aAttribute,
                              const nsString& aValue,
                              nsHTMLValue& aResult)
{
  if (aAttribute == nsHTMLAtoms::method) {
    nsGenericHTMLElement::ParseEnumValue(aValue, kFormMethodTable, aResult);
    return NS_CONTENT_ATTR_HAS_VALUE;
  }
  else if (aAttribute == nsHTMLAtoms::enctype) {
    nsGenericHTMLElement::ParseEnumValue(aValue, kFormEnctypeTable, aResult);
    return NS_CONTENT_ATTR_HAS_VALUE;
  }
  return NS_CONTENT_ATTR_NOT_THERE;
}

NS_IMETHODIMP
nsHTMLFormElement::AttributeToString(nsIAtom* aAttribute,
                              const nsHTMLValue& aValue,
                              nsString& aResult) const
{
  if (aAttribute == nsHTMLAtoms::method) {
    if (eHTMLUnit_Enumerated == aValue.GetUnit()) {
      nsGenericHTMLElement::EnumValueToString(aValue, kFormMethodTable, aResult);
      return NS_CONTENT_ATTR_HAS_VALUE;
    }
  }
  else if (aAttribute == nsHTMLAtoms::enctype) {
    if (eHTMLUnit_Enumerated == aValue.GetUnit()) {
      nsGenericHTMLElement::EnumValueToString(aValue, kFormEnctypeTable, aResult);
      return NS_CONTENT_ATTR_HAS_VALUE;
    }
  }
  return mInner.AttributeToString(aAttribute, aValue, aResult);
}

static void
MapAttributesInto(const nsIHTMLMappedAttributes* aAttributes,
                  nsIStyleContext* aContext,
                  nsIPresContext* aPresContext)
{
  // XXX write me
  nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aContext, aPresContext);
}

NS_IMETHODIMP
nsHTMLFormElement::GetMappedAttributeImpact(const nsIAtom* aAttribute,
                                            PRInt32& aHint) const
{
  if (! nsGenericHTMLElement::GetCommonMappedAttributesImpact(aAttribute, aHint)) {
    aHint = NS_STYLE_HINT_CONTENT;
  }

  return NS_OK;
}


NS_IMETHODIMP
nsHTMLFormElement::GetAttributeMappingFunctions(nsMapAttributesFunc& aFontMapFunc,
                                                nsMapAttributesFunc& aMapFunc) const
{
  aFontMapFunc = nsnull;
  aMapFunc = &MapAttributesInto;
  return NS_OK;
}



NS_IMETHODIMP
nsHTMLFormElement::HandleDOMEvent(nsIPresContext& aPresContext,
                           nsEvent* aEvent,
                           nsIDOMEvent** aDOMEvent,
                           PRUint32 aFlags,
                           nsEventStatus& aEventStatus)
{
  return mInner.HandleDOMEvent(aPresContext, aEvent, aDOMEvent,
                               aFlags, aEventStatus);
}

// nsIForm

NS_IMETHODIMP
nsHTMLFormElement::GetElementCount(PRUint32* aCount) const 
{
  mControls->GetLength(aCount); 
  return NS_OK;
}

NS_IMETHODIMP 
nsHTMLFormElement::GetElementAt(PRInt32 aIndex, nsIFormControl** aFormControl) const 
{ 
  *aFormControl = (nsIFormControl*) mControls->mElements.ElementAt(aIndex);
  NS_IF_ADDREF(*aFormControl);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLFormElement::AddElement(nsIFormControl* aChild) 
{ 
  PRBool rv = mControls->mElements.AppendElement(aChild);
  if (rv) {
    NS_ADDREF(aChild);
  }
  return rv;
}

NS_IMETHODIMP 
nsHTMLFormElement::RemoveElement(nsIFormControl* aChild, PRBool aChildIsRef) 
{ 
  PRBool rv = mControls->mElements.RemoveElement(aChild);
  if (rv && aChildIsRef) {
    NS_RELEASE(aChild);
  }
  return rv;
}

NS_IMETHODIMP
nsHTMLFormElement::GetEncoding(nsString& aEncoding)
{
  return mInner.GetAttribute(kNameSpaceID_HTML, nsHTMLAtoms::encoding, aEncoding);
}
 
NS_IMETHODIMP    
nsHTMLFormElement::GetLength(PRUint32* aLength)
{
  *aLength = mControls->mElements.Count();
  
  return NS_OK;
}

NS_IMETHODIMP    
nsHTMLFormElement::NamedItem(const nsString& aName, nsIDOMElement** aReturn)
{
  // XXX For now we just search our element list. In reality, we'll have
  // to look at all of our children, including images, objects, etc.
  nsIDOMNode *node;
  nsresult result = mControls->NamedItem(aName, &node);
  
  if ((NS_OK == result) && (nsnull != node)) {
    result = node->QueryInterface(kIDOMElementIID, (void **)aReturn);
    NS_RELEASE(node);
  }
  else {
    *aReturn = nsnull;
  }

  return result;
}

PRBool    
nsHTMLFormElement::AddProperty(JSContext *aContext, jsval aID, jsval *aVp)
{
  return mInner.AddProperty(aContext, aID, aVp);
}

PRBool    
nsHTMLFormElement::DeleteProperty(JSContext *aContext, jsval aID, jsval *aVp)
{
  return mInner.DeleteProperty(aContext, aID, aVp);
}

PRBool    
nsHTMLFormElement::GetProperty(JSContext *aContext, jsval aID, jsval *aVp)
{
  return mInner.GetProperty(aContext, aID, aVp);
}

PRBool    
nsHTMLFormElement::SetProperty(JSContext *aContext, jsval aID, jsval *aVp)
{
  return mInner.SetProperty(aContext, aID, aVp);
}

PRBool    
nsHTMLFormElement::EnumerateProperty(JSContext *aContext)
{
  return mInner.EnumerateProperty(aContext);
}

PRBool    
nsHTMLFormElement::Resolve(JSContext *aContext, jsval aID)
{
  nsCOMPtr<nsIDOMElement> element;
  char* str = JS_GetStringBytes(JS_ValueToString(aContext, aID));
  nsAutoString name(str); 
  nsresult result = NS_OK;
  PRBool ret = PR_TRUE;

  result = NamedItem(name, getter_AddRefs(element));
  if (NS_SUCCEEDED(result) && element) {
    nsCOMPtr<nsIScriptObjectOwner> owner = do_QueryInterface(element);
    
    if (owner) {
      nsCOMPtr<nsIScriptContext> scriptContext;
      
      if (nsnull != mInner.mDocument) {
        nsCOMPtr<nsIScriptContextOwner> contextOwner;

        contextOwner = getter_AddRefs(mInner.mDocument->GetScriptContextOwner());
        if (contextOwner) {
          result = contextOwner->GetScriptContext(getter_AddRefs(scriptContext));
        }
      }
      else {
        scriptContext = dont_AddRef((nsIScriptContext*)JS_GetContextPrivate(aContext));
      }

      JSObject* obj;
      if (scriptContext) {
        result = owner->GetScriptObject(scriptContext, (void**)&obj);
        if (NS_SUCCEEDED(result) && (nsnull != obj)) {
          JSObject* myObj;
          result = mInner.GetScriptObject(scriptContext, (void**)&myObj);
          ret = ::JS_DefineProperty(aContext, myObj,
                                    str, OBJECT_TO_JSVAL(obj),
                                    nsnull, nsnull, 0);
        }
      }
    }
  }
  else {
    ret = mInner.Resolve(aContext, aID);
  }

  if (NS_FAILED(result)) {
    ret = PR_FALSE;
  }

  return ret;
}

PRBool    
nsHTMLFormElement::Convert(JSContext *aContext, jsval aID)
{
  return mInner.Convert(aContext, aID);
}

void      
nsHTMLFormElement::Finalize(JSContext *aContext)
{
  mInner.Finalize(aContext);
}

// nsFormControlList implementation, this could go away if there were a lightweight collection implementation somewhere

nsFormControlList::nsFormControlList() 
{
  mRefCnt = 0;
  mScriptObject = nsnull;
}

nsFormControlList::~nsFormControlList()
{
  Clear();
}

NS_IMETHODIMP
nsFormControlList::SetScriptObject(void *aScriptObject)
{
  mScriptObject = aScriptObject;
  return NS_OK;
}

void
nsFormControlList::Clear()
{
  PRUint32 numElements = mElements.Count();
  for (PRUint32 i = 0; i < numElements; i++) {
    nsIFormControl* elem = (nsIFormControl*) mElements.ElementAt(i);
    NS_IF_RELEASE(elem);
  }
}

nsresult nsFormControlList::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
  if (NULL == aInstancePtr) {
    return NS_ERROR_NULL_POINTER;
  }
  static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
  static NS_DEFINE_IID(kIDOMHTMLCollectionIID, NS_IDOMHTMLCOLLECTION_IID);
  static NS_DEFINE_IID(kIScriptObjectOwnerIID, NS_ISCRIPTOBJECTOWNER_IID);
  if (aIID.Equals(kIDOMHTMLCollectionIID)) {
    *aInstancePtr = (void*)(nsIDOMHTMLCollection*)this;
    AddRef();
    return NS_OK;
  }
  if (aIID.Equals(kIScriptObjectOwnerIID)) {
    *aInstancePtr = (void*)(nsIScriptObjectOwner*)this;
    AddRef();
    return NS_OK;
  }
  if (aIID.Equals(kISupportsIID)) {
    *aInstancePtr = (void*)(nsISupports*)(nsIDOMHTMLCollection*)this;
    AddRef();
    return NS_OK;
  }
  return NS_NOINTERFACE;
}

NS_IMPL_ADDREF(nsFormControlList)
NS_IMPL_RELEASE(nsFormControlList)


nsresult nsFormControlList::GetScriptObject(nsIScriptContext *aContext, void** aScriptObject)
{
  nsresult res = NS_OK;
  if (nsnull == mScriptObject) {
    res = NS_NewScriptHTMLCollection(aContext, (nsISupports *)(nsIDOMHTMLCollection *)this, nsnull, (void**)&mScriptObject);
  }
  *aScriptObject = mScriptObject;
  return res;
}

nsresult nsFormControlList::ResetScriptObject()
{
  mScriptObject = nsnull;
  return NS_OK;
}

// nsIDOMHTMLCollection interface
NS_IMETHODIMP    
nsFormControlList::GetLength(PRUint32* aLength)
{
  *aLength = mElements.Count();
  return NS_OK;
}

NS_IMETHODIMP
nsFormControlList::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
{
  nsIFormControl *control = (nsIFormControl*)mElements.ElementAt(aIndex);
  if (control) {
    return control->QueryInterface(kIDOMNodeIID, (void**)aReturn); // keep the ref
  }
  *aReturn = nsnull;
  return NS_OK;
}

NS_IMETHODIMP 
nsFormControlList::NamedItem(const nsString& aName, nsIDOMNode** aReturn)
{
  PRUint32 count = mElements.Count();
  nsresult result = NS_OK;

  *aReturn = nsnull;
  for (PRUint32 i = 0; i < count && *aReturn == nsnull; i++) {
    nsIFormControl *control = (nsIFormControl*)mElements.ElementAt(i);
    if (nsnull != control) {
      nsIContent *content;
      
      result = control->QueryInterface(kIContentIID, (void **)&content);
      if (NS_OK == result) {
        nsAutoString name;
        // XXX Should it be an EqualsIgnoreCase?
        if (((content->GetAttribute(kNameSpaceID_HTML, nsHTMLAtoms::name, name) == NS_CONTENT_ATTR_HAS_VALUE) &&
             (aName.Equals(name))) ||
            ((content->GetAttribute(kNameSpaceID_HTML, nsHTMLAtoms::id, name) == NS_CONTENT_ATTR_HAS_VALUE) &&
             (aName.Equals(name)))) {
          result = control->QueryInterface(kIDOMNodeIID, (void **)aReturn);
        }
        NS_RELEASE(content);
      }
    }
  }
  
  return result;
}

