// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef NETDATA_DICTIONARY_HASHTABLE_H
#define NETDATA_DICTIONARY_HASHTABLE_H

#include "dictionary-internals.h"

// ----------------------------------------------------------------------------
// hashtable operations with simple hashtable

static inline bool compare_keys(void *key1, void *key2) {
    const char *k1 = key1;
    const char *k2 = key2;
    return strcmp(k1, k2) == 0;
}

static inline void *item_to_key(DICTIONARY_ITEM *item) {
    return (void *)item_get_name(item);
}

#define SIMPLE_HASHTABLE_VALUE_TYPE DICTIONARY_ITEM
#define SIMPLE_HASHTABLE_NAME _DICTIONARY
#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION item_to_key
#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compare_keys
#include "..//simple_hashtable.h"

static inline size_t hashtable_init_hashtable(DICTIONARY *dict) {
    SIMPLE_HASHTABLE_DICTIONARY *ht = callocz(1, sizeof(*ht));
    simple_hashtable_init_DICTIONARY(ht, 4);
    dict->index.JudyHSArray = ht;
    return 0;
}

static inline size_t hashtable_destroy_hashtable(DICTIONARY *dict) {
    SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
    if(unlikely(!ht)) return 0;

    size_t mem = sizeof(*ht) + ht->size * sizeof(SIMPLE_HASHTABLE_SLOT_DICTIONARY);
    simple_hashtable_destroy_DICTIONARY(ht);
    freez(ht);
    dict->index.JudyHSArray = NULL;

    return mem;
}

static inline void *hashtable_insert_hashtable(DICTIONARY *dict, const char *name, size_t name_len) {
    SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;

    char key[name_len+1];
    memcpy(key, name, name_len);
    key[name_len] = '\0';

    XXH64_hash_t hash = XXH3_64bits(name, name_len);
    SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, true);
    sl->hash = hash; // we will need it in insert later - it is ok to overwrite - it is the same already
    return sl;
}

static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_hashtable(DICTIONARY *dict, void *handle) {
    (void)dict;
    SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = handle;
    DICTIONARY_ITEM *item = SIMPLE_HASHTABLE_SLOT_DATA(sl);
    return item;
}

static inline void hashtable_set_item_hashtable(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
    SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
    SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = handle;
    simple_hashtable_set_slot_DICTIONARY(ht, sl, sl->hash, item);
}

static inline int hashtable_delete_hashtable(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item_to_delete) {
    (void)item_to_delete;
    SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;

    char key[name_len+1];
    memcpy(key, name, name_len);
    key[name_len] = '\0';

    XXH64_hash_t hash = XXH3_64bits(name, name_len);
    SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, false);
    DICTIONARY_ITEM *item = SIMPLE_HASHTABLE_SLOT_DATA(sl);
    if(!item) return 0; // return not-found

    simple_hashtable_del_slot_DICTIONARY(ht, sl);
    return 1; // return deleted
}

static inline DICTIONARY_ITEM *hashtable_get_hashtable(DICTIONARY *dict, const char *name, size_t name_len) {
    SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
    if(unlikely(!ht)) return NULL;

    char key[name_len+1];
    memcpy(key, name, name_len);
    key[name_len] = '\0';

    XXH64_hash_t hash = XXH3_64bits(name, name_len);
    SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, true);
    return SIMPLE_HASHTABLE_SLOT_DATA(sl);
}

// ----------------------------------------------------------------------------
// hashtable operations with Judy

static inline size_t hashtable_init_judy(DICTIONARY *dict) {
    dict->index.JudyHSArray = NULL;
    return 0;
}

