#include <stdint.h>

#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "script_memory_private.h"
#include "script_runtime_private.h"

#if UINTPTR_MAX > UINT32_MAX
/*
 * NOT_FROM_ORIGINAL_SOURCE: native 64-bit diagnostic guard. The original i386
 * allocator has no per-block allocation marker, but native porting bugs can
 * corrupt this tree before the final spin is observable. Enable with
 * coduo_script_memory_debug while the recovered 64-bit runtime is being
 * hardened.
 */
static uint8_t script_memoryDebugAllocatedBucket[SCRIPT_MEMORY_BLOCK_COUNT];
static uint32_t script_memoryDebugVisitStamp[SCRIPT_MEMORY_BLOCK_COUNT];
static uint16_t script_memoryDebugVisitStack[SCRIPT_MEMORY_BLOCK_COUNT];
static uint32_t script_memoryDebugCurrentStamp;
static uint32_t script_memoryDebugOperationCount;
static const char *script_memoryDebugOperation;
static uint16_t script_memoryDebugBlock;
static int32_t script_memoryDebugBucket;
static size_t script_memoryDebugSize;
static uint32_t script_memoryDebugPreviousOperationCount;
static const char *script_memoryDebugPreviousOperation;
static uint16_t script_memoryDebugPreviousBlock;
static int32_t script_memoryDebugPreviousBucket;
static size_t script_memoryDebugPreviousSize;

extern int32_t Cvar_VariableIntegerValue(const char *name);

