/* -*- 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 "nsDocument.h"
#include "nsIArena.h"
#include "nsIURL.h"
#include "nsString.h"
#include "nsIContent.h"
#include "nsIStyleSet.h"
#include "nsIStyleSheet.h"
#include "nsIPresShell.h"
#include "nsIDocumentObserver.h"

static NS_DEFINE_IID(kIDocumentIID, NS_IDOCUMENT_IID);

#include "nsIDOMElement.h"

static NS_DEFINE_IID(kIDOMDocumentIID, NS_IDOMDOCUMENT_IID);
static NS_DEFINE_IID(kIDOMNodeIID, NS_IDOMNODE_IID);
static NS_DEFINE_IID(kIContentIID, NS_ICONTENT_IID);
static NS_DEFINE_IID(kIDOMElementIID, NS_IDOMELEMENT_IID);
static NS_DEFINE_IID(kIScriptObjectOwnerIID, NS_ISCRIPTOBJECTOWNER_IID);


nsDocument::nsDocument()
{
  NS_INIT_REFCNT();

  mArena = nsnull;
  mDocumentTitle = nsnull;
  mDocumentURL = nsnull;
  mCharacterSet = eCharSetID_IsoLatin1;
  mParentDocument = nsnull;
  mRootContent = nsnull;
  mScriptObject = nsnull;

  Init();/* XXX */
}

nsDocument::~nsDocument()
{
  if (nsnull != mDocumentTitle) {
    delete mDocumentTitle;
    mDocumentTitle = nsnull;
  }
  NS_IF_RELEASE(mDocumentURL);

  mParentDocument = nsnull;

  // Delete references to sub-documents
  PRInt32 index = mSubDocuments.Count();
  while (--index >= 0) {
    nsIDocument* subdoc = (nsIDocument*) mSubDocuments.ElementAt(index);
    NS_RELEASE(subdoc);
  }

  NS_IF_RELEASE(mRootContent);
  // Delete references to style sheets
  index = mStyleSheets.Count();
  while (--index >= 0) {
    nsIStyleSheet* sheet = (nsIStyleSheet*) mStyleSheets.ElementAt(index);
    NS_RELEASE(sheet);
  }

  NS_IF_RELEASE(mArena);
}

nsresult nsDocument::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
  if (nsnull == aInstancePtr) {
    return NS_ERROR_NULL_POINTER;
  }
  if (aIID.Equals(kIDocumentIID)) {
    *aInstancePtr = (void*)(nsIDocument*)this;
    AddRef();
    return NS_OK;
  }
  if (aIID.Equals(kIDOMDocumentIID)) {
    *aInstancePtr = (void*)(nsIDOMDocument*)this;
    AddRef();
    return NS_OK;
  }
  if (aIID.Equals(kIScriptObjectOwnerIID)) {
    *aInstancePtr = (void*)(nsIScriptObjectOwner*)this;
    AddRef();
    return NS_OK;
  }
  static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
  if (aIID.Equals(kISupportsIID)) {
    *aInstancePtr = (void*)(nsISupports*)(nsIDocument*)this;
    AddRef();
    return NS_OK;
  }
  return NS_NOINTERFACE;
}

NS_IMPL_ADDREF(nsDocument)
NS_IMPL_RELEASE(nsDocument)

nsresult nsDocument::Init()
{
  nsresult rv = NS_NewHeapArena(&mArena, nsnull);
  if (NS_OK != rv) {
    return rv;
  }
  return NS_OK;
}

nsIArena* nsDocument::GetArena()
{
  if (nsnull != mArena) {
    NS_ADDREF(mArena);
  }
  return mArena;
}

void nsDocument::StartDocumentLoad()
{
}

void nsDocument::PauseDocumentLoad()
{
}

void nsDocument::StopDocumentLoad()
{
}

void nsDocument::WaitForDocumentLoad()
{
}

PRBool nsDocument::IsDocumentLoaded()
{
  return PR_TRUE;
}

