#ifndef UTIL_H
#define UTIL_H

#include "sys.h"
#include "string.h"
#include "memory.h"
#include "arena.h"
#include "atomic.h"

/* Utility functions and stuff that don't have a home :( */

/* ========================== *
 * Hash utils
 * ========================== */

/* FNV-1a parameters for different hash sizes:
 * https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV_hash_parameters
 */

#define HASH_FNV64_BASIS 0xCBF29CE484222325
INLINE u64 hash_fnv64(u64 seed, struct buffer buff)
{
    u64 hash = seed;
    for (u64 i = 0; i < buff.size; ++i) {
        hash ^= (u8)buff.data[i];
        hash *= 0x100000001B3;
    }
    return hash;
}

#define HASH_FNV128_BASIS U128(0x6C62272E07BB0142, 0x62B821756295C58D)
INLINE u128 hash_fnv128(u128 seed, struct buffer buff)
{
    /* FIXME: Verify MSVC version of 128 is same */
    u128 hash = seed;
    for (u64 i = 0; i < buff.size; ++i) {
        u8 c = (u8)buff.data[i];
        hash = u128_xor_u8(hash, c);
        hash = u128_mul(hash, U128(0x1000000, 0x000000000000013B));
    }
    return hash;
}

/* ========================== *
 * Fixed Dict
 *
 * Simple fixed bucket-count string->value chaining dict for generic use
 * ========================== */

struct fixed_dict_entry {
    struct string key;
    void *value;
    u64 hash;
    struct fixed_dict_entry *next;
};

struct fixed_dict_bucket {
    struct fixed_dict_entry *entry_head;
};

struct fixed_dict {
    u64 buckets_count;
    struct fixed_dict_bucket *buckets;
};

INLINE struct fixed_dict fixed_dict_init(struct arena *arena, u64 buckets_count)
{
    __prof;
    struct fixed_dict dict = { 0 };
    buckets_count = max_u64(buckets_count, 1);  /* Ensure at least 1 bucket */
    dict.buckets_count = buckets_count;
    dict.buckets = arena_push_array_zero(arena, struct fixed_dict_bucket, buckets_count);
    return dict;
}

/* arena and key must share lifetime with dict (this function does not copy the key) */
INLINE void fixed_dict_set(struct arena *arena, struct fixed_dict *dict, struct string key, void *value)
{
    __prof;

    u64 hash = hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRING(key));
    u64 index = hash % dict->buckets_count;
    struct fixed_dict_bucket *bucket = &dict->buckets[index];

    struct fixed_dict_entry *entry = bucket->entry_head;
    while (entry) {
        if (hash == entry->hash && string_eq(key, entry->key)) {
            /* Existing match found, replace its contents */
            entry->key = key;
            entry->value = value;
            return;
        }
        entry = entry->next;
    }

    /* No match found, create new entry */
    entry = arena_push(arena, struct fixed_dict_entry);
    entry->key = key;
    entry->value = value;
    entry->hash = hash;
    entry->next = bucket->entry_head;

    bucket->entry_head = entry;
}

INLINE void *fixed_dict_get(const struct fixed_dict *dict, struct string key)
{
    __prof;

    u64 hash = hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRING(key));
    u64 index = hash % dict->buckets_count;
    struct fixed_dict_bucket *bucket = &dict->buckets[index];

    for (struct fixed_dict_entry *entry = bucket->entry_head; entry; entry = entry->next) {
        if (hash == entry->hash && string_eq(key, entry->key)) {
            /* Match found */
            return entry->value;
        }
    }

    return NULL;
}

/* ========================== *
 * Sync flag
 * ========================== */

struct sync_flag {
    /* TODO: Make this a rw mutex? */
    struct sys_mutex mutex;
    struct sys_condition_variable cv;
    struct atomic_i32 flag;
};

INLINE struct sync_flag sync_flag_alloc(void)
{
    struct sync_flag sf = { 0 };
    sf.mutex = sys_mutex_alloc();
    sf.cv = sys_condition_variable_alloc();
    return sf;
}

INLINE void sync_flag_release(struct sync_flag *sf)
{
    sys_mutex_release(&sf->mutex);
    sys_condition_variable_release(&sf->cv);
}

INLINE void sync_flag_set(struct sync_flag *sf)
{
    __prof;
    if (atomic_i32_eval_compare_exchange(&sf->flag, 0, 1) == 0) {
        sys_condition_variable_broadcast(&sf->cv);
    }
}

INLINE void sync_flag_wait(struct sync_flag *sf)
{
    __prof;
    while (atomic_i32_eval(&sf->flag) == 0) {
        sys_mutex_lock(&sf->mutex);
        {
            sys_condition_variable_wait(&sf->cv, &sf->mutex);
        }
        sys_mutex_unlock(&sf->mutex);
    }
}

/* ========================== *
 * Sleep frame
 * ========================== */

INLINE void sleep_frame(sys_timestamp_t last_frame_time, f64 target_dt)
{
    __prof;
    if (last_frame_time != 0 && target_dt > 0) {
        f64 last_frame_dt = sys_timestamp_seconds(sys_timestamp() - last_frame_time);
        f64 sleep_time = target_dt - last_frame_dt;
        if (sleep_time > 0) {
            sys_sleep_precise(sleep_time);
        }
    }
}

#if 0
/* ========================== *
 * Sync buffer
 * ========================== */

struct sync_buff {
    struct arena arena;
    struct sys_mutex mutex;
};

struct sync_buff sync_buff_alloc(u64 arena_reserve)
{
}

void sync_buff_release(struct sync_buff *sb)
{

}

INLINE void sync_buff_read(void)
{
}

INLINE void sync_buff_write(void)
{
}
#endif

#endif
