// SPDX-FileCopyrightText: 2024 Redict Contributors
// SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
//
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: LGPL-3.0-only

/* This module is used to test a use case of a module that stores information
 * about keys in global memory, and relies on the enhanced data type callbacks to
 * get key name and dbid on various operations.
 *
 * it simulates a simple memory allocator. The smallest allocation unit of
 * the allocator is a mem block with a size of 4KB. Multiple mem blocks are combined
 * using a linked list. These linked lists are placed in a global dict named 'mem_pool'.
 * Each db has a 'mem_pool'. You can use the 'mem.alloc' command to allocate a specified
 * number of mem blocks, and use 'mem.free' to release the memory. Use 'mem.write', 'mem.read'
 * to write and read the specified mem block (note that each mem block can only be written once).
 * Use 'mem.usage' to get the memory usage under different dbs, and it will return the size
 * mem blocks and used mem blocks under the db.
 * The specific structure diagram is as follows:
 *
 *
 * Global variables of the module:
 *
 *                                           mem blocks link
 *                          ┌─────┬─────┐
 *                          │     │     │    ┌───┐    ┌───┐    ┌───┐
 *                          │ k1  │  ───┼───►│4KB├───►│4KB├───►│4KB│
 *                          │     │     │    └───┘    └───┘    └───┘
 *                          ├─────┼─────┤
 *    ┌───────┐      ┌────► │     │     │    ┌───┐    ┌───┐
 *    │       │      │      │ k2  │  ───┼───►│4KB├───►│4KB│
 *    │ db0   ├──────┘      │     │     │    └───┘    └───┘
 *    │       │             ├─────┼─────┤
 *    ├───────┤             │     │     │    ┌───┐    ┌───┐    ┌───┐
 *    │       │             │ k3  │  ───┼───►│4KB├───►│4KB├───►│4KB│
 *    │ db1   ├──►null      │     │     │    └───┘    └───┘    └───┘
 *    │       │             └─────┴─────┘
 *    ├───────┤                  dict
 *    │       │
 *    │ db2   ├─────────┐
 *    │       │         │
 *    ├───────┤         │   ┌─────┬─────┐
 *    │       │         │   │     │     │    ┌───┐    ┌───┐    ┌───┐
 *    │ db3   ├──►null  │   │ k1  │  ───┼───►│4KB├───►│4KB├───►│4KB│
 *    │       │         │   │     │     │    └───┘    └───┘    └───┘
 *    └───────┘         │   ├─────┼─────┤
 * mem_pool[MAX_DB]     │   │     │     │    ┌───┐    ┌───┐
 *                      └──►│ k2  │  ───┼───►│4KB├───►│4KB│
 *                          │     │     │    └───┘    └───┘
 *                          └─────┴─────┘
 *                               dict
 *
 *
 * Keys in redict database:
 *
 *                                ┌───────┐
 *                                │ size  │
 *                   ┌───────────►│ used  │
 *                   │            │ mask  │
 *     ┌─────┬─────┐ │            └───────┘                                   ┌───────┐
 *     │     │     │ │          MemAllocObject                                │ size  │
 *     │ k1  │  ───┼─┘                                           ┌───────────►│ used  │
 *     │     │     │                                             │            │ mask  │
 *     ├─────┼─────┤              ┌───────┐        ┌─────┬─────┐ │            └───────┘
 *     │     │     │              │ size  │        │     │     │ │          MemAllocObject
 *     │ k2  │  ───┼─────────────►│ used  │        │ k1  │  ───┼─┘
 *     │     │     │              │ mask  │        │     │     │
 *     ├─────┼─────┤              └───────┘        ├─────┼─────┤
 *     │     │     │            MemAllocObject     │     │     │
 *     │ k3  │  ───┼─┐                             │ k2  │  ───┼─┐
 *     │     │     │ │                             │     │     │ │
 *     └─────┴─────┘ │            ┌───────┐        └─────┴─────┘ │            ┌───────┐
 *      redict db[0] │            │ size  │          redict db[1]│            │ size  │
 *                   └───────────►│ used  │                      └───────────►│ used  │
 *                                │ mask  │                                   │ mask  │
 *                                └───────┘                                   └───────┘
 *                              MemAllocObject                              MemAllocObject
 *
 **/

#include "redictmodule.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>

static RedictModuleType *MemAllocType;

#define MAX_DB 16
RedictModuleDict *mem_pool[MAX_DB];
typedef struct MemAllocObject {
    long long size;
    long long used;
    uint64_t mask;
} MemAllocObject;