const nsString* nsDocument::GetDocumentTitle() const
{
  return mDocumentTitle;
}

nsIURL* nsDocument::GetDocumentURL() const
{
  NS_IF_ADDREF(mDocumentURL);
  return mDocumentURL;
}

nsCharSetID nsDocument::GetDocumentCharacterSet() const
{
  return mCharacterSet;
}

void nsDocument::SetDocumentCharacterSet(nsCharSetID aCharSetID)
{
  mCharacterSet = aCharSetID;
}

nsIPresShell* nsDocument::CreateShell(nsIPresContext* aContext,
                                      nsIViewManager* aViewManager,
                                      nsIStyleSet* aStyleSet)
{
  nsIPresShell* shell;
  nsresult status = NS_NewPresShell(&shell);
  if ((NS_OK == status) &&
      (NS_OK == shell->Init(this, aContext, aViewManager, aStyleSet))) {
    // Note: we don't hold a ref to the shell (it holds a ref to us)
    mPresShells.AppendElement(shell);
  }
  return shell;
}

PRBool nsDocument::DeleteShell(nsIPresShell* aShell)
{
  return mPresShells.RemoveElement(aShell);
}

PRInt32 nsDocument::GetNumberOfShells()
{
  return mPresShells.Count();
}

nsIPresShell* nsDocument::GetShellAt(PRInt32 aIndex)
{
  nsIPresShell* shell = (nsIPresShell*) mPresShells.ElementAt(aIndex);
  if (nsnull != shell) {
    NS_ADDREF(shell);
  }
  return shell;
}

nsIDocument* nsDocument::GetParentDocument()
{
  if (nsnull != mParentDocument) {
    NS_ADDREF(mParentDocument);
  }
  return mParentDocument;
}

/**
 * Note that we do *not* AddRef our parent because that would
 * create a circular reference.
 */
void nsDocument::SetParentDocument(nsIDocument* aParent)
{
  mParentDocument = aParent;
}

void nsDocument::AddSubDocument(nsIDocument* aSubDoc)
{
  NS_ADDREF(aSubDoc);
  mSubDocuments.AppendElement(aSubDoc);
}

PRInt32 nsDocument::GetNumberOfSubDocuments()
{
  return mSubDocuments.Count();
}

nsIDocument* nsDocument::GetSubDocumentAt(PRInt32 aIndex)
{
  nsIDocument* doc = (nsIDocument*) mSubDocuments.ElementAt(aIndex);
  if (nsnull != doc) {
    NS_ADDREF(doc);
  }
  return doc;
}

nsIContent* nsDocument::GetRootContent()
{
  if (nsnull != mRootContent) {
    NS_ADDREF(mRootContent);
  }
  return mRootContent;
}

void nsDocument::SetRootContent(nsIContent* aRoot)
{
  NS_IF_RELEASE(mRootContent);
  if (nsnull != aRoot) {
    mRootContent = aRoot;
    NS_ADDREF(aRoot);
  }
}

PRInt32 nsDocument::GetNumberOfStyleSheets()
{
  return mStyleSheets.Count();
}

nsIStyleSheet* nsDocument::GetStyleSheetAt(PRInt32 aIndex)
{
  nsIStyleSheet* sheet = (nsIStyleSheet*)mStyleSheets.ElementAt(aIndex);
  NS_IF_ADDREF(sheet);
  return sheet;
}

void nsDocument::AddStyleSheetToSet(nsIStyleSheet* aSheet, nsIStyleSet* aSet)
{
  aSet->AppendDocStyleSheet(aSheet);
}

