/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.jcr2spi.query;

import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.RangeIterator;

import org.apache.jackrabbit.jcr2spi.ItemManager;
import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
import org.apache.jackrabbit.spi.NodeId;
import org.apache.jackrabbit.spi.QueryInfo;
import org.apache.jackrabbit.spi.QueryResultRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implements a {@link javax.jcr.NodeIterator} returned by
 * {@link javax.jcr.query.QueryResult#getNodes()}.
 */
public class NodeIteratorImpl implements ScoreNodeIterator {

    /** Logger instance for this class */
    private static final Logger log = LoggerFactory.getLogger(NodeIteratorImpl.class);

    /** ItemManager to turn Ids into Node instances */
    private final ItemManager itemMgr;

    /**  */
    private final HierarchyManager hierarchyMgr;

    /** The QueryResultRows */
    private final RangeIterator rows;

    /** Current position of this node iterator */
    private int pos = -1;

    /** Number of invalid nodes */
    private int invalid = 0;

    /** Id of the next Node */
    private NodeId nextId;

    /** Reference to the next node instance */
    private Node next;

    /** Score for the next node */
    private double nextScore;

    /**
     * Creates a new <code>NodeIteratorImpl</code> instance.
     *
     * @param itemMgr The <code>ItemManager</code> to build <code>Node</code> instances.
     * @param hierarchyMgr The <code>HierarchyManager</code> used to retrieve the
     * HierarchyEntry objects from the ids returned by the query.
     * @param queryInfo the query result.
     */
    public NodeIteratorImpl(ItemManager itemMgr, HierarchyManager hierarchyMgr,
                            QueryInfo queryInfo) {
        this.itemMgr = itemMgr;
        this.hierarchyMgr = hierarchyMgr;
        this.rows = queryInfo.getRows();

        fetchNext();
    }

    //------------------------------------------------------< ScoreIterator >---
    /**
     * Returns the score of the node returned by {@link #nextNode()}. In other
     * words, this method returns the score value of the next <code>Node</code>.
     *
     * @return the score of the node returned by {@link #nextNode()}.
     * @throws NoSuchElementException if there is no next node.
     * @see ScoreNodeIterator#getScore()
     */
    public double getScore() throws NoSuchElementException {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return nextScore;
    }

    //-------------------------------------------------------< NodeIterator >---
    /**
     * Returns the next <code>Node</code> in the result set.
     *
     * @return the next <code>Node</code> in the result set.
     * @throws NoSuchElementException if iteration has no more <code>Node</code>s.
     * @see javax.jcr.NodeIterator#nextNode()
     */
    public Node nextNode() throws NoSuchElementException {
        if (next == null) {
            throw new NoSuchElementException();
        }
        Node n = next;
        fetchNext();
        return n;
    }

    //------------------------------------------------------< RangeIterator >---
    /**
     * Skip a number of <code>Node</code>s in this iterator.
     *
     * @param skipNum the non-negative number of <code>Node</code>s to skip
     * @throws NoSuchElementException if skipped past the last <code>Node</code>
     * in this iterator.
     * @see javax.jcr.NodeIterator#skip(long)
     */
    public void skip(long skipNum) throws NoSuchElementException {
        if (skipNum < 0) {
            throw new IllegalArgumentException("skipNum must not be negative");
        }
        if (skipNum == 0) {
            // do nothing
        } else {
            rows.skip(skipNum - 1);
            pos += skipNum - 1;
            fetchNext();
        }
    }

    /**
     * Returns the number of nodes in this iterator.
     * </p>
     * Note: The number returned by this method may differ from the number
     * of nodes actually returned by calls to hasNext() / getNextNode()! This
     * is because this iterator works on a lazy instantiation basis and while
     * iterating over the nodes some of them might have been deleted in the
     * meantime. Those will not be returned by getNextNode(). As soon as an
     * invalid node is detected, the size of this iterator is adjusted.
     *
     * @return the number of node in this iterator.
     * @see javax.jcr.RangeIterator#getSize()
     */
    public long getSize() {
        if (rows.getSize() != -1) {
            return rows.getSize() - invalid;
        } else {
            return -1;
        }
    }

    /**
     * Returns the current position in this <code>NodeIterator</code>.
     *
     * @return the current position in this <code>NodeIterator</code>.
     * @see javax.jcr.RangeIterator#getPosition()
     */
    public long getPosition() {
        return pos - invalid;
    }

    /**
     * Returns the next <code>Node</code> in the result set.
     *
     * @return the next <code>Node</code> in the result set.
     * @throws NoSuchElementException if iteration has no more <code>Node</code>s.
     * @see java.util.Iterator#next()
     */
    public Object next() throws NoSuchElementException {
        return nextNode();
    }

    /**
     * Returns <code>true</code> if there is another <code>Node</code>
     * available; <code>false</code> otherwise.
     *
     * @return <code>true</code> if there is another <code>Node</code>
     *  available; <code>false</code> otherwise.
     * @see java.util.Iterator#hasNext()
     */
    public boolean hasNext() {
        return next != null;
    }

    /**
     * @throws UnsupportedOperationException always.
     * @see Iterator#remove()
     */
    public void remove() {
        throw new UnsupportedOperationException("remove");
    }

    //------------------------------------------------------------< private >---
    /**
     * Clears {@link #next} and tries to fetch the next Node instance.
     * When this method returns {@link #next} refers to the next available
     * node instance in this iterator. If {@link #next} is null when this
     * method returns, then there are no more valid element in this iterator.
     */
    private void fetchNext() {
        // reset
        next = null;
        nextScore = 0;

        while (next == null && rows.hasNext()) {
            try {
                QueryResultRow row = (QueryResultRow) rows.next();
                nextId = row.getNodeId(null);
                Item tmp = itemMgr.getItem(hierarchyMgr.getNodeEntry(nextId));

                if (tmp.isNode()) {
                    next = (Node) tmp;
                    nextScore = row.getScore(null);
                } else {
                    log.warn("Item with Id is not a Node: " + nextId);
                    // try next
                    invalid++;
                    pos++;
                }
            } catch (Exception e) {
                log.warn("Exception retrieving Node with Id: " + nextId);
                // try next
                invalid++;
                pos++;
            }
        }
        pos++;
    }
}