MemAllocObject *createMemAllocObject(void) {
    MemAllocObject *o = RedictModule_Calloc(1, sizeof(*o));
    return o;
}

/*---------------------------- mem block apis ------------------------------------*/
#define BLOCK_SIZE 4096
struct MemBlock {
    char block[BLOCK_SIZE];
    struct MemBlock *next;
};

void MemBlockFree(struct MemBlock *head) {
    if (head) {
        struct MemBlock *block = head->next, *next;
        RedictModule_Free(head);
        while (block) {
            next = block->next;
            RedictModule_Free(block);
            block = next;
        }
    }
}
struct MemBlock *MemBlockCreate(long long num) {
    if (num <= 0) {
        return NULL;
    }

    struct MemBlock *head = RedictModule_Calloc(1, sizeof(struct MemBlock));
    struct MemBlock *block = head;
    while (--num) {
        block->next = RedictModule_Calloc(1, sizeof(struct MemBlock));
        block = block->next;
    }

    return head;
}

long long MemBlockNum(const struct MemBlock *head) {
    long long num = 0;
    const struct MemBlock *block = head;
    while (block) {
        num++;
        block = block->next;
    }

    return num;
}

size_t MemBlockWrite(struct MemBlock *head, long long block_index, const char *data, size_t size) {
    size_t w_size = 0;
    struct MemBlock *block = head;
    while (block_index-- && block) {
        block = block->next;
    }

    if (block) {
        size = size > BLOCK_SIZE ? BLOCK_SIZE:size;
        memcpy(block->block, data, size);
        w_size += size;
    }

    return w_size;
}

int MemBlockRead(struct MemBlock *head, long long block_index, char *data, size_t size) {
    size_t r_size = 0;
    struct MemBlock *block = head;
    while (block_index-- && block) {
        block = block->next;
    }

    if (block) {
        size = size > BLOCK_SIZE ? BLOCK_SIZE:size;
        memcpy(data, block->block, size);
        r_size += size;
    }

    return r_size;
}

void MemPoolFreeDb(RedictModuleCtx *ctx, int dbid) {
    RedictModuleString *key;
    void *tdata;
    RedictModuleDictIter *iter = RedictModule_DictIteratorStartC(mem_pool[dbid], "^", NULL, 0);
    while((key = RedictModule_DictNext(ctx, iter, &tdata)) != NULL) {
        MemBlockFree((struct MemBlock *)tdata);
    }
    RedictModule_DictIteratorStop(iter);
    RedictModule_FreeDict(NULL, mem_pool[dbid]);
    mem_pool[dbid] = RedictModule_CreateDict(NULL);
}

struct MemBlock *MemBlockClone(const struct MemBlock *head) {
    struct MemBlock *newhead = NULL;
    if (head) {
        newhead = RedictModule_Calloc(1, sizeof(struct MemBlock));
        memcpy(newhead->block, head->block, BLOCK_SIZE);
        struct MemBlock *newblock = newhead;
        const struct MemBlock *oldblock = head->next;
        while (oldblock) {
            newblock->next = RedictModule_Calloc(1, sizeof(struct MemBlock));
            newblock = newblock->next;
            memcpy(newblock->block, oldblock->block, BLOCK_SIZE);
            oldblock = oldblock->next;
        }
    }

    return newhead;
}

/*---------------------------- event handler ------------------------------------*/
void swapDbCallback(RedictModuleCtx *ctx, RedictModuleEvent e, uint64_t sub, void *data) {
    REDICTMODULE_NOT_USED(ctx);
    REDICTMODULE_NOT_USED(e);
    REDICTMODULE_NOT_USED(sub);

    RedictModuleSwapDbInfo *ei = data;

    // swap
    RedictModuleDict *tmp = mem_pool[ei->dbnum_first];
    mem_pool[ei->dbnum_first] = mem_pool[ei->dbnum_second];
    mem_pool[ei->dbnum_second] = tmp;
}

void flushdbCallback(RedictModuleCtx *ctx, RedictModuleEvent e, uint64_t sub, void *data) {
    REDICTMODULE_NOT_USED(ctx);
    REDICTMODULE_NOT_USED(e);
    int i;
    RedictModuleFlushInfo *fi = data;

    RedictModule_AutoMemory(ctx);

    if (sub == REDICTMODULE_SUBEVENT_FLUSHDB_START) {
        if (fi->dbnum != -1) {
           MemPoolFreeDb(ctx, fi->dbnum);
        } else {
            for (i = 0; i < MAX_DB; i++) {
                MemPoolFreeDb(ctx, i);
            }
        }
    }
}

