/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    blrange.c

Abstract:

    This module implements ranges and rangelists. These can be used
    to keep track of cached ranges of a disk for instance.

Author:

    Cenk Ergan (cenke) 11-Jan-2000

Revision History:

--*/

#include "blrange.h"

//
// Range function definitions.
//

VOID
BlRangeListInitialize (
    PBLCRANGE_LIST pRangeList,
    OPTIONAL PBLCRANGE_MERGE_ROUTINE pMergeRoutine,
    OPTIONAL PBLCRANGE_FREE_ROUTINE pFreeRoutine
    )
/*++

Routine Description:

    This routine initializes the range list whose address is passed in
    so it can be used by the other range functions.

Arguments:

    pRangeList - Address of the range list to initialize.

    pMergeRoutine - Optional routine to merge Data fields of merged
        range entries. See PBLCRANGE_MERGE_ROUTINE description.

    pFreeRoutine - Optional routine to free the memory for an entry
        that was merged into another. See PBLCRANGE_FREE_ROUTINE desc.

Return Value:

    None. [Always successful]

--*/
{
    InitializeListHead(&pRangeList->Head);
    pRangeList->NumEntries = 0;
    pRangeList->MergeRoutine = pMergeRoutine;
    pRangeList->FreeRoutine = pFreeRoutine;
}

BOOLEAN
BlRangeListAddRange (
    PBLCRANGE_LIST  pRangeList,
    PBLCRANGE_ENTRY pRangeEntry
    )
/*++

Routine Description:

    This routine adds pRangeEntry to pRangeList only if it does not
    have any overlap with other ranges in the list and its size > 0;
    If merging becomes possible it is attempted. It does not have to
    be successful.

Arguments:

    pRangeList - Address of the range list to add range to.

    pRangeEntry - Range to add to pRangeList.

Return Value:

    TRUE if addition is successful [even if merging was possible but failed]
    FALSE if not [e.g. overlap/collusion]

--*/
{
    PBLCRANGE_ENTRY pCurEntry = NULL;
    PBLCRANGE_ENTRY pLastEntry = NULL;
    LIST_ENTRY *pHead, *pNext;
    
    //
    // Handle special empty range case.
    //

    if (pRangeEntry->Range.Start == pRangeEntry->Range.End)
        return TRUE;

    //
    // Walk through the ranges in the sorted list checking for
    // overlaps and looking for the right place for us.
    //
    
    pHead = &pRangeList->Head;
    pNext = pHead->Flink;
    while (pNext != pHead)
    {
        pCurEntry = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);
        
        //
        // Check if we are completely before this entry. 
        //

        if (pRangeEntry->Range.End <= pCurEntry->Range.Start)
        {
            //
            // Insert the new entry at its place.
            //

            InsertTailList(pNext, &pRangeEntry->Link);
            pRangeList->NumEntries++;

            //
            // Check if merging is possible.
            //
            
            if (pLastEntry && (pRangeEntry->Range.Start == pLastEntry->Range.End))
            {
                BlRangeListMergeRangeEntries(
                    pRangeList,
                    pRangeEntry,
                    pLastEntry
                    );
            }

            if (pRangeEntry->Range.End == pCurEntry->Range.Start)
            {
                BlRangeListMergeRangeEntries(
                    pRangeList,
                    pRangeEntry,
                    pCurEntry
                    );
            }

            return TRUE;
        }
        
        //
        // Check if we are not completely after this entry.
        //

        if (pRangeEntry->Range.Start < pCurEntry->Range.End)
        {
            //
            // We have an overlapping range.
            //

            return FALSE;
        }
        
        //
        // We come after this entry.
        //

        pLastEntry = pCurEntry;
        pNext = pNext->Flink;
    }  

    //
    // We come after the last entry [if there is one], i.e. before the head.
    // Insert the new entry and check if merging is possible.
    //

    InsertTailList(pHead, &pRangeEntry->Link);
    pRangeList->NumEntries++;

    if (pLastEntry && (pRangeEntry->Range.Start == pLastEntry->Range.End))
    {
        BlRangeListMergeRangeEntries(
            pRangeList,
            pRangeEntry,
            pLastEntry
            );
    }

    return TRUE;
}

