/*
 * (C) Copyright The MITRE Corporation 1999  All rights reserved.
 *
 * The contents of this file are subject to the Mozilla 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/MPL/
 *
 * The program provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * The Copyright owner will not be liable for any damages suffered by
 * you as a result of using the Program. In no event will the Copyright
 * owner be liable for any special, indirect or consequential damages or
 * lost profits even if the Copyright owner has been advised of the
 * possibility of their occurrence.
 *
 * Please see release.txt distributed with this file for more information.
 *
 */
// Tom Kneeland (01/18/2000)
//
//  Implementation of the Mozilla Wrapper class for the Document Object.
//
// Modification History:
// Who  When        What
// TK   02/11/2000  Added default constructor.  If no NS object is provided
//                  simply create an nsXMLDocument to use.
// TK   02/15/2000  Fixed bug in Create Wrapper.  We must check to see if the
//                  the nsIDOM* object is NULL.  If it is, then we don't need
//                  a wrapper.
//                  Also fixed the same problem with the other factory methods
//                  that create wrappers for Mozilla objects.
//

#include "mozilladom.h"
#include "nsXMLDocument.h"
#include "iostream.h"

//
//Construct a Document Wrapper object without specificy a nsIDOMDocument
//object.  We will create an ns
//
Document::Document()
{
  nsDocument = new nsXMLDocument();
  Node::setNSObj(nsDocument, this);
}

//
//Construct a Document Wrapper object with a nsIDOMDocument object
//
Document::Document(nsIDOMDocument* document) : Node(document, this)
{
  nsDocument = document;
}

//
//Clean up the private data members
//
Document::~Document()
{
}

//
//Provide an nsIDOMDocument object for wrapping
//
void Document::setNSObj(nsIDOMDocument* document)
{
  Node::setNSObj(document);
  nsDocument = document;
}

//
//Call nsIDOMDocument::GetDocumentElement to retreive the element for this
//document.  Then call the createElement factory method to retreive an element
//wrapper for this object.
//
Element* Document::getDocumentElement()
{
  nsIDOMElement* theElement = NULL;
  Element* elemWrapper = NULL;
  nsresult retval = 5;

  if (nsDocument == NULL)
    return NULL;

  //retval = nsDocument->GetDocumentElement(&theElement);
  //if (retval == NS_OK)
  if ((retval = nsDocument->GetDocumentElement(&theElement)) == NS_OK)
    {
      cout << "Document::getDocumentElement - Mozilla Call ok" << endl;
      return createElement(theElement);
    }
  else
    {
      cout << "Document::getDocumentElement - Mozilla Call not ok(" << hex <<retval << ")" << endl;
      return NULL;
    }
}

//
//Retrieve the DocumentType from nsIDOMDocument::GetDocType.  Return it wrapped
//in the docType data member.
//
DocumentType* Document::getDoctype()
{
  nsIDOMDocumentType* theDocType = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->GetDoctype(&theDocType) == NS_OK)
    return createDocumentType(theDocType);
  else
    return NULL;
}

//
//Return a constant reference to the DOM's Implementation
//
DOMImplementation* Document::getImplementation()
{
  nsIDOMDOMImplementation* theImpl = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->GetImplementation(&theImpl) == NS_OK)
    return createDOMImplementation(theImpl);
  else
    return NULL;
}

