// 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

#include "redictmodule.h"
#include <assert.h>
#include <errno.h>
#include <strings.h>

/* LIST.GETALL key [REVERSE] */
int list_getall(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc < 2 || argc > 3) return RedictModule_WrongArity(ctx);
    int reverse = (argc == 3 &&
                   !strcasecmp(RedictModule_StringPtrLen(argv[2], NULL),
                               "REVERSE"));
    RedictModule_AutoMemory(ctx);
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ);
    if (RedictModule_KeyType(key) != REDICTMODULE_KEYTYPE_LIST) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }
    long n = RedictModule_ValueLength(key);
    RedictModule_ReplyWithArray(ctx, n);
    if (!reverse) {
        for (long i = 0; i < n; i++) {
            RedictModuleString *elem = RedictModule_ListGet(key, i);
            RedictModule_ReplyWithString(ctx, elem);
            RedictModule_FreeString(ctx, elem);
        }
    } else {
        for (long i = -1; i >= -n; i--) {
            RedictModuleString *elem = RedictModule_ListGet(key, i);
            RedictModule_ReplyWithString(ctx, elem);
            RedictModule_FreeString(ctx, elem);
        }
    }

    /* Test error condition: index out of bounds */
    assert(RedictModule_ListGet(key, n) == NULL);
    assert(errno == EDOM); /* no more elements in list */

    /* RedictModule_CloseKey(key); //implicit, done by auto memory */
    return REDICTMODULE_OK;
}

/* LIST.EDIT key [REVERSE] cmdstr [value ..]
 *
 * cmdstr is a string of the following characters:
 *
 *     k -- keep
 *     d -- delete
 *     i -- insert value from args
 *     r -- replace with value from args
 *
 * The number of occurrences of "i" and "r" in cmdstr) should correspond to the
 * number of args after cmdstr.
 *
 * Reply with a RESP3 Map, containing the number of edits (inserts, replaces, deletes)
 * performed, as well as the last index and the entry it points to.
 */
int list_edit(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc < 3) return RedictModule_WrongArity(ctx);
    RedictModule_AutoMemory(ctx);
    int argpos = 1; /* the next arg */

    /* key */
    int keymode = REDICTMODULE_READ | REDICTMODULE_WRITE;
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[argpos++], keymode);
    if (RedictModule_KeyType(key) != REDICTMODULE_KEYTYPE_LIST) {
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    }

    /* REVERSE */
    int reverse = 0;
    if (argc >= 4 &&
        !strcasecmp(RedictModule_StringPtrLen(argv[argpos], NULL), "REVERSE")) {
        reverse = 1;
        argpos++;
    }

    /* cmdstr */
    size_t cmdstr_len;
    const char *cmdstr = RedictModule_StringPtrLen(argv[argpos++], &cmdstr_len);

    /* validate cmdstr vs. argc */
    long num_req_args = 0;
    long min_list_length = 0;
    for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
        char c = cmdstr[cmdpos];
        if (c == 'i' || c == 'r') num_req_args++;
        if (c == 'd' || c == 'r' || c == 'k') min_list_length++;
    }
    if (argc < argpos + num_req_args) {
        return RedictModule_ReplyWithError(ctx, "ERR too few args");
    }
    if ((long)RedictModule_ValueLength(key) < min_list_length) {
        return RedictModule_ReplyWithError(ctx, "ERR list too short");
    }

    /* Iterate over the chars in cmdstr (edit instructions) */
    long long num_inserts = 0, num_deletes = 0, num_replaces = 0;
    long index = reverse ? -1 : 0;
    RedictModuleString *value;

    for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
        switch (cmdstr[cmdpos]) {
        case 'i': /* insert */
            value = argv[argpos++];
            assert(RedictModule_ListInsert(key, index, value) == REDICTMODULE_OK);
            index += reverse ? -1 : 1;
            num_inserts++;
            break;
        case 'd': /* delete */
            assert(RedictModule_ListDelete(key, index) == REDICTMODULE_OK);
            num_deletes++;
            break;
        case 'r': /* replace */
            value = argv[argpos++];
            assert(RedictModule_ListSet(key, index, value) == REDICTMODULE_OK);
            index += reverse ? -1 : 1;
            num_replaces++;
            break;
        case 'k': /* keep */
            index += reverse ? -1 : 1;
            break;
        }
    }

    RedictModuleString *v = RedictModule_ListGet(key, index);
    RedictModule_ReplyWithMap(ctx, v ? 5 : 4);
    RedictModule_ReplyWithCString(ctx, "i");
    RedictModule_ReplyWithLongLong(ctx, num_inserts);
    RedictModule_ReplyWithCString(ctx, "d");
    RedictModule_ReplyWithLongLong(ctx, num_deletes);
    RedictModule_ReplyWithCString(ctx, "r");
    RedictModule_ReplyWithLongLong(ctx, num_replaces);
    RedictModule_ReplyWithCString(ctx, "index");
    RedictModule_ReplyWithLongLong(ctx, index);
    if (v) {
        RedictModule_ReplyWithCString(ctx, "entry");
        RedictModule_ReplyWithString(ctx, v);
        RedictModule_FreeString(ctx, v);
    } 

    RedictModule_CloseKey(key);
    return REDICTMODULE_OK;
}