BOOLEAN
BlRangeListFindOverlaps (
    PBLCRANGE_LIST pRangeList,
    PBLCRANGE pRange,
    PBLCRANGE_ENTRY *pOverlapsBuffer,
    ULONG OverlapsBufferSize,
    OUT ULONG *pNumOverlaps
    )
/*++

Routine Description:

    This routine will find ranges in pRangeList that overlap with
    pRange and put pointers to them into pOverlapsBuffer one after the
    other. If all overlapping ranges cannot be copied because
    pOverlapsBuffer is NULL or OverlapsBufferSize is 0 or not enough,
    the function will return FALSE and but still put the number of
    overlapping ranges in pNumOverlaps. You can calculate the required
    buffer size from this.

Arguments:

    pRangeList - Address of the range list to search for overlaps.

    pRange - We will look for range entries that overlap with pRange.

    pOverlapsBuffer - Pointer to buffer we can fill with pointers to
        overlapping ranges.

    OverlapsBufferSize - Size up to which we can fill pOverlapsBuffer.

    pNumOverlaps - Number of overlapping ranges will always be put
        here.

Return Value:

    TRUE if successful, FALSE if not. 

--*/
{
    PBLCRANGE_ENTRY pCurEntry;
    LIST_ENTRY *pHead, *pNext;
    ULONG NumOverlaps = 0;
    ULONG RequiredOverlapsBufferSize = 0;

    //
    // Handle special empty range case.
    //

    if (pRange->Start == pRange->End)
    {
        *pNumOverlaps = NumOverlaps;
        return (pOverlapsBuffer != NULL);
    }

    //
    // Walk through the ranges in the sorted list and copy over ones
    // that overlap into callers buffer if there is enough space.
    //

    pHead = &pRangeList->Head;
    pNext = pHead->Flink;
    while (pNext != pHead)
    {
        pCurEntry = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);
        
        if ((pRange->End > pCurEntry->Range.Start) &&
            (pRange->Start < pCurEntry->Range.End))
        {
            //
            // This entry overlaps.
            //

            RequiredOverlapsBufferSize += sizeof(PBLCRANGE_ENTRY);
            if (pOverlapsBuffer && 
                (OverlapsBufferSize >= RequiredOverlapsBufferSize))
            {
                pOverlapsBuffer[NumOverlaps] = pCurEntry;
            }
            NumOverlaps++;
        }

        pNext = pNext->Flink;
    }

    *pNumOverlaps = NumOverlaps;
    
    return (pOverlapsBuffer && 
            (OverlapsBufferSize >= RequiredOverlapsBufferSize));
}


BOOLEAN
BlRangeListFindDistinctRanges (
    PBLCRANGE_LIST pRangeList,
    PBLCRANGE pRange,
    PBLCRANGE pDistinctRanges,
    ULONG BufferSize,
    OUT ULONG *pNumRanges
    )
