/*
 * This file is part of the DOM implementation for KDE.
 *
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
 *           (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
 *           (C) 2005, 2009 Maksim Orlovich (maksim@kde.org)
 *           (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
 *           (C) 2007 David Smith (catfish.man@gmail.com)
 *
 * 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.
 *
 *
 * The code for ClassNodeListImpl was originally licensed under the following terms
 * (but in this version is available only as above):
 *     Redistribution and use in source and binary forms, with or without
 *     modification, are permitted provided that the following conditions
 *     are met:
 *
 *     1.  Redistributions of source code must retain the above copyright
 *         notice, this list of conditions and the following disclaimer.
 *     2.  Redistributions in binary form must reproduce the above copyright
 *         notice, this list of conditions and the following disclaimer in the
 *         documentation and/or other materials provided with the distribution.
 *     3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *         its contributors may be used to endorse or promote products derived
 *         from this software without specific prior written permission.
 *
 *     THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 *     EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *     DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 *     DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "dom_nodelistimpl.h"
#include "dom_nodeimpl.h"
#include "dom_docimpl.h"
#include <kdebug.h>

using namespace DOM;
using namespace khtml;

NodeImpl *NodeListImpl::item( unsigned long index ) const
{
    unsigned long requestIndex = index;

    m_cache->updateNodeListInfo(m_refNode->document());

    NodeImpl* n;
    bool usedCache = false;
    if (m_cache->current.node) {
        //Compute distance from the requested index to the cache node
        unsigned long cacheDist = qAbs(long(index) - long(m_cache->position));

        if (cacheDist < (unsigned long)index) { //Closer to the cached position
            usedCache = true;
            if (index >= m_cache->position) { //Go ahead
                unsigned long relIndex = index - m_cache->position;
                n = recursiveItem(m_refNode, m_cache->current.node, relIndex);
            } else { //Go backwards
                unsigned long relIndex = m_cache->position - index;
                n = recursiveItemBack(m_refNode, m_cache->current.node, relIndex);
            }
        }
    }

    if (!usedCache)
        n = recursiveItem(m_refNode, m_refNode->firstChild(), index);

    //We always update the cache state, to make starting iteration
    //where it was left off easy.
    m_cache->current.node  = n;
    m_cache->position = requestIndex;
    return n;
}

unsigned long NodeListImpl::length() const
{
    m_cache->updateNodeListInfo(m_refNode->document());
    if (!m_cache->hasLength) {
        m_cache->length    = calcLength( m_refNode );
        m_cache->hasLength = true;
    }
    return m_cache->length;
}

unsigned long NodeListImpl::calcLength(NodeImpl *start) const
{
    unsigned long len = 0;
    for(NodeImpl *n = start->firstChild(); n != 0; n = n->nextSibling()) {
        bool recurse = true;
        if (nodeMatches(n, recurse))
                len++;
        if (recurse)
            len+= NodeListImpl::calcLength(n);
    }

    return len;
}

NodeListImpl::NodeListImpl( NodeImpl *n, int type, CacheFactory* factory )
{
    m_refNode = n;
    m_refNode->ref();

    m_cache = m_refNode->document()->acquireCachedNodeListInfo(
                  factory, n, type );
}

NodeListImpl::~NodeListImpl()
{
    m_refNode->document()->releaseCachedNodeListInfo(m_cache);
    m_refNode->deref();
}

/**
 Next item in the pre-order walk of tree from node, but not going outside
 absStart
*/
static NodeImpl* helperNext(NodeImpl* node, NodeImpl* absStart)
{
    //Walk up until we wind a sibling to go to.
    while (!node->nextSibling() && node != absStart)
        node = node->parentNode();

    if (node != absStart)
        return node->nextSibling();
    else
        return 0;
}

