/*
 *  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.directory.mavibot.btree;


import java.io.IOException;
import java.lang.reflect.Array;
import java.util.List;


/**
 * A MVCC Node. It stores the keys and references to its children page. It does not
 * contain any value.
 *
 * @param <K> The type for the Key
 * @param <V> The type for the stored value
 *
 * @author <a href="mailto:[email protected]">Apache Directory Project</a>
 */
/* No qualifier */class PersistedNode<K, V> extends AbstractPage<K, V>
{
    /**
     * Creates a new Node which will contain only one key, with references to
     * a left and right page. This is a specific constructor used by the btree
     * when the root was full when we added a new value.
     *
     * @param btree the parent BTree
     * @param revision the Node revision
     * @param nbElems The number of elements in this Node
     */
    @SuppressWarnings("unchecked")
    PersistedNode( BTree<K, V> btree, long revision, int nbElems )
    {
        super( btree, revision, nbElems );

        // Create the children array
        children = ( PersistedPageHolder<K, V>[] ) Array.newInstance( PersistedPageHolder.class, nbElems + 1 );
    }


    /**
     * Creates a new Node which will contain only one key, with references to
     * a left and right page. This is a specific constructor used by the btree
     * when the root was full when we added a new value.
     *
     * @param btree the parent BTree
     * @param revision the Node revision
     * @param key The new key
     * @param leftPage The left page
     * @param rightPage The right page
     */
    @SuppressWarnings("unchecked")
    PersistedNode( BTree<K, V> btree, long revision, K key, Page<K, V> leftPage, Page<K, V> rightPage )
    {
        super( btree, revision, 1 );

        // Create the children array, and store the left and right children
        children = ( PersistedPageHolder<K, V>[] ) Array.newInstance( PersistedPageHolder.class,
            btree.getPageSize() + 1 );

        children[0] = new PersistedPageHolder<K, V>( btree, leftPage );
        children[1] = new PersistedPageHolder<K, V>( btree, rightPage );

        // Create the keys array and store the pivot into it
        // We get the type of array to create from the btree
        // Yes, this is an hack...
        keys = ( KeyHolder<K>[] ) Array.newInstance( PersistedKeyHolder.class, btree.getPageSize() );

        keys[0] = new PersistedKeyHolder<K>( btree.getKeySerializer(), key );
    }


    /**
     * Creates a new Node which will contain only one key, with references to
     * a left and right page. This is a specific constructor used by the btree
     * when the root was full when we added a new value.
     *
     * @param btree the parent BTree
     * @param revision the Node revision
     * @param key The new key
     * @param leftPage The left page
     * @param rightPage The right page
     */
    @SuppressWarnings("unchecked")
    PersistedNode( BTree<K, V> btree, long revision, K key, PageHolder<K, V> leftPage, PageHolder<K, V> rightPage )
    {
        super( btree, revision, 1 );

        // Create the children array, and store the left and right children
        children = ( PageHolder<K, V>[] ) Array.newInstance( PageHolder.class,
            btree.getPageSize() + 1 );

        children[0] = leftPage;
        children[1] = rightPage;

        // Create the keys array and store the pivot into it
        keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, btree.getPageSize() );

        keys[0] = new PersistedKeyHolder<K>( btree.getKeySerializer(), key );
    }


    /**
     * {@inheritDoc}
     */
    public InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
    {
        // Find the key into this leaf
        int pos = findPos( key );

        if ( pos < 0 )
        {
            // The key has been found in the page. As it's a Node, that means
            // we must go down in the right child to insert the value
            pos = -( pos++ );
        }

        // Get the child page into which we will insert the <K, V> tuple
        Page<K, V> child = children[pos].getValue();

        // and insert the <K, V> into this child
        InsertResult<K, V> result = child.insert( key, value, revision );

        // Ok, now, we have injected the <K, V> tuple down the tree. Let's check
        // the result to see if we have to split the current page
        if ( result instanceof ExistsResult )
        {
            return result;
        }
        else if ( result instanceof ModifyResult )
        {
            // The child has been modified.
            return replaceChild( revision, ( ModifyResult<K, V> ) result, pos );
        }
        else
        {
            // The child has been split. We have to insert the new pivot in the
            // current page, and to reference the two new pages
            SplitResult<K, V> splitResult = ( SplitResult<K, V> ) result;
            K pivot = splitResult.getPivot();
            Page<K, V> leftPage = splitResult.getLeftPage();
            Page<K, V> rightPage = splitResult.getRightPage();

            // We have to deal with the two cases :
            // - the current page is full, we have to split it
            // - the current page is not full, we insert the new pivot
            if ( nbElems == btree.getPageSize() )
            {
                // The page is full
                result = addAndSplit( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos );
            }
            else
            {
                // The page can contain the new pivot, let's insert it
                result = insertChild( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos );
            }

            return result;
        }
    }


    /**
     * Modifies the current node after a remove has been done in one of its children.
     * The node won't be merged with another node.
     *
     * @param removeResult The result of a remove operation
     * @param index the position of the key, not transformed
     * @param pos The position of the key, as a positive value
     * @param found If the key has been found in the page
     * @return The new result
     * @throws IOException If we have an error while trying to access the page
     */
    private RemoveResult<K, V> handleRemoveResult( RemoveResult<K, V> removeResult, int index, int pos, boolean found )
        throws IOException
    {
        // Simplest case : the element has been removed from the underlying page,
        // we just have to copy the current page an modify the reference to link to
        // the modified page.
        PersistedNode<K, V> newPage = copy( revision );

        Page<K, V> modifiedPage = removeResult.getModifiedPage();

        if ( found )
        {
            newPage.children[index + 1] = createHolder( modifiedPage );
        }
        else
        {
            newPage.children[index] = createHolder( modifiedPage );
        }

        if ( pos < 0 )
        {
            newPage.keys[index].setKey( removeResult.getModifiedPage().getLeftMostKey() );
        }

        // Modify the result and return
        removeResult.setModifiedPage( newPage );
        removeResult.addCopiedPage( this );

        return removeResult;
    }


    /**
     * Handles the removal of an element from the root page, when two of its children
     * have been merged.
     *
     * @param mergedResult The merge result
     * @param pos The position in the current root
     * @param found Tells if the removed key is present in the root page
     * @return The resulting root page
     * @throws IOException If we have an error while trying to access the page
     */
    private RemoveResult<K, V> handleRootRemove( MergedWithSiblingResult<K, V> mergedResult, int pos, boolean found )
        throws IOException
    {
        RemoveResult<K, V> removeResult = null;

        // If the current node contains only one key, then the merged result will be
        // the new root. Deal with this case
        if ( nbElems == 1 )
        {
            removeResult = new RemoveResult<K, V>( mergedResult.getCopiedPages(), mergedResult.getModifiedPage(),
                mergedResult.getRemovedElement() );

            removeResult.addCopiedPage( this );
        }
        else
        {
            // Remove the element and update the reference to the changed pages
            removeResult = removeKey( mergedResult, revision, pos );
        }

        return removeResult;
    }


    /**
     * Borrows an element from the right sibling, creating a new sibling with one
     * less element and creating a new page where the element to remove has been
     * deleted and the borrowed element added on the right.
     *
     * @param revision The new revision for all the pages
     * @param sibling The right sibling
     * @param pos The position of the element to remove
     * @return The resulting pages
     * @throws IOException If we have an error while trying to access the page
     */
    private DeleteResult<K, V> borrowFromRight( long revision, MergedWithSiblingResult<K, V> mergedResult,
        PersistedNode<K, V> sibling, int pos ) throws IOException
    {
        // Create the new sibling, with one less element at the beginning
        PersistedNode<K, V> newSibling = new PersistedNode<K, V>( btree, revision, sibling.getNbElems() - 1 );

        K siblingKey = sibling.children[0].getValue().getLeftMostKey();

        // Copy the keys and children of the old sibling in the new sibling
        System.arraycopy( sibling.keys, 1, newSibling.keys, 0, newSibling.getNbElems() );
        System.arraycopy( sibling.children, 1, newSibling.children, 0, newSibling.getNbElems() + 1 );

        // Create the new page and add the new element at the end
        // First copy the current node, with the same size
        PersistedNode<K, V> newNode = new PersistedNode<K, V>( btree, revision, nbElems );

        // Copy the keys and the values up to the insertion position
        int index = Math.abs( pos );

        // Copy the key and children from sibling
        newNode.keys[nbElems - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), siblingKey ); // 1
        newNode.children[nbElems] = sibling.children[0]; // 8

        if ( index < 2 )
        {
            // Copy the keys
            System.arraycopy( keys, 1, newNode.keys, 0, nbElems - 1 );

            // Inject the modified page
            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[0] = createHolder( modifiedPage );

            // Copy the children
            System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 );
        }
        else
        {
            if ( index > 2 )
            {
                // Copy the keys before the deletion point
                System.arraycopy( keys, 0, newNode.keys, 0, index - 2 ); // 4
            }

            // Inject the new modified page key
            newNode.keys[index - 2] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult
                .getModifiedPage()
                .getLeftMostKey() ); // 2

            if ( index < nbElems )
            {
                // Copy the remaining keys after the deletion point
                System.arraycopy( keys, index, newNode.keys, index - 1, nbElems - index ); // 3

                // Copy the remaining children after the deletion point
                System.arraycopy( children, index + 1, newNode.children, index, nbElems - index ); // 7
            }

            // Copy the children before the deletion point
            System.arraycopy( children, 0, newNode.children, 0, index - 1 ); // 5

            // Inject the modified page
            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[index - 1] = createHolder( modifiedPage ); // 6
        }

        // Create the result
        DeleteResult<K, V> result = new BorrowedFromRightResult<K, V>( mergedResult.getCopiedPages(), newNode,
            newSibling, mergedResult.getRemovedElement() );

        result.addCopiedPage( this );
        result.addCopiedPage( sibling );

        return result;
    }


    /**
     * Borrows an element from the left sibling, creating a new sibling with one
     * less element and creating a new page where the element to remove has been
     * deleted and the borrowed element added on the left.
     *
     * @param revision The new revision for all the pages
     * @param sibling The left sibling
     * @param pos The position of the element to remove
     * @return The resulting pages
     * @throws IOException If we have an error while trying to access the page
     */
    private DeleteResult<K, V> borrowFromLeft( long revision, MergedWithSiblingResult<K, V> mergedResult,
        PersistedNode<K, V> sibling, int pos ) throws IOException
    {
        // The sibling is on the left, borrow the rightmost element
        Page<K, V> siblingChild = sibling.children[sibling.nbElems].getValue();

        // Create the new sibling, with one less element at the end
        PersistedNode<K, V> newSibling = new PersistedNode<K, V>( btree, revision, sibling.getNbElems() - 1 );

        // Copy the keys and children of the old sibling in the new sibling
        System.arraycopy( sibling.keys, 0, newSibling.keys, 0, newSibling.getNbElems() );
        System.arraycopy( sibling.children, 0, newSibling.children, 0, newSibling.getNbElems() + 1 );

        // Create the new page and add the new element at the beginning
        // First copy the current node, with the same size
        PersistedNode<K, V> newNode = new PersistedNode<K, V>( btree, revision, nbElems );

        // Sets the first children
        newNode.children[0] = createHolder( siblingChild ); //1

        int index = Math.abs( pos );

        if ( index < 2 )
        {
            newNode.keys[0] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult.getModifiedPage()
                .getLeftMostKey() );
            System.arraycopy( keys, 1, newNode.keys, 1, nbElems - 1 );

            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[1] = createHolder( modifiedPage );
            System.arraycopy( children, 2, newNode.children, 2, nbElems - 1 );
        }
        else
        {
            // Set the first key
            newNode.keys[0] = new PersistedKeyHolder<K>( btree.getKeySerializer(), children[0].getValue()
                .getLeftMostKey() ); //2

            if ( index > 2 )
            {
                // Copy the keys before the deletion point
                System.arraycopy( keys, 0, newNode.keys, 1, index - 2 ); // 4
            }

            // Inject the modified key
            newNode.keys[index - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult
                .getModifiedPage()
                .getLeftMostKey() ); // 3

            if ( index < nbElems )
            {
                // Add copy the remaining keys after the deletion point
                System.arraycopy( keys, index, newNode.keys, index, nbElems - index ); // 5

                // Copy the remaining children after the insertion point
                System.arraycopy( children, index + 1, newNode.children, index + 1, nbElems - index ); // 8
            }

            // Copy the children before the insertion point
            System.arraycopy( children, 0, newNode.children, 1, index - 1 ); // 6

            // Insert the modified page
            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[index] = createHolder( modifiedPage ); // 7
        }

        // Create the result
        DeleteResult<K, V> result = new BorrowedFromLeftResult<K, V>( mergedResult.getCopiedPages(), newNode,
            newSibling,
            mergedResult.getRemovedElement() );

        result.addCopiedPage( this );
        result.addCopiedPage( sibling );

        return result;
    }


    /**
     * We have to merge the node with its sibling, both have N/2 elements before the element
     * removal.
     *
     * @param revision The revision
     * @param mergedResult The result of the merge
     * @param sibling The Page we will merge the current page with
     * @param isLeft Tells if the sibling is on the left
     * @param pos The position of the key that has been removed
     * @return The page resulting of the merge
     * @throws IOException If we have an error while trying to access the page
     */
    private DeleteResult<K, V> mergeWithSibling( long revision, MergedWithSiblingResult<K, V> mergedResult,
        PersistedNode<K, V> sibling, boolean isLeft, int pos ) throws IOException
    {
        // Create the new node. It will contain N - 1 elements (the maximum number)
        // as we merge two nodes that contain N/2 elements minus the one we remove
        PersistedNode<K, V> newNode = new PersistedNode<K, V>( btree, revision, btree.getPageSize() );
        Tuple<K, V> removedElement = mergedResult.getRemovedElement();
        int half = btree.getPageSize() / 2;
        int index = Math.abs( pos );

        if ( isLeft )
        {
            // The sibling is on the left. Copy all of its elements in the new node first
            System.arraycopy( sibling.keys, 0, newNode.keys, 0, half ); //1
            System.arraycopy( sibling.children, 0, newNode.children, 0, half + 1 ); //2

            // Then copy all the elements up to the deletion point
            if ( index < 2 )
            {
                newNode.keys[half] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult
                    .getModifiedPage()
                    .getLeftMostKey() );
                System.arraycopy( keys, 1, newNode.keys, half + 1, half - 1 );

                Page<K, V> modifiedPage = mergedResult.getModifiedPage();
                newNode.children[half + 1] = createHolder( modifiedPage );
                System.arraycopy( children, 2, newNode.children, half + 2, half - 1 );
            }
            else
            {
                // Copy the left part of the node keys up to the deletion point
                // Insert the new key
                newNode.keys[half] = new PersistedKeyHolder<K>( btree.getKeySerializer(), children[0].getValue()
                    .getLeftMostKey() ); // 3

                if ( index > 2 )
                {
                    System.arraycopy( keys, 0, newNode.keys, half + 1, index - 2 ); //4
                }

                // Inject the new merged key
                newNode.keys[half + index - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult
                    .getModifiedPage().getLeftMostKey() ); //5

                if ( index < half )
                {
                    System.arraycopy( keys, index, newNode.keys, half + index, half - index ); //6
                    System.arraycopy( children, index + 1, newNode.children, half + index + 1, half - index ); //9
                }

                // Copy the children before the deletion point
                System.arraycopy( children, 0, newNode.children, half + 1, index - 1 ); // 7

                // Inject the new merged child
                Page<K, V> modifiedPage = mergedResult.getModifiedPage();
                newNode.children[half + index] = createHolder( modifiedPage ); //8
            }
        }
        else
        {
            // The sibling is on the right.
            if ( index < 2 )
            {
                // Copy the keys
                System.arraycopy( keys, 1, newNode.keys, 0, half - 1 );

                // Insert the first child
                Page<K, V> modifiedPage = mergedResult.getModifiedPage();
                newNode.children[0] = createHolder( modifiedPage );

                // Copy the node children
                System.arraycopy( children, 2, newNode.children, 1, half - 1 );
            }
            else
            {
                // Copy the keys and children before the deletion point
                if ( index > 2 )
                {
                    // Copy the first keys
                    System.arraycopy( keys, 0, newNode.keys, 0, index - 2 ); //1
                }

                // Copy the first children
                System.arraycopy( children, 0, newNode.children, 0, index - 1 ); //6

                // Inject the modified key
                newNode.keys[index - 2] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult
                    .getModifiedPage()
                    .getLeftMostKey() ); //2

                // Inject the modified children
                Page<K, V> modifiedPage = mergedResult.getModifiedPage();
                newNode.children[index - 1] = createHolder( modifiedPage ); // 7

                // Add the remaining node's key if needed
                if ( index < half )
                {
                    System.arraycopy( keys, index, newNode.keys, index - 1, half - index ); //5

                    // Add the remining children if below half
                    System.arraycopy( children, index + 1, newNode.children, index, half - index ); // 8
                }
            }

            // Inject the new key from sibling
            newNode.keys[half - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), sibling.findLeftMost()
                .getKey() ); //3

            // Copy the sibling keys
            System.arraycopy( sibling.keys, 0, newNode.keys, half, half );

            // Add the sibling children
            System.arraycopy( sibling.children, 0, newNode.children, half, half + 1 ); // 9
        }

        // And create the result
        DeleteResult<K, V> result = new MergedWithSiblingResult<K, V>( mergedResult.getCopiedPages(), newNode,
            removedElement );

        result.addCopiedPage( this );
        result.addCopiedPage( sibling );

        return result;
    }


    /**
     * {@inheritDoc}
     */
    /* no qualifier */ DeleteResult<K, V> delete( K key, V value, long revision, Page<K, V> parent, int parentPos )
        throws IOException
    {
        // We first try to delete the element from the child it belongs to
        // Find the key in the page
        int pos = findPos( key );
        boolean found = pos < 0;
        int index = pos;
        Page<K, V> child = null;
        DeleteResult<K, V> deleteResult = null;

        if ( found )
        {
            index = -( pos + 1 );
            child = children[-pos].getValue();
            deleteResult = ((AbstractPage<K, V>)child).delete( key, value, revision, this, -pos );
        }
        else
        {
            child = children[pos].getValue();
            deleteResult = ((AbstractPage<K, V>)child).delete( key, value, revision, this, pos );
        }

        // If the key is not present in the tree, we simply return
        if ( deleteResult instanceof NotPresentResult )
        {
            // Nothing to do...
            return deleteResult;
        }

        // If we just modified the child, return a modified page
        if ( deleteResult instanceof RemoveResult )
        {
            RemoveResult<K, V> removeResult = handleRemoveResult( ( RemoveResult<K, V> ) deleteResult, index, pos,
                found );

            return removeResult;
        }

        // If we had to borrow an element in the child, then have to update
        // the current page
        if ( deleteResult instanceof BorrowedFromSiblingResult )
        {
            RemoveResult<K, V> removeResult = handleBorrowedResult( ( BorrowedFromSiblingResult<K, V> ) deleteResult,
                pos );

            return removeResult;
        }

        // Last, not least, we have merged two child pages. We now have to remove
        // an element from the local page, and to deal with the result.
        if ( deleteResult instanceof MergedWithSiblingResult )
        {
            MergedWithSiblingResult<K, V> mergedResult = ( MergedWithSiblingResult<K, V> ) deleteResult;

            // If the parent is null, then this page is the root page.
            if ( parent == null )
            {
                RemoveResult<K, V> result = handleRootRemove( mergedResult, pos, found );

                return result;
            }

            // We have some parent. Check if the current page is not half full
            int halfSize = btree.getPageSize() / 2;

            if ( nbElems > halfSize )
            {
                // The page has more than N/2 elements.
                // We simply remove the element from the page, and if it was the leftmost,
                // we return the new pivot (it will replace any instance of the removed
                // key in its parents)
                RemoveResult<K, V> result = removeKey( mergedResult, revision, pos );

                return result;
            }
            else
            {
                // We will remove one element from a page that will have less than N/2 elements,
                // which will lead to some reorganization : either we can borrow an element from
                // a sibling, or we will have to merge two pages
                int siblingPos = selectSibling( parent, parentPos );

                PersistedNode<K, V> sibling = ( PersistedNode<K, V> ) ( ( ( PersistedNode<K, V> ) parent ).children[siblingPos]
                    .getValue() );

                if ( sibling.getNbElems() > halfSize )
                {
                    // The sibling contains enough elements
                    // We can borrow the element from the sibling
                    if ( siblingPos < parentPos )
                    {
                        DeleteResult<K, V> result = borrowFromLeft( revision, mergedResult, sibling, pos );

                        return result;
                    }
                    else
                    {
                        // Borrow from the right
                        DeleteResult<K, V> result = borrowFromRight( revision, mergedResult, sibling, pos );

                        return result;
                    }
                }
                else
                {
                    // We need to merge the sibling with the current page
                    DeleteResult<K, V> result = mergeWithSibling( revision, mergedResult, sibling,
                        ( siblingPos < parentPos ), pos );

                    return result;
                }
            }
        }

        // We should never reach this point
        return null;
    }


    /**
     * The deletion in a children has moved an element from one of its sibling. The key
     * is present in the current node.
     * @param borrowedResult The result of the deletion from the children
     * @param pos The position the key was found in the current node
     * @return The result
     * @throws IOException If we have an error while trying to access the page
     */
    private RemoveResult<K, V> handleBorrowedResult( BorrowedFromSiblingResult<K, V> borrowedResult, int pos )
        throws IOException
    {
        Page<K, V> modifiedPage = borrowedResult.getModifiedPage();
        Page<K, V> modifiedSibling = borrowedResult.getModifiedSibling();

        PersistedNode<K, V> newPage = copy( revision );

        if ( pos < 0 )
        {
            pos = -( pos + 1 );

            if ( borrowedResult.isFromRight() )
            {
                // Update the keys
                newPage.keys[pos] = new PersistedKeyHolder<K>( btree.getKeySerializer(), modifiedPage.findLeftMost()
                    .getKey() );
                newPage.keys[pos + 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), modifiedSibling
                    .findLeftMost()
                    .getKey() );

                // Update the children
                newPage.children[pos + 1] = createHolder( modifiedPage );
                newPage.children[pos + 2] = createHolder( modifiedSibling );
            }
            else
            {
                // Update the keys
                newPage.keys[pos] = new PersistedKeyHolder<K>( btree.getKeySerializer(), modifiedPage.findLeftMost()
                    .getKey() );

                // Update the children
                newPage.children[pos] = createHolder( modifiedSibling );
                newPage.children[pos + 1] = createHolder( modifiedPage );
            }
        }
        else
        {
            if ( borrowedResult.isFromRight() )
            {
                // Update the keys
                newPage.keys[pos] = new PersistedKeyHolder<K>( btree.getKeySerializer(), modifiedSibling.findLeftMost()
                    .getKey() );

                // Update the children
                newPage.children[pos] = createHolder( modifiedPage );
                newPage.children[pos + 1] = createHolder( modifiedSibling );
            }
            else
            {
                // Update the keys
                newPage.keys[pos - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), modifiedPage
                    .findLeftMost()
                    .getKey() );

                // Update the children
                newPage.children[pos - 1] = createHolder( modifiedSibling );
                newPage.children[pos] = createHolder( modifiedPage );
            }
        }

        // Modify the result and return
        RemoveResult<K, V> removeResult = new RemoveResult<K, V>( borrowedResult.getCopiedPages(), newPage,
            borrowedResult.getRemovedElement() );

        removeResult.addCopiedPage( this );

        return removeResult;
    }


    /**
     * Remove the key at a given position.
     *
     * @param mergedResult The page we will remove a key from
     * @param revision The revision of the modified page
     * @param pos The position into the page of the element to remove
     * @return The modified page with the <K,V> element added
     * @throws IOException If we have an error while trying to access the page
     */
    private RemoveResult<K, V> removeKey( MergedWithSiblingResult<K, V> mergedResult, long revision, int pos )
        throws IOException
    {
        // First copy the current page, but remove one element in the copied page
        PersistedNode<K, V> newNode = new PersistedNode<K, V>( btree, revision, nbElems - 1 );

        int index = Math.abs( pos ) - 2;

        //
        if ( index < 0 )
        {
            // Copy the keys and the children
            System.arraycopy( keys, 1, newNode.keys, 0, newNode.nbElems );
            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[0] = createHolder( modifiedPage );
            System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 );
        }
        else
        {
            // Copy the keys
            if ( index > 0 )
            {
                System.arraycopy( keys, 0, newNode.keys, 0, index );
            }

            newNode.keys[index] = new PersistedKeyHolder<K>( btree.getKeySerializer(), mergedResult.getModifiedPage()
                .findLeftMost().getKey() );

            if ( index < nbElems - 2 )
            {
                System.arraycopy( keys, index + 2, newNode.keys, index + 1, nbElems - index - 2 );
            }

            // Copy the children
            System.arraycopy( children, 0, newNode.children, 0, index + 1 );

            Page<K, V> modifiedPage = mergedResult.getModifiedPage();
            newNode.children[index + 1] = createHolder( modifiedPage );

            if ( index < nbElems - 2 )
            {
                System.arraycopy( children, index + 3, newNode.children, index + 2, nbElems - index - 2 );
            }
        }

        // Create the result
        RemoveResult<K, V> result = new RemoveResult<K, V>( mergedResult.getCopiedPages(), newNode,
            mergedResult.getRemovedElement() );

        result.addCopiedPage( this );

        return result;
    }


    /**
     * {@inheritDoc}
     */
    /* No qualifier */KeyHolder<K> getKeyHolder( int pos )
    {
        if ( pos < nbElems )
        {
            return keys[pos];
        }
        else
        {
            return null;
        }
    }


    /**
     * Set the value at a give position
     *
     * @param pos The position in the values array
     * @param value the value to inject
     */
    /* no qualifier */void setValue( int pos, PersistedPageHolder<K, V> value )
    {
        children[pos] = value;
    }


    /**
     * This method is used when we have to replace a child in a page when we have
     * found the key in the tree (the value will be changed, so we have made
     * copies of the existing pages).
     *
     * @param revision The current revision
     * @param result The modified page
     * @param pos The position of the found key
     * @return A modified page
     * @throws IOException If we have an error while trying to access the page
     */
    private InsertResult<K, V> replaceChild( long revision, ModifyResult<K, V> result, int pos ) throws IOException
    {
        // Just copy the current page and update its revision
        Page<K, V> newPage = copy( revision );

        // Last, we update the children table of the newly created page
        // to point on the modified child
        Page<K, V> modifiedPage = result.getModifiedPage();

        ( ( PersistedNode<K, V> ) newPage ).children[pos] = createHolder( modifiedPage );

        // We can return the result, where we update the modifiedPage,
        // to avoid the creation of a new object
        result.setModifiedPage( newPage );

        result.addCopiedPage( this );

        return result;
    }


    /**
     * Creates a new holder containing a reference to a Page
     *
     * @param page The page we will refer to
     * @return A holder containing a reference to the child page
     * @throws IOException If we have an error while trying to access the page
     */
    private PageHolder<K, V> createHolder( Page<K, V> page ) throws IOException
    {
        PageHolder<K, V> holder = ( ( PersistedBTree<K, V> ) btree ).getRecordManager().writePage( btree,
            page,
            revision );

        return holder;
    }


    /**
     * Adds a new key into a copy of the current page at a given position. We return the
     * modified page. The new page will have one more key than the current page.
     *
     * @param copiedPages the list of copied pages
     * @param revision The revision of the modified page
     * @param key The key to insert
     * @param leftPage The left child
     * @param rightPage The right child
     * @param pos The position into the page
     * @return The modified page with the <K,V> element added
     * @throws IOException If we have an error while trying to access the page
     */
    private InsertResult<K, V> insertChild( List<Page<K, V>> copiedPages, long revision, K key, Page<K, V> leftPage,
        Page<K, V> rightPage, int pos )
        throws IOException
    {
        // First copy the current page, but add one element in the copied page
        PersistedNode<K, V> newNode = new PersistedNode<K, V>( btree, revision, nbElems + 1 );

        // Copy the keys and the children up to the insertion position
        if ( nbElems > 0 )
        {
            System.arraycopy( keys, 0, newNode.keys, 0, pos );
            System.arraycopy( children, 0, newNode.children, 0, pos );
        }

        // Add the new key and children
        newNode.keys[pos] = new PersistedKeyHolder<K>( btree.getKeySerializer(), key );

        // If the BTree is managed, we now have to write the modified page on disk
        // and to add this page to the list of modified pages
        newNode.children[pos] = createHolder( leftPage );
        newNode.children[pos + 1] = createHolder( rightPage );

        // And copy the remaining keys and children
        if ( nbElems > 0 )
        {
            System.arraycopy( keys, pos, newNode.keys, pos + 1, keys.length - pos );
            System.arraycopy( children, pos + 1, newNode.children, pos + 2, children.length - pos - 1 );
        }

        // Create the result
        InsertResult<K, V> result = new ModifyResult<K, V>( copiedPages, newNode, null );
        result.addCopiedPage( this );

        return result;
    }


    /**
     * Splits a full page into two new pages, a left, a right and a pivot element. The new pages will
     * each contains half of the original elements. <br/>
     * The pivot will be computed, depending on the place
     * we will inject the newly added element. <br/>
     * If the newly added element is in the middle, we will use it
     * as a pivot. Otherwise, we will use either the last element in the left page if the element is added
     * on the left, or the first element in the right page if it's added on the right.
     *
     * @param copiedPages the list of copied pages
     * @param revision The new revision for all the created pages
     * @param pivot The key that will be move up after the split
     * @param leftPage The left child
     * @param rightPage The right child
     * @param pos The position of the insertion of the new element
     * @return An OverflowPage containing the pivot, and the new left and right pages
     * @throws IOException If we have an error while trying to access the page
     */
    private InsertResult<K, V> addAndSplit( List<Page<K, V>> copiedPages, long revision, K pivot, Page<K, V> leftPage,
        Page<K, V> rightPage, int pos ) throws IOException
    {
        int middle = btree.getPageSize() >> 1;

        // Create two new pages
        PersistedNode<K, V> newLeftPage = new PersistedNode<K, V>( btree, revision, middle );
        PersistedNode<K, V> newRightPage = new PersistedNode<K, V>( btree, revision, middle );

        // Determinate where to store the new value
        // If it's before the middle, insert the value on the left,
        // the key in the middle will become the new pivot
        if ( pos < middle )
        {
            // Copy the keys and the children up to the insertion position
            System.arraycopy( keys, 0, newLeftPage.keys, 0, pos );
            System.arraycopy( children, 0, newLeftPage.children, 0, pos );

            // Add the new element
            newLeftPage.keys[pos] = new PersistedKeyHolder<K>( btree.getKeySerializer(), pivot );
            newLeftPage.children[pos] = createHolder( leftPage );
            newLeftPage.children[pos + 1] = createHolder( rightPage );

            // And copy the remaining elements minus the new pivot
            System.arraycopy( keys, pos, newLeftPage.keys, pos + 1, middle - pos - 1 );
            System.arraycopy( children, pos + 1, newLeftPage.children, pos + 2, middle - pos - 1 );

            // Copy the keys and the children in the right page
            System.arraycopy( keys, middle, newRightPage.keys, 0, middle );
            System.arraycopy( children, middle, newRightPage.children, 0, middle + 1 );

            // Create the result
            pivot = keys[middle - 1].getKey();

            if ( pivot == null )
            {
                pivot = keys[middle - 1].getKey();
            }

            InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, pivot, newLeftPage,
                newRightPage );
            result.addCopiedPage( this );

            return result;
        }
        else if ( pos == middle )
        {
            // A special case : the pivot will be propagated up in the tree
            // The left and right pages will be spread on the two new pages
            // Copy the keys and the children up to the insertion position (here, middle)
            System.arraycopy( keys, 0, newLeftPage.keys, 0, middle );
            System.arraycopy( children, 0, newLeftPage.children, 0, middle );
            newLeftPage.children[middle] = createHolder( leftPage );

            // And process the right page now
            System.arraycopy( keys, middle, newRightPage.keys, 0, middle );
            System.arraycopy( children, middle + 1, newRightPage.children, 1, middle );
            newRightPage.children[0] = createHolder( rightPage );

            // Create the result
            InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, pivot, newLeftPage, newRightPage );
            result.addCopiedPage( this );

            return result;
        }
        else
        {
            // Copy the keys and the children up to the middle
            System.arraycopy( keys, 0, newLeftPage.keys, 0, middle );
            System.arraycopy( children, 0, newLeftPage.children, 0, middle + 1 );

            // Copy the keys and the children in the right page up to the pos
            System.arraycopy( keys, middle + 1, newRightPage.keys, 0, pos - middle - 1 );
            System.arraycopy( children, middle + 1, newRightPage.children, 0, pos - middle - 1 );

            // Add the new element
            newRightPage.keys[pos - middle - 1] = new PersistedKeyHolder<K>( btree.getKeySerializer(), pivot );
            newRightPage.children[pos - middle - 1] = createHolder( leftPage );
            newRightPage.children[pos - middle] = createHolder( rightPage );

            // And copy the remaining elements minus the new pivot
            System.arraycopy( keys, pos, newRightPage.keys, pos - middle, nbElems - pos );
            System.arraycopy( children, pos + 1, newRightPage.children, pos + 1 - middle, nbElems - pos );

            // Create the result
            pivot = keys[middle].getKey();

            if ( pivot == null )
            {
                pivot = keys[middle].getKey();
            }

            InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, pivot, newLeftPage,
                newRightPage );
            result.addCopiedPage( this );

            return result;
        }
    }


    /**
     * Copies the current page and all its keys, with a new revision.
     *
     * @param revision The new revision
     * @return The copied page
     */
    protected PersistedNode<K, V> copy( long revision )
    {
        PersistedNode<K, V> newPage = new PersistedNode<K, V>( btree, revision, nbElems );

        // Copy the keys
        System.arraycopy( keys, 0, newPage.keys, 0, nbElems );

        // Copy the children
        System.arraycopy( children, 0, newPage.children, 0, nbElems + 1 );

        return newPage;
    }


    /**
     * {@inheritDoc}
     */
    public K getLeftMostKey()
    {
        return children[0].getValue().getLeftMostKey();
    }


    /**
     * {@inheritDoc}
     */
    public K getRightMostKey()
    {
        int index = ( nbElems + 1 ) - 1;

        if ( children[index] != null )
        {
            return children[index].getValue().getRightMostKey();
        }

        return children[nbElems - 1].getValue().getRightMostKey();
    }


    /**
     * {@inheritDoc}
     */
    public boolean isLeaf()
    {
        return false;
    }


    /**
     * {@inheritDoc}
     */
    public boolean isNode()
    {
        return true;
    }


    /**
     * @see Object#toString()
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder();

        sb.append( "Node[" );
        sb.append( super.toString() );
        sb.append( "] -> {" );

        if ( nbElems > 0 )
        {
            // Start with the first child
            if ( children[0] == null )
            {
                sb.append( "null" );
            }
            else
            {
                sb.append( 'r' ).append( children[0].getValue().getRevision() );
            }

            for ( int i = 0; i < nbElems; i++ )
            {
                sb.append( "|<" ).append( keys[i] ).append( ">|" );

                if ( children[i + 1] == null )
                {
                    sb.append( "null" );
                }
                else
                {
                    sb.append( 'r' ).append( children[i + 1].getValue().getRevision() );
                }
            }
        }

        sb.append( "}" );

        return sb.toString();
    }
}