/*++

Routine Description:

    This routine will look at ranges in pRangeList that overlap with
    pRange and extract the overlaps from pRange, thus keeping track of
    those ranges that are distinct. If all distinct ranges cannot be
    put into pDistinctRanges buffer because pDistinctRanges is NULL or
    BufferSize is 0 or not enough, the function will return FALSE and
    but still put the number of resulting distinct ranges in
    pNumRanges. You can calculate the required buffer size from
    this.

Arguments:

    pRangeList - Address of the range list.

    pRange - We will extract distinct ranges in pRange that do not 
        overlap with other ranges in pRangeList.

    pDistinctRanges - Pointer to buffer we can fill with distinct
        ranges.

    BufferSize - Size up to which we can fill pDistinctRanges buffer.

    pNumRanges - Number of resulting distinct ranges will always be
        put here.

Return Value:

    TRUE if successful, FALSE if not. 

--*/
{
    PBLCRANGE_ENTRY pCurEntry;
    BLCRANGE RemainingRange = *pRange;
    ULONGLONG OverlapStart;
    ULONGLONG OverlapEnd;
    LIST_ENTRY *pHead, *pNext;
    ULONG NumRanges = 0;
    ULONG RequiredBufferSize = 0;

    if (pRange->Start == pRange->End)
    {
        *pNumRanges = NumRanges;
        return (pDistinctRanges != NULL);
    }
    
    //
    // Looking at each range in the sorted list, we carve out overlap
    // and distinct zones from the start of our range.
    //

    pHead = &pRangeList->Head;
    pNext = pHead->Flink;
    while (pNext != pHead)
    {
        pCurEntry = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);

        //
        // Is there still anything remaining from the range that we
        // have not carved out as overlap or distinct?
        //

        if (RemainingRange.Start >= RemainingRange.End)
            break;

        //
        // There are three possibilities:
        //

        //
        // 1. Is the range completely before the current range?
        //

        if (RemainingRange.End <= pCurEntry->Range.Start)
        {
            //
            // The whole range is distinct.
            //

            RequiredBufferSize += sizeof(BLCRANGE);
            if (pDistinctRanges && (RequiredBufferSize <= BufferSize))
            {
                pDistinctRanges[NumRanges].Start = RemainingRange.Start;
                pDistinctRanges[NumRanges].End = RemainingRange.End;
            }
            NumRanges++;
            
            RemainingRange.Start = RemainingRange.End;
        }
        
        //
        // 2. Are we completely beyond the current range?
        //

        if (RemainingRange.Start >= pCurEntry->Range.End)
        {
            //
            // We cannot carve out anything from the remaining range.
            // Fall through to processing the next entry.
            //
        }

        //
        // 3. Is the remaining range overlaps with the current range.
        //

        if ((RemainingRange.End > pCurEntry->Range.Start) &&
            (RemainingRange.Start < pCurEntry->Range.End))
        {
            OverlapStart = BLRGMAX(RemainingRange.Start,
                                   pCurEntry->Range.Start); 
            OverlapEnd = BLRGMIN(RemainingRange.End,
                                 pCurEntry->Range.End);
            
            if (OverlapStart > pRange->Start)
            {
                //
                // There is a distinct region before the overlap
                //
                RequiredBufferSize += sizeof(BLCRANGE);
                if (pDistinctRanges && (RequiredBufferSize <= BufferSize))
                {
                    pDistinctRanges[NumRanges].Start = RemainingRange.Start;
                    pDistinctRanges[NumRanges].End = OverlapStart;
                }
                NumRanges++;
            }

            RemainingRange.Start = OverlapEnd;
        }     

        pNext = pNext->Flink;
    }

    //
    // The remaining range (if there is any) is also distinct.
    //

    if (RemainingRange.Start < RemainingRange.End)
    {
        RequiredBufferSize += sizeof(BLCRANGE);
        if (pDistinctRanges && (RequiredBufferSize <= BufferSize))
        {
            pDistinctRanges[NumRanges].Start = RemainingRange.Start;
            pDistinctRanges[NumRanges].End = RemainingRange.End;
        }
        NumRanges++;
    }
    
    *pNumRanges = NumRanges;

    return (pDistinctRanges &&
            RequiredBufferSize <= BufferSize);
}

BOOLEAN
BlRangeListMergeRangeEntries (
    PBLCRANGE_LIST pRangeList,
    PBLCRANGE_ENTRY pDestEntry,
    PBLCRANGE_ENTRY pSrcEntry
    )