void nsDocument::AddStyleSheet(nsIStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null arg");
  mStyleSheets.AppendElement(aSheet);
  NS_ADDREF(aSheet);

  PRInt32 count = mPresShells.Count();
  PRInt32 index;
  for (index = 0; index < count; index++) {
    nsIPresShell* shell = (nsIPresShell*)mPresShells.ElementAt(index);
    nsIStyleSet* set = shell->GetStyleSet();
    if (nsnull != set) {
      AddStyleSheetToSet(aSheet, set);
      NS_RELEASE(set);
    }
  }

  count = mObservers.Count();
  for (index = 0; index < count; index++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers.ElementAt(index);
    observer->StyleSheetAdded(aSheet);
  }
}

// Note: We don't hold a reference to the document observer; we assume
// that it has a live reference to the document.
void nsDocument::AddObserver(nsIDocumentObserver* aObserver)
{
  mObservers.AppendElement(aObserver);
}

PRBool nsDocument::RemoveObserver(nsIDocumentObserver* aObserver)
{
  return mObservers.RemoveElement(aObserver);
}

void nsDocument::ContentChanged(nsIContent* aContent,
                                nsISubContent* aSubContent,
                                PRInt32 aChangeType)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentChanged(aContent, aSubContent, aChangeType);
  }
}

void nsDocument::ContentAppended(nsIContent* aContainer)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentAppended(aContainer);
  }
}

void nsDocument::ContentInserted(nsIContent* aContainer,
                                 nsIContent* aChild,
                                 PRInt32 aIndexInContainer)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentInserted(aContainer, aChild, aIndexInContainer);
  }
}

void nsDocument::ContentReplaced(nsIContent* aContainer,
                                 nsIContent* aOldChild,
                                 nsIContent* aNewChild,
                                 PRInt32 aIndexInContainer)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentReplaced(aContainer, aOldChild, aNewChild,
                              aIndexInContainer);
  }
}

void nsDocument::ContentWillBeRemoved(nsIContent* aContainer,
                                      nsIContent* aChild,
                                      PRInt32 aIndexInContainer)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentWillBeRemoved(aContainer, aChild, aIndexInContainer);
  }
}

void nsDocument::ContentHasBeenRemoved(nsIContent* aContainer,
                                       nsIContent* aChild,
                                       PRInt32 aIndexInContainer)
{
  PRInt32 count = mObservers.Count();
  for (PRInt32 i = 0; i < count; i++) {
    nsIDocumentObserver*  observer = (nsIDocumentObserver*)mObservers[i];
    observer->ContentHasBeenRemoved(aContainer, aChild, aIndexInContainer);
  }
}

nsresult nsDocument::GetScriptObject(JSContext *aContext, void** aScriptObject)
{
  nsresult res = NS_OK;

  if (nsnull == mScriptObject) {
    res = NS_NewScriptDocument(aContext, this, nsnull, (JSObject**)&mScriptObject);
  }
  *aScriptObject = mScriptObject;

  return res;
}

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

//
// nsIDOMDocument interface
//
nsresult nsDocument::GetNodeType(PRInt32 *aType)
{
  *aType = nsIDOMNode::DOCUMENT;
  return NS_OK;
}

nsresult nsDocument::GetParentNode(nsIDOMNode **aNode)
{
  return NS_ERROR_FAILURE;
}

nsresult nsDocument::GetChildNodes(nsIDOMNodeIterator **aIterator)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::HasChildNodes()
{
  if (nsnull != mRootContent) {
    return NS_OK;
  }
  else {
    return NS_ERROR_FAILURE;
  }
}

nsresult nsDocument::GetFirstChild(nsIDOMNode **aNode)
{
  if (nsnull != mRootContent) {
    nsresult res = mRootContent->QueryInterface(kIDOMNodeIID, (void**)aNode);
    NS_ASSERTION(NS_OK == res, "Must be a DOM Node");
    return res;
  }
  
  return NS_ERROR_FAILURE;
}

nsresult nsDocument::GetPreviousSibling(nsIDOMNode **aNode)
{
  // no siblings
  return NS_ERROR_FAILURE;
}

nsresult nsDocument::GetNextSibling(nsIDOMNode **aNode)
{
  // no siblings
  return NS_ERROR_FAILURE;
}

