#pragma once
#include "cli_common.h"
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h>

#ifdef __cplusplus
extern "C" {
#endif

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

#define OUTPUT_STANDARD 0
#define OUTPUT_RAW 1
#define OUTPUT_CSV 2
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"

#define CLUSTER_MANAGER_SLOTS               16384
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT     60000
#define CLUSTER_MANAGER_MIGRATE_PIPELINE    10
#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2

#define CLUSTER_MANAGER_INVALID_HOST_ARG \
    "[ERR] Invalid arguments: you need to pass either a valid " \
    "address (ie. 120.0.0.1:7000) or space separated IP " \
    "and port (ie. 120.0.0.1 7000)\n"
#define CLUSTER_MANAGER_MODE() (config.cluster_manager_command.name != NULL)
#define CLUSTER_MANAGER_MASTERS_COUNT(nodes, replicas) (nodes/(replicas + 1))
#define CLUSTER_MANAGER_COMMAND(n,...) \
        (redisCommand(n->context, __VA_ARGS__))

#define CLUSTER_MANAGER_NODE_ARRAY_FREE(array) zfree(array->alloc)

#define CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err) \
    clusterManagerLogErr("Node %s:%d replied with error:\n%s\n", \
                         n->ip, n->port, err);

#define clusterManagerLogInfo(...) \
    clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_INFO,__VA_ARGS__)

#define clusterManagerLogErr(...) \
    clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_ERR,__VA_ARGS__)

#define clusterManagerLogWarn(...) \
    clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_WARN,__VA_ARGS__)

#define clusterManagerLogOk(...) \
    clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_SUCCESS,__VA_ARGS__)

#define CLUSTER_MANAGER_FLAG_MYSELF     1 << 0
#define CLUSTER_MANAGER_FLAG_SLAVE      1 << 1
#define CLUSTER_MANAGER_FLAG_FRIEND     1 << 2
#define CLUSTER_MANAGER_FLAG_NOADDR     1 << 3
#define CLUSTER_MANAGER_FLAG_DISCONNECT 1 << 4
#define CLUSTER_MANAGER_FLAG_FAIL       1 << 5

#define CLUSTER_MANAGER_CMD_FLAG_FIX            1 << 0
#define CLUSTER_MANAGER_CMD_FLAG_SLAVE          1 << 1
#define CLUSTER_MANAGER_CMD_FLAG_YES            1 << 2
#define CLUSTER_MANAGER_CMD_FLAG_AUTOWEIGHTS    1 << 3
#define CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER    1 << 4
#define CLUSTER_MANAGER_CMD_FLAG_SIMULATE       1 << 5
#define CLUSTER_MANAGER_CMD_FLAG_REPLACE        1 << 6
#define CLUSTER_MANAGER_CMD_FLAG_COPY           1 << 7
#define CLUSTER_MANAGER_CMD_FLAG_COLOR          1 << 8
#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS   1 << 9
#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
#define CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY 1 << 11
#define CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY 1 << 12

#define CLUSTER_MANAGER_OPT_GETFRIENDS  1 << 0
#define CLUSTER_MANAGER_OPT_COLD        1 << 1
#define CLUSTER_MANAGER_OPT_UPDATE      1 << 2
#define CLUSTER_MANAGER_OPT_QUIET       1 << 6
#define CLUSTER_MANAGER_OPT_VERBOSE     1 << 7

#define CLUSTER_MANAGER_LOG_LVL_INFO    1
#define CLUSTER_MANAGER_LOG_LVL_WARN    2
#define CLUSTER_MANAGER_LOG_LVL_ERR     3
#define CLUSTER_MANAGER_LOG_LVL_SUCCESS 4

#define CLUSTER_JOIN_CHECK_AFTER        20

#define LOG_COLOR_BOLD      "29;1m"
#define LOG_COLOR_RED       "31;1m"
#define LOG_COLOR_GREEN     "32;1m"
#define LOG_COLOR_YELLOW    "33;1m"
#define LOG_COLOR_RESET     "0m"

/* cliConnect() flags. */
#define CC_FORCE (1<<0)         /* Re-connect if already connected. */
#define CC_QUIET (1<<1)         /* Don't log connecting errors. */

/* Dict Helpers */

uint64_t dictSdsHash(const void *key);
int dictSdsKeyCompare(void *privdata, const void *key1,
    const void *key2);
void dictSdsDestructor(void *privdata, void *val);
void dictListDestructor(void *privdata, void *val);

/* Cluster Manager Command Info */
typedef struct clusterManagerCommand {
    char *name;
    int argc;
    char **argv;
    int flags;
    int replicas;
    char *from;
    char *to;
    char **weight;
    int weight_argc;
    char *master_id;
    int slots;
    int timeout;
    int pipeline;
    float threshold;
    char *backup_dir;
    char *from_user;
    char *from_pass;
    int from_askpass;
} clusterManagerCommand;

void createClusterManagerCommand(char *cmdname, int argc, char **argv);

