/** 
 * @file    kPoolAlloc.cpp
 *
 * @internal
 * Copyright (C) 2013-2022 by LMI Technologies Inc.  All rights reserved.
 */
#include <kApi/Utils/kPoolAlloc.h>
#include <kApi/Data/kMath.h>
#include <kApi/Threads/kLock.h>

kBeginClassEx(k, kPoolAlloc)
    kAddPrivateVMethod(kPoolAlloc, kObject, VRelease)
    kAddPrivateVMethod(kPoolAlloc, kAlloc, VGet)
    kAddPrivateVMethod(kPoolAlloc, kAlloc, VFree)
kEndClassEx()

kFx(kStatus) kPoolAlloc_Construct(kPoolAlloc* object, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kAssert((sizeof(xkPoolAllocBlockHeader) % kALIGN_ANY_SIZE) == 0); 
    kAssert((sizeof(xkPoolAllocBufferHeader) % kALIGN_ANY_SIZE) == 0); 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kPoolAlloc), object)); 

    if (!kSuccess(status = xkPoolAlloc_Init(*object, kTypeOf(kPoolAlloc), alloc)))
    {
        kAlloc_FreeRef(alloc, object); 
    }

    return status; 
} 

kFx(kStatus) xkPoolAlloc_Init(kPoolAlloc object, kType type, kAlloc alloc)
{
    kObjR(kPoolAlloc, object); 

    kCheck(kAlloc_Init(object, type, alloc)); 

    obj->lock = kNULL;
    obj->inheritPriority = kFALSE;
    obj->nominalBlockSize = xkPOOL_ALLOC_DEFAULT_BLOCK_SIZE; 
    obj->maxBlockBufferSize = xkPOOL_ALLOC_DEFAULT_MAX_BLOCK_BUFFER_SIZE; 
    obj->blockCapacity = xkPOOL_ALLOC_DEFAULT_BLOCK_CAPACITY; 
    obj->blockReuseEnabled = xkPOOL_ALLOC_DEFAULT_BLOCK_REUSE_ENABLED; 
    obj->maxCachedBufferSize = xkPOOL_ALLOC_DEFAULT_MAX_CACHED_BUFFER_SIZE; 
    obj->cacheCapacity = xkPOOL_ALLOC_DEFAULT_CACHE_CAPACITY; 
    obj->totalCapacity = xkPOOL_ALLOC_DEFAULT_TOTAL_CAPACITY; 
    obj->blockBasedRankCount = 0;
    obj->cacheBasedRankCount = 0;
    obj->managedRankCount = 0;
    obj->maxManagedBufferSize = 0;
    obj->blockAllocId = 0;
    obj->cacheAllocId = 0;
    obj->unmanagedAllocId = 0;
    obj->isStarted = kFALSE;
    kZero(obj->rankInfo);
    kZero(obj->unassignedBlockList);
    obj->blockCount = 0;
    obj->minBlockCount = 0;
    obj->cacheSize = 0;
    obj->totalSize = 0;

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_VRelease(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 

    if (obj->isStarted)
    {
        kCheck(kPoolAlloc_ClearAll(object)); 
    }

    kCheck(kObject_Destroy(obj->lock)); 

    kCheck(kAlloc_VRelease(object)); 

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_EnablePriorityInheritance(kPoolAlloc object, kBool enable)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->inheritPriority = enable;

    return kOK; 
}

kFx(kStatus) kPoolAlloc_SetBlockSize(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->nominalBlockSize = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_BlockSize(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->nominalBlockSize; 
}

kFx(kStatus) kPoolAlloc_SetMaxBlockBufferSize(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->maxBlockBufferSize = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_MaxBlockBufferSize(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->maxBlockBufferSize; 
}

kFx(kStatus) kPoolAlloc_SetBlockCapacity(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->blockCapacity = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_BlockCapacity(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->blockCapacity; 
}

kFx(kStatus) kPoolAlloc_EnableBlockReuse(kPoolAlloc object, kBool enabled)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->blockReuseEnabled = enabled; 

    return kOK; 
}

kFx(kBool) kPoolAlloc_BlockReuseEnabled(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->blockReuseEnabled; 
}

kFx(kStatus) kPoolAlloc_SetMaxCachedBufferSize(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->maxCachedBufferSize = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_MaxCachedBufferSize(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->maxCachedBufferSize; 
}

kFx(kStatus) kPoolAlloc_SetCacheCapacity(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->cacheCapacity = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_CacheCapacity(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->cacheCapacity; 
}

kFx(kStatus) kPoolAlloc_SetTotalCapacity(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kCheckState(!obj->isStarted); 

    obj->totalCapacity = size; 

    return kOK; 
}

kFx(kSize) kPoolAlloc_TotalCapacity(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
   
    return obj->totalCapacity; 
}

kFx(kStatus) kPoolAlloc_Start(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
    kSize hardMaxBufferSize = xkPOOL_ALLOC_MAX_MANAGED_BUFFER_SIZE; 
    kSize i; 

    kCheckState(!obj->isStarted); 
    kCheckState(obj->maxBlockBufferSize <= obj->nominalBlockSize); 

    kCheck(kLock_ConstructEx(&obj->lock, (obj->inheritPriority) ? xkLOCK_OPTION_PRIORITY_INHERITANCE : kLOCK_OPTION_NONE, kObject_Alloc(object))); 

    //create randomized GUIDs for allocation type idenifiers
    do 
    {
        obj->blockAllocId = kRandomSize(); 
        obj->cacheAllocId = kRandomSize(); 
        obj->unmanagedAllocId = kRandomSize(); 
    } 
    while ((obj->blockAllocId == obj->cacheAllocId) || (obj->cacheAllocId == obj->unmanagedAllocId)); 

    //pre-calculate various parameters
    if (obj->maxBlockBufferSize == 0)                       obj->blockBasedRankCount = 0; 
    else if (obj->maxBlockBufferSize >= hardMaxBufferSize)  obj->blockBasedRankCount = xkPOOL_ALLOC_RANK_CAPACITY; 
    else                                                    obj->blockBasedRankCount = (kSize) kMath_Log2Ceil32u((k32u)obj->maxBlockBufferSize) + 1; 

    if (obj->maxCachedBufferSize == 0)                      obj->cacheBasedRankCount = 0; 
    else if (obj->maxCachedBufferSize >= hardMaxBufferSize) obj->cacheBasedRankCount = xkPOOL_ALLOC_RANK_CAPACITY; 
    else                                                    obj->cacheBasedRankCount = (kSize) kMath_Log2Ceil32u((k32u)obj->maxCachedBufferSize) + 1; 

    obj->managedRankCount = kMax_(obj->blockBasedRankCount, obj->cacheBasedRankCount); 
    obj->maxManagedBufferSize = (obj->managedRankCount > 0) ? ((kSize)1 << (obj->managedRankCount-1)) : 0; 

    for (i = 0; i < xkPOOL_ALLOC_RANK_CAPACITY; ++i)
    {
        xkPoolAllocRank* rankInfo = &obj->rankInfo[i]; 

        rankInfo->rank = i; 
        rankInfo->size = (kSize)1 << i;

        rankInfo->bufferStride = sizeof(xkPoolAllocBufferHeader) + rankInfo->size; 
        rankInfo->bufferStride = kSize_Align(rankInfo->bufferStride, kALIGN_ANY); 

        if (i < obj->blockBasedRankCount)
        {
            rankInfo->isBlockBased = kTRUE;  
            rankInfo->buffersPerBlock = xkPoolAlloc_BlockContentSize(object) / rankInfo->bufferStride;  
        }
        else if (i < obj->cacheBasedRankCount)
        {
            rankInfo->isCached = kTRUE;  
        }
    }

    obj->isStarted = kTRUE; 

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_VGet(kPoolAlloc object, kSize size, void* mem)
{
    kObj(kPoolAlloc, object); 
    kBool isAllocated = kFALSE; 

    kLock_Enter(obj->lock); 

    kTry
    {   
        if (size == 0)
        {
            *(void**)mem = kNULL; 
            isAllocated = kTRUE; 
        }
        else if (size <= obj->maxManagedBufferSize)
        {
            kSize rank = (kSize) kMath_Log2Ceil32u((k32u)size);       
            xkPoolAllocRank* rankInfo = &obj->rankInfo[rank]; 

            if (rankInfo->isBlockBased)
            {
                if (rankInfo->freeBufferList.count == 0)
                {                 
                    xkPoolAlloc_PrepareFreeBlockBasedBuffer(object, rank); 
                }

                if (rankInfo->freeBufferList.count > 0)
                {
                    xkPoolAllocBufferHeader* buffer = rankInfo->freeBufferList.first; 

                    kAssert(buffer->info.block->rank == rank); 

                    xkPoolAlloc_RemoveFromBufferList(object, &rankInfo->freeBufferList, buffer); 
                    xkPoolAlloc_AddBlockRef(object, buffer->info.block); 

                    *(void**)mem = kPointer_ByteOffset(buffer, sizeof(xkPoolAllocBufferHeader));     
                    isAllocated = kTRUE; 
                }
            }
            else if (rankInfo->isCached)
            {
                if (rankInfo->freeBufferList.count == 0)
                {                 
                    xkPoolAlloc_AllocCachedBuffers(object, rank, 1); 
                }

                if (rankInfo->freeBufferList.count > 0)
                {
                    xkPoolAllocBufferHeader* buffer = rankInfo->freeBufferList.first; 

                    xkPoolAlloc_RemoveFromBufferList(object, &rankInfo->freeBufferList, buffer); 
                    obj->cacheSize -= rankInfo->size; 

                    *(void**)mem = kPointer_ByteOffset(buffer, sizeof(xkPoolAllocBufferHeader));                         
                    isAllocated = kTRUE; 
                }
            }
        }

        if (!isAllocated)
        {
            kTest(xkPoolAlloc_AllocUnmanagedBuffer(object, size, mem));
        }
    }
    kFinally
    {
        kLock_Exit(obj->lock); 
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_VFree(kPoolAlloc object, void* mem)
{
    kObj(kPoolAlloc, object); 
    kAlloc innerAlloc = kObject_Alloc(object); 

    kLock_Enter(obj->lock); 

    kTry
    {   
        if (!kIsNull(mem))
        {
            xkPoolAllocBufferHeader* buffer = (xkPoolAllocBufferHeader*) kPointer_ByteOffset(mem, -1*(kSSize)sizeof(xkPoolAllocBufferHeader)); 
               
            if (buffer->type == obj->blockAllocId)
            {
                xkPoolAllocBlockHeader* block = buffer->info.block; 
                xkPoolAllocRank* rankInfo = &obj->rankInfo[block->rank]; 

                //CPU cache optimization: insert free buffer at front of list, so that most-recently-used buffer will be reused first
                xkPoolAlloc_InsertIntoBufferList(object, &rankInfo->freeBufferList, rankInfo->freeBufferList.first, buffer); 
                xkPoolAlloc_RemoveBlockRef(object, block); 
            }
            else if (buffer->type == obj->cacheAllocId)
            {
                xkPoolAllocRank* rankInfo = &obj->rankInfo[buffer->info.rank]; 

                if ((obj->cacheSize + rankInfo->size) <= obj->cacheCapacity)
                {
                    //CPU cache optimization: insert free buffer at front of list, so that most-recently-used buffer will be reused first
                    xkPoolAlloc_InsertIntoBufferList(object, &rankInfo->freeBufferList, rankInfo->freeBufferList.first, buffer); 
                    obj->cacheSize += rankInfo->size; 
                }
                else
                {
                    obj->totalSize -= sizeof(xkPoolAllocBufferHeader) + rankInfo->size; 
                    rankInfo->bufferCount--; 

                    kTest(kAlloc_Free(innerAlloc, buffer));             
                }
            }
            else if (buffer->type == obj->unmanagedAllocId)
            {
                obj->totalSize -= sizeof(xkPoolAllocBufferHeader) + buffer->info.size; 

                kTest(kAlloc_Free(innerAlloc, buffer));             
            }
            else
            {
                //if this assertion is reached, an invalid buffer was passed to this function (corrupted, or not from this allocator)
                kAssert(kFALSE); 
                kThrow(kERROR_PARAMETER); 
            }
        }
    }
    kFinally
    {
        kLock_Exit(obj->lock); 
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kPoolAlloc_Reserve(kPoolAlloc object, kSize size)
{
    kObj(kPoolAlloc, object); 

    kLock_Enter(obj->lock); 

    kTry
    {   
        obj->minBlockCount = kDivideCeilUInt_(size, obj->nominalBlockSize); 

        while (obj->blockCount < obj->minBlockCount)
        {
            kTest(xkPoolAlloc_AllocBlock(object)); 
        }
    }
    kFinally
    {
        kLock_Exit(obj->lock); 
        kEndFinally(); 
    }

    return kOK;
}

kFx(kStatus) kPoolAlloc_ReserveAt(kPoolAlloc object, kSize rank, kSize size)
{
    kObj(kPoolAlloc, object); 

    kLock_Enter(obj->lock); 

    kTry
    {   
        if (rank < obj->blockBasedRankCount)
        {
            kTest(xkPoolAlloc_ReserveBlocksAt(object, rank, size)); 
        }
        else if (rank < obj->cacheBasedRankCount)
        {
            kTest(xkPoolAlloc_ReserveCachedBuffersAt(object, rank, size)); 
        }
        else
        {
            kThrow(kERROR_PARAMETER); 
        }
    }
    kFinally
    {
        kLock_Exit(obj->lock); 
        kEndFinally(); 
    }

    return kOK;
}

kFx(kStatus) xkPoolAlloc_ReserveBlocksAt(kPoolAlloc object, kSize rank, kSize size)
{
    kObj(kPoolAlloc, object); 
    xkPoolAllocRank* rankInfo = &obj->rankInfo[rank]; 

    rankInfo->minBufferCount = kDivideCeilUInt_(size, rankInfo->size); 

    //reuse existing blocks, if possible
    if (obj->blockReuseEnabled && (rankInfo->minBufferCount > rankInfo->bufferCount))
    {
        xkPoolAlloc_RecycleBlocks(object, kDivideCeilUInt_(rankInfo->minBufferCount - rankInfo->bufferCount, rankInfo->buffersPerBlock)); 
    }

    //allocate the rest
    while ((rankInfo->bufferCount + obj->unassignedBlockList.count*rankInfo->buffersPerBlock) < rankInfo->minBufferCount)
    {
        kCheck(xkPoolAlloc_AllocBlock(object)); 
    }

    //assign allocated buffers to the requested rank
    while (rankInfo->bufferCount < rankInfo->minBufferCount)
    {       
        xkPoolAllocBlockHeader* block = obj->unassignedBlockList.first; 

        kAssert(obj->unassignedBlockList.count > 0); 

        xkPoolAlloc_RemoveFromBlockList(object, &obj->unassignedBlockList, block); 
        xkPoolAlloc_InsertIntoBlockList(object, &rankInfo->newBlockList, kNULL, block);

        block->rank = rankInfo->rank; 
        rankInfo->bufferCount += rankInfo->buffersPerBlock;            
    }

    return kOK;
}

kFx(kStatus) xkPoolAlloc_ReserveCachedBuffersAt(kPoolAlloc object, kSize rank, kSize size)
{
    kObj(kPoolAlloc, object); 
    xkPoolAllocRank* rankInfo = &obj->rankInfo[rank]; 

    rankInfo->minBufferCount = kDivideCeilUInt_(size, rankInfo->size); 

    if (rankInfo->bufferCount < rankInfo->minBufferCount)
    {
        kCheck(xkPoolAlloc_AllocCachedBuffers(object, rank, rankInfo->minBufferCount - rankInfo->bufferCount)); 
    }

    return kOK;
}

kFx(kStatus) kPoolAlloc_Clear(kPoolAlloc object)
{
    return xkPoolAlloc_ClearEx(object, kFALSE); 
}

kFx(kStatus) kPoolAlloc_ClearAll(kPoolAlloc object)
{
    return xkPoolAlloc_ClearEx(object, kTRUE); 
}

kFx(kStatus) xkPoolAlloc_PrepareFreeBlockBasedBuffer(kPoolAlloc object, kSize rank)
{
    kObj(kPoolAlloc, object); 
    xkPoolAllocRank* rankInfo = &obj->rankInfo[rank]; 

    if (rankInfo->freeBufferList.count == 0)
    {
        //if we don't have a "new" block to draw from, attempt to reuse or allocate one
        if (rankInfo->newBlockList.count == 0)
        {
            if ((obj->unassignedBlockList.count == 0) && obj->blockReuseEnabled)
            {
                xkPoolAlloc_RecycleBlocks(object, 1); 
            }
           
            if ((obj->unassignedBlockList.count == 0))
            {
                xkPoolAlloc_AllocBlock(object); 
            }

            if (obj->unassignedBlockList.count > 0)
            {
                xkPoolAllocBlockHeader* block = obj->unassignedBlockList.first; 

                xkPoolAlloc_RemoveFromBlockList(object, &obj->unassignedBlockList, block); 
                xkPoolAlloc_InsertIntoBlockList(object, &rankInfo->newBlockList, kNULL, block); 

                block->rank = rank; 
                rankInfo->bufferCount += rankInfo->buffersPerBlock; 
            }
        }

        //if we now have a "new" block to draw from, get a free buffer from the block
        if (rankInfo->newBlockList.count > 0)
        {
            xkPoolAllocBlockHeader* block = rankInfo->newBlockList.first;
            xkPoolAllocBufferHeader* buffer = (xkPoolAllocBufferHeader*) kPointer_ByteOffset(block, (kSSize)(sizeof(xkPoolAllocBlockHeader) + rankInfo->bufferStride*block->allocCount)); 

            kAssert(block->rank == rank); 

            buffer->type = obj->blockAllocId; 
            buffer->info.block = block; 
            buffer->next = kNULL; 
            buffer->previous = kNULL; 

            xkPoolAlloc_InsertIntoBufferList(object, &rankInfo->freeBufferList, kNULL, buffer); 

            block->allocCount++; 

            if (block->allocCount == rankInfo->buffersPerBlock)
            {
                xkPoolAlloc_RemoveFromBlockList(object, &rankInfo->newBlockList, block); 
                xkPoolAlloc_RemoveBlockRef(object, block); 
            }
        }
    }

    return (rankInfo->freeBufferList.count > 0) ? kOK : kERROR_NOT_FOUND; 
}

kFx(void) xkPoolAlloc_RemoveFromBlockList(kPoolAlloc object, xkPoolAllocBlockList* list, xkPoolAllocBlockHeader* item)
{
    if (!kIsNull(item->previous))
    {
        item->previous->next = item->next; 
    }
    
    if (!kIsNull(item->next))
    {
        item->next->previous = item->previous; 
    }

    if (list->first == item)
    {
        list->first = item->next; 
    }

    if (list->last == item)
    {
        list->last = item->previous; 
    }

    item->next = kNULL; 
    item->previous = kNULL;

    list->count--; 
} 

kFx(void) xkPoolAlloc_InsertIntoBlockList(kPoolAlloc object, xkPoolAllocBlockList* list, xkPoolAllocBlockHeader* before, xkPoolAllocBlockHeader* item)
{
    item->previous = kNULL; 
    item->next = kNULL; 

    if (kIsNull(list->first))
    {
        list->first = item; 
        list->last = item; 
    }
    else if (kIsNull(before))
    {
        item->previous = list->last; 
        list->last->next = item; 
        list->last = item;         
    }
    else if (before == list->first)
    {
        item->next = list->first; 
        list->first->previous = item; 
        list->first = item;         
    }
    else
    {
        item->next = before; 
        item->previous = before->previous; 
        before->previous->next = item; 
        before->previous = item;         
    }    

    list->count++; 
} 

kFx(void) xkPoolAlloc_RemoveFromBufferList(kPoolAlloc object, xkPoolAllocBufferList* list, xkPoolAllocBufferHeader* item)
{
    if (!kIsNull(item->previous))
    {
        item->previous->next = item->next; 
    }
    
    if (!kIsNull(item->next))
    {
        item->next->previous = item->previous; 
    }

    if (list->first == item)
    {
        list->first = item->next; 
    }

    if (list->last == item)
    {
        list->last = item->previous; 
    }

    item->next = kNULL; 
    item->previous = kNULL;

    list->count--; 
} 

kFx(void) xkPoolAlloc_InsertIntoBufferList(kPoolAlloc object, xkPoolAllocBufferList* list, xkPoolAllocBufferHeader* before, xkPoolAllocBufferHeader* item)
{
    item->previous = kNULL; 
    item->next = kNULL; 

    if (kIsNull(list->first))
    {
        list->first = item; 
        list->last = item; 
    }
    else if (kIsNull(before))
    {
        item->previous = list->last; 
        list->last->next = item; 
        list->last = item;         
    }
    else if (before == list->first)
    {
        item->next = list->first; 
        list->first->previous = item; 
        list->first = item;         
    }
    else
    {
        item->next = before; 
        item->previous = before->previous; 
        before->previous->next = item; 
        before->previous = item;         
    }    

    list->count++; 
} 

kFx(kStatus) xkPoolAlloc_AddBlockRef(kPoolAlloc object, xkPoolAllocBlockHeader* block)
{
    kObj(kPoolAlloc, object); 

    if (block->refCount == 0)
    {
        xkPoolAlloc_RemoveFromBlockList(object, &obj->rankInfo[block->rank].freeBlockList, block); 
    }

    block->refCount++; 

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_RemoveBlockRef(kPoolAlloc object, xkPoolAllocBlockHeader* block)
{
    kObj(kPoolAlloc, object); 

    block->refCount--; 

    if (block->refCount == 0)
    {
        xkPoolAlloc_InsertIntoBlockList(object, &obj->rankInfo[block->rank].freeBlockList, kNULL, block); 
    }

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_RecycleBlocks(kPoolAlloc object, kSize maxCount)
{
    kObj(kPoolAlloc, object); 
    kSize count = 0; 
    kSize i; 
    
    for (i = 0; i < obj->blockBasedRankCount; ++i)
    {
        kSize rankIndex = obj->blockBasedRankCount - i - 1;     //largest rank first (fewer buffers to unhook)
        xkPoolAllocRank* rankInfo = &obj->rankInfo[rankIndex]; 
            
        //try to reuse surplus "free" blocks
        while ((rankInfo->freeBlockList.count > 0) && 
               (rankInfo->bufferCount >= (rankInfo->minBufferCount + rankInfo->buffersPerBlock)))
        {
            kCheck(xkPoolAlloc_ReturnBlockToUnassigned(object, &rankInfo->freeBlockList, rankInfo->freeBlockList.last)); 

            if (++count == maxCount)
            {
                return kOK;
            }
        }

        //try to reuse surplus "new" blocks 
        while ((rankInfo->newBlockList.count > 0) && (rankInfo->newBlockList.last->refCount == 1) && 
               (rankInfo->bufferCount >= (rankInfo->minBufferCount + rankInfo->buffersPerBlock)))
        {
            kCheck(xkPoolAlloc_ReturnBlockToUnassigned(object, &rankInfo->newBlockList, rankInfo->newBlockList.last)); 

            if (++count == maxCount)
            {
                return kOK;
            }
        }      
    }

    return kOK;
}

kFx(kStatus) xkPoolAlloc_ReturnBlockToUnassigned(kPoolAlloc object, xkPoolAllocBlockList* list, xkPoolAllocBlockHeader* block)
{
    kObj(kPoolAlloc, object); 
    xkPoolAllocRank* rankInfo = &obj->rankInfo[block->rank]; 
    kSize i; 

    for (i = 0; i < block->allocCount; ++i)
    {
        xkPoolAllocBufferHeader* buffer = (xkPoolAllocBufferHeader*) kPointer_ByteOffset(block, (kSSize)(sizeof(xkPoolAllocBlockHeader) + i*rankInfo->bufferStride)); 

        xkPoolAlloc_RemoveFromBufferList(object, &rankInfo->freeBufferList, buffer); 
    }

    rankInfo->bufferCount -= rankInfo->buffersPerBlock; 

    block->allocCount = 0; 
    block->rank = kSIZE_NULL; 
    block->refCount = 1; 

    xkPoolAlloc_RemoveFromBlockList(object, list, block); 
    xkPoolAlloc_InsertIntoBlockList(object, &obj->unassignedBlockList, kNULL, block);

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_AllocBlock(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
    kAlloc innerAlloc = kObject_Alloc(object); 
    kSize allocSize = xkPoolAlloc_AllocBlockSize(object); 
    xkPoolAllocBlockHeader* block = kNULL; 

    if (((obj->blockCount + 1)*obj->nominalBlockSize > obj->blockCapacity) || 
        ((obj->totalSize + allocSize) > obj->totalCapacity))
    {
        return kERROR_MEMORY; 
    }

    kCheck(kAlloc_Get(innerAlloc, allocSize, &block)); 

    block->allocCount = 0; 
    block->refCount = 1; 
    block->rank = kSIZE_NULL; 
    block->next = kNULL; 
    block->previous = kNULL; 

    obj->blockCount++; 
    obj->totalSize += allocSize; 

    xkPoolAlloc_InsertIntoBlockList(object, &obj->unassignedBlockList, kNULL, block); 

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_ClearEx(kPoolAlloc object, kBool all)
{
    kObj(kPoolAlloc, object); 
    kSize i; 

    kLock_Enter(obj->lock); 

    kTry
    {
        //clear reservations, if requested
        if (all)
        {            
            kPoolAlloc_Reserve(object, 0); 

            for (i = 0; i < obj->managedRankCount; ++i)
            {
                kPoolAlloc_ReserveAt(object, i, 0); 
            }
        }

        //blocks
        kTest(xkPoolAlloc_RecycleBlocks(object, kSIZE_MAX)); 
        kTest(xkPoolAlloc_FreeUnassignedBlocks(object)); 

        //cached buffers
        kTest(xkPoolAlloc_FreeCachedBuffers(object)); 
    }
    kFinally
    {
        kLock_Exit(obj->lock); 
        kEndFinally(); 
    }

    return kOK;
}

kFx(kStatus) xkPoolAlloc_AllocCachedBuffers(kPoolAlloc object, kSize rank, kSize count)
{
    kObj(kPoolAlloc, object); 
    xkPoolAllocRank* rankInfo = &obj->rankInfo[rank]; 
    kAlloc innerAlloc = kObject_Alloc(object); 
    kSize allocSize = sizeof(xkPoolAllocBufferHeader) + rankInfo->size; 
    kSize i; 

    for (i = 0; i < count; ++i)
    {
        xkPoolAllocBufferHeader* buffer = kNULL; 

        if ((obj->totalSize + allocSize) > obj->totalCapacity)
        {
            return kERROR_MEMORY; 
        }

        kCheck(kAlloc_Get(innerAlloc, allocSize, &buffer)); 

        buffer->type = obj->cacheAllocId; 
        buffer->info.rank = rank; 
        buffer->next = kNULL; 
        buffer->previous = kNULL; 

        xkPoolAlloc_InsertIntoBufferList(object, &rankInfo->freeBufferList, kNULL, buffer); 

        rankInfo->bufferCount++; 
        obj->cacheSize += rankInfo->size;          
        obj->totalSize += allocSize; 
    }

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_AllocUnmanagedBuffer(kPoolAlloc object, kSize size, void* mem)
{
    kObj(kPoolAlloc, object); 
    kAlloc innerAlloc = kObject_Alloc(object); 
    kSize allocSize = sizeof(xkPoolAllocBufferHeader) + size; 
    xkPoolAllocBufferHeader* buffer = kNULL; 

    if ((obj->totalSize + allocSize) > obj->totalCapacity)
    {
        return kERROR_MEMORY; 
    }

    kCheck(kAlloc_Get(innerAlloc, allocSize, &buffer)); 

    buffer->type = obj->unmanagedAllocId; 
    buffer->info.size = size; 
    buffer->next = kNULL; 
    buffer->previous = kNULL; 

    obj->totalSize += allocSize; 

    *(void**)mem = kPointer_ByteOffset(buffer, sizeof(xkPoolAllocBufferHeader));   

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_FreeUnassignedBlocks(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
    kAlloc internalAlloc = kObject_Alloc(object); 

    while ((obj->blockCount > obj->minBlockCount) && (obj->unassignedBlockList.count > 0))
    {
        xkPoolAllocBlockHeader* block = obj->unassignedBlockList.first; 

        xkPoolAlloc_RemoveFromBlockList(object, &obj->unassignedBlockList, block); 
        
        kCheck(kAlloc_Free(internalAlloc, block)); 

        obj->totalSize -= xkPoolAlloc_AllocBlockSize(object); 
        obj->blockCount--; 
    }

    return kOK; 
}

kFx(kStatus) xkPoolAlloc_FreeCachedBuffers(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
    kAlloc internalAlloc = kObject_Alloc(object); 
    kSize i;

    for (i = obj->blockBasedRankCount; i < obj->cacheBasedRankCount; ++i)
    {
        xkPoolAllocRank* rankInfo = &obj->rankInfo[i]; 

        kAssert(rankInfo->isCached); 

        while ((rankInfo->bufferCount > rankInfo->minBufferCount) && (rankInfo->freeBufferList.count > 0))
        {
            xkPoolAllocBufferHeader* buffer = rankInfo->freeBufferList.first;

            xkPoolAlloc_RemoveFromBufferList(object, &rankInfo->freeBufferList, buffer); 

            kCheck(kAlloc_Free(internalAlloc, buffer)); 

            obj->cacheSize -= rankInfo->size; 
            obj->totalSize -= sizeof(xkPoolAllocBufferHeader) + rankInfo->size; 

            rankInfo->bufferCount--; 
        }
    }

    return kOK; 
}

kFx(kSize) kPoolAlloc_TotalSize(kPoolAlloc object)
{
    kObj(kPoolAlloc, object); 
    kSize size; 

    kLock_Enter(obj->lock); 
    {
        size = obj->totalSize; 
    }
    kLock_Exit(obj->lock); 

    return size; 
}

kFx(kSize) kPoolAlloc_BufferCountAt(kPoolAlloc object, kSize rank)
{
    kObj(kPoolAlloc, object); 
    kSize count = 0; 

    kLock_Enter(obj->lock); 
    {
        if (rank < obj->managedRankCount)
        {
            count += obj->rankInfo[rank].bufferCount; 
        }
    }
    kLock_Exit(obj->lock); 

    return count; 
}
