#pragma once
#include "common.hpp"
#include "config.hpp"
#include "test.hpp"


typedef uint32_t cachekey_t;
#define CACHEKEY_HEX_LEN 8


cachekey_t hash(const char*);
cachekey_t hash(const char*, size_t);
cachekey_t hash(const char*, size_t, cachekey_t);

void hash2str(cachekey_t, char*);


template <class T> class HashMap {
    private:
        T* map;
        const size_t size;

    public:
        HashMap(size_t);
        ~HashMap();

        INLINE T* begin() { return map; } ///< iterator: first valid entry
        INLINE T* end() { return map + size; } ///< iterator: first invalid entry
        INLINE T* at(cachekey_t pos) { return map + (pos % size); };
        INLINE T* operator[](cachekey_t k) { return at(k); }
        INLINE const T* at(cachekey_t pos) const { return map + (pos % size); };
        INLINE const T* operator[](cachekey_t k) const { return at(k); }

        void clean() { memset(map, 0, size*sizeof(T)); } ///< full reset to zeroes (slow)
};


template <class K, class T> class HashCache: public ConfigInst<size_t> {
    private:
        typedef struct {
            K key;
            T data;
        } node_t;
        HashMap<node_t> map;
        cachekey_t revision;

    public:
        HashCache(size_t);

        // allow cachekey versions, as well s.t. it is not always recomputed:
        T* at(const K&); ///< returns entry if it exists NOTE: make sure to use strncpy ot sth similar s.t. keys are padded for hashing
        const T* at(const K&) const; ///< returns entry if it exists NOTE: make sure to use strncpy ot sth similar s.t. keys are padded for hashing
        T at(const K&, const T&) const; ///< returns entry or default value
        T* get(const K&); ///< returns entry and overwrites hash
        T* get(const K&, bool&); ///< returns entry and overwrites hash upon miss, signalling whether it was a hit or not
        T* it(T* = NULL); ///< returns incremented "iterator"

        void invalidate() { ++revision; } ; ///< cause cache misses (fast)
        bool is_valid() const { return true; }
};


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


template <class T> HashMap<T>::HashMap(size_t _size): size(_size) {
    assert(size);
    map = (T*)calloc(size, sizeof(T));
}


template <class T> HashMap<T>::~HashMap() {
    free(map);
}


template <class K, class T> HashCache<K, T>::HashCache(size_t size): map(size), revision(0) {
}


template <class K, class T> T* HashCache<K, T>::at(const K& key) {
    node_t* node = map.at(hash((const char*)&key, sizeof(K)) + revision);
    if (memcmp(&node->key, &key, sizeof(K)) != 0) {
        return NULL;
    }
    return &node->data;
}


template <class K, class T> const T* HashCache<K, T>::at(const K& key) const {
    const node_t* node = map.at(hash((const char*)&key, sizeof(K)) + revision);
    if (memcmp(&node->key, &key, sizeof(K)) != 0) {
        return NULL;
    }
    return &node->data;
}


template <class K, class T> T HashCache<K, T>::at(const K& key, const T& def) const {
    const T* t = at(key);
    return t? *t: def;
}


template <class K, class T> T* HashCache<K, T>::get(const K& key) {
    node_t* node = map.at(hash((const char*)&key, sizeof(K)) + revision);
    memcpy((void*)&node->key, (const void*)&key, sizeof(K));
    return &node->data;
}


template <class K, class T> T* HashCache<K, T>::get(const K& key, bool& hit) {
    node_t* node = map.at(hash((const char*)&key, sizeof(K)) + revision);
    if (memcmp(&node->key, &key, sizeof(K)) != 0) {
        memcpy(&node->key, &key, sizeof(K));
        hit = false;
    } else {
        hit = true;
    }
    return &node->data;
}


template <class K, class T> T* HashCache<K, T>::it(T* last) {
    if (!last) {
        return &map.begin()->data;
    }
    last = (T*)((char*)last + sizeof(node_t));
    return ((node_t*)last >= map.end())? NULL: last;
}