/*
 * 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 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.getBranchKeyEnd;
import static org.apache.cassandra.utils.btree.BTree.getKeyEnd;
import static org.apache.cassandra.utils.btree.BTree.getLeafKeyEnd;
import static org.apache.cassandra.utils.btree.BTree.isLeaf;

/**
 * An internal class for searching and iterating through a tree.  As it traverses the tree,
 * it adds the nodes visited to a stack.  This allows us to backtrack from a child node
 * to its parent.
 *
 * As we navigate the tree, we destructively modify this stack.
 *
 * Path is only intended to be used via Cursor.
 */
class Path
{
    // operations corresponding to the ones in NavigableSet
    static enum Op
    {
        CEIL,   // the least element greater than or equal to the given element
        FLOOR,  // the greatest element less than or equal to the given element
        HIGHER, // the least element strictly greater than the given element
        LOWER   // the greatest element strictly less than the given element
    }

    // the path to the searched-for key
    Object[][] path;
    // the index within the node of our path at a given depth
    byte[] indexes;
    // current depth.  nothing in path[i] for i > depth is valid.
    byte depth;

    Path() { }
    Path(int depth)
    {
        this.path = new Object[depth][];
        this.indexes = new byte[depth];
    }

    void ensureDepth(Object[] btree)
    {
        int depth = BTree.depth(btree);
        if (path == null || path.length < depth)
        {
            path = new Object[depth][];
            indexes = new byte[depth];
        }
    }

    void moveEnd(Object[] node, boolean forwards)
    {
        push(node, getKeyEnd(node));
        if (!forwards)
            predecessor();
    }

    void moveStart(Object[] node, boolean forwards)
    {
        push(node, -1);
        if (forwards)
            successor();
    }

    /**
     * Find the provided key in the tree rooted at node, and store the root to it in the path
     *
     * @param node       the tree to search in
     * @param comparator the comparator defining the order on the tree
     * @param target     the key to search for
     * @param mode       the type of search to perform
     * @param forwards   if the path should be setup for forward or backward iteration
     * @param <V>
     */
    <V> void find(Object[] node, Comparator<V> comparator, Object target, Op mode, boolean forwards)
    {
        // TODO : should not require parameter 'forwards' - consider modifying index to represent both
        // child and key position, as opposed to just key position (which necessitates a different value depending
        // on which direction you're moving in. Prerequisite for making Path public and using to implement general
        // search

        depth = -1;
        if (target instanceof BTree.Special)
        {
            if (target == POSITIVE_INFINITY)
                moveEnd(node, forwards);
            else if (target == NEGATIVE_INFINITY)
                moveStart(node, forwards);
            else
                throw new AssertionError();
            return;
        }

        while (true)
        {
            int keyEnd = getKeyEnd(node);

            // search for the target in the current node
            int i = BTree.find(comparator, target, node, 0, keyEnd);
            if (i >= 0)
            {
                // exact match. transform exclusive bounds into the correct index by moving back or forwards one
                push(node, i);
                switch (mode)
                {
                    case HIGHER:
                        successor();
                        break;
                    case LOWER:
                        predecessor();
                }
                return;
            }
            i = -i - 1;

            // traverse into the appropriate child
            if (!isLeaf(node))
            {
                push(node, forwards ? i - 1 : i);
                node = (Object[]) node[keyEnd + i];
                continue;
            }

            // bottom of the tree and still not found.  pick the right index to satisfy Op
            switch (mode)
            {
                case FLOOR:
                case LOWER:
                    i--;
            }

            if (i < 0)
            {
                push(node, 0);
                predecessor();
            }
            else if (i >= keyEnd)
            {
                push(node, keyEnd - 1);
                successor();
            }
            else
            {
                push(node, i);
            }

            return;
        }
    }

    private boolean isRoot()
    {
        return depth == 0;
    }

    private void pop()
    {
        depth--;
    }

    Object[] currentNode()
    {
        return path[depth];
    }

    byte currentIndex()
    {
        return indexes[depth];
    }

    private void push(Object[] node, int index)
    {
        path[++depth] = node;
        indexes[depth] = (byte) index;
    }

    void setIndex(int index)
    {
        indexes[depth] = (byte) index;
    }

    // move to the next key in the tree
    void successor()
    {
        Object[] node = currentNode();
        int i = currentIndex();

        if (!isLeaf(node))
        {
            // if we're on a key in a branch, we MUST have a descendant either side of us,
            // so we always go down the left-most child until we hit a leaf
            node = (Object[]) node[getBranchKeyEnd(node) + i + 1];
            while (!isLeaf(node))
            {
                push(node, -1);
                node = (Object[]) node[getBranchKeyEnd(node)];
            }
            push(node, 0);
            return;
        }

        // if we haven't reached the end of this leaf, just increment our index and return
        i += 1;
        if (i < getLeafKeyEnd(node))
        {
            // moved to the next key in the same leaf
            setIndex(i);
            return;
        }

        // we've reached the end of this leaf,
        // so go up until we reach something we've not finished visiting
        while (!isRoot())
        {
            pop();
            i = currentIndex() + 1;
            node = currentNode();
            if (i < getKeyEnd(node))
            {
                setIndex(i);
                return;
            }
        }

        // we've visited the last key in the root node, so we're done
        setIndex(getKeyEnd(node));
    }

    // move to the previous key in the tree
    void predecessor()
    {
        Object[] node = currentNode();
        int i = currentIndex();

        if (!isLeaf(node))
        {
            // if we're on a key in a branch, we MUST have a descendant either side of us
            // so we always go down the right-most child until we hit a leaf
            node = (Object[]) node[getBranchKeyEnd(node) + i];
            while (!isLeaf(node))
            {
                i = getBranchKeyEnd(node);
                push(node, i);
                node = (Object[]) node[i * 2];
            }
            push(node, getLeafKeyEnd(node) - 1);
            return;
        }

        // if we haven't reached the beginning of this leaf, just decrement our index and return
        i -= 1;
        if (i >= 0)
        {
            setIndex(i);
            return;
        }

        // we've reached the beginning of this leaf,
        // so go up until we reach something we've not finished visiting
        while (!isRoot())
        {
            pop();
            i = currentIndex() - 1;
            if (i >= 0)
            {
                setIndex(i);
                return;
            }
        }

        // we've visited the last key in the root node, so we're done
        setIndex(-1);
    }

    Object currentKey()
    {
        return currentNode()[currentIndex()];
    }

    int compareTo(Path that, boolean forwards)
    {
        int d = Math.min(this.depth, that.depth);
        for (int i = 0; i <= d; i++)
        {
            int c = this.indexes[i] - that.indexes[i];
            if (c != 0)
                return c;
        }
        // identical indices up to depth, so if somebody is lower depth they are on a later item if iterating forwards
        // and an earlier item if iterating backwards, as the node at max common depth must be a branch if they are
        // different depths, and branches that are currently descended into lag the child index they are in when iterating forwards,
        // i.e. if they are in child 0 they record an index of -1 forwards, or 0 when backwards
        d = this.depth - that.depth;
        return forwards ? d : -d;
    }
}

