/* -*- 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.org 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):
 *   Pierre Phaneuf <pp@ludusdesign.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 "nscore.h"
#include "nsHTMLAttributes.h"
#include "nsIHTMLStyleSheet.h"
#include "nsIPresContext.h"
#include "nsIDocument.h"
#include "nsINodeInfo.h"
#include "nsString.h"
#include "nsISupportsArray.h"
#include "nsCRT.h"
#include "nsHTMLAtoms.h"
#include "nsIHTMLContent.h"
#include "nsVoidArray.h"
#include "nsCOMPtr.h"
#include "nsUnicharUtils.h"

#include "nsIStyleSet.h"
#include "nsRuleWalker.h"

MOZ_DECL_CTOR_COUNTER(HTMLAttribute)

struct HTMLAttribute {
  HTMLAttribute(void)
    : mValue(),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    mAttribute.mAtom = nsnull;
  }

  HTMLAttribute(nsIAtom* aAttribute, const nsAString& aValue)
    : mAttribute(aAttribute),
      mValue(aValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    mAttribute.Addref();
  }

  HTMLAttribute(nsHTMLAttrName aAttribute, const nsHTMLValue& aValue)
    : mAttribute(aAttribute),
      mValue(aValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    mAttribute.Addref();
  }

  HTMLAttribute(const HTMLAttribute& aCopy)
    : mAttribute(aCopy.mAttribute),
      mValue(aCopy.mValue),
      mNext(nsnull)
  {
    MOZ_COUNT_CTOR(HTMLAttribute);
    mAttribute.Addref();
  }

  ~HTMLAttribute(void)
  {
    MOZ_COUNT_DTOR(HTMLAttribute);
    mAttribute.Release();
  }

  HTMLAttribute& operator=(const HTMLAttribute& aCopy)
  {
    mAttribute.Release();
    mAttribute = aCopy.mAttribute;
    mAttribute.Addref();
    mValue = aCopy.mValue;
    return *this;
  }

  PRBool operator==(const HTMLAttribute& aOther) const
  {
    return PRBool((mAttribute.mBits == aOther.mAttribute.mBits) &&
                  (mValue == aOther.mValue));
  }

  PRUint32 HashValue(void) const
  {
    return mAttribute.mBits ^ mValue.HashValue();
  }

  void Reset(void)
  {
    mAttribute.Release();
    mValue.Reset();
  }

  void Set(nsIAtom* aAttribute, const nsHTMLValue& aValue)
  {
    mAttribute.Release();
    mAttribute.mAtom = aAttribute;
    mAttribute.Addref();
    mValue = aValue;
  }

  void Set(nsIAtom* aAttribute, const nsAString& aValue)
  {
    mAttribute.Release();
    mAttribute.mAtom = aAttribute;
    mAttribute.Addref();
    mValue.SetStringValue(aValue);
  }

  void Set(nsINodeInfo* aAttribute, const nsAString& aValue)
  {
    mAttribute.Release();
    mAttribute.SetNodeInfo(aAttribute);
    mAttribute.Addref();
    mValue.SetStringValue(aValue);
  }

#ifdef DEBUG
  void AppendToString(nsString& aBuffer) const
  {
    if (mAttribute.mBits) {
      nsAutoString temp;
      if (mAttribute.IsAtom())
          mAttribute.mAtom->ToString(temp);
      else
          mAttribute.GetNodeInfo()->GetQualifiedName(temp);
      aBuffer.Append(temp);
      if (eHTMLUnit_Null != mValue.GetUnit()) {
        aBuffer.Append(NS_LITERAL_STRING(" = "));
        mValue.AppendToString(aBuffer);
      }
    }
    else {
      aBuffer.Append(NS_LITERAL_STRING("null"));
    }
  }

  void ToString(nsString& aBuffer) const
  {
    if (mAttribute.mBits) {
      if (mAttribute.IsAtom())
          mAttribute.mAtom->ToString(aBuffer);
      else
          mAttribute.GetNodeInfo()->GetQualifiedName(aBuffer);
      if (eHTMLUnit_Null != mValue.GetUnit()) {
        aBuffer.Append(NS_LITERAL_STRING(" = "));
        mValue.AppendToString(aBuffer);
      }
    }
    else {
      aBuffer.Assign(NS_LITERAL_STRING("null"));
    }
  }
#endif // DEBUG

  static void
  CopyHTMLAttributes(HTMLAttribute* aSource, HTMLAttribute** aDest)
  {
    while (aSource && aDest) {
      (*aDest) = new HTMLAttribute(*aSource);
      if (! *aDest) {
        break;
      }
      aDest = &((*aDest)->mNext);
      aSource = aSource->mNext;
    }
  }

  static void
  DeleteHTMLAttributes(HTMLAttribute* aAttr)
  {
    while (aAttr) {
      HTMLAttribute* deadBeef = aAttr;
      aAttr = aAttr->mNext;
      delete deadBeef;
    }
  }

  static HTMLAttribute*
  FindHTMLAttribute(nsIAtom* aAttrName, HTMLAttribute* aAttr)
  {
    while (aAttr) {
      if (aAttrName == aAttr->mAttribute.mAtom) {
        return aAttr;
      }
      aAttr = aAttr->mNext;
    }
    return nsnull;
  }

  static const HTMLAttribute*
  FindHTMLAttribute(nsIAtom* aAttrName, const HTMLAttribute* aAttr)
  {
    while (aAttr) {
      if (aAttrName == aAttr->mAttribute.mAtom) {
        return aAttr;
      }
      aAttr = aAttr->mNext;
    }
    return nsnull;
  }

  static HTMLAttribute*
  FindHTMLAttribute(nsIAtom* aAttrName, PRInt32 aNamespaceID,
                    HTMLAttribute* aAttr)
  {
    // Just a precaution
    if (aNamespaceID == kNameSpaceID_None)
      return FindHTMLAttribute(aAttrName, aAttr);

    while (aAttr) {
      if (!aAttr->mAttribute.IsAtom() &&
          aAttr->mAttribute.GetNodeInfo()->Equals(aAttrName, aNamespaceID)) {
        return aAttr;
      }
      aAttr = aAttr->mNext;
    }
    return nsnull;
  }

  static PRBool
  RemoveHTMLAttribute(nsIAtom* aAttrName, HTMLAttribute** aAttr)
  {
    while (*aAttr) {
      if ((*aAttr)->mAttribute.mAtom == aAttrName) {
        HTMLAttribute*  attr = *aAttr;
        *aAttr = (*aAttr)->mNext;
        delete attr;
        return PR_TRUE;
      }
      aAttr = &((*aAttr)->mNext);
    }
    return PR_FALSE;
  }

  static PRBool
  RemoveHTMLAttribute(nsIAtom* aAttrName, PRInt32 aNamespaceID,
                      HTMLAttribute** aAttr)
  {
    if (aNamespaceID == kNameSpaceID_None)
      return RemoveHTMLAttribute(aAttrName, aAttr);

    while (*aAttr) {
      if (!(*aAttr)->mAttribute.IsAtom() &&
          (*aAttr)->mAttribute.GetNodeInfo()->Equals(aAttrName,
                                                     aNamespaceID)) {
        HTMLAttribute*  attr = *aAttr;
        *aAttr = (*aAttr)->mNext;
        delete attr;
        return PR_TRUE;
      }
      aAttr = &((*aAttr)->mNext);
    }
    return PR_FALSE;
  }

  nsHTMLAttrName  mAttribute;
  nsHTMLValue     mValue;
  HTMLAttribute*  mNext;
};

// ----------------

MOZ_DECL_CTOR_COUNTER(nsHTMLClassList)

// ----------------

class nsHTMLMappedAttributes : public nsIHTMLMappedAttributes
{
public:
  nsHTMLMappedAttributes(void);
  nsHTMLMappedAttributes(const nsHTMLMappedAttributes& aCopy);
  virtual ~nsHTMLMappedAttributes(void);

  NS_DECL_ISUPPORTS

  NS_IMETHOD Init(nsIHTMLStyleSheet* aSheet,
                  nsMapRuleToAttributesFunc aMapRuleFunc);
  NS_IMETHOD Clone(nsHTMLMappedAttributes** aInstancePtrResult) const;
  NS_IMETHOD Reset(void);
  NS_IMETHOD SetMappingFunction(nsMapRuleToAttributesFunc aMapRuleFunc);

  NS_IMETHOD SetAttribute(nsIAtom* aAttrName, const nsAString& aValue);
  NS_IMETHOD SetAttribute(nsIAtom* aAttrName, const nsHTMLValue& aValue);
  NS_IMETHOD UnsetAttribute(nsIAtom* aAttrName, PRInt32& aAttrCount);

  NS_IMETHOD GetAttribute(nsIAtom* aAttrName,
                          nsHTMLValue& aValue) const;
  NS_IMETHOD GetAttribute(nsIAtom* aAttrName,
                          const nsHTMLValue** aValue) const;
  NS_IMETHOD_(PRBool) HasAttribute(nsIAtom* aAttrName) const;

  NS_IMETHOD GetAttributeCount(PRInt32& aCount) const;

  NS_IMETHOD Equals(const nsIHTMLMappedAttributes* aAttributes, PRBool& aResult) const;
  NS_IMETHOD HashValue(PRUint32& aValue) const;

  NS_IMETHOD AddUse(void);
  NS_IMETHOD ReleaseUse(void);
  NS_IMETHOD GetUseCount(PRInt32& aUseCount) const;

  NS_IMETHOD SetUniqued(PRBool aUniqued);
  NS_IMETHOD GetUniqued(PRBool& aUniqued);
  NS_IMETHOD DropStyleSheetReference(void);

  // nsIStyleRule 
  NS_IMETHOD GetStyleSheet(nsIStyleSheet*& aSheet) const;
  NS_IMETHOD SetStyleSheet(nsIHTMLStyleSheet* aSheet);
  
  // The new mapping functions.
  NS_IMETHOD MapRuleInfoInto(nsRuleData* aRuleData);

#ifdef DEBUG
  NS_IMETHOD List(FILE* out, PRInt32 aIndent) const;
#endif

  nsIHTMLStyleSheet*  mSheet;
  PRInt32             mUseCount;
  PRInt32             mAttrCount;
  HTMLAttribute       mFirst;
  nsMapRuleToAttributesFunc mRuleMapper;
  PRBool              mUniqued;
};

nsHTMLMappedAttributes::nsHTMLMappedAttributes(void)
  : mSheet(nsnull),
    mUseCount(0),
    mAttrCount(0),
    mFirst(),
    mRuleMapper(nsnull),
    mUniqued(PR_FALSE)
{
}

nsHTMLMappedAttributes::nsHTMLMappedAttributes(const nsHTMLMappedAttributes& aCopy)
  : mSheet(aCopy.mSheet),
    mUseCount(0),
    mAttrCount(aCopy.mAttrCount),
    mFirst(aCopy.mFirst),
    mRuleMapper(aCopy.mRuleMapper),
    mUniqued(PR_FALSE)
{

  HTMLAttribute::CopyHTMLAttributes(aCopy.mFirst.mNext, &(mFirst.mNext));
}

nsHTMLMappedAttributes::~nsHTMLMappedAttributes(void)
{
  Reset();
}

NS_IMPL_ISUPPORTS2(nsHTMLMappedAttributes,
                   nsIHTMLMappedAttributes,
                   nsIStyleRule)

NS_IMETHODIMP
nsHTMLMappedAttributes::Init(nsIHTMLStyleSheet* aSheet,
                             nsMapRuleToAttributesFunc aMapRuleFunc)
{
  mSheet = aSheet;
  mRuleMapper = aMapRuleFunc;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Clone(nsHTMLMappedAttributes** aInstancePtrResult) const
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  nsHTMLMappedAttributes* clone = new nsHTMLMappedAttributes(*this);

  if (! clone) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  NS_ADDREF(clone);
  *aInstancePtrResult = clone;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Reset(void)
{
  mAttrCount = 0;
  mFirst.Reset();
  HTMLAttribute::DeleteHTMLAttributes(mFirst.mNext);
  mUniqued = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetMappingFunction(nsMapRuleToAttributesFunc aMapRuleFunc)
{
  mRuleMapper = aMapRuleFunc;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetAttribute(nsIAtom* aAttrName, const nsAString& aValue)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (mFirst.mAttribute.mBits) {  // do we already have any?
    HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
    if (attr) {
      attr->mValue.SetStringValue(aValue);
    }
    else {
      // add new attribute
      // keep these arbitrarily sorted so they'll equal regardless of set order
      if (aAttrName < mFirst.mAttribute.mAtom) {  // before first, move first down one
        attr = new HTMLAttribute(mFirst);
        if (attr) {
          attr->mNext = mFirst.mNext;
          mFirst.mNext = attr;
          mFirst.Set(aAttrName, aValue);
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      else {
        attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          HTMLAttribute* prev = &mFirst;
          while (prev->mNext && (prev->mNext->mAttribute.mAtom < aAttrName)) {
            prev = prev->mNext;
          }
          attr->mNext = prev->mNext;
          prev->mNext = attr;
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      mAttrCount++;
    }
  }
  else {
    mFirst.Set(aAttrName, aValue);
    mAttrCount++;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetAttribute(nsIAtom* aAttrName, const nsHTMLValue& aValue)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (mFirst.mAttribute.mBits) {  // do we already have any?
    HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
    if (attr) {
      attr->mValue = aValue;
    }
    else {
      // add new attribute
      // keep these arbitrarily sorted so they'll hash regardless of set order
      if (aAttrName < mFirst.mAttribute.mAtom) {  // before first, move first down one
        attr = new HTMLAttribute(mFirst);
        if (attr) {
          attr->mNext = mFirst.mNext;
          mFirst.mNext = attr;
          mFirst.Set(aAttrName, aValue);
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      else {
        attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          HTMLAttribute* prev = &mFirst;
          while (prev->mNext && (prev->mNext->mAttribute.mAtom < aAttrName)) {
            prev = prev->mNext;
          }
          attr->mNext = prev->mNext;
          prev->mNext = attr;
        }
        else {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      mAttrCount++;
    }
  }
  else {
    mFirst.Set(aAttrName, aValue);
    mAttrCount++;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::UnsetAttribute(nsIAtom* aAttrName, PRInt32& aAttrCount)
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  if (aAttrName == mFirst.mAttribute.mAtom) {
    if (mFirst.mNext) {
      HTMLAttribute*  attr = mFirst.mNext;
      mFirst = *attr;
      mFirst.mNext = attr->mNext;
      delete attr;
    }
    else {
      mFirst.Reset();
    }
    mAttrCount--;
  }
  else {
    if (HTMLAttribute::RemoveHTMLAttribute(aAttrName, &(mFirst.mNext))) {
      mAttrCount--;
    }
  }
  aAttrCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttribute(nsIAtom* aAttrName,
                                     nsHTMLValue& aValue) const
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
  if (attr) {
    aValue = attr->mValue;
    return ((attr->mValue.GetUnit() == eHTMLUnit_Null) ?
            NS_CONTENT_ATTR_NO_VALUE :
            NS_CONTENT_ATTR_HAS_VALUE);
  }
  aValue.Reset();
  return NS_CONTENT_ATTR_NOT_THERE;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttribute(nsIAtom* aAttrName,
                                     const nsHTMLValue** aValue) const
{
  if (! aAttrName) {
    return NS_ERROR_NULL_POINTER;
  }

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
  if (attr) {
    *aValue = &attr->mValue;
    return ((attr->mValue.GetUnit() == eHTMLUnit_Null) ?
            NS_CONTENT_ATTR_NO_VALUE :
            NS_CONTENT_ATTR_HAS_VALUE);
  }
  *aValue = nsnull;
  return NS_CONTENT_ATTR_NOT_THERE;
}

NS_IMETHODIMP_(PRBool)
nsHTMLMappedAttributes::HasAttribute(nsIAtom* aAttrName) const
{
  if (!aAttrName)
    return PR_FALSE;

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName, &mFirst);
  return attr != nsnull;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetAttributeCount(PRInt32& aCount) const
{
  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::Equals(const nsIHTMLMappedAttributes* aOther, PRBool& aResult) const
{
  const nsHTMLMappedAttributes* other = (const nsHTMLMappedAttributes*)aOther;

  if (this == other) {
    aResult = PR_TRUE;
  }
  else {
    aResult = PR_FALSE;
    if ((mRuleMapper == other->mRuleMapper) &&
        (mAttrCount == other->mAttrCount)) {
      const HTMLAttribute* attr = &mFirst;
      const HTMLAttribute* otherAttr = &(other->mFirst);

      aResult = PR_TRUE;
      while (attr) {
        if (! ((*attr) == (*otherAttr))) {
          aResult = PR_FALSE;
          break;
        }
        attr = attr->mNext;
        otherAttr = otherAttr->mNext;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::HashValue(PRUint32& aValue) const
{
  aValue = NS_PTR_TO_INT32(mRuleMapper);
  
  const HTMLAttribute* attr = &mFirst;
  while (nsnull != attr) {
    if (attr->mAttribute.mAtom) {
      aValue = aValue ^ attr->HashValue();
    }
    attr = attr->mNext;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::AddUse(void)
{
  mUseCount++;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::ReleaseUse(void)
{
  mUseCount--;

  if (mSheet && (0 == mUseCount) && mUniqued) {
    mSheet->DropMappedAttributes(this);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetUseCount(PRInt32& aUseCount) const
{
  aUseCount = mUseCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetUniqued(PRBool aUniqued)
{
  mUniqued = aUniqued;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::GetUniqued(PRBool& aUniqued)
{
  aUniqued = mUniqued;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::DropStyleSheetReference(void)
{
  mSheet = nsnull;
  mUniqued = PR_FALSE;
  return NS_OK;
}


NS_IMETHODIMP
nsHTMLMappedAttributes::GetStyleSheet(nsIStyleSheet*& aSheet) const
{
  aSheet = mSheet;
  NS_IF_ADDREF(aSheet);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::SetStyleSheet(nsIHTMLStyleSheet* aSheet)
{
  if (mSheet && mUniqued) {
    mSheet->DropMappedAttributes(this);
  }
  mSheet = aSheet;  // not ref counted
  mUniqued = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLMappedAttributes::MapRuleInfoInto(nsRuleData* aRuleData)
{
  if (0 < mAttrCount) {
    if (mRuleMapper) {
      (*mRuleMapper)(this, aRuleData);
    }
  }
  return NS_OK;
}

#ifdef DEBUG
NS_IMETHODIMP
nsHTMLMappedAttributes::List(FILE* out, PRInt32 aIndent) const
{
  const HTMLAttribute*  attr = &mFirst;
  nsAutoString buffer;

  while (nsnull != attr) {
    PRInt32 index;
    for (index = aIndent; --index >= 0; ) fputs("  ", out);
    attr->ToString(buffer);
    fputs(NS_LossyConvertUCS2toASCII(buffer).get(), out);
    fputs("\n", out);
    attr = attr->mNext;
  }
  return NS_OK;
}
#endif

//--------------------

nsHTMLAttributes::nsHTMLAttributes(void)
  : mAttrNames(mNameBuffer),
    mAttrCount(0),
    mAttrSize(kHTMLAttrNameBufferSize),
    mFirstUnmapped(nsnull),
    mMapped(nsnull),
    mID(nsnull),
    mFirstClass(nsnull)
{
}

nsHTMLAttributes::nsHTMLAttributes(const nsHTMLAttributes& aCopy)
  : mAttrNames(mNameBuffer),
    mAttrCount(aCopy.mAttrCount),
    mAttrSize(kHTMLAttrNameBufferSize),
    mFirstUnmapped(nsnull),
    mMapped(aCopy.mMapped),
    mID(aCopy.mID),
    mFirstClass(aCopy.mFirstClass)
{
  if (mAttrCount) {
    if (mAttrSize < mAttrCount) {
      mAttrNames = new nsHTMLAttrName[mAttrCount];
      if (mAttrNames) {
        mAttrSize = mAttrCount;
      }
      else {  // new buffer failed, deal with it
        mAttrNames = mNameBuffer;
        mAttrCount = 0;
      }
    }
    PRInt32 index = mAttrCount;
    while (0 < index--) {
      mAttrNames[index] = aCopy.mAttrNames[index];
      mAttrNames[index].Addref();
    }
  }

  HTMLAttribute::CopyHTMLAttributes(aCopy.mFirstUnmapped, &mFirstUnmapped);
  if (mMapped) {
    mMapped->AddUse();
    NS_ADDREF(mMapped);
  }

  NS_IF_ADDREF(mID);
}

nsHTMLAttributes::~nsHTMLAttributes(void)
{
  Reset();
}

const PRUnichar kNullCh = PRUnichar('\0');

static void ParseClasses(const nsAString& aClassString, nsHTMLClassList& aClassList)
{
  nsAutoString  classStr(aClassString);  // copy to work buffer
  classStr.Append(kNullCh);  // put an extra null at the end

  PRUnichar* start = (PRUnichar*)(const PRUnichar*)classStr.get();
  PRUnichar* end   = start;

  nsHTMLClassList*  list = &aClassList;
  while (list && (kNullCh != *start)) {
    while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) {  // skip leading space
      start++;
    }
    end = start;

    while ((kNullCh != *end) && (PR_FALSE == nsCRT::IsAsciiSpace(*end))) { // look for space or end
      end++;
    }
    *end = kNullCh; // end string here

    if (start < end) {
      if (! list->mAtom) {
        list->mAtom = NS_NewAtom(start);
      }
      else {
        list->mNext = new nsHTMLClassList(NS_NewAtom(start));
        list = list->mNext;
      }
    }

    start = ++end;
  }
}

nsresult
nsHTMLAttributes::SetAttributeName(nsHTMLAttrName aAttrName, PRBool& aFound)
{
  PRInt32 index = mAttrCount;
  if (aAttrName.IsAtom()) {
    while (0 < index--) {
      if (mAttrNames[index].mAtom == aAttrName.mAtom) {
        aFound = PR_TRUE;
        return NS_OK;
      }
    }
  }
  else {
    nsINodeInfo* ni = aAttrName.GetNodeInfo();
    while (0 < index--) {
      if (!mAttrNames[index].IsAtom() && 
          ni->NameAndNamespaceEquals(mAttrNames[index].GetNodeInfo())) {
        aFound = PR_TRUE;
        return NS_OK;
      }
    }
  }
  aFound = PR_FALSE;
  if (mAttrCount == mAttrSize) {  // no more room, grow buffer
    nsHTMLAttrName* buffer = new nsHTMLAttrName[mAttrSize + 4]; 
    if (buffer) {
      memcpy(buffer, mAttrNames, sizeof(nsHTMLAttrName) * mAttrCount);
      mAttrSize += 4;
      if (mAttrNames != mNameBuffer) {
        delete [] mAttrNames;
      }
      mAttrNames = buffer;
    }
    else {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }
  mAttrNames[mAttrCount] = aAttrName;
  mAttrNames[mAttrCount++].Addref();
  return NS_OK;
}

nsresult
nsHTMLAttributes::UnsetAttributeName(nsIAtom* aAttrName, PRBool& aFound)
{
  PRInt32 index = mAttrCount;
  while (0 < index--) {
    if (mAttrNames[index].mAtom == aAttrName) {
      mAttrCount--;
      if (mAttrNames != mNameBuffer &&
          mAttrCount <= (kHTMLAttrNameBufferSize / 2)) {
        // go back to using internal buffer
        if (0 < index) {
          memcpy(mNameBuffer, mAttrNames, index * sizeof(nsIAtom*));
        }
        if (index < mAttrCount) {
          memcpy(&mNameBuffer[index], &mAttrNames[index + 1], 
                 (mAttrCount - index) * sizeof(nsIAtom*));
        }
        delete [] mAttrNames;
        mAttrNames = mNameBuffer;
        mAttrSize = kHTMLAttrNameBufferSize;
      }
      else {
        if (index < mAttrCount) {
          memmove(&(mAttrNames[index]), &(mAttrNames[index + 1]), 
                  (mAttrCount - index) * sizeof(nsIAtom*));
        }
      }
      NS_RELEASE(aAttrName);
      aFound = PR_TRUE;
      return NS_OK;
    }
  }
  aFound = PR_FALSE;
  return NS_OK;
}

nsresult
nsHTMLAttributes::UnsetAttributeName(nsIAtom* aAttrName, PRInt32 aNamespaceID,
                                     PRBool& aFound)
{
  NS_ASSERTION(aNamespaceID != kNameSpaceID_None,
               "namespaced UnsetAttributeName called with null namespace");
  PRInt32 index = mAttrCount;
  while (0 < index--) {
    if (!mAttrNames[index].IsAtom() &&
        mAttrNames[index].GetNodeInfo()->Equals(aAttrName, aNamespaceID)) {
      mAttrNames[index].Release();
      mAttrCount--;
      if (mAttrNames != mNameBuffer &&
          mAttrCount <= (kHTMLAttrNameBufferSize / 2)) {
        // go back to using internal buffer
        if (0 < index) {
          memcpy(mNameBuffer, mAttrNames, index * sizeof(nsIAtom*));
        }
        if (index < mAttrCount) {
          memcpy(&mNameBuffer[index], &mAttrNames[index + 1], 
                 (mAttrCount - index) * sizeof(nsIAtom*));
        }
        delete [] mAttrNames;
        mAttrNames = mNameBuffer;
        mAttrSize = kHTMLAttrNameBufferSize;
      }
      else {
        if (index < mAttrCount) {
          memmove(&(mAttrNames[index]), &(mAttrNames[index + 1]),
                  (mAttrCount - index) * sizeof(nsIAtom*));
        }
      }
      aFound = PR_TRUE;
      return NS_OK;
    }
  }
  aFound = PR_FALSE;
  return NS_OK;
}

nsresult
nsHTMLAttributes::EnsureSingleMappedFor(nsIHTMLContent* aContent,
                                        nsIHTMLStyleSheet* aSheet,
                                        PRBool aCreate)
{
  nsresult result = NS_OK;
  if (mMapped) {
    nsHTMLMappedAttributes* single;
    result = mMapped->Clone(&single);
    if (NS_SUCCEEDED(result)) {
      mMapped->ReleaseUse();
      NS_RELEASE(mMapped);
      mMapped = single;
      mMapped->AddUse();
    }
  }
  else if (aCreate) {  // create one
    mMapped = new nsHTMLMappedAttributes();
    if (mMapped) {
      NS_ADDREF(mMapped);
      mMapped->AddUse();
      if (aContent) {
        nsMapRuleToAttributesFunc mapRuleFunc;
        aContent->GetAttributeMappingFunction(mapRuleFunc);
        result = mMapped->Init(aSheet, mapRuleFunc);
      }
    }
    else {
      result = NS_ERROR_OUT_OF_MEMORY;
    }
  }
  return result;
}

nsresult
nsHTMLAttributes::UniqueMapped(nsIHTMLStyleSheet* aSheet)
{
  nsresult result = NS_OK;

  if (aSheet) {
    nsIHTMLMappedAttributes*  mapped;
    result = aSheet->UniqueMappedAttributes(mMapped, mapped);
    if (NS_SUCCEEDED(result)) {
      if (mapped != mMapped) {
        mMapped->ReleaseUse();
        NS_RELEASE(mMapped);
        mMapped = (nsHTMLMappedAttributes*)mapped; // take ref
        mMapped->AddUse();
      }
      else {
        NS_RELEASE(mapped);
      }
    }
  }
  return result;
}

NS_IMETHODIMP
nsHTMLAttributes::SetAttributeFor(nsIAtom* aAttrName, const nsAString& aValue,
                                  PRBool aMappedToStyle, nsIHTMLContent* aContent,
                                  nsIHTMLStyleSheet* aSheet)
{
  nsresult result = NS_OK;

  if (nsHTMLAtoms::id == aAttrName) {
    NS_IF_RELEASE(mID);
    mID = NS_NewAtom(aValue);
  }
  else if (nsHTMLAtoms::kClass == aAttrName) {
    mFirstClass.Reset();
    ParseClasses(aValue, mFirstClass);
  }

  PRBool  haveAttr;
  result = SetAttributeName(aAttrName, haveAttr);
  if (NS_SUCCEEDED(result)) {
    if (aMappedToStyle) {
      result = EnsureSingleMappedFor(aContent, aSheet, PR_TRUE);
      if (mMapped) {
        result = mMapped->SetAttribute(aAttrName, aValue);
        UniqueMapped(aSheet);
      }
    }
    else {
      if (haveAttr) {
        HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);
        NS_ASSERTION(attr, "failed to find attribute");
        if (attr) {
          attr->mValue.SetStringValue(aValue);
        }
      }
      else {
        HTMLAttribute*  attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          attr->mNext = mFirstUnmapped;
          mFirstUnmapped = attr;
        }
        else {
          result = NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }
  }

  return result;
}

NS_IMETHODIMP
nsHTMLAttributes::SetAttributeFor(nsIAtom* aAttrName,
                                  const nsHTMLValue& aValue,
                                  PRBool aMappedToStyle,
                                  nsIHTMLContent* aContent,
                                  nsIHTMLStyleSheet* aSheet,
                                  PRInt32& aCount)
{
  nsresult result = NS_OK;

  if (eHTMLUnit_Null == aValue.GetUnit()) {
    return UnsetAttributeFor(aAttrName, kNameSpaceID_None, aContent,
                             aSheet, aCount);
  }
  
  if (nsHTMLAtoms::id == aAttrName) {
    NS_IF_RELEASE(mID);
    if (eHTMLUnit_String == aValue.GetUnit()) {
      nsAutoString  buffer;
      mID = NS_NewAtom(aValue.GetStringValue(buffer));
    }
  }
  else if (nsHTMLAtoms::kClass == aAttrName) {
    mFirstClass.Reset();
    if (eHTMLUnit_String == aValue.GetUnit()) {
      nsAutoString  buffer;
      aValue.GetStringValue(buffer);
      ParseClasses(buffer, mFirstClass);
    }
  }

  PRBool  haveAttr;
  result = SetAttributeName(aAttrName, haveAttr);
  if (NS_SUCCEEDED(result)) {
    if (aMappedToStyle) {
      result = EnsureSingleMappedFor(aContent, aSheet, PR_TRUE);
      if (mMapped) {
        result = mMapped->SetAttribute(aAttrName, aValue);
        UniqueMapped(aSheet);
      }
    }
    else {
      if (haveAttr) {
        HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);
        NS_ASSERTION(attr, "failed to find attribute");
        if (attr) {
          attr->mValue = aValue;
        }
      }
      else {
        HTMLAttribute*  attr = new HTMLAttribute(aAttrName, aValue);
        if (attr) {
          attr->mNext = mFirstUnmapped;
          mFirstUnmapped = attr;
        }
        else {
          result = NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }
  }
  aCount = mAttrCount;

  return result;
}

NS_IMETHODIMP
nsHTMLAttributes::UnsetAttributeFor(nsIAtom* aAttrName,
                                    PRInt32 aNamespaceID,
                                    nsIHTMLContent* aContent,
                                    nsIHTMLStyleSheet* aSheet,
                                    PRInt32& aCount)
{
  nsresult result = NS_OK;
  PRBool haveAttr;

  if (aNamespaceID == kNameSpaceID_None) {
    if (nsHTMLAtoms::id == aAttrName) {
      NS_IF_RELEASE(mID);
    }
    else if (nsHTMLAtoms::kClass == aAttrName) {
      mFirstClass.Reset();
    }
    result = UnsetAttributeName(aAttrName, haveAttr);
  }
  else {
    result = UnsetAttributeName(aAttrName, aNamespaceID, haveAttr);
  }

  if (NS_SUCCEEDED(result) && haveAttr) {
    if (!HTMLAttribute::RemoveHTMLAttribute(aAttrName, aNamespaceID,
                                            &mFirstUnmapped) &&
        aNamespaceID == kNameSpaceID_None) {
      // must be mapped
      if (mMapped) {
        EnsureSingleMappedFor(aContent, aSheet, PR_FALSE);
        PRInt32 mappedCount = 0;
        mMapped->UnsetAttribute(aAttrName, mappedCount);
        if (! mappedCount) {  // toss it when empty
          mMapped->ReleaseUse();
          NS_RELEASE(mMapped);
        }
        else {
          UniqueMapped(aSheet);
        }
      }
    }
  }

  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::GetAttribute(nsIAtom* aAttrName,
                               nsHTMLValue& aValue) const
{
  nsresult result = NS_CONTENT_ATTR_NOT_THERE;

  if (mMapped) {
    result = mMapped->GetAttribute(aAttrName, aValue);
  }

  if (NS_CONTENT_ATTR_NOT_THERE == result) {
    const HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);

    if (attr) {
      aValue = attr->mValue;
      result = ((attr->mValue.GetUnit() == eHTMLUnit_Null) ? 
                NS_CONTENT_ATTR_NO_VALUE : 
                NS_CONTENT_ATTR_HAS_VALUE);
    }
    else {
      aValue.Reset();
    }
  }

  return result;
}

NS_IMETHODIMP
nsHTMLAttributes::GetAttribute(nsIAtom* aAttrName,
                               const nsHTMLValue** aValue) const
{
  nsresult result = NS_CONTENT_ATTR_NOT_THERE;

  if (mMapped) {
    result = mMapped->GetAttribute(aAttrName, aValue);
  }

  if (NS_CONTENT_ATTR_NOT_THERE == result) {
    const HTMLAttribute*  attr = HTMLAttribute::FindHTMLAttribute(aAttrName, mFirstUnmapped);

    if (attr) {
      *aValue = &attr->mValue;
      result = ((attr->mValue.GetUnit() == eHTMLUnit_Null) ? 
                NS_CONTENT_ATTR_NO_VALUE : 
                NS_CONTENT_ATTR_HAS_VALUE);
    }
    else {
      *aValue = nsnull;
    }
  }

  return result;
}

NS_IMETHODIMP_(PRBool)
nsHTMLAttributes::HasAttribute(nsIAtom* aAttrName, PRInt32 aNamespaceID) const
{
  if (mMapped && aNamespaceID == kNameSpaceID_None &&
      mMapped->HasAttribute(aAttrName))
    return PR_TRUE;

  return (PRBool)(HTMLAttribute::FindHTMLAttribute(aAttrName, aNamespaceID, mFirstUnmapped) != nsnull);
}

NS_IMETHODIMP
nsHTMLAttributes::SetAttributeFor(nsINodeInfo* aAttrName,
                                  const nsAString& aValue)
{
  NS_ENSURE_ARG_POINTER(aAttrName);
  nsresult rv;
#ifdef DEBUG
  {
    PRInt32 namespaceID = aAttrName->GetNamespaceID();
    NS_ASSERTION(namespaceID != kNameSpaceID_None, "namespace is null in SetAttributeFor");
  }
#endif

  PRBool haveAttr;
  rv = SetAttributeName(aAttrName, haveAttr);
  NS_ENSURE_SUCCESS(rv, rv);
  if (haveAttr) {
    PRInt32 namespaceID = aAttrName->GetNamespaceID();
    nsCOMPtr<nsIAtom> localName = aAttrName->GetNameAtom();

    HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(localName,
                                                           namespaceID,
                                                           mFirstUnmapped);
    NS_ASSERTION(attr, "failed to find attribute");
    if (!attr)
      return NS_ERROR_FAILURE;
    attr->mValue.SetStringValue(aValue);
  }
  else {
    HTMLAttribute* attr = new HTMLAttribute(aAttrName, aValue);
    NS_ENSURE_TRUE(attr, NS_ERROR_OUT_OF_MEMORY);
    attr->mNext = mFirstUnmapped;
    mFirstUnmapped = attr;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::GetAttribute(nsIAtom* aAttrName, PRInt32 aNamespaceID,
                               nsIAtom** aPrefix,
                               const nsHTMLValue** aValue) const
{
  NS_ASSERTION(aNamespaceID != kNameSpaceID_None, "namespace is null in UnsetAttributeFor");

  const HTMLAttribute* attr = HTMLAttribute::FindHTMLAttribute(aAttrName,
                                                               aNamespaceID,
                                                               mFirstUnmapped);
  if (!attr) {
    *aValue = nsnull;
    return NS_CONTENT_ATTR_NOT_THERE;
  }

  *aValue = &attr->mValue;

  // AddRefs
  *aPrefix = attr->mAttribute.GetNodeInfo()->GetPrefixAtom().get();

  return attr->mValue.GetUnit() == eHTMLUnit_Null ? 
         NS_CONTENT_ATTR_NO_VALUE : 
         NS_CONTENT_ATTR_HAS_VALUE;
}


NS_IMETHODIMP
nsHTMLAttributes::GetAttributeNameAt(PRInt32 aIndex,
                                     PRInt32* aNamespaceID,
                                     nsIAtom** aName,
                                     nsIAtom** aPrefix) const
{
  nsresult result = NS_ERROR_ILLEGAL_VALUE;

  if ((0 <= aIndex) && (aIndex < mAttrCount)) {
    if (mAttrNames[aIndex].IsAtom()) {
        *aNamespaceID = kNameSpaceID_None;
        *aName = mAttrNames[aIndex].mAtom;
        NS_ADDREF(*aName);
        *aPrefix = nsnull;
    }
    else {
        nsINodeInfo* ni = mAttrNames[aIndex].GetNodeInfo();
        *aNamespaceID = ni->GetNamespaceID();

        // AddRefs
        *aName = ni->GetNameAtom().get();
        // AddRefs
        *aPrefix = ni->GetPrefixAtom().get();
    }
    result = NS_OK;
  }
  return result;
}

NS_IMETHODIMP
nsHTMLAttributes::GetAttributeCount(PRInt32& aCount) const
{
  aCount = mAttrCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::GetID(nsIAtom** aResult) const
{
  *aResult = mID;
  NS_IF_ADDREF(*aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::GetClasses(nsVoidArray& aArray) const
{
  aArray.Clear();
  const nsHTMLClassList* classList = &mFirstClass;
  while (classList && classList->mAtom) {
    aArray.AppendElement(classList->mAtom); // NOTE atom is not addrefed
    classList = classList->mNext;
  }
  return NS_OK;
}

NS_IMETHODIMP_(PRBool)
nsHTMLAttributes::HasClass(nsIAtom* aClass, PRBool aCaseSensitive) const
{
  NS_PRECONDITION(aClass, "unexpected null pointer");
  if (mFirstClass.mAtom) {
    const nsHTMLClassList* classList = &mFirstClass;
    if (aCaseSensitive) {
      do {
        if (classList->mAtom == aClass)
          return PR_TRUE;
        classList = classList->mNext;
      } while (classList);
    } else {
      const char* class1;
      aClass->GetUTF8String(&class1);
      // This length calculation (and the |aCaseSensitive| check above) could
      // theoretically be pulled out of another loop by creating a separate
      // |HasClassCI| function.
      do {
        const char* class2;
        classList->mAtom->GetUTF8String(&class2);
        if (nsCRT::strcasecmp(class1, class2) == 0)
          return PR_TRUE;
        classList = classList->mNext;
      } while (classList);
    }
  }
  return PR_FALSE;
}

#ifdef UNIQUE_ATTR_SUPPORT
NS_IMETHODIMP
nsHTMLAttributes::AddContentRef(void)
{
  ++mContentRefCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::ReleaseContentRef(void)
{
  --mContentRefCount;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::GetContentRefCount(PRInt32& aCount) const
{
  aCount = mContentRefCount;
  return NS_OK;
}
#endif

NS_IMETHODIMP
nsHTMLAttributes::Clone(nsHTMLAttributes** aInstancePtrResult) const
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  nsHTMLAttributes* clone = new nsHTMLAttributes(*this);

  if (nsnull == clone) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  *aInstancePtrResult = clone;
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::SetStyleSheet(nsIHTMLStyleSheet* aSheet)
{
  if (mMapped && (aSheet != mMapped->mSheet)) {
    mMapped->SetStyleSheet(aSheet);
    return UniqueMapped(aSheet);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::WalkMappedAttributeStyleRules(nsRuleWalker* aRuleWalker) const
{
  if (aRuleWalker && mMapped)
    aRuleWalker->Forward((nsIStyleRule*)mMapped);
  return NS_OK;
}

NS_IMETHODIMP
nsHTMLAttributes::Reset(void)
{
  // Release atoms first, then the table if it was malloc'd
  PRInt32 i, n = mAttrCount;
  for (i = 0; i < n; i++) {
    mAttrNames[i].Release();
  }
  if (mAttrNames != mNameBuffer) {
    delete [] mAttrNames;
    mAttrNames = mNameBuffer;
    mAttrSize = kHTMLAttrNameBufferSize;
  }
  mAttrCount = 0;

  if (mFirstUnmapped) {
    HTMLAttribute::DeleteHTMLAttributes(mFirstUnmapped);
  }

  if (mMapped) {
    mMapped->ReleaseUse();
    NS_RELEASE(mMapped);
  }

  NS_IF_RELEASE(mID);
  mFirstClass.Reset();
  return NS_OK;
}

#ifdef DEBUG
NS_IMETHODIMP
nsHTMLAttributes::List(FILE* out, PRInt32 aIndent) const
{
  PRInt32 index = 0;
  while (index < mAttrCount) {
    PRInt32 indent;
    for (indent = aIndent; --indent >= 0; ) fputs("  ", out);

    nsHTMLValue value;
    nsAutoString  buffer;
    if (mAttrNames[index].IsAtom()) {
      GetAttribute(mAttrNames[index].mAtom, value);
      mAttrNames[index].mAtom->ToString(buffer);
    }
    else {
      nsINodeInfo* ni = mAttrNames[index].GetNodeInfo();
      PRInt32 namespaceID = ni->GetNamespaceID();
      nsCOMPtr<nsIAtom> localName = ni->GetNameAtom();
      nsCOMPtr<nsIAtom> prefix;
      const nsHTMLValue *tmp;
      GetAttribute(localName, namespaceID, getter_AddRefs(prefix), &tmp);
      value = *tmp;
      ni->GetQualifiedName(buffer);
    }
    if (eHTMLUnit_Null != value.GetUnit()) {
      buffer.Append(NS_LITERAL_STRING(" = "));
      value.AppendToString(buffer);
    }
    fputs(NS_LossyConvertUCS2toASCII(buffer).get(), out);
  }
  return NS_OK;
}
#endif

nsresult
NS_NewHTMLAttributes(nsHTMLAttributes** aInstancePtrResult)
{
  *aInstancePtrResult = new nsHTMLAttributes();
  NS_ENSURE_TRUE(*aInstancePtrResult, NS_ERROR_OUT_OF_MEMORY);

  return NS_OK;
}