NodeImpl *NodeListImpl::recursiveItem ( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const
{
    for(NodeImpl *n = start; n != 0; n = helperNext(n, absStart)) {
        bool recurse = true;
        if (nodeMatches(n, recurse))
            if (!offset--)
                return n;

        NodeImpl *depthSearch = recurse ? recursiveItem(n, n->firstChild(), offset) : 0;
        if (depthSearch)
            return depthSearch;
    }

    return 0; // no matching node in this subtree
}

NodeImpl *NodeListImpl::recursiveItemBack ( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const
{
    //### it might be cleaner/faster to split nodeMatches and recursion
    //filtering.
    bool dummy   = true;
    NodeImpl* n  = start;

    do {
        bool recurse = true;

        //Check whether the current node matches.
        if (nodeMatches(n, dummy))
            if (!offset--)
                return n;

        if (n->previousSibling()) {
            //Move to the last node of this whole subtree that we should recurse into
            n       = n->previousSibling();
            recurse = true;

            while (n->lastChild()) {
                (void)nodeMatches(n, recurse);
                if (!recurse)
                   break; //Don't go there
                n = n->lastChild();
            }
        } else {
            //We're done with this whole subtree, so move up
            n = n->parentNode();
        }
    }
    while (n && n != absStart);

    return 0;
}

// ---------------------------------------------------------------------------

NodeListImpl::Cache* NodeListImpl::Cache::makeStructuralOnly()
{
   return new Cache(DocumentImpl::TV_Structural); // will check the same ver twice
}

NodeListImpl::Cache* NodeListImpl::Cache::makeNameOrID()
{
   return new Cache(DocumentImpl::TV_IDNameHref);
}

NodeListImpl::Cache* NodeListImpl::Cache::makeClassName()
{
   return new Cache(DocumentImpl::TV_Class);
}

NodeListImpl::Cache::Cache(unsigned short relSecondaryVer):relevantSecondaryVer(relSecondaryVer)
{}

NodeListImpl::Cache::~Cache()
{}

void NodeListImpl::Cache::clear(DocumentImpl* doc)
{
   hasLength = false;
   current.node = 0;
   version          = doc->domTreeVersion(DocumentImpl::TV_Structural);
   secondaryVersion = doc->domTreeVersion(relevantSecondaryVer);
}

void NodeListImpl::Cache::updateNodeListInfo(DocumentImpl* doc)
{
    //If version doesn't match, clear
    if (doc->domTreeVersion(DocumentImpl::TV_Structural) != version ||
        doc->domTreeVersion(relevantSecondaryVer) != secondaryVersion)
        clear(doc);
}

// ---------------------------------------------------------------------------
ChildNodeListImpl::ChildNodeListImpl( NodeImpl *n ): NodeListImpl(n, CHILD_NODES, NodeListImpl::Cache::makeStructuralOnly)
{}

bool ChildNodeListImpl::nodeMatches( NodeImpl* /*testNode*/, bool& doRecurse ) const
{
    doRecurse = false;
    return true;
}

// ---------------------------------------------------------------------------
TagNodeListImpl::TagNodeListImpl(NodeImpl *n, NamespaceName namespaceName, LocalName localName, PrefixName prefix)
  : NodeListImpl(n, UNCACHEABLE, NodeListImpl::Cache::makeStructuralOnly),
    m_namespaceAware(false)
{
    m_namespace = namespaceName;
    m_localName = localName;
    m_prefix = prefix;
}

TagNodeListImpl::TagNodeListImpl( NodeImpl *n, const DOMString &namespaceURI, const DOMString &localName )
  : NodeListImpl(n, UNCACHEABLE, NodeListImpl::Cache::makeStructuralOnly),
    m_namespaceAware(true)
{
    if (namespaceURI == "*")
        m_namespace = NamespaceName::fromId(anyNamespace);
    else
        m_namespace = NamespaceName::fromString(namespaceURI);
    if (localName == "*")
        m_localName = LocalName::fromId(anyLocalName);
    else
        m_localName = LocalName::fromString(localName);
    m_prefix = PrefixName::fromId(emptyPrefix);
}


bool TagNodeListImpl::nodeMatches(NodeImpl *testNode, bool& /*doRecurse*/) const
{
    if (testNode->nodeType() != Node::ELEMENT_NODE)
        return false;

    if (m_namespaceAware) {
        return (m_namespace.id() == anyNamespace || m_namespace.id() == namespacePart(testNode->id())) &&
            (m_localName.id() == anyLocalName || m_localName.id() == localNamePart(testNode->id()));
    } else {
        return (m_localName.id() == anyLocalName) || (m_localName.id() == localNamePart(testNode->id()) &&
            m_prefix == static_cast<ElementImpl*>(testNode)->prefixName());
    }
}

// ---------------------------------------------------------------------------
NameNodeListImpl::NameNodeListImpl(NodeImpl *n, const DOMString &t )
  : NodeListImpl(n, UNCACHEABLE, NodeListImpl::Cache::makeNameOrID),
    nodeName(t)
{}

bool NameNodeListImpl::nodeMatches( NodeImpl *testNode, bool& /*doRecurse*/ ) const
{
    if ( testNode->nodeType() != Node::ELEMENT_NODE ) return false;
    return static_cast<ElementImpl *>(testNode)->getAttribute(ATTR_NAME) == nodeName;
}

// ---------------------------------------------------------------------------
ClassNodeListImpl::ClassNodeListImpl(NodeImpl* rootNode, const DOMString& classNames)
    : NodeListImpl(rootNode, UNCACHEABLE, NodeListImpl::Cache::makeClassName)
{
    m_classNames.parseClassAttribute(classNames.string(), m_refNode->document()->inCompatMode());
}

bool ClassNodeListImpl::nodeMatches(NodeImpl *testNode, bool& doRecurse) const
{
    if (!testNode->isElementNode()) {
        doRecurse = false;
        return false;
    }

    if (!testNode->hasClass())
        return false;

    if (!m_classNames.size())
        return false;

    const ClassNames&  classes = static_cast<ElementImpl*>(testNode)->classNames();
    for (size_t i = 0; i < m_classNames.size(); ++i) {
        if (!classes.contains(m_classNames[i]))
            return false;
    }

    return true;
}