/*---------------------------- command implementation ------------------------------------*/

/* MEM.ALLOC key block_num */
int MemAlloc_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc != 3) {
        return RedictModule_WrongArity(ctx);
    }

    long long block_num;
    if ((RedictModule_StringToLongLong(argv[2], &block_num) != REDICTMODULE_OK) || block_num <= 0) {
        return RedictModule_ReplyWithError(ctx, "ERR invalid block_num: must be a value greater than 0");
    }

    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ | REDICTMODULE_WRITE);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(key) != MemAllocType) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    MemAllocObject *o;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        o = createMemAllocObject();
        RedictModule_ModuleTypeSetValue(key, MemAllocType, o);
    } else {
        o = RedictModule_ModuleTypeGetValue(key);
    }

    struct MemBlock *mem = MemBlockCreate(block_num);
    RedictModule_Assert(mem != NULL);
    RedictModule_DictSet(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], mem);
    o->size = block_num;
    o->used = 0;
    o->mask = 0;

    RedictModule_ReplyWithLongLong(ctx, block_num);
    RedictModule_ReplicateVerbatim(ctx);
    return REDICTMODULE_OK;
}

/* MEM.FREE key */
int MemFree_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc != 2) {
        return RedictModule_WrongArity(ctx);
    }

    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(key) != MemAllocType) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    int ret = 0;
    MemAllocObject *o;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        RedictModule_ReplyWithLongLong(ctx, ret);
        return REDICTMODULE_OK;
    } else {
        o = RedictModule_ModuleTypeGetValue(key);
    }

    int nokey;
    struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], &nokey);
    if (!nokey && mem) {
        RedictModule_DictDel(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], NULL);
        MemBlockFree(mem);
        o->used = 0;
        o->size = 0;
        o->mask = 0;
        ret = 1;
    }

    RedictModule_ReplyWithLongLong(ctx, ret);
    RedictModule_ReplicateVerbatim(ctx);
    return REDICTMODULE_OK;
}

/* MEM.WRITE key block_index data */
int MemWrite_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc != 4) {
        return RedictModule_WrongArity(ctx);
    }

    long long block_index;
    if ((RedictModule_StringToLongLong(argv[2], &block_index) != REDICTMODULE_OK) || block_index < 0) {
        return RedictModule_ReplyWithError(ctx, "ERR invalid block_index: must be a value greater than 0");
    }

    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ | REDICTMODULE_WRITE);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(key) != MemAllocType) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    MemAllocObject *o;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        return RedictModule_ReplyWithError(ctx, "ERR Memory has not been allocated");
    } else {
        o = RedictModule_ModuleTypeGetValue(key);
    }

    if (o->mask & (1UL << block_index)) {
        return RedictModule_ReplyWithError(ctx, "ERR block is busy");
    }

    int ret = 0;
    int nokey;
    struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], &nokey);
    if (!nokey && mem) {
        size_t len;
        const char *buf = RedictModule_StringPtrLen(argv[3], &len);
        ret = MemBlockWrite(mem, block_index, buf, len);
        o->mask |= (1UL << block_index);
        o->used++;
    }

    RedictModule_ReplyWithLongLong(ctx, ret);
    RedictModule_ReplicateVerbatim(ctx);
    return REDICTMODULE_OK;
}

/* MEM.READ key block_index */
int MemRead_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc != 3) {
        return RedictModule_WrongArity(ctx);
    }

    long long block_index;
    if ((RedictModule_StringToLongLong(argv[2], &block_index) != REDICTMODULE_OK) || block_index < 0) {
        return RedictModule_ReplyWithError(ctx, "ERR invalid block_index: must be a value greater than 0");
    }

    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(key) != MemAllocType) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    MemAllocObject *o;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        return RedictModule_ReplyWithError(ctx, "ERR Memory has not been allocated");
    } else {
        o = RedictModule_ModuleTypeGetValue(key);
    }

    if (!(o->mask & (1UL << block_index))) {
        return RedictModule_ReplyWithNull(ctx);
    }

    int nokey;
    struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], &nokey);
    RedictModule_Assert(nokey == 0 && mem != NULL);

    char buf[BLOCK_SIZE];
    MemBlockRead(mem, block_index, buf, sizeof(buf));

    /* Assuming that the contents are all c-style strings */
    RedictModule_ReplyWithStringBuffer(ctx, buf, strlen(buf));
    return REDICTMODULE_OK;
}

