/* -*- 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 "nsIStyleSet.h"
#include "nsIStyleSheet.h"
#include "nsIStyleRule.h"
#include "nsIStyleContext.h"
#include "nsISupportsArray.h"
#include "nsIFrame.h"
#include "nsHashtable.h"


static NS_DEFINE_IID(kIStyleSetIID, NS_ISTYLE_SET_IID);

class ContextKey : public nsHashKey {
public:
  ContextKey(nsIStyleContext* aContext);
  ContextKey(nsIStyleContext* aParent, nsISupportsArray* aRules);
  virtual ~ContextKey(void);

  void        SetContext(nsIStyleContext* aContext);

  PRBool      Equals(const nsHashKey* aOther) const;
  PRUint32    HashValue(void) const;
  nsHashKey*  Clone(void) const;

private:
  ContextKey(void);
  ContextKey(const ContextKey& aCopy);
  ContextKey& operator=(const ContextKey& aCopy) const;

protected:
  nsIStyleContext* mContext;
  nsIStyleContext* mParent;
  nsISupportsArray* mRules;
};

ContextKey::ContextKey(nsIStyleContext* aContext)
  : mContext(aContext),
    mParent(nsnull),
    mRules(nsnull)
{
  NS_IF_ADDREF(mContext);
}

ContextKey::ContextKey(nsIStyleContext* aParent, nsISupportsArray* aRules)
  : mContext(nsnull),
    mParent(aParent),
    mRules(aRules)
{
  NS_IF_ADDREF(mParent);
  NS_IF_ADDREF(mRules);
}

ContextKey::ContextKey(const ContextKey& aCopy)
  : mContext(aCopy.mContext),
    mParent(aCopy.mParent),
    mRules(aCopy.mRules)
{
  NS_IF_ADDREF(mContext);
  NS_IF_ADDREF(mParent);
  NS_IF_ADDREF(mRules);
}

ContextKey::~ContextKey(void)
{
  NS_IF_RELEASE(mContext);
  NS_IF_RELEASE(mParent);
  NS_IF_RELEASE(mRules);
}

void ContextKey::SetContext(nsIStyleContext* aContext)
{
  if (aContext != mContext) {
    NS_IF_RELEASE(mContext);
    mContext = aContext;
    NS_IF_ADDREF(mContext);
  }
  NS_IF_RELEASE(mParent);
  NS_IF_RELEASE(mRules);
}

PRBool ContextKey::Equals(const nsHashKey* aOther) const
{
  PRBool result = PR_TRUE;
  const ContextKey* other = (const ContextKey*)aOther;

  if (other != this) {
    if ((nsnull == mContext) || (nsnull == other->mContext) || (mContext != other->mContext)) {
      nsIStyleContext* otherParent = other->mParent;
      if ((nsnull == otherParent) && (nsnull != other->mContext)) {
        otherParent = other->mContext->GetParent();
      }
      else {
        NS_IF_ADDREF(otherParent);  // simulate the above addref
      }

      if (mParent == otherParent) {
        nsISupportsArray* otherRules = other->mRules;
        if ((nsnull == otherRules) && (nsnull != other->mContext)) {
          otherRules = other->mContext->GetStyleRules();
        }
        else {
          NS_IF_ADDREF(otherRules); // simulate the above addref
        }
        if ((nsnull != mRules) && (nsnull != otherRules)) {
          result = mRules->Equals(otherRules);
        }
        else {
          result = PRBool((nsnull == mRules) && (nsnull == otherRules));
        }
        NS_IF_RELEASE(otherRules);
      }
      else {
        result = PR_FALSE;
      }
      NS_IF_RELEASE(otherParent);
    }
  }
  return result;
}

PRUint32 ContextKey::HashValue(void) const
{
  if (nsnull != mContext) {
    return mContext->HashValue();
  }
  // we don't have a context yet, compute it like it would
  PRUint32 hashValue = ((nsnull != mParent) ? mParent->HashValue() : 0);
  if (nsnull != mRules) {
    PRInt32 index = mRules->Count();
    while (0 <= --index) {
      nsIStyleRule* rule = (nsIStyleRule*)mRules->ElementAt(index);
      PRUint32 hash = rule->HashValue();
      hashValue ^= (hash & 0x7FFFFFFF);
      NS_RELEASE(rule);
    }
  }
  return hashValue;
}

nsHashKey* ContextKey::Clone(void) const
{
  return new ContextKey(*this);
}


class StyleSetImpl : public nsIStyleSet {
public:
  StyleSetImpl();

  NS_DECL_ISUPPORTS

  virtual void AppendOverrideStyleSheet(nsIStyleSheet* aSheet);
  virtual void InsertOverrideStyleSheetAfter(nsIStyleSheet* aSheet,
                                             nsIStyleSheet* aAfterSheet);
  virtual void InsertOverrideStyleSheetBefore(nsIStyleSheet* aSheet,
                                              nsIStyleSheet* aBeforeSheet);
  virtual void RemoveOverrideStyleSheet(nsIStyleSheet* aSheet);
  virtual PRInt32 GetNumberOfOverrideStyleSheets();
  virtual nsIStyleSheet* GetOverrideStyleSheetAt(PRInt32 aIndex);

  virtual void AppendDocStyleSheet(nsIStyleSheet* aSheet);
  virtual void InsertDocStyleSheetAfter(nsIStyleSheet* aSheet,
                                        nsIStyleSheet* aAfterSheet);
  virtual void InsertDocStyleSheetBefore(nsIStyleSheet* aSheet,
                                         nsIStyleSheet* aBeforeSheet);
  virtual void RemoveDocStyleSheet(nsIStyleSheet* aSheet);
  virtual PRInt32 GetNumberOfDocStyleSheets();
  virtual nsIStyleSheet* GetDocStyleSheetAt(PRInt32 aIndex);

  virtual void AppendBackstopStyleSheet(nsIStyleSheet* aSheet);
  virtual void InsertBackstopStyleSheetAfter(nsIStyleSheet* aSheet,
                                             nsIStyleSheet* aAfterSheet);
  virtual void InsertBackstopStyleSheetBefore(nsIStyleSheet* aSheet,
                                              nsIStyleSheet* aBeforeSheet);
  virtual void RemoveBackstopStyleSheet(nsIStyleSheet* aSheet);
  virtual PRInt32 GetNumberOfBackstopStyleSheets();
  virtual nsIStyleSheet* GetBackstopStyleSheetAt(PRInt32 aIndex);

  virtual nsIStyleContext* ResolveStyleFor(nsIPresContext* aPresContext,
                                           nsIContent* aContent,
                                           nsIFrame* aParentFrame);

  // xxx style rules enumeration

  virtual void List(FILE* out = stdout, PRInt32 aIndent = 0);

private:
  // These are not supported and are not implemented!
  StyleSetImpl(const StyleSetImpl& aCopy);
  StyleSetImpl& operator=(const StyleSetImpl& aCopy);

protected:
  virtual ~StyleSetImpl();
  PRBool EnsureArray(nsISupportsArray** aArray);
  PRInt32 RulesMatching(nsISupportsArray* aSheets,
                        nsIPresContext* aPresContext,
                        nsIContent* aContent,
                        nsIFrame* aParentFrame,
                        nsISupportsArray* aResults);
  void  List(FILE* out, PRInt32 aIndent, nsISupportsArray* aSheets);

  nsISupportsArray* mOverrideSheets;
  nsISupportsArray* mDocSheets;
  nsISupportsArray* mBackstopSheets;
  nsHashtable mStyleContexts;
};


StyleSetImpl::StyleSetImpl()
  : mOverrideSheets(nsnull),
    mDocSheets(nsnull),
    mBackstopSheets(nsnull)
{
  NS_INIT_REFCNT();
}

PRBool ReleaseContext(nsHashKey *aKey, void *aData)
{
  ((nsIStyleContext*)aData)->Release();
  return PR_TRUE;
}

StyleSetImpl::~StyleSetImpl()
{
  NS_IF_RELEASE(mOverrideSheets);
  NS_IF_RELEASE(mDocSheets);
  NS_IF_RELEASE(mBackstopSheets);
  mStyleContexts.Enumerate(ReleaseContext);
}

NS_IMPL_ISUPPORTS(StyleSetImpl, kIStyleSetIID)

PRBool StyleSetImpl::EnsureArray(nsISupportsArray** aArray)
{
  if (nsnull == *aArray) {
    if (NS_OK != NS_NewISupportsArray(aArray)) {
      return PR_FALSE;
    }
  }
  return PR_TRUE;
}

// ----- Override sheets

void StyleSetImpl::AppendOverrideStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mOverrideSheets)) {
    mOverrideSheets->AppendElement(aSheet);
  }
}

void StyleSetImpl::InsertOverrideStyleSheetAfter(nsIStyleSheet* aSheet,
                                                 nsIStyleSheet* aAfterSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mOverrideSheets)) {
    PRInt32 index = mOverrideSheets->IndexOf(aAfterSheet);
    mOverrideSheets->InsertElementAt(aSheet, ++index);
  }
}

void StyleSetImpl::InsertOverrideStyleSheetBefore(nsIStyleSheet* aSheet,
                                                  nsIStyleSheet* aBeforeSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mOverrideSheets)) {
    PRInt32 index = mOverrideSheets->IndexOf(aBeforeSheet);
    mOverrideSheets->InsertElementAt(aSheet, ((-1 < index) ? index : 0));
  }
}

void StyleSetImpl::RemoveOverrideStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");

  if (nsnull != mOverrideSheets) {
    mOverrideSheets->RemoveElement(aSheet);
  }
}

PRInt32 StyleSetImpl::GetNumberOfOverrideStyleSheets()
{
  if (nsnull != mOverrideSheets) {
    return mOverrideSheets->Count();
  }
  return 0;
}

nsIStyleSheet* StyleSetImpl::GetOverrideStyleSheetAt(PRInt32 aIndex)
{
  nsIStyleSheet* sheet = nsnull;
  if (nsnull == mOverrideSheets) {
    sheet = (nsIStyleSheet*)mOverrideSheets->ElementAt(aIndex);
  }
  return sheet;
}

// -------- Doc Sheets

void StyleSetImpl::AppendDocStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mDocSheets)) {
    mDocSheets->AppendElement(aSheet);
  }
}

void StyleSetImpl::InsertDocStyleSheetAfter(nsIStyleSheet* aSheet,
                                         nsIStyleSheet* aAfterSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mDocSheets)) {
    PRInt32 index = mDocSheets->IndexOf(aAfterSheet);
    mDocSheets->InsertElementAt(aSheet, ++index);
  }
}

void StyleSetImpl::InsertDocStyleSheetBefore(nsIStyleSheet* aSheet,
                                          nsIStyleSheet* aBeforeSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mDocSheets)) {
    PRInt32 index = mDocSheets->IndexOf(aBeforeSheet);
    mDocSheets->InsertElementAt(aSheet, ((-1 < index) ? index : 0));
  }
}

void StyleSetImpl::RemoveDocStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");

  if (nsnull != mDocSheets) {
    mDocSheets->RemoveElement(aSheet);
  }
}

PRInt32 StyleSetImpl::GetNumberOfDocStyleSheets()
{
  if (nsnull != mDocSheets) {
    return mDocSheets->Count();
  }
  return 0;
}

nsIStyleSheet* StyleSetImpl::GetDocStyleSheetAt(PRInt32 aIndex)
{
  nsIStyleSheet* sheet = nsnull;
  if (nsnull == mDocSheets) {
    sheet = (nsIStyleSheet*)mDocSheets->ElementAt(aIndex);
  }
  return sheet;
}

// ------ backstop sheets

void StyleSetImpl::AppendBackstopStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mBackstopSheets)) {
    mBackstopSheets->AppendElement(aSheet);
  }
}

void StyleSetImpl::InsertBackstopStyleSheetAfter(nsIStyleSheet* aSheet,
                                                 nsIStyleSheet* aAfterSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mBackstopSheets)) {
    PRInt32 index = mBackstopSheets->IndexOf(aAfterSheet);
    mBackstopSheets->InsertElementAt(aSheet, ++index);
  }
}

void StyleSetImpl::InsertBackstopStyleSheetBefore(nsIStyleSheet* aSheet,
                                                  nsIStyleSheet* aBeforeSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  if (EnsureArray(&mBackstopSheets)) {
    PRInt32 index = mBackstopSheets->IndexOf(aBeforeSheet);
    mBackstopSheets->InsertElementAt(aSheet, ((-1 < index) ? index : 0));
  }
}

void StyleSetImpl::RemoveBackstopStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");

  if (nsnull != mBackstopSheets) {
    mBackstopSheets->RemoveElement(aSheet);
  }
}

PRInt32 StyleSetImpl::GetNumberOfBackstopStyleSheets()
{
  if (nsnull != mBackstopSheets) {
    return mBackstopSheets->Count();
  }
  return 0;
}

nsIStyleSheet* StyleSetImpl::GetBackstopStyleSheetAt(PRInt32 aIndex)
{
  nsIStyleSheet* sheet = nsnull;
  if (nsnull == mBackstopSheets) {
    sheet = (nsIStyleSheet*)mBackstopSheets->ElementAt(aIndex);
  }
  return sheet;
}

PRInt32 StyleSetImpl::RulesMatching(nsISupportsArray* aSheets,
                                    nsIPresContext* aPresContext,
                                    nsIContent* aContent,
                                    nsIFrame* aParentFrame,
                                    nsISupportsArray* aResults)
{
  PRInt32 ruleCount = 0;

  if (nsnull != aSheets) {
    PRInt32 sheetCount = aSheets->Count();
    PRInt32 index;
    for (index = 0; index < sheetCount; index++) {
      nsIStyleSheet* sheet = (nsIStyleSheet*)aSheets->ElementAt(index);
      ruleCount += sheet->RulesMatching(aPresContext, aContent, aParentFrame,
                                        aResults);
      NS_RELEASE(sheet);
    }
  }
  return ruleCount;
}

nsIStyleContext* StyleSetImpl::ResolveStyleFor(nsIPresContext* aPresContext,
                                               nsIContent* aContent,
                                               nsIFrame* aParentFrame)
{
  nsIStyleContext*  result = nsnull;

  nsIStyleContext* parentContext = nsnull;
  
  if (nsnull != aParentFrame) {
    aParentFrame->GetStyleContext(aPresContext, parentContext);
    NS_ASSERTION(nsnull != parentContext, "parent must have style context");
  }

  // want to check parent frame's context for cached child context first

  // then do a brute force rule search

  nsISupportsArray*  rules = nsnull;
  if (NS_OK == NS_NewISupportsArray(&rules)) {
    PRInt32 ruleCount = RulesMatching(mOverrideSheets, aPresContext, aContent, aParentFrame, rules);
    ruleCount += RulesMatching(mDocSheets, aPresContext, aContent, aParentFrame, rules);
    ruleCount += RulesMatching(mBackstopSheets, aPresContext, aContent, aParentFrame, rules);

    // then check for cached ruleSet to context or create
    ContextKey tempKey(parentContext, rules);
    result = (nsIStyleContext*)mStyleContexts.Get(&tempKey);
    if (nsnull == result) {
      if (NS_OK == NS_NewStyleContext(&result, rules, aPresContext, aContent, aParentFrame)) {
        tempKey.SetContext(result);
        mStyleContexts.Put(&tempKey, result);  // hashtable clones key, so this is OK (table gets first ref)
        NS_ADDREF(result);  // add ref for the caller
//fprintf(stdout, "+");
      }
    }
    else {
      NS_ADDREF(result);
//fprintf(stdout, "-");
    }
    NS_RELEASE(rules);
  }

  NS_IF_RELEASE(parentContext);

  return result;
}

// xxx style rules enumeration

void StyleSetImpl::List(FILE* out, PRInt32 aIndent, nsISupportsArray* aSheets)
{
  PRInt32 count = ((nsnull != aSheets) ? aSheets->Count() : 0);

  for (PRInt32 index = 0; index < count; index++) {
    nsIStyleSheet* sheet = (nsIStyleSheet*)aSheets->ElementAt(index);
    sheet->List(out, aIndent);
    fputs("\n", out);
    NS_RELEASE(sheet);
  }
}

void StyleSetImpl::List(FILE* out, PRInt32 aIndent)
{
  List(out, aIndent, mOverrideSheets);
  List(out, aIndent, mDocSheets);
  List(out, aIndent, mBackstopSheets);
}


NS_LAYOUT nsresult
NS_NewStyleSet(nsIStyleSet** aInstancePtrResult)
{
  if (aInstancePtrResult == nsnull) {
    return NS_ERROR_NULL_POINTER;
  }

  StyleSetImpl  *it = new StyleSetImpl();

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

  return it->QueryInterface(kIStyleSetIID, (void **) aInstancePtrResult);
}
