/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * (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.
 *
 * Contributor(s): Tom Kneeland
 *                 Peter Van der Beken <peter.vanderbeken@pandora.be>
 *
 */

/**
 * Implementation of the wrapper class to convert the Mozilla nsIDOMDocument
 * interface into a TransforMIIX Document interface.
 */

#include "mozilladom.h"
#include "nsIDocument.h"
#include "nsIDOMAttr.h"
#include "nsIDOMComment.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentFragment.h"
#include "nsIDOMElement.h"
#include "nsIDOMProcessingInstruction.h"
#include "nsIDOMElement.h"
#include "nsIDOMText.h"

// Need to determine if these are well-chosen.
#define WRAPPER_INITIAL_SIZE 256
#define ATTR_INITIAL_SIZE 128

struct txWrapperHashEntry : public PLDHashEntryHdr
{
    MozillaObjectWrapper* mWrapper;
};

PR_STATIC_CALLBACK(const void *)
txWrapperHashGetKey(PLDHashTable *table, PLDHashEntryHdr *entry)
{
    txWrapperHashEntry *e =
        NS_STATIC_CAST(txWrapperHashEntry *, entry);
    return e->mWrapper->getNSObj();
}

PR_STATIC_CALLBACK(void)
txWrapperHashClearEntry(PLDHashTable *table,
                        PLDHashEntryHdr *entry)
{
    txWrapperHashEntry *e = NS_STATIC_CAST(txWrapperHashEntry *, entry);
    // Don't delete the document wrapper, this is only called from its
    // destructor.
    if (e->mWrapper != table->data) {
        delete e->mWrapper;
    }
}

PR_STATIC_CALLBACK(PRBool)
txWrapperHashMatchEntry(PLDHashTable *table,
                        const PLDHashEntryHdr *entry,
                        const void *key)
{
    const txWrapperHashEntry *e =
        NS_STATIC_CAST(const txWrapperHashEntry *, entry);
    return e->mWrapper->getNSObj() == key;
}

struct txAttributeHashEntry : public PLDHashEntryHdr
{
    Attr* mAttribute;
};

PR_STATIC_CALLBACK(const void *)
txAttributeHashGetKey(PLDHashTable *table, PLDHashEntryHdr *entry)
{
    txAttributeHashEntry *e =
        NS_STATIC_CAST(txAttributeHashEntry *, entry);
    return e->mAttribute->GetKey();
}

PR_STATIC_CALLBACK(PLDHashNumber)
txAttributeHashHashKey(PLDHashTable *table, const void *key)
{
    const txAttributeNodeKey* attrKey =
        NS_STATIC_CAST(const txAttributeNodeKey *, key);
    return attrKey->GetHash();
}

PR_STATIC_CALLBACK(PRBool)
txAttributeHashMatchEntry(PLDHashTable *table,
                               const PLDHashEntryHdr *entry,
                               const void *key)
{
    const txAttributeHashEntry *e =
        NS_STATIC_CAST(const txAttributeHashEntry *, entry);
    const txAttributeNodeKey* key1 = e->mAttribute->GetKey();
    const txAttributeNodeKey* key2 =
        NS_STATIC_CAST(const txAttributeNodeKey *, key);

    return key1->Equals(*key2);
}

PR_STATIC_CALLBACK(void)
txAttributeHashClearEntry(PLDHashTable *table,
                          PLDHashEntryHdr *entry)
{
    txAttributeHashEntry *e =
        NS_STATIC_CAST(txAttributeHashEntry *, entry);
    delete e->mAttribute;
}

/**
 * Construct a wrapper with the specified Mozilla object. The caller is
 * responsible for deleting the wrapper object!
 *
 * @param aDocument the nsIDOMDocument you want to wrap
 */
Document::Document(nsIDOMDocument* aDocument) : Node(aDocument, this)
{
#ifdef DEBUG
    mInHashTableDeletion = PR_FALSE;
#endif

    static PLDHashTableOps wrapper_hash_table_ops =
    {
        PL_DHashAllocTable,
        PL_DHashFreeTable,
        txWrapperHashGetKey,
        PL_DHashVoidPtrKeyStub,
        txWrapperHashMatchEntry,
        PL_DHashMoveEntryStub,
        txWrapperHashClearEntry,
        PL_DHashFinalizeStub,
    };
    PRBool success = PL_DHashTableInit(&mWrapperHashTable,
                                       &wrapper_hash_table_ops,
                                       this,
                                       sizeof(txWrapperHashEntry),
                                       WRAPPER_INITIAL_SIZE);
    if (success) {
        txWrapperHashEntry* entry =
            NS_STATIC_CAST(txWrapperHashEntry *,
                           PL_DHashTableOperate(&mWrapperHashTable,
                                                aDocument,
                                                PL_DHASH_ADD));

        NS_ASSERTION(entry, "Out-of-memory creating an entry.");
        if (entry && !entry->mWrapper) {
            entry->mWrapper = this;
        }
    }
    else {
        mWrapperHashTable.ops = nsnull;
    }

    static PLDHashTableOps attribute_hash_table_ops =
    {
        PL_DHashAllocTable,
        PL_DHashFreeTable,
        txAttributeHashGetKey,
        txAttributeHashHashKey,
        txAttributeHashMatchEntry,
        PL_DHashMoveEntryStub,
        txAttributeHashClearEntry,
        PL_DHashFinalizeStub,
    };
    success = PL_DHashTableInit(&mAttributeNodes,
                                &attribute_hash_table_ops,
                                nsnull,
                                sizeof(txAttributeHashEntry),
                                ATTR_INITIAL_SIZE);
    if (!success) {
        mAttributeNodes.ops = nsnull;
    }
}

/**
 * Destructor
 */
Document::~Document()
{
#ifdef DEBUG
    mInHashTableDeletion = PR_TRUE;
#endif
    if (mAttributeNodes.ops) {
        PL_DHashTableFinish(&mAttributeNodes);
    }
    if (mWrapperHashTable.ops) {
        PL_DHashTableFinish(&mWrapperHashTable);
    }
}

/**
 * This macro can be used to declare a createWrapper implementation
 * for the supplied wrapper class.
 */
#define IMPL_CREATE_WRAPPER(_txClass)                           \
IMPL_CREATE_WRAPPER2(_txClass, create##_txClass)

/**
 * This macro can be used to declare a createWrapper implementation
 * for the supplied wrapper class. The function parameter defines
 * the function name for the implementation function.
 */
#define IMPL_CREATE_WRAPPER2(_txClass, _function)               \
_txClass* Document::_function(nsIDOM##_txClass* aNsObject)      \
{                                                               \
    NS_ASSERTION(aNsObject,                                     \
                 "Need a Mozilla object to create a wrapper."); \
    if (!mWrapperHashTable.ops) {                               \
        return new _txClass(aNsObject, this);                   \
    }                                                           \
                                                                \
    txWrapperHashEntry* entry =                                 \
        NS_STATIC_CAST(txWrapperHashEntry *,                    \
                       PL_DHashTableOperate(&mWrapperHashTable, \
                                            aNsObject,          \
                                            PL_DHASH_ADD));     \
                                                                \
    NS_ASSERTION(entry, "Out-of-memory creating an entry.");    \
    if (!entry) {                                               \
        return nsnull;                                          \
    }                                                           \
    if (!entry->mWrapper) {                                     \
        entry->mWrapper = new _txClass(aNsObject, this);        \
        NS_ASSERTION(entry->mWrapper,                           \
                     "Out-of-memory creating a wrapper.");      \
        if (!entry->mWrapper) {                                 \
            PL_DHashTableRawRemove(&mWrapperHashTable, entry);  \
            return nsnull;                                      \
        }                                                       \
    }                                                           \
    return NS_STATIC_CAST(_txClass*, entry->mWrapper);          \
}

/**
 * Call nsIDOMDocument::GetDocumentElement to get the document's
 * DocumentElement.
 *
 * @return the document element
 */
Element* Document::getDocumentElement()
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMElement> element;
    nsDocument->GetDocumentElement(getter_AddRefs(element));
    if (!element) {
        return nsnull;
    }
    return createElement(element);
}

/**
 * Call nsIDOMDocument::CreateDocumentFragment to create a DocumentFragment.
 *
 * @return the DocumentFragment
 */
Node* Document::createDocumentFragment()
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMDocumentFragment> fragment;
    nsDocument->CreateDocumentFragment(getter_AddRefs(fragment));
    if (!fragment) {
        return nsnull;
    }
    return createNode(fragment);
}

/**
 * Call nsIDOMDocument::GetElementById to get Element with ID.
 *
 * @param aID the name of ID referencing the element
 *
 * @return the Element
 */
Element* Document::getElementById(const nsAString& aID)
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMElement> element;
    nsDocument->GetElementById(aID, getter_AddRefs(element));
    if (!element) {
        return nsnull;
    }
    return createElement(element);
}