//
// DEFFER this functionality to the NODE parent class
//Ensure that no Element node is inserted if the document already has an
//associated Element child.
//
/*
Node* Document::insertBefore(Node* newChild, Node* refChild)
{
  Node* returnVal = NULL;

  NodeDefinition* pCurrentNode = NULL;
  NodeDefinition* pNextNode = NULL;

  //Convert to a NodeDefinition Pointer
  NodeDefinition* pNewChild = (NodeDefinition*)newChild;
  NodeDefinition* pRefChild = (NodeDefinition*)refChild;

  //Check to see if the reference node is a child of this node
  if ((refChild != NULL) && (pRefChild->getParentNode() != this))
    return NULL;

  switch (pNewChild->getNodeType())
    {
      case Node::DOCUMENT_FRAGMENT_NODE :
        pCurrentNode = (NodeDefinition*)pNewChild->getFirstChild();
        while (pCurrentNode)
          {
            pNextNode = (NodeDefinition*)pCurrentNode->getNextSibling();

            //Make sure that if the current node is an Element, the document
            //doesn't already have one.
            if ((pCurrentNode->getNodeType() != Node::ELEMENT_NODE) ||
                ((pCurrentNode->getNodeType() == Node::ELEMENT_NODE) &&
                  (documentElement == NULL)))
              {
                pCurrentNode = (NodeDefinition*)pNewChild->removeChild(pCurrentNode);
                implInsertBefore(pCurrentNode, pRefChild);

                if (pCurrentNode->getNodeType() == Node::ELEMENT_NODE)
                  documentElement = (Element*)pCurrentNode;
              }
            pCurrentNode = pNextNode;
          }
        returnVal = newChild;
        break;

      case Node::PROCESSING_INSTRUCTION_NODE :
      case Node::COMMENT_NODE :
      case Node::DOCUMENT_TYPE_NODE :
        returnVal = implInsertBefore(pNewChild, pRefChild);
        break;

      case Node::ELEMENT_NODE :
        if (!documentElement)
          {
          documentElement = (Element*)pNewChild;
          returnVal = implInsertBefore(pNewChild, pRefChild);
          }
        else
          returnVal = NULL;
        break;
      default:
        returnVal =  NULL;
    }

  return returnVal;
}
*/

//
//  DEFFER this functionality to the NODE implementation
//Ensure that if the newChild is an Element and the Document already has an
//element, then oldChild should be specifying the existing element.  If not
//then the replacement can not take place.
//
/*
Node* Document::replaceChild(Node* newChild, Node* oldChild)
{
  Node* replacedChild = NULL;

  if (newChild->getNodeType() != Node::ELEMENT_NODE)
    {
      //The new child is not an Element, so perform replacement
      replacedChild = NodeDefinition::replaceChild(newChild, oldChild);

      //If old node was an Element, then the document's element has been
      //replaced with a non-element node.  Therefore clear the documentElement
      //pointer
      if (replacedChild && (oldChild->getNodeType() == Node::ELEMENT_NODE))
        documentElement = NULL;

      return replacedChild;
    }
  else
    {
      //A node is being replaced with an Element.  If the document does not
      //have an elemet yet, then just allow the replacemetn to take place.
      if (!documentElement)
        replacedChild = NodeDefinition::replaceChild(newChild, oldChild);
      else if (oldChild->getNodeType() == Node::ELEMENT_NODE)
        replacedChild = NodeDefinition::replaceChild(newChild, oldChild);

      if (replacedChild)
        documentElement = (Element*)newChild;

      return replacedChild;
    }
}
*/

//
//  DEFFER this functionality to the NODE Implementation
//Update the documentElement pointer if the associated Element node is being
//removed.
//
/*
Node* Document::removeChild(Node* oldChild)
{
  Node* removedChild = NULL;

  removedChild = NodeDefinition::removeChild(oldChild);

  if (removedChild && (removedChild->getNodeType() == Node::ELEMENT_NODE))
    documentElement = NULL;

  return removedChild;
}
*/

//
//Call the nsIDOMDocument::CreateDocumentFragment function, then create or
//retrieve a wrapper class for it.
//
DocumentFragment* Document::createDocumentFragment()
{
  nsIDOMDocumentFragment* fragment = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateDocumentFragment(&fragment) == NS_OK)
    return createDocumentFragment(fragment);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a preexisting wrapper object.  If it does not
// exists, create one and store it in the hash table.  If it does exist, simply
// return it to the caller.
//
DocumentFragment* 
   Document::createDocumentFragment(nsIDOMDocumentFragment* fragment)
{
  DocumentFragment* docFragWrapper = NULL;

  if (fragment)
    {
      docFragWrapper = 
	(DocumentFragment*)wrapperHashTable.retrieve((Int32)fragment);
      
      if (!docFragWrapper)
	{
	  docFragWrapper = new DocumentFragment(fragment, this);
	  wrapperHashTable.add(docFragWrapper, (Int32)fragment);
	}
    }

  return docFragWrapper;
}