/*++

Routine Description:

    Merges SrcEntry and DestEntry range entries into DestEntry by
    calling BlRangeEntryMerge. If successful it tries to remove
    pSrcEntry from the range list it is in and free its memory by
    calling the FreeRoutine specified on the list.

Arguments:

    pRangeList - Range list pDestEntry and pSrcEntry belong to.

    pDestEntry - Range entry that we will merge into.

    pSrcEntry - Range entry that will be merged into pDestEntry,
        removed from its list and free'ed.

Return Value:

    TRUE if successful, FALSE if not. The success is mainly determined
    by calls to a MergeRoutine if specified on the list.

--*/
{

    if(BlRangeEntryMerge(pDestEntry,
                         pSrcEntry,
                         pRangeList->MergeRoutine))
    {
        //
        // Remove pSrcEntry from the list since it is merged into
        // pDestEntry now.
        // 

        pRangeList->NumEntries--;
        RemoveEntryList(&pSrcEntry->Link);

        //
        // Free the removed entry.
        //

        if (pRangeList->FreeRoutine) pRangeList->FreeRoutine(pSrcEntry);

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

BOOLEAN
BlRangeEntryMerge (
    PBLCRANGE_ENTRY pDestEntry,
    PBLCRANGE_ENTRY pSrcEntry,
    OPTIONAL PBLCRANGE_MERGE_ROUTINE pMergeRoutine
    )
/*++

Routine Description:

    Merges SrcEntry and DestEntry range entries into DestEntry. It
    uses pMergeRoutine if specified to merge the user's Data field of
    the range entries.

Arguments:

    pDestEntry - Range entry that we will merge into
  
    pSrcEntry - Range entry that will be merged into pDestEntry

    pMergeRoutine - Optional routine to merge Data fields of
        merged range entries. See PBLCRANGE_MERGE_ROUTINE description.

Return Value:

    TRUE if successful, FALSE if not. The success is mainly
    determined by calls to the pMergeRoutine if specified.

--*/
{
    BLCRANGE_ENTRY TempDest = *pDestEntry;
    BOOLEAN RetVal = TRUE;

    if (pMergeRoutine)
    {
        RetVal = pMergeRoutine(&TempDest, pSrcEntry);
    }
    
    if (RetVal)
    {
        TempDest.Range.Start = BLRGMIN(TempDest.Range.Start,
                                       pSrcEntry->Range.Start);
        TempDest.Range.End = BLRGMAX(TempDest.Range.End,
                                     pSrcEntry->Range.End);

        *pDestEntry = TempDest;
    }

    return RetVal;
}

VOID
BlRangeListRemoveRange (
    PBLCRANGE_LIST pRangeList,
    PBLCRANGE pRange
)
/*++

Routine Description:

    Find the ranges that overlap with pRange, remove them from the
    list and free them. It may be possible to reclaim non-overlapping
    parts of range entries by allowing the caller to specify a
    DivideRoutine in an _Ex version of this function. This function
    would be called for invalidating part of the cache, if the range
    list is being used for a disk cache.

Arguments:

    pRangeList - Range entry list we are removing range entries that
        overlap with pRange from.

    pRange - Range to remove from the range entry list.

Return Value:

    None.

--*/
{
    PBLCRANGE_ENTRY pCurEntry;
    LIST_ENTRY *pHead, *pNext;

    //
    // Handle special empty range case.
    //

    if (pRange->Start == pRange->End)
    {
        return;
    }
    
    //
    // Looking at each range in the list, remove the ones that overlap with
    // pRange even slightly.
    //

    pHead = &pRangeList->Head;
    pNext = pHead->Flink;
    while (pNext != pHead)
    {
        pCurEntry = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);
        pNext = pNext->Flink;

        if ((pRange->End > pCurEntry->Range.Start) &&
            (pRange->Start < pCurEntry->Range.End))
        {
            pRangeList->NumEntries--;
            RemoveEntryList(&pCurEntry->Link);
            if (pRangeList->FreeRoutine) pRangeList->FreeRoutine(pCurEntry);
        }     
    }

    return;
}

VOID
BlRangeListRemoveAllRanges (
    PBLCRANGE_LIST pRangeList
    )
/*++

Routine Description:

    Remove all ranges from the list and free them. 

Arguments:

    pRangeList - Range entry list we are removing range entries that
        overlap with pRange from.

Return Value:

    None.

--*/
{
    PBLCRANGE_ENTRY pCurEntry;
    LIST_ENTRY *pHead, *pNext;
    
    pHead = &pRangeList->Head;
    pNext = pHead->Flink;
    while (pNext != pHead)
    {
        pCurEntry = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);

        pRangeList->NumEntries--;
        RemoveEntryList(&pCurEntry->Link);      
        if (pRangeList->FreeRoutine) pRangeList->FreeRoutine(pCurEntry);

        pNext = pNext->Flink;
    }

    return;
}

#ifdef BLRANGE_SELF_TEST

//
// In order to to test blrange implementation, define
// BLRANGE_SELF_TEST and call BlRangeSelfTest from you program passing
// in a function to output debug results.
//

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

//
// Keep MAX_RANDOM above 1000 or you may hit difficulties creating new
// entries.
//

#define MAX_RANDOM 10000

VOID
GetRandom_GetNewSeed(
    VOID
    )
{
    srand((unsigned)time(NULL));
}

ULONG
GetRandom(
    VOID
    )
{
    return (rand() * 10000 / RAND_MAX);
}

typedef 
int
(*PBLCRANGE_SELFTEST_FPRINTF_ROUTINE) (
    void *stream,
    const char *format,
    ...
    );

