/*
 * 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.cassandra.utils.btree;

import java.util.Comparator;
import java.util.Iterator;

import static org.apache.cassandra.utils.btree.BTree.NEGATIVE_INFINITY;
import static org.apache.cassandra.utils.btree.BTree.POSITIVE_INFINITY;
import static org.apache.cassandra.utils.btree.BTree.getLeafKeyEnd;
import static org.apache.cassandra.utils.btree.BTree.isLeaf;

/**
 * An extension of Path which provides a public interface for iterating over or counting a subrange of the tree
 *
 * @param <V>
 */
public final class Cursor<K, V extends K> extends Path implements Iterator<V>
{
    /*
     * Conceptually, a Cursor derives two Paths, one for the first object in the slice requested (inclusive),
     * and one for the last (exclusive).  Then hasNext just checks, have we reached the last yet, and next
     * calls successor() to get to the next item in the Tree.
     *
     * To optimize memory use, we summarize the last Path as just endNode/endIndex, and inherit from Path for
     *
     * the first one.
     */

    // the last node covered by the requested range
    private Object[] endNode;
    // the index within endNode that signals we're finished -- that is, endNode[endIndex] is NOT part of the Cursor
    private byte endIndex;

    private boolean forwards;

    /**
     * Reset this cursor for the provided tree, to iterate over its entire range
     *
     * @param btree    the tree to iterate over
     * @param forwards if false, the cursor will start at the end and move backwards
     */
    public void reset(Object[] btree, boolean forwards)
    {
        _reset(btree, null, NEGATIVE_INFINITY, false, POSITIVE_INFINITY, false, forwards);
    }

    /**
     * Reset this cursor for the provided tree, to iterate between the provided start and end
     *
     * @param btree      the tree to iterate over
     * @param comparator the comparator that defines the ordering over the items in the tree
     * @param lowerBound the first item to include, inclusive
     * @param upperBound the last item to include, exclusive
     * @param forwards   if false, the cursor will start at the end and move backwards
     */
    public void reset(Object[] btree, Comparator<K> comparator, K lowerBound, K upperBound, boolean forwards)
    {
        _reset(btree, comparator, lowerBound, true, upperBound, false, forwards);
    }

    /**
     * Reset this cursor for the provided tree, to iterate between the provided start and end
     *
     * @param btree               the tree to iterate over
     * @param comparator          the comparator that defines the ordering over the items in the tree
     * @param lowerBound          the first item to include
     * @param inclusiveLowerBound should include start in the iterator, if present in the tree
     * @param upperBound          the last item to include
     * @param inclusiveUpperBound should include end in the iterator, if present in the tree
     * @param forwards            if false, the cursor will start at the end and move backwards
     */
    public void reset(Object[] btree, Comparator<K> comparator, K lowerBound, boolean inclusiveLowerBound, K upperBound, boolean inclusiveUpperBound, boolean forwards)
    {
        _reset(btree, comparator, lowerBound, inclusiveLowerBound, upperBound, inclusiveUpperBound, forwards);
    }

    private void _reset(Object[] btree, Comparator<K> comparator, Object lowerBound, boolean inclusiveLowerBound, Object upperBound, boolean inclusiveUpperBound, boolean forwards)
    {
        ensureDepth(btree);
        if (lowerBound == null)
            lowerBound = NEGATIVE_INFINITY;
        if (upperBound == null)
            upperBound = POSITIVE_INFINITY;

        this.forwards = forwards;

        Path findLast = new Path(this.path.length);
        if (forwards)
        {
            findLast.find(btree, comparator, upperBound, inclusiveUpperBound ? Op.HIGHER : Op.CEIL, true);
            find(btree, comparator, lowerBound, inclusiveLowerBound ? Op.CEIL : Op.HIGHER, true);
        }
        else
        {
            findLast.find(btree, comparator, lowerBound, inclusiveLowerBound ? Op.LOWER : Op.FLOOR, false);
            find(btree, comparator, upperBound, inclusiveUpperBound ? Op.FLOOR : Op.LOWER, false);
        }
        int c = this.compareTo(findLast, forwards);
        if (forwards ? c > 0 : c < 0)
        {
            endNode = currentNode();
            endIndex = currentIndex();
        }
        else
        {
            endNode = findLast.currentNode();
            endIndex = findLast.currentIndex();
        }
    }

    public boolean hasNext()
    {
        return path[depth] != endNode || indexes[depth] != endIndex;
    }

    public V next()
    {
        Object r = currentKey();
        if (forwards)
            successor();
        else
            predecessor();
        return (V) r;
    }

    public int count()
    {
        if (!forwards)
            throw new IllegalStateException("Count can only be run on forward cursors");
        int count = 0;
        int next;
        while ((next = consumeNextLeaf()) >= 0)
            count += next;
        return count;
    }

    /**
     * @return the number of objects consumed by moving out of the next (possibly current) leaf
     */
    private int consumeNextLeaf()
    {
        Object[] node = currentNode();
        int r = 0;

        if (!isLeaf(node))
        {
            // if we're not in a leaf, then calling successor once will take us to a leaf, since the next
            // key will be in the leftmost subtree of whichever branch is next.  For instance, if we
            // are in the root node of the tree depicted by http://cis.stvincent.edu/html/tutorials/swd/btree/btree1.gif,
            // successor() will take us to the leaf containing N and O.
            int i = currentIndex();
            if (node == endNode && i == endIndex)
                return -1;
            r = 1;
            successor();
            node = currentNode();
        }

        if (node == endNode)
        {
            // only count up to endIndex, and don't call successor()
            if (currentIndex() == endIndex)
                return r > 0 ? r : -1;
            r += endIndex - currentIndex();
            setIndex(endIndex);
            return r;
        }

        // count the remaining objects in this leaf
        int keyEnd = getLeafKeyEnd(node);
        r += keyEnd - currentIndex();
        setIndex(keyEnd);
        successor();
        return r;
    }

    public void remove()
    {
        throw new UnsupportedOperationException();
    }
}