/* MEM.USAGE dbid */
int MemUsage_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc != 2) {
        return RedictModule_WrongArity(ctx);
    }

    long long dbid;
    if ((RedictModule_StringToLongLong(argv[1], (long long *)&dbid) != REDICTMODULE_OK)) {
        return RedictModule_ReplyWithError(ctx, "ERR invalid value: must be a integer");
    }

    if (dbid < 0 || dbid >= MAX_DB) {
        return RedictModule_ReplyWithError(ctx, "ERR dbid out of range");
    }


    long long size = 0, used = 0;

    void *data;
    RedictModuleString *key;
    RedictModuleDictIter *iter = RedictModule_DictIteratorStartC(mem_pool[dbid], "^", NULL, 0);
    while((key = RedictModule_DictNext(ctx, iter, &data)) != NULL) {
        int dbbackup = RedictModule_GetSelectedDb(ctx);
        RedictModule_SelectDb(ctx, dbid);
        RedictModuleKey *openkey = RedictModule_OpenKey(ctx, key, REDICTMODULE_READ);
        int type = RedictModule_KeyType(openkey);
        RedictModule_Assert(type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(openkey) == MemAllocType);
        MemAllocObject *o = RedictModule_ModuleTypeGetValue(openkey);
        used += o->used;
        size += o->size;
        RedictModule_CloseKey(openkey);
        RedictModule_SelectDb(ctx, dbbackup);
    }
    RedictModule_DictIteratorStop(iter);

    RedictModule_ReplyWithArray(ctx, 4);
    RedictModule_ReplyWithSimpleString(ctx, "total");
    RedictModule_ReplyWithLongLong(ctx, size);
    RedictModule_ReplyWithSimpleString(ctx, "used");
    RedictModule_ReplyWithLongLong(ctx, used);
    return REDICTMODULE_OK;
}

/* MEM.ALLOCANDWRITE key block_num block_index data block_index data ... */
int MemAllocAndWrite_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx);

    if (argc < 3) {
        return RedictModule_WrongArity(ctx);
    }

    long long block_num;
    if ((RedictModule_StringToLongLong(argv[2], &block_num) != REDICTMODULE_OK) || block_num <= 0) {
        return RedictModule_ReplyWithError(ctx, "ERR invalid block_num: must be a value greater than 0");
    }

    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ | REDICTMODULE_WRITE);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY && RedictModule_ModuleTypeGetType(key) != MemAllocType) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    MemAllocObject *o;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        o = createMemAllocObject();
        RedictModule_ModuleTypeSetValue(key, MemAllocType, o);
    } else {
        o = RedictModule_ModuleTypeGetValue(key);
    }

    struct MemBlock *mem = MemBlockCreate(block_num);
    RedictModule_Assert(mem != NULL);
    RedictModule_DictSet(mem_pool[RedictModule_GetSelectedDb(ctx)], argv[1], mem);
    o->used = 0;
    o->mask = 0;
    o->size = block_num;

    int i = 3;
    long long block_index;
    for (; i < argc; i++) {
        /* Security is guaranteed internally, so no security check. */
        RedictModule_StringToLongLong(argv[i], &block_index);
        size_t len;
        const char * buf = RedictModule_StringPtrLen(argv[i + 1], &len);
        MemBlockWrite(mem, block_index, buf, len);
        o->used++;
        o->mask |= (1UL << block_index);
    }

    RedictModule_ReplyWithSimpleString(ctx, "OK");
    RedictModule_ReplicateVerbatim(ctx);
    return REDICTMODULE_OK;
}

/*---------------------------- type callbacks ------------------------------------*/

void *MemAllocRdbLoad(RedictModuleIO *rdb, int encver) {
    if (encver != 0) {
        return NULL;
    }

    MemAllocObject *o = createMemAllocObject();
    o->size = RedictModule_LoadSigned(rdb);
    o->used = RedictModule_LoadSigned(rdb);
    o->mask = RedictModule_LoadUnsigned(rdb);

    const RedictModuleString *key = RedictModule_GetKeyNameFromIO(rdb);
    int dbid = RedictModule_GetDbIdFromIO(rdb);

    if (o->size) {
        size_t size;
        char *tmpbuf;
        long long num = o->size;
        struct MemBlock *head = RedictModule_Calloc(1, sizeof(struct MemBlock));
        tmpbuf = RedictModule_LoadStringBuffer(rdb, &size);
        memcpy(head->block, tmpbuf, size > BLOCK_SIZE ? BLOCK_SIZE:size);
        RedictModule_Free(tmpbuf);
        struct MemBlock *block = head;
        while (--num) {
            block->next = RedictModule_Calloc(1, sizeof(struct MemBlock));
            block = block->next;

            tmpbuf = RedictModule_LoadStringBuffer(rdb, &size);
            memcpy(block->block, tmpbuf, size > BLOCK_SIZE ? BLOCK_SIZE:size);
            RedictModule_Free(tmpbuf);
        }

        RedictModule_DictSet(mem_pool[dbid], (RedictModuleString *)key, head);
    }

    return o;
}