PBLCRANGE_SELFTEST_FPRINTF_ROUTINE g_fpTestPrintf = NULL;
VOID *g_pTestStream = NULL;

BOOLEAN
BlRangeSelfTest_MergeRoutine (
    PBLCRANGE_ENTRY pDestEntry,
    PBLCRANGE_ENTRY pSrcEntry
    )
{
    g_fpTestPrintf(g_pTestStream,
                   "  Merging RangeDest %I64u-%I64u RangeSrc %I64u-%I64u : ",
                   pDestEntry->Range.Start, 
                   pDestEntry->Range.End,
                   pSrcEntry->Range.Start, 
                   pSrcEntry->Range.End);
    
    if (GetRandom() < (MAX_RANDOM / 5))
    {
        g_fpTestPrintf(g_pTestStream,"FAIL\n");
        return FALSE;
    }
    else
    {
        g_fpTestPrintf(g_pTestStream,"SUCCESS\n");
        return TRUE;
    }
}

VOID
BlRangeSelfTest_FreeRoutine (
    PBLCRANGE_ENTRY pRangeEntry
    )
{
    g_fpTestPrintf(g_pTestStream,
                   "  Freeing range %I64u-%I64u \n",
                   pRangeEntry->Range.Start, 
                   pRangeEntry->Range.End);

    free(pRangeEntry);
}

BLCRANGE
BlRangeSelfTest_RandomRange(
    VOID
    )
{
    BLCRANGE RetRange;
    ULONG Rand1;
    ULONG Rand2;
    ULONGLONG Size;
    ULONG i;

    Rand1 = GetRandom();
    Rand2 = GetRandom();

    RetRange.Start = BLRGMIN(Rand1, Rand2);
    RetRange.End = BLRGMAX(Rand1, Rand2);

    //
    // Make sure that ranges are small and there are not just a couple
    // of big ones.
    //

    for (i = 0; i < 3; i++)
    {
        if ((Size = (RetRange.End - RetRange.Start)) > MAX_RANDOM / 20)
        {
            RetRange.Start += (Size / 2);
        }
        else
        {
            break;
        }
    }

    return RetRange;
}

PBLCRANGE_ENTRY
BlRangeSelfTest_CreateNewEntry(
    VOID
    )
{
    PBLCRANGE_ENTRY pNewEntry;
   
    pNewEntry = malloc(sizeof(BLCRANGE_ENTRY));

    if (pNewEntry) 
    {
        pNewEntry->Range = BlRangeSelfTest_RandomRange();
    }

    return pNewEntry;
}

VOID
BlRangeSelfTest_FreeEntry(
    PBLCRANGE_ENTRY pEntry
    )
{
    free(pEntry);
}

typedef enum _BLRANGE_OP_TYPE
{
    BLRANGE_OP_ADD_RANGE,
    BLRANGE_OP_ADD_MERGE_RANGE,
    BLRANGE_OP_REMOVE_RANGE,
    BLRANGE_OP_FIND_OVERLAP,
    BLRANGE_OP_FIND_DISTINCT,
    BLRANGE_OP_MAX_OP_NO, // Leave this at the end of the enumeration.
} BLRANGE_OP_TYPE;

VOID
BlRangeSelfTest(
    PBLCRANGE_SELFTEST_FPRINTF_ROUTINE TestOutFPrintf,
    PVOID TestOutStream,
    ULONG NumIterations
    )
