#ifndef RT_DS_H
#define RT_DS_H

/* Datastructure Library */

#include "runtime.h"

#ifdef __cplusplus
extern "C" {
#endif

/* A minheap.
 *
 * The memory pointed to by keys and values is not owned by the minheap
 * and instead provided by the caller. */
typedef struct {
    int *keys;
    void *values;
    size_t value_size;
    size_t capacity;
    int size;
} rt_minheap;

/* Comparison function for rtMinheapUpdate.
 * The function should return 0 if *a and *b are considered equal,
 * and a different (non-zero) value if they are non-equal.
 *
 * Note that memcmp fits this requirement.
 */
typedef int rt_minheap_cmp_fn(const void *a, const void *b, size_t n);

/* Takes the arrays and re-orders the values to create a minheap. */
RT_DLLEXPORT rt_minheap
rtCreateMinheap(int *keys, void *values, size_t value_size, size_t capacity, int initial_size);

/* Copies the value with the smallest key to min_value */
RT_DLLEXPORT void rtMinheapPeek(const rt_minheap *minheap, void *min_value);

/* Copies the value with the smallest key to min_value and removes it from the heap */
RT_DLLEXPORT void rtMinheapPop(rt_minheap *minheap, void *min_value);

/* Pushes a new value into the minheap */
RT_DLLEXPORT void rtMinheapPush(rt_minheap *minheap, int key, const void *value);

/* Changes the key of an existing value, or inserts it, if it's not found.
 *
 * Uses memcmp if cmp is NULL. */
RT_DLLEXPORT void
rtMinheapUpdate(rt_minheap *minheap, const void *value, int new_key, rt_minheap_cmp_fn *cmp);

static RT_INLINE int rtMinheapIsEmpty(rt_minheap *minheap) {
    return minheap->size == 0;
}

/* This function is expected to copy the contents of current (current_size bytes) to the new memory. */
typedef void *rt_hashtable_grow_memory_fn(void *current, size_t current_size, size_t new_size, void *userdata);

/* A hashtable. 
 * 
 * Maps 64 bit keys to 64 bit values. 
 * The hashtable is _not_ thread safe.
 */
typedef struct {
    uint64_t *keys;
    uint64_t *values;

    uint64_t capacity;
    uint64_t used_slots;

    void *grow_memory_userdata;
    rt_hashtable_grow_memory_fn *GrowMemoryCallback;
} rt_hashtable;

#define RT_HASH_TABLE_MEMORY_REQUIRED(_capacity) (2 * sizeof(uint64_t) * (_capacity))

/* Default implementation for allocating from an arena */
RT_DLLEXPORT void *
rtHashtableGrowMemoryFromArena(void *current, size_t current_size, size_t new_size, void *userdata);

/* Capacity must be a power of two.
 * If grow_memory_cb is NULL, the hashtable will not support growth above the initial capacity. */
RT_DLLEXPORT rt_hashtable rtCreateHashtable(uint64_t capacity,
                                            void *memory,
                                            rt_hashtable_grow_memory_fn *grow_memory_cb,
                                            void *userdata);

/* Returns
 * RT_OUT_OF_MEMORY if the hashtable is full or
 * RT_SUCCESS. */
RT_DLLEXPORT rt_result rtHashtableInsert(rt_hashtable *hashtable, uint64_t key, uint64_t value);

/* Returns default_value, if the key is not present in the hashtable */
RT_DLLEXPORT uint64_t rtHashtableLookup(const rt_hashtable *hashtable,
                                        uint64_t key,
                                        uint64_t default_value);

RT_DLLEXPORT void rtHashtableRemove(rt_hashtable *hashtable, uint64_t key);


/* A ringbuffer based queue. The queue is NOT threadsafe */
typedef struct {
    void *entries;

    uint64_t capacity;
    uint64_t head;
    uint64_t tail;
    uint64_t entry_size;
} rt_queue;

RT_DLLEXPORT rt_queue rtCreateQueue(uint64_t capacity, void *memory, uint64_t entry_size);

static RT_INLINE int rtQueueIsEmpty(const rt_queue *queue) {
    return queue->head == queue->tail;
}

static RT_INLINE int rtQueueIsFull(const rt_queue *queue) {
    return ((queue->tail + 1) % queue->capacity) == queue->head;
}

RT_DLLEXPORT rt_result rtQueuePush(rt_queue *queue, const void *element);

RT_DLLEXPORT rt_result rtQueuePop(rt_queue *queue, void *dst);

#ifdef __cplusplus
}
#endif

#endif