void MemAllocRdbSave(RedictModuleIO *rdb, void *value) {
    MemAllocObject *o = value;
    RedictModule_SaveSigned(rdb, o->size);
    RedictModule_SaveSigned(rdb, o->used);
    RedictModule_SaveUnsigned(rdb, o->mask);

    const RedictModuleString *key = RedictModule_GetKeyNameFromIO(rdb);
    int dbid = RedictModule_GetDbIdFromIO(rdb);

    if (o->size) {
        int nokey;
        struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[dbid], (RedictModuleString *)key, &nokey);
        RedictModule_Assert(nokey == 0 && mem != NULL);

        struct MemBlock *block = mem;
        while (block) {
            RedictModule_SaveStringBuffer(rdb, block->block, BLOCK_SIZE);
            block = block->next;
        }
    }
}

void MemAllocAofRewrite(RedictModuleIO *aof, RedictModuleString *key, void *value) {
    MemAllocObject *o = (MemAllocObject *)value;
    if (o->size) {
        int dbid = RedictModule_GetDbIdFromIO(aof);
        int nokey;
        size_t i = 0, j = 0;
        struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[dbid], (RedictModuleString *)key, &nokey);
        RedictModule_Assert(nokey == 0 && mem != NULL);
        size_t array_size = o->size * 2;
        RedictModuleString ** string_array = RedictModule_Calloc(array_size, sizeof(RedictModuleString *));
        while (mem) {
            string_array[i] = RedictModule_CreateStringFromLongLong(NULL, j);
            string_array[i + 1] = RedictModule_CreateString(NULL, mem->block, BLOCK_SIZE);
            mem = mem->next;
            i += 2;
            j++;
        }
        RedictModule_EmitAOF(aof, "mem.allocandwrite", "slv", key, o->size, string_array, array_size);
        for (i = 0; i < array_size; i++) {
            RedictModule_FreeString(NULL, string_array[i]);
        }
        RedictModule_Free(string_array);
    } else {
        RedictModule_EmitAOF(aof, "mem.allocandwrite", "sl", key, o->size);
    }
}

void MemAllocFree(void *value) {
    RedictModule_Free(value);
}

void MemAllocUnlink(RedictModuleString *key, const void *value) {
    REDICTMODULE_NOT_USED(key);
    REDICTMODULE_NOT_USED(value);

    /* When unlink and unlink2 exist at the same time, we will only call unlink2. */
    RedictModule_Assert(0);
}

void MemAllocUnlink2(RedictModuleKeyOptCtx *ctx, const void *value) {
    MemAllocObject *o = (MemAllocObject *)value;

    const RedictModuleString *key = RedictModule_GetKeyNameFromOptCtx(ctx);
    int dbid = RedictModule_GetDbIdFromOptCtx(ctx);

    if (o->size) {
        void *oldval;
        RedictModule_DictDel(mem_pool[dbid], (RedictModuleString *)key, &oldval);
        RedictModule_Assert(oldval != NULL);
        MemBlockFree((struct MemBlock *)oldval);
    }
}

void MemAllocDigest(RedictModuleDigest *md, void *value) {
    MemAllocObject *o = (MemAllocObject *)value;
    RedictModule_DigestAddLongLong(md, o->size);
    RedictModule_DigestAddLongLong(md, o->used);
    RedictModule_DigestAddLongLong(md, o->mask);

    int dbid = RedictModule_GetDbIdFromDigest(md);
    const RedictModuleString *key = RedictModule_GetKeyNameFromDigest(md);

    if (o->size) {
        int nokey;
        struct MemBlock *mem = (struct MemBlock *)RedictModule_DictGet(mem_pool[dbid], (RedictModuleString *)key, &nokey);
        RedictModule_Assert(nokey == 0 && mem != NULL);

        struct MemBlock *block = mem;
        while (block) {
            RedictModule_DigestAddStringBuffer(md, (const char *)block->block, BLOCK_SIZE);
            block = block->next;
        }
    }
}