/* NOT_FROM_ORIGINAL_SOURCE: native diagnostic cvar gate. */
static qboolean coduo_engine_script_memory_debug_enabled(void)
{
    return Cvar_VariableIntegerValue("coduo_script_memory_debug") != 0 ? qtrue
                                                                       : qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: native diagnostic operation history. */
static void coduo_engine_script_memory_debug_set_operation(
    const char *operation, uint16_t blockIndex, int32_t bucket, size_t size)
{
    script_memoryDebugPreviousOperationCount =
        script_memoryDebugOperationCount;
    script_memoryDebugPreviousOperation = script_memoryDebugOperation;
    script_memoryDebugPreviousBlock = script_memoryDebugBlock;
    script_memoryDebugPreviousBucket = script_memoryDebugBucket;
    script_memoryDebugPreviousSize = script_memoryDebugSize;

    script_memoryDebugOperation = operation;
    script_memoryDebugBlock = blockIndex;
    script_memoryDebugBucket = bucket;
    script_memoryDebugSize = size;
    script_memoryDebugOperationCount++;
}

/* NOT_FROM_ORIGINAL_SOURCE: native diagnostic sentinel validation. */
static void coduo_engine_script_memory_debug_validate_sentinel(
    const char *phase)
{
    if (coduo_engine_script_memory_debug_enabled() == qfalse) {
        return;
    }

    if (script_memoryBlocks[0].freeNode.left != 0 ||
        script_memoryBlocks[0].freeNode.right != 0) {
        Com_Error(1,
                  "\x15"
                  "ScriptMemory debug: sentinel block 0 dirty left %u "
                  "right %u payload %u during %s op %u %s block %u "
                  "bucket %d size %zu prev op %u %s block %u bucket %d "
                  "size %zu",
                  (unsigned)script_memoryBlocks[0].freeNode.left,
                  (unsigned)script_memoryBlocks[0].freeNode.right,
                  script_memoryBlocks[0].freeNode.payload, phase,
                  script_memoryDebugOperationCount,
                  script_memoryDebugOperation,
                  (unsigned)script_memoryDebugBlock,
                  script_memoryDebugBucket, script_memoryDebugSize,
                  script_memoryDebugPreviousOperationCount,
                  script_memoryDebugPreviousOperation,
                  (unsigned)script_memoryDebugPreviousBlock,
                  script_memoryDebugPreviousBucket,
                  script_memoryDebugPreviousSize);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: native diagnostic free-tree validation. */
static void coduo_engine_script_memory_debug_validate_bucket(
    int32_t bucket, const char *phase)
{
    if (coduo_engine_script_memory_debug_enabled() == qfalse) {
        return;
    }

    if (bucket < 0 || bucket >= SCRIPT_MEMORY_BUCKET_COUNT) {
        Com_Error(1,
                  "\x15"
                  "ScriptMemory debug: invalid bucket %d during %s op %u "
                  "%s block %u bucket %d size %zu",
                  bucket, phase, script_memoryDebugOperationCount,
                  script_memoryDebugOperation,
                  (unsigned)script_memoryDebugBlock,
                  script_memoryDebugBucket, script_memoryDebugSize);
    }

    uint32_t stamp = ++script_memoryDebugCurrentStamp;
    if (stamp == 0) {
        for (uint32_t index = 0; index < SCRIPT_MEMORY_BLOCK_COUNT; ++index) {
            script_memoryDebugVisitStamp[index] = 0;
        }
        stamp = ++script_memoryDebugCurrentStamp;
    }

    uint32_t stackCount = 0;
    uint16_t root = script_memoryFreeRoots[bucket];
    if (root != 0) {
        script_memoryDebugVisitStack[stackCount++] = root;
    }

    while (stackCount != 0) {
        uint16_t node = script_memoryDebugVisitStack[--stackCount];
        if (node == 0) {
            continue;
        }

        if (script_memoryDebugVisitStamp[node] == stamp) {
            Com_Error(1,
                      "\x15"
                      "ScriptMemory debug: cycle in bucket %d at block %u "
                      "during %s op %u %s block %u bucket %d size %zu",
                      bucket, (unsigned)node, phase,
                      script_memoryDebugOperationCount,
                      script_memoryDebugOperation,
                      (unsigned)script_memoryDebugBlock,
                      script_memoryDebugBucket, script_memoryDebugSize);
        }
        script_memoryDebugVisitStamp[node] = stamp;

        if (script_memoryDebugAllocatedBucket[node] != 0) {
            Com_Error(1,
                      "\x15"
                      "ScriptMemory debug: bucket %d references allocated "
                      "block %u marker %u during %s op %u %s block %u "
                      "bucket %d size %zu",
                      bucket, (unsigned)node,
                      (unsigned)script_memoryDebugAllocatedBucket[node],
                      phase, script_memoryDebugOperationCount,
                      script_memoryDebugOperation,
                      (unsigned)script_memoryDebugBlock,
                      script_memoryDebugBucket, script_memoryDebugSize);
        }

        script_memory_block_t *block = &script_memoryBlocks[node];
        if (block->freeNode.left != 0) {
            if (stackCount >= SCRIPT_MEMORY_BLOCK_COUNT) {
                Com_Error(1,
                          "\x15"
                          "ScriptMemory debug: traversal overflow in bucket "
                          "%d during %s op %u %s block %u bucket %d "
                          "size %zu",
                          bucket, phase, script_memoryDebugOperationCount,
                          script_memoryDebugOperation,
                          (unsigned)script_memoryDebugBlock,
                          script_memoryDebugBucket, script_memoryDebugSize);
            }
            script_memoryDebugVisitStack[stackCount++] =
                block->freeNode.left;
        }
        if (block->freeNode.right != 0) {
            if (stackCount >= SCRIPT_MEMORY_BLOCK_COUNT) {
                Com_Error(1,
                          "\x15"
                          "ScriptMemory debug: traversal overflow in bucket "
                          "%d during %s op %u %s block %u bucket %d "
                          "size %zu",
                          bucket, phase, script_memoryDebugOperationCount,
                          script_memoryDebugOperation,
                          (unsigned)script_memoryDebugBlock,
                          script_memoryDebugBucket, script_memoryDebugSize);
            }
            script_memoryDebugVisitStack[stackCount++] =
                block->freeNode.right;
        }
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: native diagnostic full-tree validation. */
static void coduo_engine_script_memory_debug_validate_all_buckets(
    const char *phase)
{
    coduo_engine_script_memory_debug_validate_sentinel(phase);
    for (int32_t bucket = 0; bucket < SCRIPT_MEMORY_BUCKET_COUNT; ++bucket) {
        coduo_engine_script_memory_debug_validate_bucket(bucket, phase);
    }
}
#endif

int32_t MT_GetSubTreeSize(uint16_t blockIndex)
{
    if (blockIndex == 0) {
        return 0;
    }

    script_memory_block_t *block = &script_memoryBlocks[blockIndex];
    return MT_GetSubTreeSize(block->freeNode.left) +
           MT_GetSubTreeSize(block->freeNode.right) + 1;
}

void MT_DumpTree(void)
{
    Com_Printf("********************************\n");
    for (int32_t bucket = 0; bucket < SCRIPT_MEMORY_BUCKET_COUNT; ++bucket) {
        int32_t freeCount =
            MT_GetSubTreeSize(script_memoryFreeRoots[bucket]);
        int32_t bucketSize = 1 << bucket;

        Com_Printf("%d subtree has %d * %d = %d free buckets\n", bucket,
                   freeCount, bucketSize, freeCount << bucket);
    }
    Com_Printf("********************************\n");
    Com_Printf("********************************\n");
    Com_Printf("total memory alloc buckets: %d (%d instances)\n",
               script_memoryAllocatedBucketCount,
               script_memoryAllocatedInstanceCount);
    Com_Printf("total memory free buckets: %d\n",
               SCRIPT_MEMORY_INDEX_MASK - script_memoryAllocatedBucketCount);
    Com_Printf("********************************\n");
}

int32_t Scr_GetStringUsage(void)
{
    return script_memoryAllocatedBucketCount;
}

void MT_InitBits(void)
{
    for (uint32_t value = 0; value < SCRIPT_MEMORY_LOOKUP_COUNT; ++value) {
        uint8_t popcount = 0;
        for (uint32_t bits = value; bits != 0; bits >>= 1) {
            if ((bits & 1U) != 0) {
                popcount++;
            }
        }
        script_memoryPopcountTable[value] = popcount;

        uint8_t trailingZeros = 8;
        while ((((1 << trailingZeros) - 1U) & value) != 0) {
            trailingZeros--;
        }
        script_memoryTrailingZeroBits[value] = trailingZeros;

        uint8_t bitWidth = 0;
        for (uint32_t bits = value; bits != 0; bits >>= 1) {
            bitWidth++;
        }
        script_memoryBitWidthTable[value] = bitWidth;
    }
}

int32_t MT_GetScore(uint16_t blockIndex)
{
    uint32_t distance = SCRIPT_MEMORY_BLOCK_COUNT - blockIndex;
    uint32_t lowByte = distance & 0xffU;
    uint32_t highByte = (distance >> 8) & 0xffU;
    uint8_t trailingZeros = script_memoryTrailingZeroBits[lowByte];

    if (lowByte == 0) {
        trailingZeros += script_memoryTrailingZeroBits[highByte];
    }

    return (1 << trailingZeros) +
           (int32_t)(distance - script_memoryPopcountTable[lowByte] -
                     script_memoryPopcountTable[highByte]);
}

void MT_AddMemoryNode(uint16_t blockIndex, int32_t bucket)
{
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_set_operation("insert", blockIndex,
                                                    bucket, 0);
    if (coduo_engine_script_memory_debug_enabled() != qfalse &&
        script_memoryDebugAllocatedBucket[blockIndex] != 0) {
        Com_Error(1,
                  "\x15"
                  "ScriptMemory debug: inserting allocated block %u marker "
                  "%u into bucket %d op %u",
                  (unsigned)blockIndex,
                  (unsigned)script_memoryDebugAllocatedBucket[blockIndex],
                  bucket, script_memoryDebugOperationCount);
    }
    coduo_engine_script_memory_debug_validate_all_buckets("pre-insert");
    uint32_t debugLoopGuard = 0;
#endif
    uint16_t *link = &script_memoryFreeRoots[bucket];
    uint16_t current = *link;

    if (current != 0) {
        int32_t blockKey = MT_GetScore(blockIndex);
        int32_t center = 0;
        int32_t span = SCRIPT_MEMORY_BLOCK_COUNT;

        do {
#if UINTPTR_MAX > UINT32_MAX
            if (coduo_engine_script_memory_debug_enabled() != qfalse &&
                ++debugLoopGuard > SCRIPT_MEMORY_BLOCK_COUNT) {
                Com_Error(1,
                          "\x15"
                          "ScriptMemory debug: insert loop bucket %d "
                          "block %u current %u",
                          bucket, (unsigned)blockIndex, (unsigned)current);
            }
#endif
            int32_t currentKey = MT_GetScore(current);
            if (currentKey < blockKey) {
                for (;;) {
#if UINTPTR_MAX > UINT32_MAX
                    if (coduo_engine_script_memory_debug_enabled() != qfalse &&
                        ++debugLoopGuard > SCRIPT_MEMORY_BLOCK_COUNT) {
                        Com_Error(1,
                                  "\x15"
                                  "ScriptMemory debug: insert promote loop "
                                  "bucket %d block %u current %u",
                                  bucket, (unsigned)blockIndex,
                                  (unsigned)current);
                    }
#endif
                    *link = blockIndex;
                    script_memoryBlocks[blockIndex] =
                        script_memoryBlocks[current];

                    if (current == 0) {
                        break;
                    }

                    span >>= 1;
                    int32_t centerDelta;
                    if ((int32_t)current < center) {
                        link = &script_memoryBlocks[blockIndex].freeNode.left;
                        centerDelta = -span;
                    } else {
                        link = &script_memoryBlocks[blockIndex].freeNode.right;
                        centerDelta = span;
                    }
                    center += centerDelta;
                    blockIndex = current;
                    current = *link;
                }
#if UINTPTR_MAX > UINT32_MAX
                coduo_engine_script_memory_debug_validate_all_buckets(
                    "post-insert");
#endif
                return;
            }

            span >>= 1;
            if ((int32_t)blockIndex < center) {
                link = &script_memoryBlocks[current].freeNode.left;
                currentKey = -span;
            } else {
                link = &script_memoryBlocks[current].freeNode.right;
                currentKey = span;
            }
            center += currentKey;
            current = *link;
        } while (current != 0);
    }

    *link = blockIndex;
    script_memoryBlocks[blockIndex].freeNode.left = 0;
    script_memoryBlocks[blockIndex].freeNode.right = 0;
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_validate_all_buckets("post-insert");
#endif
}

qboolean MT_RemoveMemoryNode(uint16_t blockIndex, int32_t bucket)
{
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_set_operation("remove", blockIndex,
                                                    bucket, 0);
    coduo_engine_script_memory_debug_validate_all_buckets("pre-remove");
    uint32_t debugLoopGuard = 0;
#endif
    int32_t center = 0;
    int32_t span = SCRIPT_MEMORY_BLOCK_COUNT;
    uint16_t *link = &script_memoryFreeRoots[bucket];

    for (;;) {
#if UINTPTR_MAX > UINT32_MAX
        if (coduo_engine_script_memory_debug_enabled() != qfalse &&
            ++debugLoopGuard > SCRIPT_MEMORY_BLOCK_COUNT) {
            Com_Error(1,
                      "\x15"
                      "ScriptMemory debug: remove search loop bucket %d "
                      "block %u",
                      bucket, (unsigned)blockIndex);
        }
#endif
        uint16_t current = *link;
        if (current == 0) {
            return qfalse;
        }

        if (blockIndex == current) {
            break;
        }

        if (blockIndex == center) {
            return qfalse;
        }

        span >>= 1;
        if ((int32_t)blockIndex < center) {
            link = &script_memoryBlocks[current].freeNode.left;
            center -= span;
        } else {
            link = &script_memoryBlocks[current].freeNode.right;
            center += span;
        }
    }

    script_memory_block_t replacement = script_memoryBlocks[blockIndex];
    for (;;) {
#if UINTPTR_MAX > UINT32_MAX
        if (coduo_engine_script_memory_debug_enabled() != qfalse &&
            ++debugLoopGuard > SCRIPT_MEMORY_BLOCK_COUNT) {
            Com_Error(1,
                      "\x15"
                      "ScriptMemory debug: remove replacement loop bucket %d "
                      "block %u",
                      bucket, (unsigned)blockIndex);
        }
#endif
        uint16_t next;
        if (replacement.freeNode.left == 0) {
            next = replacement.freeNode.right;
            *link = next;
            if (next == 0) {
#if UINTPTR_MAX > UINT32_MAX
                coduo_engine_script_memory_debug_validate_all_buckets(
                    "post-remove");
#endif
                return qtrue;
            }
            link = &script_memoryBlocks[next].freeNode.right;
        } else if (replacement.freeNode.right == 0) {
            next = replacement.freeNode.left;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.left;
        } else if (MT_GetScore(replacement.freeNode.left) <
                   MT_GetScore(replacement.freeNode.right)) {
            next = replacement.freeNode.right;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.right;
        } else {
            next = replacement.freeNode.left;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.left;
        }

        script_memory_block_t moved = script_memoryBlocks[next];
        script_memoryBlocks[next] = replacement;
        replacement = moved;
    }
}

void MT_RemoveHeadMemoryNode(int32_t bucket)
{
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_set_operation(
        "pop", script_memoryFreeRoots[bucket], bucket, 0);
    coduo_engine_script_memory_debug_validate_all_buckets("pre-pop");
    uint32_t debugLoopGuard = 0;
#endif
    uint16_t *link = &script_memoryFreeRoots[bucket];
    script_memory_block_t replacement = script_memoryBlocks[*link];

    for (;;) {
#if UINTPTR_MAX > UINT32_MAX
        if (coduo_engine_script_memory_debug_enabled() != qfalse &&
            ++debugLoopGuard > SCRIPT_MEMORY_BLOCK_COUNT) {
            Com_Error(1,
                      "\x15"
                      "ScriptMemory debug: pop replacement loop bucket %d",
                      bucket);
        }
#endif
        uint16_t next;
        if (replacement.freeNode.left == 0) {
            next = replacement.freeNode.right;
            *link = next;
            if (next == 0) {
#if UINTPTR_MAX > UINT32_MAX
                coduo_engine_script_memory_debug_validate_all_buckets(
                    "post-pop");
#endif
                return;
            }
            link = &script_memoryBlocks[next].freeNode.right;
        } else if (replacement.freeNode.right == 0) {
            next = replacement.freeNode.left;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.left;
        } else if (MT_GetScore(replacement.freeNode.left) <
                   MT_GetScore(replacement.freeNode.right)) {
            next = replacement.freeNode.right;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.right;
        } else {
            next = replacement.freeNode.left;
            *link = next;
            link = &script_memoryBlocks[next].freeNode.left;
        }

        script_memory_block_t moved = script_memoryBlocks[next];
        script_memoryBlocks[next] = replacement;
        replacement = moved;
    }
}

void MT_Init(void)
{
#if UINTPTR_MAX > UINT32_MAX
    for (uint32_t block = 0; block < SCRIPT_MEMORY_BLOCK_COUNT; ++block) {
        script_memoryDebugAllocatedBucket[block] = 0;
        script_memoryDebugVisitStamp[block] = 0;
    }
    script_memoryDebugCurrentStamp = 0;
    script_memoryDebugOperationCount = 0;
    script_memoryDebugOperation = "init";
    script_memoryDebugBlock = 0;
    script_memoryDebugBucket = 0;
    script_memoryDebugSize = 0;
    script_memoryDebugPreviousOperationCount = 0;
    script_memoryDebugPreviousOperation = "init";
    script_memoryDebugPreviousBlock = 0;
    script_memoryDebugPreviousBucket = 0;
    script_memoryDebugPreviousSize = 0;
#endif

    script_memoryAllocatorHeader.arenaBase = script_memoryBlocks;
    MT_InitBits();
    for (int32_t bucket = 0; bucket < SCRIPT_MEMORY_BUCKET_COUNT; ++bucket) {
        script_memoryFreeRoots[bucket] = 0;
    }

    script_memoryAllocatorHeader.prev = &script_memoryAllocatorHeader;
    script_memoryAllocatorHeader.next = &script_memoryAllocatorHeader;
    script_memoryAllocatorHeader.reserved = 0;
    script_memoryBlocks[0].freeNode.left = 0;
    script_memoryBlocks[0].freeNode.right = 0;

    for (int32_t bucket = 0; bucket < SCRIPT_MEMORY_MAX_BUCKET; ++bucket) {
        MT_AddMemoryNode((uint16_t)(1 << bucket), bucket);
    }

    script_memoryAllocatedInstanceCount = 0;
    script_memoryAllocatedBucketCount = 0;
}

void MT_Error(const char *message, size_t size)
{
    MT_DumpTree();
#if UINTPTR_MAX > UINT32_MAX
    /*
     * Native64 support: the i386 path keeps the original "%d" size word.
     * Native size_t is wider, so use a matching format instead of relying on
     * printf consuming only the low 32 bits of a 64-bit vararg slot.
     */
    Scr_TerminalError(
        va("%s: failed allocation of %zu bytes for script usage", message,
           size));
#else
    Scr_TerminalError(
        va("%s: failed allocation of %d bytes for script usage", message,
           size));
#endif
}

int32_t MT_GetSize(size_t size)
{
    int32_t sizeWord = (int32_t)size;

    if (sizeWord > (SCRIPT_MEMORY_MAX_ALLOCATION_SIZE - 1)) {
        MT_Error("MT_GetSize: max allocation exceeded",
                 size);
        return 0;
    }

    int32_t roundedSize = sizeWord + (SCRIPT_MEMORY_BLOCK_SIZE - 1);
    if (roundedSize < 0) {
        roundedSize += SCRIPT_MEMORY_BLOCK_SIZE - 1;
    }
    int32_t blockCountMinusOne =
        (roundedSize >> 3) - 1;

    /* ORIGINAL_BINARY_BUG: stock performs only this signed upper-bound check.
     * Nonpositive sizes therefore index before the bit-width table. */
    if (blockCountMinusOne <= (SCRIPT_MEMORY_LOOKUP_COUNT - 1)) {
        return script_memoryBitWidthTable[blockCountMinusOne];
    }

    return script_memoryBitWidthTable[blockCountMinusOne >> 8] + 8;
}

uint16_t MT_AllocIndex(size_t size, int32_t type)
{
    (void)type;

    int32_t requestedBucket = MT_GetSize(size);
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_set_operation("alloc", 0,
                                                    requestedBucket, size);
#endif
    int32_t bucket = requestedBucket;

    while (bucket <= SCRIPT_MEMORY_MAX_BUCKET) {
        uint16_t blockIndex = script_memoryFreeRoots[bucket];
        if (blockIndex != 0) {
            MT_RemoveHeadMemoryNode(bucket);
            while (bucket != requestedBucket) {
                bucket--;
                MT_AddMemoryNode(
                    (uint16_t)((1 << bucket) + blockIndex), bucket);
            }

            script_memoryAllocatedInstanceCount++;
            script_memoryAllocatedBucketCount += 1 << requestedBucket;
#if UINTPTR_MAX > UINT32_MAX
            script_memoryDebugAllocatedBucket[blockIndex] =
                (uint8_t)(requestedBucket + 1);
#endif
            return blockIndex;
        }

        bucket++;
    }

    MT_Error("MT_AllocIndex", size);
    return 0;
}

void MT_FreeIndex(uint16_t blockIndex, size_t size)
{
    int32_t bucket = MT_GetSize(size);

#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_memory_debug_set_operation("free", blockIndex,
                                                    bucket, size);
    coduo_engine_script_memory_debug_validate_sentinel("pre-free");
    uint8_t expectedMarker = (uint8_t)(bucket + 1);
    uint8_t actualMarker = script_memoryDebugAllocatedBucket[blockIndex];
    if (coduo_engine_script_memory_debug_enabled() != qfalse &&
        actualMarker != expectedMarker) {
        Com_Error(1,
                  "\x15"
                  "MT_FreeIndex: invalid free block %u size %zu "
                  "bucket %d marker %u",
                  (unsigned)blockIndex, size, bucket,
                  (unsigned)actualMarker);
    }
    script_memoryDebugAllocatedBucket[blockIndex] = 0;
#endif

    script_memoryAllocatedInstanceCount--;
    script_memoryAllocatedBucketCount -= 1 << bucket;

    for (; bucket != SCRIPT_MEMORY_MAX_BUCKET; ++bucket) {
        uint16_t buddy = (uint16_t)(blockIndex ^ (1 << bucket));
        if (MT_RemoveMemoryNode(buddy, bucket) == qfalse) {
            break;
        }

        blockIndex = (uint16_t)(blockIndex & ~(1 << bucket));
    }

    MT_AddMemoryNode(blockIndex, bucket);
}

void *MT_Alloc(size_t size, int32_t type)
{
    uint16_t blockIndex = MT_AllocIndex(size, type);
    return &script_memoryBlocks[blockIndex];
}

void MT_Free(void *ptr, size_t size)
{
    script_memory_block_t *block = ptr;
    uint16_t blockIndex =
        (uint16_t)((block - script_memoryBlocks) & SCRIPT_MEMORY_INDEX_MASK);

    MT_FreeIndex(blockIndex, size);
}

qboolean MT_Realloc(size_t lhsSize, size_t rhsSize)
{
    return MT_GetSize(lhsSize) ==
                   MT_GetSize(rhsSize)
               ? qtrue
               : qfalse;
}
