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

#define UNUSED(V) ((void) V)

/* This function implements all commands in this module. All we care about is
 * the COMMAND metadata anyway. */
int kspec_impl(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
    UNUSED(argv);
    UNUSED(argc);

    /* Handle getkeys-api introspection (for "kspec.nonewithgetkeys")  */
    if (RedictModule_IsKeysPositionRequest(ctx)) {
        for (int i = 1; i < argc; i += 2)
            RedictModule_KeyAtPosWithFlags(ctx, i, REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS);

        return REDICTMODULE_OK;
    }

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

int createKspecNone(RedictModuleCtx *ctx) {
    /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec). */
    if (RedictModule_CreateCommand(ctx,"kspec.none",kspec_impl,"",1,-1,2) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;
    return REDICTMODULE_OK;
}

int createKspecNoneWithGetkeys(RedictModuleCtx *ctx) {
    /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec), but also has a getkeys callback */
    if (RedictModule_CreateCommand(ctx,"kspec.nonewithgetkeys",kspec_impl,"getkeys-api",1,-1,2) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;
    return REDICTMODULE_OK;
}

int createKspecTwoRanges(RedictModuleCtx *ctx) {
    /* Test that two position/range-based key specs are combined to produce the
     * legacy (first,last,step) values representing both keys. */
    if (RedictModule_CreateCommand(ctx,"kspec.tworanges",kspec_impl,"",0,0,0) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    RedictModuleCommand *command = RedictModule_GetCommand(ctx,"kspec.tworanges");
    RedictModuleCommandInfo info = {
        .version = REDICTMODULE_COMMAND_INFO_VERSION,
        .arity = -2,
        .key_specs = (RedictModuleCommandKeySpec[]){
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 1,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {0,1,0}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 2,
                /* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
            },
            {0}
        }
    };
    if (RedictModule_SetCommandInfo(command, &info) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}

int createKspecTwoRangesWithGap(RedictModuleCtx *ctx) {
    /* Test that two position/range-based key specs are combined to produce the
     * legacy (first,last,step) values representing just one key. */
    if (RedictModule_CreateCommand(ctx,"kspec.tworangeswithgap",kspec_impl,"",0,0,0) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    RedictModuleCommand *command = RedictModule_GetCommand(ctx,"kspec.tworangeswithgap");
    RedictModuleCommandInfo info = {
        .version = REDICTMODULE_COMMAND_INFO_VERSION,
        .arity = -2,
        .key_specs = (RedictModuleCommandKeySpec[]){
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 1,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {0,1,0}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 3,
                /* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
            },
            {0}
        }
    };
    if (RedictModule_SetCommandInfo(command, &info) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}

int createKspecKeyword(RedictModuleCtx *ctx) {
    /* Only keyword-based specs. The legacy triple is wiped and set to (0,0,0). */
    if (RedictModule_CreateCommand(ctx,"kspec.keyword",kspec_impl,"",3,-1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    RedictModuleCommand *command = RedictModule_GetCommand(ctx,"kspec.keyword");
    RedictModuleCommandInfo info = {
        .version = REDICTMODULE_COMMAND_INFO_VERSION,
        .key_specs = (RedictModuleCommandKeySpec[]){
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_KEYWORD,
                .bs.keyword.keyword = "KEYS",
                .bs.keyword.startfrom = 1,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {-1,1,0}
            },
            {0}
        }
    };
    if (RedictModule_SetCommandInfo(command, &info) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}

int createKspecComplex1(RedictModuleCtx *ctx) {
    /* First is a range a single key. The rest are keyword-based specs. */
    if (RedictModule_CreateCommand(ctx,"kspec.complex1",kspec_impl,"",1,1,1) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    RedictModuleCommand *command = RedictModule_GetCommand(ctx,"kspec.complex1");
    RedictModuleCommandInfo info = {
        .version = REDICTMODULE_COMMAND_INFO_VERSION,
        .key_specs = (RedictModuleCommandKeySpec[]){
            {
                .flags = REDICTMODULE_CMD_KEY_RO,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 1,
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_KEYWORD,
                .bs.keyword.keyword = "STORE",
                .bs.keyword.startfrom = 2,
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_KEYWORD,
                .bs.keyword.keyword = "KEYS",
                .bs.keyword.startfrom = 2,
                .find_keys_type = REDICTMODULE_KSPEC_FK_KEYNUM,
                .fk.keynum = {0,1,1}
            },
            {0}
        }
    };
    if (RedictModule_SetCommandInfo(command, &info) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}

int createKspecComplex2(RedictModuleCtx *ctx) {
    /* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */
    if (RedictModule_CreateCommand(ctx,"kspec.complex2",kspec_impl,"",0,0,0) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    RedictModuleCommand *command = RedictModule_GetCommand(ctx,"kspec.complex2");
    RedictModuleCommandInfo info = {
        .version = REDICTMODULE_COMMAND_INFO_VERSION,
        .key_specs = (RedictModuleCommandKeySpec[]){
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_KEYWORD,
                .bs.keyword.keyword = "STORE",
                .bs.keyword.startfrom = 5,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {0,1,0}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 1,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {0,1,0}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RO | REDICTMODULE_CMD_KEY_ACCESS,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 2,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {0,1,0}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_INDEX,
                .bs.index.pos = 3,
                .find_keys_type = REDICTMODULE_KSPEC_FK_KEYNUM,
                .fk.keynum = {0,1,1}
            },
            {
                .flags = REDICTMODULE_CMD_KEY_RW | REDICTMODULE_CMD_KEY_UPDATE,
                .begin_search_type = REDICTMODULE_KSPEC_BS_KEYWORD,
                .bs.keyword.keyword = "MOREKEYS",
                .bs.keyword.startfrom = 5,
                .find_keys_type = REDICTMODULE_KSPEC_FK_RANGE,
                .fk.range = {-1,1,0}
            },
            {0}
        }
    };
    if (RedictModule_SetCommandInfo(command, &info) == REDICTMODULE_ERR)
        return REDICTMODULE_ERR;

    return REDICTMODULE_OK;
}

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

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

    if (createKspecNone(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecNoneWithGetkeys(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecTwoRanges(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecTwoRangesWithGap(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecKeyword(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecComplex1(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    if (createKspecComplex2(ctx) == REDICTMODULE_ERR) return REDICTMODULE_ERR;
    return REDICTMODULE_OK;
}