/* Reply based on errno as set by the List API functions. */
static int replyByErrno(RedictModuleCtx *ctx) {
    switch (errno) {
    case EDOM:
        return RedictModule_ReplyWithError(ctx, "ERR index out of bounds");
    case ENOTSUP:
        return RedictModule_ReplyWithError(ctx, REDICTMODULE_ERRORMSG_WRONGTYPE);
    default: assert(0); /* Can't happen */
    }
}

/* LIST.GET key index */
int list_get(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc != 3) return RedictModule_WrongArity(ctx);
    long long index;
    if (RedictModule_StringToLongLong(argv[2], &index) != REDICTMODULE_OK) {
        return RedictModule_ReplyWithError(ctx, "ERR index must be a number");
    }
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ);
    RedictModuleString *value = RedictModule_ListGet(key, index);
    if (value) {
        RedictModule_ReplyWithString(ctx, value);
        RedictModule_FreeString(ctx, value);
    } else {
        replyByErrno(ctx);
    }
    RedictModule_CloseKey(key);
    return REDICTMODULE_OK;
}

/* LIST.SET key index value */
int list_set(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc != 4) return RedictModule_WrongArity(ctx);
    long long index;
    if (RedictModule_StringToLongLong(argv[2], &index) != REDICTMODULE_OK) {
        RedictModule_ReplyWithError(ctx, "ERR index must be a number");
        return REDICTMODULE_OK;
    }
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_WRITE);
    if (RedictModule_ListSet(key, index, argv[3]) == REDICTMODULE_OK) {
        RedictModule_ReplyWithSimpleString(ctx, "OK");
    } else {
        replyByErrno(ctx);
    }
    RedictModule_CloseKey(key);
    return REDICTMODULE_OK;
}

/* LIST.INSERT key index value
 *
 * If index is negative, value is inserted after, otherwise before the element
 * at index.
 */
int list_insert(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc != 4) return RedictModule_WrongArity(ctx);
    long long index;
    if (RedictModule_StringToLongLong(argv[2], &index) != REDICTMODULE_OK) {
        RedictModule_ReplyWithError(ctx, "ERR index must be a number");
        return REDICTMODULE_OK;
    }
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_WRITE);
    if (RedictModule_ListInsert(key, index, argv[3]) == REDICTMODULE_OK) {
        RedictModule_ReplyWithSimpleString(ctx, "OK");
    } else {
        replyByErrno(ctx);
    }
    RedictModule_CloseKey(key);
    return REDICTMODULE_OK;
}

/* LIST.DELETE key index */
int list_delete(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    if (argc != 3) return RedictModule_WrongArity(ctx);
    long long index;
    if (RedictModule_StringToLongLong(argv[2], &index) != REDICTMODULE_OK) {
        RedictModule_ReplyWithError(ctx, "ERR index must be a number");
        return REDICTMODULE_OK;
    }
    RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_WRITE);
    if (RedictModule_ListDelete(key, index) == REDICTMODULE_OK) {
        RedictModule_ReplyWithSimpleString(ctx, "OK");
    } else {
        replyByErrno(ctx);
    }
    RedictModule_CloseKey(key);
    return REDICTMODULE_OK;
}

int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    REDICTMODULE_NOT_USED(argv);
    REDICTMODULE_NOT_USED(argc);
    if (RedictModule_Init(ctx, "list", 1, REDICTMODULE_APIVER_1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.getall", list_getall, "",
                                  1, 1, 1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.edit", list_edit, "write",
                                  1, 1, 1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.get", list_get, "write",
                                  1, 1, 1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.set", list_set, "write",
                                  1, 1, 1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.insert", list_insert, "write",
                                  1, 1, 1) == REDICTMODULE_OK &&
        RedictModule_CreateCommand(ctx, "list.delete", list_delete, "write",
                                  1, 1, 1) == REDICTMODULE_OK) {
        return REDICTMODULE_OK;
    } else {
        return REDICTMODULE_ERR;
    }
}