/**
 * Create a wrapper for a nsIDOMElement, reuses an existing wrapper if possible.
 *
 * @param aElement the nsIDOMElement you want to wrap
 *
 * @return the Element
 */
IMPL_CREATE_WRAPPER(Element)

/**
 * Call nsIDOMDocument::CreateElementNS to create an Element.
 *
 * @param aNamespaceURI the URI of the namespace for the element
 * @param aTagName the name of the element you want to create
 *
 * @return the Element
 */
Element* Document::createElementNS(const nsAString& aNamespaceURI,
                                   const nsAString& aTagName)
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMElement> element;
    nsDocument->CreateElementNS(aNamespaceURI, aTagName,
                                getter_AddRefs(element));
    if (!element) {
        return nsnull;
    }
    return createElement(element);
}

/**
 * Create a wrapper for a nsIDOMAttr, reuses an existing wrapper if possible.
 *
 * @param aAttr the nsIDOMAttr you want to wrap
 *
 * @return the Attribute
 */
Attr* Document::createAttribute(nsIDOMAttr* aAttr)
{
    NS_ASSERTION(aAttr,
                 "Need a Mozilla object to create a wrapper.");
    if (!aAttr) {
        return nsnull;
    }

    nsCOMPtr<nsIDOMElement> owner;
    aAttr->GetOwnerElement(getter_AddRefs(owner));
    nsCOMPtr<nsIContent> parent = do_QueryInterface(owner);

    nsAutoString nameString;
    aAttr->GetLocalName(nameString);
    nsCOMPtr<nsIAtom> localName = do_GetAtom(nameString);

    nsAutoString ns;
    aAttr->GetNamespaceURI(ns);
    PRInt32 namespaceID = kNameSpaceID_None;
    if (!ns.IsEmpty()) {
        NS_ASSERTION(gTxNameSpaceManager, "No namespace manager");
        if (gTxNameSpaceManager) {
            gTxNameSpaceManager->GetNameSpaceID(ns, namespaceID);
        }
    }

    if (!mAttributeNodes.ops) {
        return nsnull;
    }

    txAttributeNodeKey hashKey(parent, localName, namespaceID);

    txAttributeHashEntry* entry =
        NS_STATIC_CAST(txAttributeHashEntry *,
                       PL_DHashTableOperate(&mAttributeNodes,
                                            &hashKey,
                                            PL_DHASH_ADD));

    NS_ASSERTION(entry, "Out-of-memory creating an entry.");
    if (!entry) {
        return nsnull;
    }
    if (!entry->mAttribute) {
        entry->mAttribute = new Attr(aAttr, this);
        NS_ASSERTION(entry->mAttribute,
                     "Out-of-memory creating an attribute wrapper.");
        if (!entry->mAttribute) {
            PL_DHashTableRawRemove(&mAttributeNodes, entry);
            return nsnull;
        }
    }

    return entry->mAttribute;
}

