//-------------------------------------------------------------------------
// Desc:	B-tree block splitting.
// Tabs:	3
//
//		Copyright (c) 1991-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		This program 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 General Public License for more details.
//
//		You should have received a copy of the GNU General Public License
//		along with this program; if not, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: fssplblk.cpp 12289 2006-01-19 14:56:21 -0700 (Thu, 19 Jan 2006) dsanders $
//-------------------------------------------------------------------------

#include "flaimsys.h"

FSTATIC RCODE FSBtResetStack(
	FDB *			pDb,
	LFILE *		pLFile,
	BTSK **		ppStack,
	FLMBYTE *	pElement,
	FLMUINT		uiElmLen);

FSTATIC RCODE FSMoveToNextBlk(
	FDB *			pDb,
	LFILE *		pLFile,
	BTSK **		ppStack,
	FLMUINT		nextBlkNum,
	FLMBYTE *	pElement,
	FLMUINT		elmLen,
	FLMUINT		uiBlockSize,
	FLMUINT *	blkNumRV,
	FLMUINT *	curElmRV);

/****************************************************************************
Desc: Split a block using different algorithms depending on context
****************************************************************************/
RCODE FSBlkSplit(
	FDB *			pDb,
	LFILE *		pLFile,
	BTSK **		ppStack,
	FLMBYTE *	pElement,
	FLMUINT		elmLen)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiBlockSize = pDb->pFile->FileHdr.uiBlockSize;
	BTSK *		pStack = *ppStack;
	FLMUINT		oldCurElm = pStack->uiCurElm;
	FLMUINT		uiElmOvhd = pStack->uiElmOvhd;
	FLMUINT		tempWord;
	FLMUINT		elmKeyLen;
	FLMUINT		prevKeyCnt;
	FLMUINT		curElm;
	FLMBYTE *	curElmPtr;
	FLMBYTE *	pBlk;
	FLMUINT		blkNum = pStack->uiBlkAddr;
	FLMUINT		uiBlkEnd;
	BTSK			newBlkStk;
	BTSK			nextBlkStk;
	FLMBYTE *	newBlkPtr;
	FLMBYTE *	nextBlkPtr;
	FLMUINT		newBlkNum;
	FLMUINT		nextBlkNum;
	FLMUINT		blkNumRestore = 0;
	FLMUINT		curElmRestore = 0;
	FLMBOOL		bNewRootFlag;
	FLMBOOL		bDoubleSplit;
	DB_STATS *	pDbStats;

	bNewRootFlag = FALSE;
	bDoubleSplit = FALSE;
	
	FSInitStackCache( &newBlkStk, 1);
	FSInitStackCache( &nextBlkStk, 1);

	if ((pDbStats = pDb->pDbStats) != NULL)
	{
		LFILE_STATS*	pLFileStats;

		if ((pLFileStats = fdbGetLFileStatPtr( pDb, pLFile)) != NULL)
		{
			pLFileStats->bHaveStats = pDbStats->bHaveStats = TRUE;
			pLFileStats->ui64BlockSplits++;
		}
	}

	// If there is room to move data to the next block then do it
	// and update the parent block with the new last element. Otherwise...
	// Divide data from current block and next block into the new block
	// (2/3 split). Delete parent element and update parent /w 2 elements.
	
	if (pStack->uiFlags & NO_STACK)
	{
		if (RC_BAD( rc = FSBtResetStack( pDb, pLFile, &pStack, pElement, elmLen)))
		{
			goto Exit;
		}
	}

	// Log the block before modifying it

	if (RC_BAD( rc = FSLogPhysBlk( pDb, pStack)))
	{
		goto Exit;
	}

	pBlk = pStack->pBlk;
	bNewRootFlag = BH_IS_ROOT_BLK( pBlk);

	if ((nextBlkNum = FB2UD( &pBlk[BH_NEXT_BLK])) != BT_END)
	{

		// Try to move the elements from the current block to the next block
		// while inserting the element. If this succeeds than we are better
		// off than spliting blocks. If succeeds then all block operations
		// have been taken care of.

		if (RC_OK( rc = FSMoveToNextBlk( pDb, pLFile, &pStack, nextBlkNum,
					 pElement, elmLen, uiBlockSize, &blkNumRestore, &curElmRestore)))
		{
			goto FSBlkSplit_position;
		}

		if (rc != FERR_BLOCK_FULL)
		{
			goto Exit;
		}
	}

	// Initialize variables, create a new block and setup header. This is
	// common stuff that will be done if you are splitting a RIGHT-MOST
	// block or a middle block. Remember we could be working with leaf or
	// non-leaf blks.

	if (RC_BAD( rc = ScaCreateBlock( pDb, pLFile, &newBlkStk.pSCache)))
	{
		goto Exit;
	}

	newBlkStk.pBlk = newBlkStk.pSCache->pucBlk;

	newBlkPtr = GET_CABLKPTR( &newBlkStk);
	newBlkNum = GET_BH_ADDR( newBlkPtr);
	
	pBlk = pStack->pBlk;
	uiBlkEnd = pStack->uiBlkEnd;
	
	UD2FBA( nextBlkNum, &newBlkPtr[BH_NEXT_BLK]);
	UD2FBA( blkNum, &newBlkPtr[BH_PREV_BLK]);
	UD2FBA( newBlkNum, &pBlk[BH_NEXT_BLK]);

	// Write over the root bit if present as set type.

	newBlkPtr[BH_TYPE] = pBlk[BH_TYPE] = (FLMBYTE) (BH_GET_TYPE( pBlk));
	newBlkPtr[BH_LEVEL] = pBlk[BH_LEVEL];
	tempWord = FB2UW( &pBlk[BH_LOG_FILE_NUM]);
	UW2FBA( tempWord, &newBlkPtr[BH_LOG_FILE_NUM]);
	UW2FBA( BH_OVHD, &newBlkPtr[BH_BLK_END]);

	// In both cases (middle or end split) if you split at the wrong place
	// you will still not be able to fit the element[] in the block. The
	// while loop will insure at least a split into two blocks where there
	// may not be room to move any elements from the next block.

	pStack->uiCurElm = (nextBlkNum == BT_END)
						? (FFILE_MAX_FILL * pDb->pFile->FileHdr.uiBlockSize / 100) 
						: ((uiBlockSize / 20) * 13);			// Leave at least 65% full

	if (RC_BAD( rc = FSBtScanTo( pStack, NULL, 0, 0)))
	{
		goto Exit;
	}

	if (pStack->uiCmpStatus == BT_GT_KEY)
	{

		// Save the key in the pKeyBuf

		curElmPtr = &pBlk[pStack->uiCurElm];
		if (pStack->uiBlkType == BHT_NON_LEAF_DATA)
		{
			flmCopyDrnKey( pStack->pKeyBuf, curElmPtr);
		}
		else
		{
			elmKeyLen = (FLMUINT) (BBE_GET_KL( curElmPtr));
			
			if (elmKeyLen)
			{
				// Copy key into pKeyBuf
				
				prevKeyCnt = (FLMUINT) (BBE_GET_PKC( curElmPtr));
				f_memcpy( &pStack->pKeyBuf[prevKeyCnt], &curElmPtr[uiElmOvhd],
							elmKeyLen);
			}
		}
	}

	curElm = pStack->uiCurElm;

	// Check to see if the new element will fit whereever it goes. Don't
	// try to optimally place it because the next block may move stuff
	// over.

	while ((curElm > oldCurElm) && 
			 (curElm + elmLen + uiElmOvhd + uiElmOvhd > uiBlockSize))
	{
		FSBtPrevElm( pDb, pLFile, pStack);
		curElm = pStack->uiCurElm;
	}

	newBlkStk.uiBlkAddr = newBlkNum;
	newBlkStk.pKeyBuf = pStack->pKeyBuf;
	FSBlkToStack( &newBlkStk);
	newBlkStk.uiKeyBufSize = pStack->uiKeyBufSize;

	curElmPtr = &pBlk[curElm];

	if (curElm == oldCurElm)
	{

		// Decide whether to place the new element in the current or new
		// block.  Give preference to placing with the current block.
		
		if ((curElm + elmLen + uiElmOvhd < uiBlockSize) &&
			 ((uiBlkEnd - curElm) > uiElmOvhd))
		{
			 // Place with the current block
			
			goto Addto_Current_Blk;
		}

		if (uiBlkEnd > curElm)
		{
			// Move if not at end of block
			
			FSBlkMoveElms( &newBlkStk, curElmPtr, (FLMUINT) (uiBlkEnd - curElm),
							  pStack->pKeyBuf);
		}

		// Set the block end in the current blocks header & restore values

		pStack->uiBlkEnd = curElm;
		UW2FBA( curElm, &pBlk[BH_BLK_END]);
		blkNumRestore = newBlkNum;
		
		// Move if not at end of block
		
		curElmRestore = BH_OVHD;
		newBlkStk.uiCurElm = curElmRestore;

		if (newBlkStk.uiBlkEnd + elmLen + uiElmOvhd > uiBlockSize)
		{
			// Double split - move element later
			
			bDoubleSplit = 1;
		}
		else
		{
			FSBlkMoveElms( &newBlkStk, pElement, elmLen, NULL);
		}
	}
	else if (curElm > oldCurElm)
	{
Addto_Current_Blk:

		// First move stuff over to the new blk

		FSBlkMoveElms( &newBlkStk, curElmPtr, (FLMUINT) (uiBlkEnd - curElm),
						  pStack->pKeyBuf);

		// Set the block end in the current blocks header

		pStack->uiBlkEnd = curElm;
		UW2FBA( curElm, &pBlk[BH_BLK_END]);
		blkNumRestore = blkNum;
		curElmRestore = oldCurElm;
		
		// Setup to insert element
		
		pStack->uiCurElm = curElmRestore;
		FSBlkMoveElms( pStack, pElement, elmLen, NULL);
	}
	else if (curElm < oldCurElm)
	{
		blkNumRestore = newBlkNum;
		FSBlkMoveElms( &newBlkStk, curElmPtr, (FLMUINT) (oldCurElm - curElm),
						  pStack->pKeyBuf);
		newBlkStk.uiCurElm = newBlkStk.uiBlkEnd;
		curElmRestore = newBlkStk.uiCurElm;

		// May not fit with 1K blocks - check for double split

		if (curElmRestore + elmLen + 
			 (uiBlkEnd - oldCurElm) + uiElmOvhd > uiBlockSize)
		{
			bDoubleSplit = 1;
		}
		else
		{
			FSBlkMoveElms( &newBlkStk, pElement, elmLen, NULL);
		}

		newBlkStk.uiCurElm = newBlkStk.uiBlkEnd;
		pStack->uiCurElm = oldCurElm;

		if (RC_BAD( rc = FSBtScanTo( pStack, NULL, 0, 0)))
		{
			goto Exit;
		}

		if (oldCurElm < uiBlkEnd)
		{
			FSBlkMoveElms( &newBlkStk, &pBlk[oldCurElm],
							  (FLMUINT) (uiBlkEnd - oldCurElm), pStack->pKeyBuf);
		}

		// Set the block end in the current blocks header

		pStack->uiBlkEnd = curElm;
		UW2FBA( curElm, &pBlk[BH_BLK_END]);
	}

	// All done with the current block - unpin and set to dirty.
	//
	// All done moving data from current block to new block.  If created new
	// right most block check if new to create new root block and init new
	// root (easy) else try to move stuff from the next block into the new
	// block.
	
	if (nextBlkNum == BT_END)
	{
		FLMUINT	uiLfNum = pLFile->uiLfNum;

		// We are done with block

		FSReleaseBlock( &newBlkStk, FALSE);

		// At the root?

		if (bNewRootFlag)
		{
			FLMBYTE	byType;

			// Create a new root block

			if (pStack->uiLevel + 1 >= BH_MAX_LEVELS)
			{
				rc = RC_SET( FERR_BTREE_FULL);
				goto Exit;
			}

			// Set the block type

			if (pLFile->uiLfType == LF_INDEX)
			{
				if (pLFile->pIxd->uiFlags & IXD_POSITIONING)
				{
					byType = BHT_NON_LEAF_COUNTS + BHT_ROOT_BLK;
				}
				else
				{
					byType = BHT_NON_LEAF + BHT_ROOT_BLK;
				}
			}
			else
			{
				byType = BHT_NON_LEAF_DATA + BHT_ROOT_BLK;
			}

			// Move all pStack elements down by doing a shift

			shiftN( (FLMBYTE*) pStack,
					 (FLMUINT) (sizeof(BTSK) * (pStack->uiLevel + 1)),
					 (FLMINT) sizeof(BTSK));

			// Create a new block

			if (RC_BAD( rc = ScaCreateBlock( pDb, pLFile, &pStack->pSCache)))
			{
				goto Exit;
			}

			pBlk = pStack->pBlk = pStack->pSCache->pucBlk;

			// Set prev/next block addresses to BT_END

			UD2FBA( BT_END, &pBlk[BH_PREV_BLK]);
			UD2FBA( BT_END, &pBlk[BH_NEXT_BLK]);
			UW2FBA( BH_OVHD, &pBlk[BH_BLK_END]);

			// Set logical file number in the block header and block type

			UW2FBA( uiLfNum, &pBlk[BH_LOG_FILE_NUM]);

			pBlk[BH_TYPE] = byType;
			pBlk[BH_LEVEL] = (FLMBYTE) (++(pStack->uiLevel));
			pStack->uiBlkAddr = GET_BH_ADDR( pBlk);
			FSBlkToStack( pStack);
			pLFile->uiRootBlk = pStack->uiBlkAddr;

			// Always update the pLFile because level is incremented

			rc = flmLFileWrite( pDb, pLFile);
			pStack++;
			*ppStack = pStack;

			if (RC_BAD( rc))
			{
				goto Exit;
			}
		}
	}
	else
	{	
		FLMINT	iBytesToMove;

		// Move stuff from the right block
		// Remember that newBlk is still pinned		
		
		nextBlkStk.pKeyBuf = pStack->pKeyBuf;
		if (RC_BAD( rc = FSGetBlock( pDb, pLFile, nextBlkNum, &nextBlkStk)))
		{
			goto Exit;
		}

		if (RC_BAD( rc = FSLogPhysBlk( pDb, &nextBlkStk)))
		{
			goto Exit;
		}

		nextBlkStk.uiKeyBufSize = pStack->uiKeyBufSize;
		nextBlkPtr = nextBlkStk.pBlk;
		UD2FBA( newBlkNum, &nextBlkPtr[BH_PREV_BLK]);

		// Try to move so that the two blocks have about the same free space

		iBytesToMove = (FLMINT) ((nextBlkStk.uiBlkEnd - newBlkStk.uiBlkEnd) / 2);

		if (iBytesToMove > 100)
		{

			// Log the block before modifying it.

			nextBlkStk.uiCurElm = iBytesToMove + BH_OVHD;
			if (RC_BAD( rc = FSBtScanTo( &nextBlkStk, NULL, 0, 0)))
			{
				goto Exit;
			}

			while ((nextBlkStk.uiCurElm > BH_OVHD) && 
					 (nextBlkStk.uiCurElm + newBlkStk.uiBlkEnd + 
						uiElmOvhd - BH_OVHD >= uiBlockSize))
			{
				(void) FSBtPrevElm( pDb, pLFile, &nextBlkStk);
			}

			// Never try to move elements from the next block to the new 
			// block if we are positioned on the first element.  Never try
			// to move if on LEM or at the end
			
			if ((nextBlkStk.uiCurElm > BH_OVHD) &&
				 (nextBlkStk.uiCurElm + uiElmOvhd < nextBlkStk.uiBlkEnd))
			{
				FLMUINT	tempEnd;

				// Save the key in the pKeyBuf
				
				curElmPtr = &nextBlkPtr[nextBlkStk.uiCurElm];
				if (pStack->uiBlkType != BHT_NON_LEAF_DATA)
				{
					elmKeyLen = (FLMUINT) (BBE_GET_KL( curElmPtr));
					
					if (elmKeyLen)
					{
						// Copy key into pKeyBuf
						
						prevKeyCnt = (FLMUINT) (BBE_GET_PKC( curElmPtr));
						f_memcpy( &(nextBlkStk.pKeyBuf)[prevKeyCnt],
									&curElmPtr[uiElmOvhd], elmKeyLen);
					}
				}

				tempWord = nextBlkStk.uiCurElm;
				newBlkStk.uiCurElm = newBlkStk.uiBlkEnd;

				FSBlkMoveElms( &newBlkStk, &nextBlkPtr[BH_OVHD],
								  (FLMUINT) (tempWord - BH_OVHD), NULL);

				// Move the elements in the next block DOWN expanding PKC

				tempEnd = nextBlkStk.uiBlkEnd;

				// Make sure uiBlkEnd is reality or move will not work!

				nextBlkStk.uiBlkEnd = nextBlkStk.uiCurElm = BH_OVHD;
				UW2FBA( BH_OVHD, &nextBlkPtr[BH_BLK_END]);

				FSBlkMoveElms( &nextBlkStk, &nextBlkPtr[tempWord],
								  (FLMUINT) (tempEnd - tempWord), nextBlkStk.pKeyBuf);

				if ((pStack - 1)->uiBlkType == BHT_NON_LEAF_COUNTS)
				{
					if (RC_BAD( rc = FSUpdateAdjacentBlkCounts( pDb, pLFile, pStack,
								  &nextBlkStk)))
					{
						goto Exit;
					}
				}
			}
		}

		FSReleaseBlock( &newBlkStk, FALSE);
	}

	// Insert the new last element in the current block replacing what is
	// there. Insert the last element from the new block. All blocks should
	// be dirty and unpined! This means we have to read them in again.

	if (RC_BAD( rc = FSGetBlock( pDb, pLFile, blkNum, pStack)))
	{
		goto Exit;
	}

	if (pStack->uiCurElm >= pStack->uiBlkEnd)
	{
		pStack->uiCurElm = curElm;
	}

	// Passing 0 means insert only.

	if (RC_BAD( rc = FSNewLastBlkElm( pDb, pLFile, &pStack,
				  (nextBlkNum == BT_END) ? 0 : FSNLBE_LESS)))
	{
		*ppStack = pStack;
		goto Exit;
	}

	if (RC_BAD( rc = FSAdjustStack( pDb, pLFile, pStack, TRUE)))
	{
		if (rc != FERR_BT_END_OF_DATA)
		{
			goto Exit;
		}
	}

	// Parent is positioned to the nextBlk element.

	if (RC_BAD( rc = FSGetBlock( pDb, pLFile, newBlkNum, pStack)))
	{
		goto Exit;
	}

	if ((nextBlkNum == BT_END) && !bNewRootFlag)
	{
		FLMUINT		uiNewRefCount;
		FLMUINT		uiOldRefCount;
		FLMBYTE*		pTmpElement;

		// Only update the counts if the inserting a key and not replacing.

		if ((pStack - 1)->uiBlkType == BHT_NON_LEAF_COUNTS)
		{
			if (RC_BAD( rc = FSBlockCounts( pStack, BH_OVHD, pStack->uiBlkEnd,
						  NULL, NULL, &uiNewRefCount)))
			{
				goto Exit;
			}
		}

		pStack--;

		// Modify the parent last element marker (LEM) to point to the new
		// last block. THEN delete the previous element that also pointer to
		// the new last block.
		//
		// Read the parent block
		
		if (RC_BAD( rc = FSGetBlock( pDb, pLFile, pStack->uiBlkAddr, pStack)))
		{
			return (rc);
		}

		if (RC_BAD( rc = FSLogPhysBlk( pDb, pStack)))
		{
			return (rc);
		}

		// Change where element points to in the last element marker (LEM)

		pTmpElement = pStack->pBlk + pStack->uiCurElm;
		FSSetChildBlkAddr( pTmpElement, newBlkNum, pStack->uiElmOvhd);

		if (pStack->uiBlkType == BHT_NON_LEAF_COUNTS)
		{
			uiOldRefCount = FB2UD( &pTmpElement[BNE_CHILD_COUNT]);
			if (RC_BAD( rc = FSChangeBlkCounts( pDb, pStack,
						  (FLMINT) (uiNewRefCount - uiOldRefCount))))
			{
				goto Exit;
			}

			UD2FBA( uiNewRefCount, &pTmpElement[BNE_CHILD_COUNT]);
		}

		pStack++;
	}
	else
	{

		// Inserts the new element into the tree.

		rc = FSNewLastBlkElm( pDb, pLFile, &pStack, 0);
	}