nsresult nsDocument::InsertBefore(nsIDOMNode *newChild, nsIDOMNode *refChild)
{
  // a document has only one child
  return NS_ERROR_FAILURE;
}

nsresult nsDocument::ReplaceChild(nsIDOMNode *newChild, nsIDOMNode *oldChild)
{
  NS_PRECONDITION(nsnull != newChild && nsnull != oldChild, "null arg");
  nsIContent* content;
  
  nsresult res = oldChild->QueryInterface(kIContentIID, (void**)&content);
  if (NS_OK == res) {
    // check that we are replacing the root content
    if (content == mRootContent) {
      nsIContent* newContent;
      res = newChild->QueryInterface(kIContentIID, (void**)&newContent);
      if (NS_OK == res) {
        SetRootContent(newContent);
        NS_RELEASE(newContent);
      }
      else NS_ASSERTION(0, "Must be an nsIContent"); // nsIContent not supported. Who are you?
    }

    NS_RELEASE(content);
  }
  else NS_ASSERTION(0, "Must be an nsIContent"); // nsIContent not supported. Who are you?

  return res;
}

nsresult nsDocument::RemoveChild(nsIDOMNode *oldChild)
{
  NS_PRECONDITION(nsnull != oldChild, "null arg");
  nsIContent* content;
  
  nsresult res = oldChild->QueryInterface(kIContentIID, (void**)&content);
  if (NS_OK == res) {
    if (content == mRootContent) {
      NS_RELEASE(mRootContent);
      mRootContent = nsnull;
    }
  }

  return res;
}

nsresult nsDocument::GetMasterDoc(nsIDOMDocument **aDocument)
{
  AddRef();
  *aDocument = (nsIDOMDocument*)this;
  return NS_OK;
}

nsresult nsDocument::SetMasterDoc(nsIDOMDocument *aDocument)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::GetDocumentType(nsIDOMNode **aDocType)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::SetDocumentType(nsIDOMNode *aNode)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::GetDocumentElement(nsIDOMElement **aElement)        
{
  nsresult res = NS_ERROR_FAILURE;

  if (nsnull != mRootContent) {
    res = mRootContent->QueryInterface(kIDOMElementIID, (void**)aElement);
    NS_ASSERTION(NS_OK == res, "Must be a DOM Element");
  }
  
  return res;
}

nsresult nsDocument::SetDocumentElement(nsIDOMElement *aElement)
{
  NS_PRECONDITION(nsnull != aElement, "null arg");
  nsIContent* content;
  
  nsresult res = aElement->QueryInterface(kIContentIID, (void**)&content);
  if (NS_OK == res) {
    SetRootContent(content);
    NS_RELEASE(content);
  }
  else NS_ASSERTION(0, "Must be an nsIContent"); // nsIContent not supported. Who are you?

  return res;
}

nsresult nsDocument::GetDocumentContext(nsIDOMDocumentContext **aDocContext)       
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::SetDocumentContext(nsIDOMDocumentContext *aContext)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateDocumentContext(nsIDOMDocumentContext **aDocContext)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateElement(nsString &aTagName, 
                                    nsIDOMAttributeList *aAttributes, 
                                    nsIDOMElement **aElement)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateTextNode(nsString &aData, nsIDOMText** aTextNode)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateComment(nsString &aData, nsIDOMComment **aComment)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreatePI(nsString &aName, nsString &aData, nsIDOMPI **aPI)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateAttribute(nsString &aName, 
                                      nsIDOMNode *value, 
                                      nsIDOMAttribute **aAttribute)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateAttributeList(nsIDOMAttributeList **aAttributesList)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::CreateTreeIterator(nsIDOMNode **aNode, nsIDOMTreeIterator **aTreeIterator)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsDocument::GetElementsByTagName(nsString &aTagname, nsIDOMNodeIterator **aIterator)
{
  //XXX TBI
  return NS_ERROR_NOT_IMPLEMENTED;
}