/*++

Routine Description:

    Range routines self test routine.

Arguments:

    TestOutFPrintf - Pointer to a routine like fprintf that will be used to
        print the output.

    TestOutStream - Argument to be passed to fpPrintf as its first argument.

    NumIterations - Number of random operations to perform in this self test.

Return Value:

    None.

--*/
{
    BLCRANGE_LIST RangeList;
    ULONG Rand1;
    ULONG Rand2;
    BLCRANGE Range1;
    PBLCRANGE_ENTRY pEntry1;
    PBLCRANGE_ENTRY pEntry2;
    BLRANGE_OP_TYPE OpType;
    PLIST_ENTRY pHead;
    PLIST_ENTRY pNext;
    PBLCRANGE_ENTRY *pOverlaps;
    PBLCRANGE pDistinctRanges;
    ULONG BufSize;
    ULONG NumDistincts;
    ULONG NumOverlaps;
    ULONG RandEntryNo;
    
    //
    // Simulation Parameters.
    //
    
    ULONG StartNumRanges = 10;
    
    ULONG CurIterIdx;
    ULONG CurRangeIdx;
    ULONG CurEntryIdx;

    //
    // Set global output function and stream variable so merge/free etc.
    // routines can also output.
    //

    g_fpTestPrintf = TestOutFPrintf;
    g_pTestStream = TestOutStream;

    //
    // Set semi-random starting point for pseudorandom number generation.
    //
    
    GetRandom_GetNewSeed();


    //
    // Initialize the range list.
    //

    BlRangeListInitialize(&RangeList, 
                          BlRangeSelfTest_MergeRoutine,
                          BlRangeSelfTest_FreeRoutine);
    
    //
    // Try to add StartNumRanges random entries.
    //

    for(CurRangeIdx = 0; CurRangeIdx < StartNumRanges; CurRangeIdx++)
    {
        pEntry1 = BlRangeSelfTest_CreateNewEntry();
        
        if (!pEntry1) continue;
        
        g_fpTestPrintf(g_pTestStream,
                       "AddStartRange %I64u-%I64u : ",
                       pEntry1->Range.Start,
                       pEntry1->Range.End);
                       
        if (BlRangeListAddRange(&RangeList, pEntry1))
        {
            g_fpTestPrintf(g_pTestStream, "SUCCESS\n");
        }
        else
        {
            g_fpTestPrintf(g_pTestStream, "FAILED\n");
            BlRangeSelfTest_FreeEntry(pEntry1);
        }
    }

    for(CurIterIdx = 0; CurIterIdx < NumIterations; CurIterIdx++)
    {
        //
        // Print out the current list.
        //

        g_fpTestPrintf(g_pTestStream, "List: ");
        pHead = &RangeList.Head;
        pNext = pHead->Flink;
        while (pNext != pHead)
        {
            pEntry1 = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);
            
            g_fpTestPrintf(g_pTestStream, 
                           "%I64u-%I64u ", 
                           pEntry1->Range.Start,
                           pEntry1->Range.End);

            pNext = pNext->Flink;
        }
        g_fpTestPrintf(g_pTestStream, "\n");
        
    get_new_optype:
        OpType = GetRandom() % BLRANGE_OP_MAX_OP_NO;
        
        switch (OpType)
        {
        case BLRANGE_OP_ADD_RANGE:

            pEntry1 = BlRangeSelfTest_CreateNewEntry();
            g_fpTestPrintf(g_pTestStream,
                           "AddRange %I64u-%I64u : ",
                           pEntry1->Range.Start,
                           pEntry1->Range.End);
            
            if (BlRangeListAddRange(&RangeList, pEntry1))
            {
                g_fpTestPrintf(g_pTestStream, "SUCCESS\n");
            }
            else
            {
                g_fpTestPrintf(g_pTestStream, "FAILED\n");
                BlRangeSelfTest_FreeEntry(pEntry1);
            }
            break;

        case BLRANGE_OP_ADD_MERGE_RANGE:
            
            RandEntryNo = GetRandom() * RangeList.NumEntries / MAX_RANDOM;
            
            pHead = &RangeList.Head;
            pNext = pHead->Flink;

            for (CurEntryIdx = 0; CurEntryIdx < RandEntryNo; CurEntryIdx++)
            {
                pNext = pNext->Flink;
            }
            
            if (pNext == pHead) goto get_new_optype;

            pEntry1 = CONTAINING_RECORD(pNext, BLCRANGE_ENTRY, Link);
            pEntry2 = BlRangeSelfTest_CreateNewEntry();

            if (GetRandom() > (MAX_RANDOM / 2))
            {
                pEntry2->Range.Start = pEntry1->Range.End;
                pEntry2->Range.End = pEntry2->Range.Start + 
                    (GetRandom() * (MAX_RANDOM - pEntry2->Range.Start)) / MAX_RANDOM;
            }
            else
            {
                pEntry2->Range.End = pEntry1->Range.Start;
                pEntry2->Range.Start = pEntry2->Range.End - 
                    (GetRandom() * pEntry2->Range.End) / MAX_RANDOM;
            }

            g_fpTestPrintf(g_pTestStream,
                           "MergeAddRange %I64u-%I64u : ",
                           pEntry2->Range.Start,
                           pEntry2->Range.End);
            
            if (BlRangeListAddRange(&RangeList, pEntry2))
            {
                g_fpTestPrintf(g_pTestStream, "SUCCESS\n");
            }
            else
            {
                g_fpTestPrintf(g_pTestStream, "FAILED\n");
                BlRangeSelfTest_FreeEntry(pEntry2);
            }
            break;

        case BLRANGE_OP_REMOVE_RANGE:
            
            Range1 = BlRangeSelfTest_RandomRange();

            g_fpTestPrintf(g_pTestStream, 
                           "RemoveRange %I64u-%I64u\n",
                           Range1.Start,
                           Range1.End);

            BlRangeListRemoveRange(&RangeList, &Range1);

            break;

        case BLRANGE_OP_FIND_OVERLAP:

            Range1 = BlRangeSelfTest_RandomRange();

            g_fpTestPrintf(g_pTestStream, 
                           "FindOverlaps %I64u-%I64u : ",
                           Range1.Start,
                           Range1.End);

            BlRangeListFindOverlaps(&RangeList,
                                    &Range1,
                                    NULL,
                                    0,
                                    &NumOverlaps);

            g_fpTestPrintf(g_pTestStream, "%u Overlaps... ", NumOverlaps);

            BufSize = NumOverlaps * sizeof(PBLCRANGE_ENTRY);
            pOverlaps = malloc(BufSize);
                        
            if (!pOverlaps) goto get_new_optype;

            if (BlRangeListFindOverlaps(&RangeList,
                                        &Range1,
                                        pOverlaps,
                                        BufSize,
                                        &NumOverlaps) &&
                (BufSize == NumOverlaps * sizeof(PBLCRANGE_ENTRY)))
            {
                g_fpTestPrintf(g_pTestStream, "SUCCESS\n");
            }
            else
            {
                g_fpTestPrintf(g_pTestStream, "FAIL\n");
                free(pOverlaps);
                break;
            }
          
            for (CurEntryIdx = 0; CurEntryIdx < NumOverlaps; CurEntryIdx++)
            {
                g_fpTestPrintf(g_pTestStream, 
                               "  %I64u-%I64u\n",
                               pOverlaps[CurEntryIdx]->Range.Start,
                               pOverlaps[CurEntryIdx]->Range.End);
            }

            free(pOverlaps);

            break;
            
        case BLRANGE_OP_FIND_DISTINCT:

            Range1 = BlRangeSelfTest_RandomRange();

            g_fpTestPrintf(g_pTestStream, 
                           "FindDistincts %I64u-%I64u : ",
                           Range1.Start,
                           Range1.End);

            BlRangeListFindDistinctRanges(&RangeList,
                                          &Range1,
                                          NULL,
                                          0,
                                          &NumDistincts);

            g_fpTestPrintf(g_pTestStream, "%u Distincts... ", NumDistincts);

            BufSize = NumDistincts * sizeof(BLCRANGE);
            pDistinctRanges = malloc(BufSize);
                        
            if (!pDistinctRanges) goto get_new_optype;

            if (BlRangeListFindDistinctRanges(&RangeList,
                                              &Range1,
                                              pDistinctRanges,
                                              BufSize,
                                              &NumDistincts) &&
                (BufSize == NumDistincts * sizeof(BLCRANGE)))
            {
                g_fpTestPrintf(g_pTestStream, "SUCCESS\n");
            }
            else
            {
                g_fpTestPrintf(g_pTestStream, "FAIL\n");
                free(pDistinctRanges);
                break;
            }

            for (CurRangeIdx = 0; CurRangeIdx < NumDistincts; CurRangeIdx++)
            {
                g_fpTestPrintf(g_pTestStream, 
                               "  %I64u-%I64u\n",
                               pDistinctRanges[CurRangeIdx].Start,
                               pDistinctRanges[CurRangeIdx].End);
            }

            free(pDistinctRanges);

            break;
            
        default:
            g_fpTestPrintf(g_pTestStream, "ERR: INVALID OP CODE!");
            goto get_new_optype;
        }
    }

    return;
}

#endif // BLRANGE_SELF_TEST