// Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
// SPDX-FileCopyrightText: 2024 Redict Contributors
// SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
//
// SPDX-License-Identifier: BSD-3-Clause

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

static RedictModuleType *HelloType;

/* ========================== Internal data structure  =======================
 * This is just a linked list of 64 bit integers where elements are inserted
 * in-place, so it's ordered. There is no pop/push operation but just insert
 * because it is enough to show the implementation of new data types without
 * making things complex. */

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

struct HelloTypeObject {
    struct HelloTypeNode *head;
    size_t len; /* Number of elements added. */
};

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

void HelloTypeInsert(struct HelloTypeObject *o, int64_t ele) {
    struct HelloTypeNode *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 HelloTypeReleaseObject(struct HelloTypeObject *o) {
    struct HelloTypeNode *cur, *next;
    cur = o->head;
    while(cur) {
        next = cur->next;
        RedictModule_Free(cur);
        cur = next;
    }
    RedictModule_Free(o);
}

/* ========================= "hellotype" type commands ======================= */

/* HELLOTYPE.INSERT key value */
int HelloTypeInsert_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) != HelloType)
    {
        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");
    }

    /* Create an empty value object if the key is currently empty. */
    struct HelloTypeObject *hto;
    if (type == REDICTMODULE_KEYTYPE_EMPTY) {
        hto = createHelloTypeObject();
        RedictModule_ModuleTypeSetValue(key,HelloType,hto);
    } else {
        hto = RedictModule_ModuleTypeGetValue(key);
    }

    /* Insert the new element. */
    HelloTypeInsert(hto,value);
    RedictModule_SignalKeyAsReady(ctx,argv[1]);

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

/* HELLOTYPE.RANGE key first count */
int HelloTypeRange_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    RedictModule_AutoMemory(ctx); /* Use automatic memory management. */

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

    long long first, count;
    if (RedictModule_StringToLongLong(argv[2],&first) != REDICTMODULE_OK ||
        RedictModule_StringToLongLong(argv[3],&count) != REDICTMODULE_OK ||
        first < 0 || count < 0)
    {
        return RedictModule_ReplyWithError(ctx,
            "ERR invalid first or count parameters");
    }

    struct HelloTypeObject *hto = RedictModule_ModuleTypeGetValue(key);
    struct HelloTypeNode *node = hto ? hto->head : NULL;
    RedictModule_ReplyWithArray(ctx,REDICTMODULE_POSTPONED_LEN);
    long long arraylen = 0;
    while(node && count--) {
        RedictModule_ReplyWithLongLong(ctx,node->value);
        arraylen++;
        node = node->next;
    }
    RedictModule_ReplySetArrayLength(ctx,arraylen);
    return REDICTMODULE_OK;
}