//
//Call the nsIDOMDocument::CreateElement function, and retrieve or create
//a wrapper object for it.
//
Element* Document::createElement(const DOMString& tagName)
{
  nsIDOMElement* element = NULL;

  if (nsDocument->CreateElement(tagName.getConstNSString(), &element) == NS_OK)
    return createElement(element);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a precreated wrapper object.  If one exists, 
//return it to the caller.  If it doens't exist, create one, store it in the 
//hash, and return it to the caller.
//
Element* Document::createElement(nsIDOMElement* element)
{
  Element* elemWrapper = NULL;

  if (element)
    {
      elemWrapper = (Element*)wrapperHashTable.retrieve((Int32)element);
      
      if (!elemWrapper)
	{
	  elemWrapper = new Element(element, this);
	  wrapperHashTable.add(elemWrapper, (Int32)element);
	}
    }

  return elemWrapper;
}

//
//Call the nsIDOMDocument::CreateAttribute function, then create or retrieve a
//wrapper object for it.
//
Attr* Document::createAttribute(const DOMString& name)
{
  nsIDOMAttr* attr = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateAttribute(name.getConstNSString(), &attr) == NS_OK)
    return createAttribute(attr);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a precreated object.  If one exists, return it,
//else, create one, store it in the table, and return it.
//
Attr* Document::createAttribute(nsIDOMAttr* attr)
{
  Attr* attrWrapper = NULL;

  if (attr)
    {
      attrWrapper = (Attr*)wrapperHashTable.retrieve((Int32)attr);

      if (!attrWrapper)
	{
	  attrWrapper = new Attr(attr, this);
	  wrapperHashTable.add(attrWrapper, (Int32)attr);
	}
    }

  return attrWrapper;
}

//
//Call the nsIDOMDocument::CreateTextNode function, then create, or retrieve a 
//wrapper object for it.
//
Text* Document::createTextNode(const DOMString& theData)
{
  nsIDOMText* text = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateTextNode(theData.getConstNSString(), &text) == NS_OK)
    return createTextNode(text);
  else
    return NULL;
}

//
//Check the wrapperHashTable to see if a precreated object exists.  If it does 
//then return it.  Else, create a new one, insert it into the hash table, and 
//then return it.
//
Text* Document::createTextNode(nsIDOMText* text)
{
  Text* textWrapper = NULL;

  if (text)
    {
      textWrapper = (Text*)wrapperHashTable.retrieve((Int32)text);

      if (!textWrapper)
	{
	  textWrapper = new Text(text, this);
	  wrapperHashTable.add(textWrapper, (Int32)text);
	}
    }

  return textWrapper;
}

//
//Call the nsIDOMDocument::CreateComment function, then create or retrieve a
//wrapper object to return to the caller.
//
Comment* Document::createComment(const DOMString& theData)
{
  nsIDOMComment* comment = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateComment(theData.getConstNSString(), &comment) == NS_OK)
    return createComment(comment);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a precreated object to return to the caller.
//If one does not exist, create it, store it in the hash, and return it to the
//caller.
Comment* Document::createComment(nsIDOMComment* comment)
{
  Comment* commentWrapper = NULL;

  if (comment)
    {
      commentWrapper = (Comment*)wrapperHashTable.retrieve((Int32)comment);

      if (!commentWrapper)
	{
	  commentWrapper = new Comment(comment, this);
	  wrapperHashTable.add(commentWrapper, (Int32)comment);
	}
    }

  return commentWrapper;
}

//
//Call the nsIDOMDocument::CreateCDATASection function, then create or retrieve
//a wrapper object to return to the caller.
//
CDATASection* Document::createCDATASection(const DOMString& theData)
{
  nsIDOMCDATASection* cdata = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateCDATASection(theData.getConstNSString(), &cdata) == 
      NS_OK)
    return createCDATASection(cdata);
  else
    return NULL;
}

//
//Check to see if a precreated object exists in the wrapperHashTable.  If so
//simply return it to the caller.  If not, creat one, store it in the hash, and
//return it to the caller.
//
CDATASection* Document::createCDATASection(nsIDOMCDATASection* cdata)
{
  CDATASection* cdataWrapper = NULL;

  if (cdata)
    {
      cdataWrapper = (CDATASection*)wrapperHashTable.retrieve((Int32)cdata);

      if (!cdataWrapper)
	{
	  cdataWrapper = new CDATASection(cdata, this);
	  wrapperHashTable.add(cdataWrapper, (Int32)cdata);
	}
    }

  return cdataWrapper;
}