FSBlkSplit_position:

	*ppStack = pStack;

	// Read in block - should be positioned to the current element inserted

	if (RC_OK( rc))
	{

		// Parent element is on the newBlock - see if you need to back up

		if (blkNumRestore == blkNum)
		{
			if (RC_BAD( rc = FSAdjustStack( pDb, pLFile, pStack, FALSE)))
			{
				if (rc != FERR_BT_END_OF_DATA)
				{
					goto Exit;
				}
			}
		}

		if (RC_OK( rc = FSGetBlock( pDb, pLFile, blkNumRestore, pStack)))
		{

			// Set up the key buffer (pKeyBuf) to be correct for future
			// inserts

			pStack->uiCurElm = curElmRestore;
			if (RC_BAD( rc = FSBtScanTo( pStack, NULL, 0, 0)))
			{
				goto Exit;
			}

			if (bDoubleSplit)
			{

				// Now insert the element if flag set This will cause another
				// split. RECURSIVE CALL

				rc = FSBlkSplit( pDb, pLFile, ppStack, pElement, elmLen);
			}
		}
	}

Exit:

	FSReleaseBlock( &newBlkStk, FALSE);
	FSReleaseBlock( &nextBlkStk, FALSE);
	return (rc);
}

/****************************************************************************
Desc:	Reset the pStack to set up for a block split
****************************************************************************/
FSTATIC RCODE FSBtResetStack(
	FDB *			pDb,			// Pointer to database DBC structure.
	LFILE *		pLFile,		// Logical file definition
	BTSK **		ppStack,		// Stack of variables for each level
	FLMBYTE *	pElement,	// The input element to insert
	FLMUINT		elmLen)		// Length of the element
{
	RCODE			rc;
	BTSK *		pStack = *ppStack;						// Stack holding all state info
	FLMUINT		oldPKC = pStack->uiPKC;					// Save old PKC value
	FLMUINT		oldPvElmPKC = pStack->uiPrevElmPKC; // Save old prev elm PKC value
	FLMUINT		oldBlock = pStack->uiBlkAddr;			// Save old block number
	FLMUINT		oldCurElm = pStack->uiCurElm;			// Save old current element value
	FLMUINT		uiElmOvhd = pStack->uiElmOvhd;

	if (RC_BAD( rc = FSBtSearch( pDb, pLFile, &pStack, &pElement[uiElmOvhd],
				  (elmLen - uiElmOvhd), 0)))
	{
		return (rc);
	}

	// In case of continuation elements, parse to matching curElm

	while ((oldBlock != pStack->uiBlkAddr) && (oldCurElm != pStack->uiCurElm))
	{
		if ((rc = FSBtNextElm( pDb, pLFile, pStack)) == FERR_BT_END_OF_DATA)
		{
			return (RC_SET( FERR_BTREE_ERROR));
		}
		else if (RC_BAD( rc))
		{
			return (rc);
		}
	}

	// Reset original PKC values

	pStack->uiPKC = oldPKC;
	pStack->uiPrevElmPKC = oldPvElmPKC;
	*ppStack = pStack;
	pStack->uiFlags = FULL_STACK;

	return (FERR_OK);
}