/* HELLOTYPE.LEN key */
int HelloTypeLen_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|REDICTMODULE_WRITE);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY &&
        RedictModule_ModuleTypeGetType(key) != HelloType)
    {
        return RedictModule_ReplyWithError(ctx,REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

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

/* ====================== Example of a blocking command ==================== */

/* Reply callback for blocking command HELLOTYPE.BRANGE, this will get
 * called when the key we blocked for is ready: we need to check if we
 * can really serve the client, and reply OK or ERR accordingly. */
int HelloBlock_Reply(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    REDICTMODULE_NOT_USED(argv);
    REDICTMODULE_NOT_USED(argc);

    RedictModuleString *keyname = RedictModule_GetBlockedClientReadyKey(ctx);
    RedictModuleKey *key = RedictModule_OpenKey(ctx,keyname,REDICTMODULE_READ);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_MODULE ||
        RedictModule_ModuleTypeGetType(key) != HelloType)
    {
        RedictModule_CloseKey(key);
        return REDICTMODULE_ERR;
    }

    /* In case the key is able to serve our blocked client, let's directly
     * use our original command implementation to make this example simpler. */
    RedictModule_CloseKey(key);
    return HelloTypeRange_RedictCommand(ctx,argv,argc-1);
}

/* Timeout callback for blocking command HELLOTYPE.BRANGE */
int HelloBlock_Timeout(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    REDICTMODULE_NOT_USED(argv);
    REDICTMODULE_NOT_USED(argc);
    return RedictModule_ReplyWithSimpleString(ctx,"Request timedout");
}

/* Private data freeing callback for HELLOTYPE.BRANGE command. */
void HelloBlock_FreeData(RedictModuleCtx *ctx, void *privdata) {
    REDICTMODULE_NOT_USED(ctx);
    RedictModule_Free(privdata);
}

/* HELLOTYPE.BRANGE key first count timeout -- This is a blocking version of
 * the RANGE operation, in order to show how to use the API
 * RedictModule_BlockClientOnKeys(). */
int HelloTypeBRange_RedictCommand(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc != 5) return RedictModule_WrongArity(ctx);
    RedictModule_AutoMemory(ctx); /* Use automatic memory management. */
    RedictModuleKey *key = RedictModule_OpenKey(ctx,argv[1],
        REDICTMODULE_READ|REDICTMODULE_WRITE);
    int type = RedictModule_KeyType(key);
    if (type != REDICTMODULE_KEYTYPE_EMPTY &&
        RedictModule_ModuleTypeGetType(key) != HelloType)
    {
        return RedictModule_ReplyWithError(ctx,REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    /* Parse the timeout before even trying to serve the client synchronously,
     * so that we always fail ASAP on syntax errors. */
    long long timeout;
    if (RedictModule_StringToLongLong(argv[4],&timeout) != REDICTMODULE_OK) {
        return RedictModule_ReplyWithError(ctx,
            "ERR invalid timeout parameter");
    }

    /* Can we serve the reply synchronously? */
    if (type != REDICTMODULE_KEYTYPE_EMPTY) {
        return HelloTypeRange_RedictCommand(ctx,argv,argc-1);
    }

    /* Otherwise let's block on the key. */
    void *privdata = RedictModule_Alloc(100);
    RedictModule_BlockClientOnKeys(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout,argv+1,1,privdata);
    return REDICTMODULE_OK;
}

/* ========================== "hellotype" type methods ======================= */

void *HelloTypeRdbLoad(RedictModuleIO *rdb, int encver) {
    if (encver != 0) {
        /* RedictModule_Log("warning","Can't load data with version %d", encver);*/
        return NULL;
    }
    uint64_t elements = RedictModule_LoadUnsigned(rdb);
    struct HelloTypeObject *hto = createHelloTypeObject();
    while(elements--) {
        int64_t ele = RedictModule_LoadSigned(rdb);
        HelloTypeInsert(hto,ele);
    }
    return hto;
}

void HelloTypeRdbSave(RedictModuleIO *rdb, void *value) {
    struct HelloTypeObject *hto = value;
    struct HelloTypeNode *node = hto->head;
    RedictModule_SaveUnsigned(rdb,hto->len);
    while(node) {
        RedictModule_SaveSigned(rdb,node->value);
        node = node->next;
    }
}

void HelloTypeAofRewrite(RedictModuleIO *aof, RedictModuleString *key, void *value) {
    struct HelloTypeObject *hto = value;
    struct HelloTypeNode *node = hto->head;
    while(node) {
        RedictModule_EmitAOF(aof,"HELLOTYPE.INSERT","sl",key,node->value);
        node = node->next;
    }
}

/* The goal of this function is to return the amount of memory used by
 * the HelloType value. */
size_t HelloTypeMemUsage(const void *value) {
    const struct HelloTypeObject *hto = value;
    struct HelloTypeNode *node = hto->head;
    return sizeof(*hto) + sizeof(*node)*hto->len;
}

void HelloTypeFree(void *value) {
    HelloTypeReleaseObject(value);
}

void HelloTypeDigest(RedictModuleDigest *md, void *value) {
    struct HelloTypeObject *hto = value;
    struct HelloTypeNode *node = hto->head;
    while(node) {
        RedictModule_DigestAddLongLong(md,node->value);
        node = node->next;
    }
    RedictModule_DigestEndSequence(md);
}

/* This function must be present on each Redict module. It is used in order to
 * register the commands into the Redict server. */
int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    REDICTMODULE_NOT_USED(argv);
    REDICTMODULE_NOT_USED(argc);

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

    RedictModuleTypeMethods tm = {
        .version = REDICTMODULE_TYPE_METHOD_VERSION,
        .rdb_load = HelloTypeRdbLoad,
        .rdb_save = HelloTypeRdbSave,
        .aof_rewrite = HelloTypeAofRewrite,
        .mem_usage = HelloTypeMemUsage,
        .free = HelloTypeFree,
        .digest = HelloTypeDigest
    };

    HelloType = RedictModule_CreateDataType(ctx,"hellotype",0,&tm);
    if (HelloType == NULL) return REDICTMODULE_ERR;

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

    if (RedictModule_CreateCommand(ctx,"hellotype.range",
        HelloTypeRange_RedictCommand,"readonly",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    if (RedictModule_CreateCommand(ctx,"hellotype.len",
        HelloTypeLen_RedictCommand,"readonly",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    if (RedictModule_CreateCommand(ctx,"hellotype.brange",
        HelloTypeBRange_RedictCommand,"readonly",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}