void *MemAllocCopy2(RedictModuleKeyOptCtx *ctx, const void *value) {
    const MemAllocObject *old = value;
    MemAllocObject *new = createMemAllocObject();
    new->size = old->size;
    new->used = old->used;
    new->mask = old->mask;

    int from_dbid = RedictModule_GetDbIdFromOptCtx(ctx);
    int to_dbid = RedictModule_GetToDbIdFromOptCtx(ctx);
    const RedictModuleString *fromkey = RedictModule_GetKeyNameFromOptCtx(ctx);
    const RedictModuleString *tokey = RedictModule_GetToKeyNameFromOptCtx(ctx);

    if (old->size) {
        int nokey;
        struct MemBlock *oldmem = (struct MemBlock *)RedictModule_DictGet(mem_pool[from_dbid], (RedictModuleString *)fromkey, &nokey);
        RedictModule_Assert(nokey == 0 && oldmem != NULL);
        struct MemBlock *newmem = MemBlockClone(oldmem);
        RedictModule_Assert(newmem != NULL);
        RedictModule_DictSet(mem_pool[to_dbid], (RedictModuleString *)tokey, newmem);
    }

    return new;
}

size_t MemAllocMemUsage2(RedictModuleKeyOptCtx *ctx, const void *value, size_t sample_size) {
    REDICTMODULE_NOT_USED(ctx);
    REDICTMODULE_NOT_USED(sample_size);
    uint64_t size = 0;
    MemAllocObject *o = (MemAllocObject *)value;

    size += sizeof(*o);
    size += o->size * sizeof(struct MemBlock);

    return size;
}

size_t MemAllocMemFreeEffort2(RedictModuleKeyOptCtx *ctx, const void *value) {
    REDICTMODULE_NOT_USED(ctx);
    MemAllocObject *o = (MemAllocObject *)value;
    return o->size;
}

int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    REDICTMODULE_NOT_USED(argv);
    REDICTMODULE_NOT_USED(argc);

    if (RedictModule_Init(ctx, "datatype2", 1,REDICTMODULE_APIVER_1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    RedictModuleTypeMethods tm = {
        .version = REDICTMODULE_TYPE_METHOD_VERSION,
        .rdb_load = MemAllocRdbLoad,
        .rdb_save = MemAllocRdbSave,
        .aof_rewrite = MemAllocAofRewrite,
        .free = MemAllocFree,
        .digest = MemAllocDigest,
        .unlink = MemAllocUnlink,
        // .defrag = MemAllocDefrag, // Tested in defragtest.c
        .unlink2 = MemAllocUnlink2,
        .copy2 = MemAllocCopy2,
        .mem_usage2 = MemAllocMemUsage2,
        .free_effort2 = MemAllocMemFreeEffort2,
    };

    MemAllocType = RedictModule_CreateDataType(ctx, "mem_alloc", 0, &tm);
    if (MemAllocType == NULL) {
        return REDICTMODULE_ERR;
    }

    if (RedictModule_CreateCommand(ctx, "mem.alloc", MemAlloc_RedictCommand, "write deny-oom", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    if (RedictModule_CreateCommand(ctx, "mem.free", MemFree_RedictCommand, "write deny-oom", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    if (RedictModule_CreateCommand(ctx, "mem.write", MemWrite_RedictCommand, "write deny-oom", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    if (RedictModule_CreateCommand(ctx, "mem.read", MemRead_RedictCommand, "readonly", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    if (RedictModule_CreateCommand(ctx, "mem.usage", MemUsage_RedictCommand, "readonly", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    /* used for internal aof rewrite */
    if (RedictModule_CreateCommand(ctx, "mem.allocandwrite", MemAllocAndWrite_RedictCommand, "write deny-oom", 1, 1, 1) == REDICTMODULE_ERR) {
        return REDICTMODULE_ERR;
    }

    for(int i = 0; i < MAX_DB; i++){
        mem_pool[i] = RedictModule_CreateDict(NULL);
    }

    RedictModule_SubscribeToServerEvent(ctx, RedictModuleEvent_FlushDB, flushdbCallback);
    RedictModule_SubscribeToServerEvent(ctx, RedictModuleEvent_SwapDB, swapDbCallback);

    return REDICTMODULE_OK;
}
