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

#include "../libnetdata.h"

#ifndef NETDATA_ADAPTIVE_RESORTABLE_LIST_H
#define NETDATA_ADAPTIVE_RESORTABLE_LIST_H 1

#define ARL_ENTRY_FLAG_FOUND    0x01    // the entry has been found in the source data
#define ARL_ENTRY_FLAG_EXPECTED 0x02    // the entry is expected by the program
#define ARL_ENTRY_FLAG_DYNAMIC  0x04    // the entry was dynamically allocated, from source data

typedef struct arl_entry {
    char *name;             // the keywords
    uint32_t hash;          // the hash of the keyword

    void *dst;              // the dst to pass to the processor

    uint8_t flags;          // ARL_ENTRY_FLAG_*

    // the processor to do the job
    void (*processor)(const char *name, uint32_t hash, const char *value, void *dst);

    // double linked list for fast re-linkings
    struct arl_entry *prev, *next;
} ARL_ENTRY;

typedef struct arl_base {
    char *name;

    size_t iteration;   // incremented on each iteration (arl_begin())
    size_t found;       // the number of expected keywords found in this iteration
    size_t expected;    // the number of expected keywords
    size_t wanted;      // the number of wanted keywords
                        // i.e. the number of keywords found and expected

    size_t relinkings;  // the number of relinkings we have made so far

    size_t allocated;   // the number of keywords allocated
    size_t fred;        // the number of keywords cleaned up

    size_t rechecks;    // the number of iterations between re-checks of the
                        // wanted number of keywords
                        // this is only needed in cases where the source
                        // is having less lines over time.

    size_t added;       // it is non-zero if new keywords have been added
                        // this is only needed to detect new lines have
                        // been added to the file, over time.

#ifdef NETDATA_INTERNAL_CHECKS
    size_t fast;        // the number of times we have taken the fast path
    size_t slow;        // the number of times we have taken the slow path
#endif

    // the processor to do the job
    void (*processor)(const char *name, uint32_t hash, const char *value, void *dst);

    // the linked list of the keywords
    ARL_ENTRY *head;

    // since we keep the list of keywords sorted (as found in the source data)
    // this is next keyword that we expect to find in the source data.
    ARL_ENTRY *next_keyword;
} ARL_BASE;

// create a new ARL
ARL_BASE *arl_create(const char *name, void (*processor)(const char *, uint32_t, const char *, void *), size_t rechecks);

// free an ARL
void arl_free(ARL_BASE *arl_base);

// register an expected keyword to the ARL
// together with its destination ( i.e. the output of the processor() )
ARL_ENTRY *arl_expect_custom(ARL_BASE *base, const char *keyword, void (*processor)(const char *name, uint32_t hash, const char *value, void *dst), void *dst);
#define arl_expect(base, keyword, dst) arl_expect_custom(base, keyword, NULL, dst)

// an internal call to complete the check() call
int arl_find_or_create_and_relink(ARL_BASE *base, const char *s, const char *value);

// begin an ARL iteration
void arl_begin(ARL_BASE *base);

void arl_callback_str2ull(const char *name, uint32_t hash, const char *value, void *dst);
void arl_callback_str2kernel_uint_t(const char *name, uint32_t hash, const char *value, void *dst);
void arl_callback_ssize_t(const char *name, uint32_t hash, const char *value, void *dst);

// check a keyword against the ARL
// this is to be called for each keyword read from source data
// s = the keyword, as collected
// src = the src data to be passed to the processor
// it is defined in the header file in order to be inlined
static inline int arl_check(ARL_BASE *base, const char *keyword, const char *value) {
    ARL_ENTRY *e = base->next_keyword;

#ifdef NETDATA_INTERNAL_CHECKS
    if(unlikely((base->fast + base->slow) % (base->expected + base->allocated) == 0 && (base->fast + base->slow) > (base->expected + base->allocated) * base->iteration))
        netdata_log_info("ARL '%s': Did you forget to call arl_begin()?", base->name);
#endif

    // it should be the first entry (pointed by base->next_keyword)
    if(likely(!strcmp(keyword, e->name))) {
        // it is

#ifdef NETDATA_INTERNAL_CHECKS
        base->fast++;
#endif

        e->flags |= ARL_ENTRY_FLAG_FOUND;

        // execute the processor
        if(unlikely(e->dst)) {
            e->processor(e->name, e->hash, value, e->dst);
            base->found++;
        }

        // be prepared for the next iteration
        base->next_keyword = e->next;
        if(unlikely(!base->next_keyword))
            base->next_keyword = base->head;

        // stop if we collected all the values for this iteration
        if(unlikely(base->found == base->wanted)) {
            // fprintf(stderr, "FOUND ALL WANTED 2: found = %zu, wanted = %zu, expected %zu\n", base->found, base->wanted, base->expected);
            return 1;
        }

        return 0;
    }

#ifdef NETDATA_INTERNAL_CHECKS
    base->slow++;
#endif

    // we read from source, a not-expected keyword
    return arl_find_or_create_and_relink(base, keyword, value);
}

#endif //NETDATA_ADAPTIVE_RESORTABLE_LIST_H