/**
 * Call nsIDOMDocument::CreateTextNode to create a Text node.
 *
 * @param aData the data of the text node you want to create
 *
 * @return the Text node
 */
Node* Document::createTextNode(const nsAString& aData)
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMText> text;
    nsDocument->CreateTextNode(aData, getter_AddRefs(text));
    nsCOMPtr<nsIDOMNode> node = do_QueryInterface(text);
    if (!node) {
        return nsnull;
    }
    return createNode(node);
}

/**
 * Call nsIDOMDocument::CreateComment to create a Comment node.
 *
 * @param aData the data of the comment node you want to create
 *
 * @return the Comment node
 */
Node* Document::createComment(const nsAString& aData)
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMComment> comment;
    nsDocument->CreateComment(aData, getter_AddRefs(comment));
    nsCOMPtr<nsIDOMNode> node = do_QueryInterface(comment);
    if (!node) {
        return nsnull;
    }
    return createNode(node);
}

/**
 * Call nsIDOMDocument::CreateProcessingInstruction to create a
 * ProcessingInstruction.
 *
 * @param aTarget the target of the ProcessingInstruction you want to create
 * @param aData the data of the ProcessingInstruction you want to create
 *
 * @return the ProcessingInstruction node
 */
ProcessingInstruction* Document::createProcessingInstruction(
            const nsAString& aTarget, const nsAString& aData)
{
    NSI_FROM_TX(Document);
    nsCOMPtr<nsIDOMProcessingInstruction> pi;
    nsDocument->CreateProcessingInstruction(aTarget, aData,
                                            getter_AddRefs(pi));
    if (!pi) {
        return nsnull;
    }
    return createProcessingInstruction(pi);
}

