/**
 * This file is part of the DOM implementation for KDE.
 *
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2000 Frederik Holljen (frederik.holljen@hig.no)
 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
 * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
 * Copyright (C) 2004 Apple Computer, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include <kjs/wk_config.h>
#include "NodeIterator.h"

#include "Document.h"
#include "ExceptionCode.h"
#include "NodeFilter.h"

namespace WebCore {

NodeIterator::NodeIterator(Node* rootNode, unsigned whatToShow, PassRefPtr<NodeFilter> filter, bool expandEntityReferences)
    : Traversal(rootNode, whatToShow, filter, expandEntityReferences)
    , m_beforeReferenceNode(true)
    , m_detached(false)
    , m_doc(rootNode ? rootNode->document() : 0)
{
    if (document())
        document()->attachNodeIterator(this);
}

NodeIterator::~NodeIterator()
{
    if (document())
        document()->detachNodeIterator(this);
}

Node* NodeIterator::findNextNode(Node* node) const
{
    while ((node = node->traverseNextNode(root()))) {
        // NodeIterators treat the DOM tree as a flat list of nodes.
        // In other words, FILTER_REJECT does not pass over descendants
        // of the rejected node. Hence, FILTER_REJECT is the same as FILTER_SKIP.
        if (acceptNode(node) == NodeFilter::FILTER_ACCEPT)
            break;
    }
    return node;
}

Node* NodeIterator::nextNode(ExceptionCode& ec)
{
    if (detached()) {
        ec = INVALID_STATE_ERR;
        return 0;
    }

    Node* node = referenceNode() ? referenceNode() : root();
    if (!pointerBeforeReferenceNode() || acceptNode(node) != NodeFilter::FILTER_ACCEPT)
        node = findNextNode(node);
    if (node)
        setReferenceNode(node);
    setPointerBeforeReferenceNode(false);
    return node;
}

Node* NodeIterator::findPreviousNode(Node* node) const
{
    while ((node = node->traversePreviousNode(root()))) {
        // NodeIterators treat the DOM tree as a flat list of nodes.
        // In other words, FILTER_REJECT does not pass over descendants
        // of the rejected node. Hence, FILTER_REJECT is the same as FILTER_SKIP.
        if (acceptNode(node) == NodeFilter::FILTER_ACCEPT)
            break;
    }
    return node;
}

Node* NodeIterator::previousNode(ExceptionCode&)
{
    Node* node = referenceNode() ? referenceNode() : root();
    if (pointerBeforeReferenceNode() || acceptNode(node) != NodeFilter::FILTER_ACCEPT)
        node = findPreviousNode(node);
    if (node)
        setReferenceNode(node);
    setPointerBeforeReferenceNode();
    return node;
}

void NodeIterator::detach()
{
    if (!detached() && document())
        document()->detachNodeIterator(this);
    setDetached();
}

void NodeIterator::setReferenceNode(Node* node)
{
    m_referenceNode = node;
}

void NodeIterator::notifyBeforeNodeRemoval(Node* removedNode)
{
    // Iterator is not affected if the removed node is the reference node and is the root.
    // or if removed node is not the reference node, or the ancestor of the reference node.
    if (!removedNode || removedNode == root() || !removedNode->isDescendantOf(root()))
        return;
    bool willRemoveReferenceNode = removedNode == referenceNode();
    bool willRemoveReferenceNodeAncestor = referenceNode() && referenceNode()->isDescendantOf(removedNode);
    if (!willRemoveReferenceNode && !willRemoveReferenceNodeAncestor)
        return;

    if (pointerBeforeReferenceNode()) {
        Node* node = findNextNode(removedNode);
        if (node) {
            // Move out from under the node being removed if the reference node is
            // a descendant of the node being removed.
            if (willRemoveReferenceNodeAncestor) {
                while (node && node->isDescendantOf(removedNode))
                    node = findNextNode(node);
            }
            if (node)
                setReferenceNode(node);
        } else {
            node = findPreviousNode(removedNode);
            if (node) {
                // Move out from under the node being removed if the reference node is
                // a descendant of the node being removed.
                if (willRemoveReferenceNodeAncestor) {
                    while (node && node->isDescendantOf(removedNode))
                        node = findPreviousNode(node);
                }
                if (node) {
                    // Removing last node.
                    // Need to move the pointer after the node preceding the 
                    // new reference node.
                    setReferenceNode(node);
                    setPointerBeforeReferenceNode(false);
                }
            }
        }
    } else {
        Node* node = findPreviousNode(removedNode);
        if (node) {
            // Move out from under the node being removed if the reference node is
            // a descendant of the node being removed.
            if (willRemoveReferenceNodeAncestor) {
                while (node && node->isDescendantOf(removedNode))
                    node = findPreviousNode(node);
            }
            if (node)
                setReferenceNode(node);
        } else {
            node = findNextNode(removedNode);
            // Move out from under the node being removed if the reference node is
            // a descendant of the node being removed.
            if (willRemoveReferenceNodeAncestor) {
                while (node && node->isDescendantOf(removedNode))
                    node = findPreviousNode(node);
            }
            if (node)
                setReferenceNode(node);
        }
    }
}

} // namespace WebCore