/****************************************************************************
Desc: Try to move elements between two blocks while inserting an element
****************************************************************************/
FSTATIC RCODE FSMoveToNextBlk(
	FDB *			pDb,
	LFILE *		pLFile,
	BTSK **		ppStack,
	FLMUINT		nextBlkNum,
	FLMBYTE *	pElement,
	FLMUINT		elmLen,
	FLMUINT		uiBlockSize,
	FLMUINT *	blkNumRV,
	FLMUINT *	curElmRV)
{
	RCODE			rc = FERR_OK;
	BTSK *		pStack = *ppStack;
	FLMUINT		uiBlkEnd = pStack->uiBlkEnd;
	FLMUINT		oldCurElm = pStack->uiCurElm;
	FLMBYTE *	curElmPtr;
	BTSK			nextBlkStk;
	FLMUINT		nextBlkFreeBytes;
	FLMUINT		elmKeyLen;
	FLMUINT		prevKeyCnt;
	FLMUINT		curElm;
	FLMUINT		uiElmOvhd = pStack->uiElmOvhd;
	FLMBOOL		bInsertInCurrentBlock;
	FLMBYTE *	pBlk = pStack->pBlk;

	FSInitStackCache( &nextBlkStk, 1);
	nextBlkStk.pKeyBuf = pStack->pKeyBuf;

	if (RC_BAD( rc = FSGetBlock( pDb, pLFile, nextBlkNum, &nextBlkStk)))
	{
		goto Exit;
	}

	nextBlkFreeBytes = (FLMUINT) (uiBlockSize - nextBlkStk.uiBlkEnd - uiElmOvhd);
	pStack->uiCurElm = (uiBlkEnd - (nextBlkFreeBytes / 2));

	if (RC_BAD( rc = FSBtScanTo( pStack, NULL, 0, 0)))
	{
		goto Exit;
	}

	rc = (pStack->uiCmpStatus == BT_END_OF_DATA) 
					? FERR_BT_END_OF_DATA 
					: FERR_OK;

	for (; RC_OK( rc); rc = FSBlkNextElm( pStack))
	{

		// The current element is positioned to mininum split point. Keep
		// testing till at end of block or split will fit within both blocks
		// while still adding the element to insert.
		//
		// Save the key in the pKeyBuf so can move entire element
		
		curElmPtr = CURRENT_ELM( pStack);

		if (pStack->uiBlkType == BHT_NON_LEAF_DATA)
		{
			prevKeyCnt = elmKeyLen = 0;
		}
		else
		{
			prevKeyCnt = (FLMUINT) (BBE_GET_PKC( curElmPtr));
			elmKeyLen = (FLMUINT) (BBE_GET_KL( curElmPtr));
		}

		if (elmKeyLen)
		{
			// Copy key into pKeyBuf
			
			f_memcpy( &pStack->pKeyBuf[prevKeyCnt], &curElmPtr[uiElmOvhd],
						elmKeyLen);
		}

		// Will element be inserted into the current block?

		if (oldCurElm <= (curElm = pStack->uiCurElm))
		{

			// Insert the element in the current block - could be at the end

			if (curElm + elmLen + uiElmOvhd > uiBlockSize)
			{
				rc = FERR_BT_END_OF_DATA;
				break;
			}

			// Left fits - try right block - could fail because of pkc value/

			if (prevKeyCnt + (uiBlkEnd - curElm) >= nextBlkFreeBytes)
			{
				// Doesn't fit - try again
				
				continue;
			}

			bInsertInCurrentBlock = TRUE;
		}
		else
		{

			// Moving elements from current block so no need to check it
			// Cannot remember why I put BBE_PKC_MAX in the line below

			if (elmLen + prevKeyCnt + BBE_PKC_MAX + 
				 (uiBlkEnd - curElm) >= nextBlkFreeBytes)
			{
				continue;
			}

			bInsertInCurrentBlock = FALSE;
		}

		if (RC_BAD( rc = FSLogPhysBlk( pDb, &nextBlkStk)))
		{
			goto Exit;
		}

		if (bInsertInCurrentBlock)
		{
			FSBlkMoveElms( &nextBlkStk, curElmPtr, (FLMUINT) (uiBlkEnd - curElm),
							  pStack->pKeyBuf);
							  
			pStack->uiBlkEnd = curElm;
			UW2FBA( curElm, &pBlk[BH_BLK_END]);
			*curElmRV = oldCurElm;
			pStack->uiCurElm = (*curElmRV);
			
			FSBlkMoveElms( pStack, pElement, elmLen, NULL);
			*blkNumRV = pStack->uiBlkAddr;
		}
		else
		{
			FSBlkMoveElms( &nextBlkStk, curElmPtr, (FLMUINT) (uiBlkEnd - curElm),
							  pStack->pKeyBuf);
							  
			pStack->uiBlkEnd = curElm;
			UW2FBA( curElm, &pBlk[BH_BLK_END]);
			*curElmRV = (FLMUINT) (BH_OVHD + prevKeyCnt + (oldCurElm - curElm));
			nextBlkStk.uiCurElm = (*curElmRV);
			
			FSBlkMoveElms( &nextBlkStk, pElement, elmLen, NULL);
			*blkNumRV = nextBlkNum;
		}

		if (pLFile->pIxd && (pLFile->pIxd->uiFlags & IXD_POSITIONING))
		{
			if (RC_BAD( rc = FSUpdateAdjacentBlkCounts( pDb, pLFile, pStack,
						  &nextBlkStk)))
			{
				goto Exit;
			}
		}
		break;
	}

	// If cannot move then return

	if (RC_BAD( rc))
	{
		if (rc == FERR_BT_END_OF_DATA)
		{

			// Restore old current element

			pStack->uiCurElm = oldCurElm;
			rc = RC_SET( FERR_BLOCK_FULL);
		}

		goto Exit;
	}

	// Fix up the elements in the parent block pointing to the new last
	// element in the current block. REMEMBER - pStack may change on you!

	rc = FSNewLastBlkElm( pDb, pLFile, ppStack, FSNLBE_LESS | FSNLBE_POSITION);

Exit:

	FSReleaseBlock( &nextBlkStk, FALSE);
	return (rc);
}
