// 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 emulates a linked list for lazyfree testing of modules, which
 is a simplified version of 'hellotype.c'
 */
#include "redictmodule.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>

static RedictModuleType *LazyFreeLinkType;

struct LazyFreeLinkNode {
    int64_t value;
    struct LazyFreeLinkNode *next;
};

struct LazyFreeLinkObject {
    struct LazyFreeLinkNode *head;
    size_t len; /* Number of elements added. */
};

struct LazyFreeLinkObject *createLazyFreeLinkObject(void) {
    struct LazyFreeLinkObject *o;
    o = RedictModule_Alloc(sizeof(*o));
    o->head = NULL;
    o->len = 0;
    return o;
}

void LazyFreeLinkInsert(struct LazyFreeLinkObject *o, int64_t ele) {
    struct LazyFreeLinkNode *next = o->head, *newnode, *prev = NULL;

    while(next && next->value < ele) {
        prev = next;
        next = next->next;
    }
    newnode = RedictModule_Alloc(sizeof(*newnode));
    newnode->value = ele;
    newnode->next = next;
    if (prev) {
        prev->next = newnode;
    } else {
        o->head = newnode;
    }
    o->len++;
}

void LazyFreeLinkReleaseObject(struct LazyFreeLinkObject *o) {
    struct LazyFreeLinkNode *cur, *next;
    cur = o->head;
    while(cur) {
        next = cur->next;
        RedictModule_Free(cur);
        cur = next;
    }
    RedictModule_Free(o);
}

/* LAZYFREELINK.INSERT key value */
int LazyFreeLinkInsert_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx); /* Use automatic memory management. */

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

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

    struct LazyFreeLinkObject *hto;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        hto = createLazyFreeLinkObject();
        RedictModule_ModuleTypeSetValue(key,LazyFreeLinkType,hto);
    } else {
        hto = RedictModule_ModuleTypeGetValue(key);
    }

    LazyFreeLinkInsert(hto,value);
    RedictModule_SignalKeyAsReady(ctx,argv[1]);

    RedictModule_ReplyWithLongLong(ctx,hto->len);
    RedictModule_ReplicateVerbatim(ctx);
    return REDICTMODULE_OK;
}

/* LAZYFREELINK.LEN key */
int LazyFreeLinkLen_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx); /* Use automatic memory management. */

    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) != LazyFreeLinkType)
    {
        return RedictModule_ReplyWithError(ctx,REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    struct LazyFreeLinkObject *hto = RedictModule_ModuleTypeGetValue(key);
    RedictModule_ReplyWithLongLong(ctx,hto ? hto->len : 0);
    return REDICTMODULE_OK;
}

void *LazyFreeLinkRdbLoad(RedictModuleIO *rdb, int encver) {
    if (encver != 0) {
        return NULL;
    }
    uint64_t elements = RedictModule_LoadUnsigned(rdb);
    struct LazyFreeLinkObject *hto = createLazyFreeLinkObject();
    while(elements--) {
        int64_t ele = RedictModule_LoadSigned(rdb);
        LazyFreeLinkInsert(hto,ele);
    }
    return hto;
}

void LazyFreeLinkRdbSave(RedictModuleIO *rdb, void *value) {
    struct LazyFreeLinkObject *hto = value;
    struct LazyFreeLinkNode *node = hto->head;
    RedictModule_SaveUnsigned(rdb,hto->len);
    while(node) {
        RedictModule_SaveSigned(rdb,node->value);
        node = node->next;
    }
}

void LazyFreeLinkAofRewrite(RedictModuleIO *aof, RedictModuleString *key, void *value) {
    struct LazyFreeLinkObject *hto = value;
    struct LazyFreeLinkNode *node = hto->head;
    while(node) {
        RedictModule_EmitAOF(aof,"LAZYFREELINK.INSERT","sl",key,node->value);
        node = node->next;
    }
}

void LazyFreeLinkFree(void *value) {
    LazyFreeLinkReleaseObject(value);
}

size_t LazyFreeLinkFreeEffort(RedictModuleString *key, const void *value) {
    REDICTMODULE_NOT_USED(key);
    const struct LazyFreeLinkObject *hto = value;
    return hto->len;
}

void LazyFreeLinkUnlink(RedictModuleString *key, const void *value) {
    REDICTMODULE_NOT_USED(key);
    REDICTMODULE_NOT_USED(value);
    /* Here you can know which key and value is about to be freed. */
}

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

    if (RedictModule_Init(ctx,"lazyfreetest",1,REDICTMODULE_APIVER_1)
        == REDICTMODULE_ERR) return REDICTMODULE_ERR;

    /* We only allow our module to be loaded when the Redict core version is greater than the version of my module */
    if (RedictModule_GetTypeMethodVersion() < REDICTMODULE_TYPE_METHOD_VERSION) {
        return REDICTMODULE_ERR;
    }

    RedictModuleTypeMethods tm = {
        .version = REDICTMODULE_TYPE_METHOD_VERSION,
        .rdb_load = LazyFreeLinkRdbLoad,
        .rdb_save = LazyFreeLinkRdbSave,
        .aof_rewrite = LazyFreeLinkAofRewrite,
        .free = LazyFreeLinkFree,
        .free_effort = LazyFreeLinkFreeEffort,
        .unlink = LazyFreeLinkUnlink,
    };

    LazyFreeLinkType = RedictModule_CreateDataType(ctx,"test_lazy",0,&tm);
    if (LazyFreeLinkType == NULL) return REDICTMODULE_ERR;

    if (RedictModule_CreateCommand(ctx,"lazyfreelink.insert",
        LazyFreeLinkInsert_RedictCommand,"write deny-oom",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    if (RedictModule_CreateCommand(ctx,"lazyfreelink.len",
        LazyFreeLinkLen_RedictCommand,"readonly",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}