extern redisContext *context;
extern struct config {
    char *hostip;
    int hostport;
    char *hostsocket;
    int tls;
    cliSSLconfig sslconfig;
    char *sni;
    char *cacert;
    char *cacertdir;
    char *cert;
    char *key;
    long repeat;
    long interval;
    int dbnum; /* db num currently selected */
    int input_dbnum; /* db num user input */
    int interactive;
    int shutdown;
    int monitor_mode;
    int pubsub_mode;
    int latency_mode;
    int latency_dist_mode;
    int latency_history;
    int lru_test_mode;
    long long lru_test_sample_size;
    int cluster_mode;
    int cluster_reissue_command;
    int cluster_send_asking;
    int slave_mode;
    int pipe_mode;
    int pipe_timeout;
    int getrdb_mode;
    int stat_mode;
    int scan_mode;
    int intrinsic_latency_mode;
    int intrinsic_latency_duration;
    sds pattern;
    char *rdb_filename;
    int bigkeys;
    int memkeys;
    unsigned memkeys_samples;
    int hotkeys;
    int stdinarg; /* get last arg from stdin. (-x option) */
    char *auth;
    int askpass;
    char *user;
    int output; /* output mode, see OUTPUT_* defines */
    int push_output; /* Should we display spontaneous PUSH replies */
    sds mb_delim;
    sds cmd_delim;
    char prompt[128];
    char *eval;
    int eval_ldb;
    int eval_ldb_sync;  /* Ask for synchronous mode of the Lua debugger. */
    int eval_ldb_end;   /* Lua debugging session ended. */
    int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
    int last_cmd_type;
    int verbose;
    int set_errcode;
    clusterManagerCommand cluster_manager_command;
    int no_auth_warning;
    int resp3;
    int disable_motd;
    int in_multi;
    int pre_multi_dbnum;
    int quoted_input;   /* Force input args to be treated as quoted strings */
    int force_mode;
} config;

struct clusterManager {
    list *nodes;    /* List of nodes in the configuration. */
    list *errors;
    int unreachable_masters;    /* Masters we are not able to reach. */
};
extern struct clusterManager cluster_manager;

typedef struct clusterManagerNode {
    redisContext *context;
    sds name;
    char *ip;
    int port;
    uint64_t current_epoch;
    time_t ping_sent;
    time_t ping_recv;
    int flags;
    list *flags_str; /* Flags string representations */
    sds replicate;  /* Master ID if node is a slave */
    int dirty;      /* Node has changes that can be flushed */
    uint8_t slots[CLUSTER_MANAGER_SLOTS];
    int slots_count;
    int replicas_count;
    list *friends;
    sds *migrating; /* An array of sds where even strings are slots and odd
                     * strings are the destination node IDs. */
    sds *importing; /* An array of sds where even strings are slots and odd
                     * strings are the source node IDs. */
    int migrating_count; /* Length of the migrating array (migrating slots*2) */
    int importing_count; /* Length of the importing array (importing slots*2) */
    float weight;   /* Weight used by rebalance */
    int balance;    /* Used by rebalance */
} clusterManagerNode;

/* Data structure used to represent a sequence of cluster nodes. */
typedef struct clusterManagerNodeArray {
    clusterManagerNode **nodes; /* Actual nodes array */
    clusterManagerNode **alloc; /* Pointer to the allocated memory */
    int len;                    /* Actual length of the array */
    int count;                  /* Non-NULL nodes count */
} clusterManagerNodeArray;

/* Used for the reshard table. */
typedef struct clusterManagerReshardTableItem {
    clusterManagerNode *source;
    int slot;
} clusterManagerReshardTableItem;

/* Info about a cluster internal link. */

typedef struct clusterManagerLink {
    sds node_name;
    sds node_addr;
    int connected;
    int handshaking;
} clusterManagerLink;

typedef struct typeinfo {
    char *name;
    char *sizecmd;
    char *sizeunit;
    unsigned long long biggest;
    unsigned long long count;
    unsigned long long totalsize;
    sds biggest_key;
} typeinfo;

extern typeinfo type_string;
extern typeinfo type_list;
extern typeinfo type_set;
extern typeinfo type_hash;
extern typeinfo type_zset;
extern typeinfo type_stream;
extern typeinfo type_other;

void findBigKeys(int memkeys, unsigned memkeys_samples);
int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
    int ip_count, clusterManagerNode ***offending, int *offending_len);
int clusterManagerFixMultipleSlotOwners(int slot, list *owners);
void getKeySizes(redisReply *keys, struct typeinfo **types,
    unsigned long long *sizes, int memkeys,
    unsigned memkeys_samples);
int clusterManagerFixOpenSlot(int slot);
void clusterManagerPrintSlotsList(list *slots);
int clusterManagerGetCoveredSlots(char *all_slots);
void clusterManagerOnError(sds err);
int clusterManagerIsConfigConsistent(int fLog);
void freeClusterManagerNode(clusterManagerNode *node);
void clusterManagerLog(int level, const char* fmt, ...);
int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
    int *bus_port_ptr);
int clusterManagerCheckRedisReply(clusterManagerNode *n,
    redisReply *r, char **err);
int confirmWithYes(const char *msg, int force);
int clusterManagerSetSlotOwner(clusterManagerNode *owner,
    int slot,
    int do_clear);
clusterManagerNode * clusterManagerGetNodeWithMostKeysInSlot(list *nodes,
    int slot,
    char **err);
int clusterManagerSetSlot(clusterManagerNode *node1,
    clusterManagerNode *node2,
    int slot, const char *status, char **err);
int clusterManagerMoveSlot(clusterManagerNode *source,
    clusterManagerNode *target,
    int slot, int opts,  char**err);
int clusterManagerClearSlotStatus(clusterManagerNode *node, int slot);
void clusterManagerShowNodes(void);
signed int clusterManagerCountKeysInSlot(clusterManagerNode *node,
    int slot);
void type_free(void* priv_data, void* val);
int getDbSize(void);
redisReply *sendScan(unsigned long long *it);

#ifdef __cplusplus
}
#endif