//
//Call the nsIDOMDocument::CreateProcessingInstruction function, then find or
//create a wrapper class to return to the caller.
//
ProcessingInstruction*
  Document::createProcessingInstruction(const DOMString& target,
                                        const DOMString& data)
{
  nsIDOMProcessingInstruction* pi = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateProcessingInstruction(target.getConstNSString(),
				       data.getConstNSString(), &pi) == NS_OK)
    return createProcessingInstruction(pi);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a precreated object.  If one exists, return
//it to the caller.  If not, create one, store it in the hash table, and then
//return it to the caller.
//
ProcessingInstruction* 
Document::createProcessingInstruction(nsIDOMProcessingInstruction* pi)
{
  ProcessingInstruction* piWrapper = NULL;

  if (pi)
    {
      piWrapper = (ProcessingInstruction*)wrapperHashTable.retrieve((Int32)pi);

      if (!piWrapper)
	{
	  piWrapper = new ProcessingInstruction(pi, this);
	  wrapperHashTable.add(piWrapper, (Int32)pi);
	}
    }

  return piWrapper;
}

//
//Call the nsIDOMDocument::CreateEntityReference function, then obtain a wrapper
//class to return to the user.
//
EntityReference* Document::createEntityReference(const DOMString& name)
{
  nsIDOMEntityReference* entityRef = NULL;

  if (nsDocument == NULL)
    return NULL;

  if (nsDocument->CreateEntityReference(name.getConstNSString(), 
					&entityRef) == NS_OK)
    return createEntityReference(entityRef);
  else
    return NULL;
}

//
//Check the wrapperHashTable for a wrapper class.  If one exists, simply return
//it to the caller.  Else, create one, place it in the hash, and return it to 
//the caller.
//
EntityReference* 
Document::createEntityReference(nsIDOMEntityReference* entityRef)
{
  EntityReference* entityWrapper = NULL;

  if (entityRef)
    {
      entityWrapper = 
	(EntityReference*) wrapperHashTable.retrieve((Int32)entityRef);

      if (!entityWrapper)
	{
	  entityWrapper = new EntityReference(entityRef, this);
	  wrapperHashTable.add(entityWrapper, (Int32)entityRef);
	}
    }

  return entityWrapper;
}

//
//Check the wrapperHashTable for a wrapper class.  If one exists, simply return
//it to the caller.  Else create one, place it in the hash table, and then return
//it to the caller.
Entity* Document::createEntity(nsIDOMEntity* entity)
{
  Entity* entityWrapper = NULL;

  if (entity)
    {
      entityWrapper = (Entity*)wrapperHashTable.retrieve((Int32) entity);

      if (!entity)
	{
	  entityWrapper = new Entity(entity, this);
	  wrapperHashTable.add(entityWrapper, (Int32)entity);
	}
    }

  return entityWrapper;
}

//
//Factory function for creating and hashing generic Node wrapper classes.
//Check the wrapperHashTable to see if a wrapper object already exists.  If it
//does then return it.  If one does not exist, then creat one, hash it, and 
//return it to the caller.
//
Node* Document::createNode(nsIDOMNode* node)
{
  Node* nodeWrapper = NULL;

  if (node)
    {
      nodeWrapper = (Node*)wrapperHashTable.retrieve((Int32)node);

      if (!nodeWrapper)
	{
	  nodeWrapper = new Node(node, this);
	  wrapperHashTable.add(nodeWrapper, (Int32)node);
	}
    }

  return nodeWrapper;
}

//
//Factory function for creating and hashing a DOMString object.  Note that it
//is specifically for wrapping nsString objects with MozillaString objects.
//Once the object is created it is stored in the hash table.
//
DOMString* Document::createDOMString(nsString* str)
{
  DOMString* strWrapper = NULL;

  if (str)
    {
      strWrapper = (DOMString*)wrapperHashTable.retrieve((Int32)str);

      if (!strWrapper)
	{
	  strWrapper = new DOMString(str);
	  wrapperHashTable.add(strWrapper, (Int32)str);
	}
    }

  return strWrapper;
}

//
//Factory function for creating and hashing a Notation object.
//
Notation* Document::createNotation(nsIDOMNotation* notation)
{
  Notation* notationWrapper = NULL;

  if (notation)
    {
      notationWrapper = (Notation*)wrapperHashTable.retrieve((Int32) notation);

      if (!notationWrapper)
	{
	  notationWrapper = new Notation(notation, this);
	  wrapperHashTable.add(notationWrapper, (Int32)notation);
	}
    }

  return notationWrapper;
}