/**
 * Create a wrapper for a nsIDOMProcessingInstruction, reuses an existing
 * wrapper if possible.
 *
 * @param aPi the nsIDOMProcessingInstruction you want to wrap
 *
 * @return the ProcessingInstruction node
 */
IMPL_CREATE_WRAPPER(ProcessingInstruction)

/**
 * Create a wrapper for a nsIDOMNode, reuses an existing wrapper if possible.
 *
 * @param aNode the nsIDOMNode you want to wrap
 *
 * @return the Node
 */
IMPL_CREATE_WRAPPER(Node)

/**
 * Create a wrapper for a nsIDOMNamedNodeMap, reuses an existing wrapper if
 * possible.
 *
 * @param aMap the nsIDOMNamedNodeMap you want to wrap
 *
 * @return the NamedNodeMap
 */
IMPL_CREATE_WRAPPER(NamedNodeMap)

/**
 * Create a wrapper for a nsIDOMNode, reuses an existing wrapper if possible.
 * This function creates the right kind of wrapper depending on the type of
 * the node.
 *
 * @param aNode the nsIDOMNode you want to wrap
 *
 * @return the Node
 */
Node* Document::createWrapper(nsIDOMNode* aNode)
{
    NS_ASSERTION(aNode,
                 "Need a Mozilla object to create a wrapper.");

    PRUint16 nodeType = 0;
    aNode->GetNodeType(&nodeType);

    // Look up wrapper in the hash, except for attribute nodes since
    // they're in a separate attribute hash. Let them be handled in
    // createAttribute by falling through to the switch below.
    if (nodeType != nsIDOMNode::ATTRIBUTE_NODE &&
        mWrapperHashTable.ops) {
        txWrapperHashEntry* entry =
            NS_STATIC_CAST(txWrapperHashEntry *,
                           PL_DHashTableOperate(&mWrapperHashTable,
                                                aNode,
                                                PL_DHASH_LOOKUP));
        if (entry->mWrapper) {
            return NS_STATIC_CAST(Node*, entry->mWrapper);
        }
    }

    switch (nodeType)
    {
        case nsIDOMNode::ELEMENT_NODE:
        {
            nsIDOMElement* element;
            CallQueryInterface(aNode, &element);
            Element* txElement = createElement(element);
            NS_RELEASE(element);
            return txElement;
        }
        case nsIDOMNode::ATTRIBUTE_NODE:
        {
            nsIDOMAttr* attr;
            CallQueryInterface(aNode, &attr);
            Attr* txAttr = createAttribute(attr);
            NS_RELEASE(attr);
            return txAttr;
        }
        case nsIDOMNode::CDATA_SECTION_NODE:
        case nsIDOMNode::COMMENT_NODE:
        case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
        case nsIDOMNode::DOCUMENT_TYPE_NODE:
        case nsIDOMNode::ENTITY_REFERENCE_NODE:
        case nsIDOMNode::ENTITY_NODE:
        case nsIDOMNode::NOTATION_NODE:
        case nsIDOMNode::TEXT_NODE:
        {
            return createNode(aNode);
        }
        case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
        {
            nsIDOMProcessingInstruction* pi;
            CallQueryInterface(aNode, &pi);
            ProcessingInstruction* txPi = createProcessingInstruction(pi);
            NS_RELEASE(pi);
            return txPi;
        }
        case nsIDOMNode::DOCUMENT_NODE:
        {
            if (aNode == mMozObject) {
                return this;
            }
            NS_ASSERTION(0, "We don't support creating new documents.");
            return nsnull;
        }
        default:
        {
            NS_WARNING("Don't know nodetype. This could be a failure.");
            return createNode(aNode);
        }
    }
}

PRInt32 Document::namespaceURIToID(const nsAString& aNamespaceURI)
{
    PRInt32 namesspaceID = kNameSpaceID_Unknown;
    if (gTxNameSpaceManager) {
        gTxNameSpaceManager->RegisterNameSpace(aNamespaceURI,
                                             namesspaceID);
    }
    return namesspaceID;
}

void Document::namespaceIDToURI(PRInt32 aNamespaceID, nsAString& aNamespaceURI)
{
    if (gTxNameSpaceManager) {
        gTxNameSpaceManager->GetNameSpaceURI(aNamespaceID,
                                           aNamespaceURI);
    }
}

#ifdef DEBUG
PRBool MozillaObjectWrapper::inHashTableDeletion()
{
    return mOwnerDocument->mInHashTableDeletion;
}
#endif