static inline size_t hashtable_destroy_judy(DICTIONARY *dict) {
    if(unlikely(!dict->index.JudyHSArray)) return 0;

    pointer_destroy_index(dict);

    JError_t J_Error;
    Word_t ret = JudyHSFreeArray(&dict->index.JudyHSArray, &J_Error);
    if(unlikely(ret == (Word_t) JERR)) {
        netdata_log_error("DICTIONARY: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
                          JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
    }

    netdata_log_debug(D_DICTIONARY, "Dictionary: hash table freed %lu bytes", ret);

    dict->index.JudyHSArray = NULL;
    return (size_t)ret;
}

static inline void *hashtable_insert_judy(DICTIONARY *dict, const char *name, size_t name_len) {
    JError_t J_Error;
    Pvoid_t *Rc = JudyHSIns(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
    if (unlikely(Rc == PJERR)) {
        netdata_log_error("DICTIONARY: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
                          name, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
    }

    // if *Rc == 0, new item added to the array
    // otherwise the existing item value is returned in *Rc

    // we return a pointer to a pointer, so that the caller can
    // put anything needed at the value of the index.
    // The pointer to pointer we return has to be used before
    // any other operation that may change the index (insert/delete).
    return (void *)Rc;
}

static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_judy(DICTIONARY *dict, void *handle) {
    (void)dict;
    DICTIONARY_ITEM **item_pptr = handle;
    return *item_pptr;
}

static inline void hashtable_set_item_judy(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
    (void)dict;
    DICTIONARY_ITEM **item_pptr = handle;
    *item_pptr = item;
}

static inline int hashtable_delete_judy(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item) {
    (void)item;
    if(unlikely(!dict->index.JudyHSArray)) return 0;

    JError_t J_Error;
    int ret = JudyHSDel(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
    if(unlikely(ret == JERR)) {
        netdata_log_error("DICTIONARY: Cannot delete entry with name '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d",
                          name,
                          JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
        return 0;
    }

    // Hey, this is problematic! We need the value back, not just an int with a status!
    // https://sourceforge.net/p/judy/feature-requests/23/

    if(unlikely(ret == 0)) {
        // not found in the dictionary
        return 0;
    }
    else {
        // found and deleted from the dictionary
        return 1;
    }
}

static inline DICTIONARY_ITEM *hashtable_get_judy(DICTIONARY *dict, const char *name, size_t name_len) {
    if(unlikely(!dict->index.JudyHSArray)) return NULL;

    Pvoid_t *Rc;
    Rc = JudyHSGet(dict->index.JudyHSArray, (void *)name, name_len);
    if(likely(Rc)) {
        // found in the hash table
        pointer_check(dict, (DICTIONARY_ITEM *)*Rc);
        return (DICTIONARY_ITEM *)*Rc;
    }
    else {
        // not found in the hash table
        return NULL;
    }
}

// --------------------------------------------------------------------------------------------------------------------
// select the right hashtable

static inline size_t hashtable_init_unsafe(DICTIONARY *dict) {
    if(dict->options & DICT_OPTION_INDEX_JUDY)
        return hashtable_init_judy(dict);
    else
        return hashtable_init_hashtable(dict);
}

static inline size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
    pointer_destroy_index(dict);

    if(dict->options & DICT_OPTION_INDEX_JUDY)
        return hashtable_destroy_judy(dict);
    else
        return hashtable_destroy_hashtable(dict);
}

static inline void *hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
    if(dict->options & DICT_OPTION_INDEX_JUDY)
        return hashtable_insert_judy(dict, name, name_len);
    else
        return hashtable_insert_hashtable(dict, name, name_len);
}

static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_unsafe(DICTIONARY *dict, void *handle) {
    if(dict->options & DICT_OPTION_INDEX_JUDY)
        return hashtable_insert_handle_to_item_judy(dict, handle);
    else
        return hashtable_insert_handle_to_item_hashtable(dict, handle);
}

static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item) {
    if(dict->options & DICT_OPTION_INDEX_JUDY)
        return hashtable_delete_judy(dict, name, name_len, item);
    else
        return hashtable_delete_hashtable(dict, name, name_len, item);
}

static inline DICTIONARY_ITEM *hashtable_get_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
    DICTIONARY_STATS_SEARCHES_PLUS1(dict);

    DICTIONARY_ITEM *item;

    if(dict->options & DICT_OPTION_INDEX_JUDY)
        item = hashtable_get_judy(dict, name, name_len);
    else
        item = hashtable_get_hashtable(dict, name, name_len);

    if(item)
        pointer_check(dict, item);

    return item;
}

static inline void hashtable_set_item_unsafe(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
    if(dict->options & DICT_OPTION_INDEX_JUDY)
        hashtable_set_item_judy(dict, handle, item);
    else
        hashtable_set_item_hashtable(dict, handle, item);
}

#endif //NETDATA_DICTIONARY_HASHTABLE_H