//
//Factory function for creating and hashing DOM Implementation objects
//
DOMImplementation* 
Document::createDOMImplementation(nsIDOMDOMImplementation* impl)
{
  DOMImplementation* implWrapper = NULL;

  if (impl)
    {
      implWrapper = (DOMImplementation*)wrapperHashTable.retrieve((Int32)impl);

      if (!implWrapper)
	{
	  implWrapper = new DOMImplementation(impl, this);
	  wrapperHashTable.add(implWrapper, (Int32)impl);
	}
    }

  return implWrapper;
}

//
//Factory function for creating and hashing DOM DocumentType objects.
//
DocumentType* Document::createDocumentType(nsIDOMDocumentType* doctype)
{
  DocumentType* doctypeWrapper = NULL;

  if (doctype)
    {
      doctypeWrapper = (DocumentType*)wrapperHashTable.retrieve((Int32)doctype);

      if (!doctypeWrapper)
	{
	  doctypeWrapper = new DocumentType(doctype, this);
	  wrapperHashTable.add(doctypeWrapper, (Int32)doctype);
	}
    }

  return doctypeWrapper;
}

//
//Factory function for creating and hashing NodeList objects
//
NodeList* Document::createNodeList(nsIDOMNodeList* list)
{
  NodeList* listWrapper = NULL;

  if (list)
    {
      listWrapper = (NodeList*)wrapperHashTable.retrieve((Int32)list);

      if (!listWrapper)
	{
	  listWrapper = new NodeList(list, this);
	  wrapperHashTable.add(listWrapper, (Int32)list);
	}
    }

  return listWrapper;
}

//
//Factory function for creating and hashing NamedNodeMap objects
//
NamedNodeMap* Document::createNamedNodeMap(nsIDOMNamedNodeMap* map)
{
  NamedNodeMap* mapWrapper = NULL;

  if (map)
    {
      mapWrapper = (NamedNodeMap*)wrapperHashTable.retrieve((Int32)map);

      if (!mapWrapper)
	{
	  mapWrapper = new NamedNodeMap(map, this);
	  wrapperHashTable.add(mapWrapper, (Int32)map);
	}
    }

  return mapWrapper;
}

//
//Remove the specified hashed object from wrapperHashTable and return it to the
//caller.
//
MITREObject* Document::removeWrapper(Int32 hashValue)
{
  return wrapperHashTable.remove(hashValue);
}

//
//Add the specified wrapper to the hash table using the specified hash value
//
void Document::addWrapper(MITREObject* obj, Int32 hashValue)
{
  wrapperHashTable.add(obj, hashValue);
}

//
//Determine what kind of node this is, and create the appropriate wrapper for it
//
Node* Document::createWrapper(nsIDOMNode* node)
{
  unsigned short nodeType = 0;

  //
  //TK 02/15/2000 - Must make sure node is not null.
  if (!node)
    return NULL;

  node->GetNodeType(&nodeType);

  switch (nodeType)
    {
    case nsIDOMNode::ELEMENT_NODE:
      return createElement((nsIDOMElement*)node);
      break;

    case nsIDOMNode::ATTRIBUTE_NODE:
      return createAttribute((nsIDOMAttr*)node);
      break;

    case nsIDOMNode::TEXT_NODE:
      return createTextNode((nsIDOMText*)node);
      break;

    case nsIDOMNode::CDATA_SECTION_NODE:
      return createCDATASection((nsIDOMCDATASection*)node);
      break;

    case nsIDOMNode::COMMENT_NODE:
      return createComment((nsIDOMComment*)node);
      break;

    case nsIDOMNode::ENTITY_REFERENCE_NODE:
      return createEntityReference((nsIDOMEntityReference*)node);
      break;

    case nsIDOMNode::ENTITY_NODE:
      return createEntity((nsIDOMEntity*)node);
      break;

    case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
      return createProcessingInstruction((nsIDOMProcessingInstruction*)node);
      break;

    case nsIDOMNode::DOCUMENT_TYPE_NODE:
      return createDocumentType((nsIDOMDocumentType*)node);
      break;

    case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
      return createDocumentFragment((nsIDOMDocumentFragment*)node);
      break;
      
    case nsIDOMNode::NOTATION_NODE:
      return createNotation((nsIDOMNotation*)node);
      break;

    default:
      return createNode(node);
    }
}
