/*
 * DWN - Desktop Window Manager
 * Abstract Hash Map Implementation
 */

#include "core/wm_hashmap.h"
#include "core/wm_string.h"

#include <stdlib.h>
#include <string.h>

/*==============================================================================
 * Constants
 *============================================================================*/

#define WM_HASHMAP_INITIAL_CAPACITY 16
#define WM_HASHMAP_LOAD_FACTOR 0.75

/*==============================================================================
 * Entry Structure
 *============================================================================*/

typedef struct WmHashMapEntry {
    uint32_t hash;
    void *key;
    void *value;
    struct WmHashMapEntry *next;
} WmHashMapEntry;

/*==============================================================================
 * Hash Map Structure
 *============================================================================*/

struct WmHashMap {
    WmHashMapEntry **buckets;
    size_t bucket_count;
    size_t size;
    WmHashFunc hash_func;
    WmKeyEqualFunc key_equal;
    WmFreeFunc key_free;
    WmFreeFunc value_free;
};

/*==============================================================================
 * Internal Helpers
 *============================================================================*/

static void hashmap_clone_insert(void *key, void *value, void *user_data) {
    wm_hashmap_insert((WmHashMap *)user_data, key, value);
}

/*==============================================================================
 * Default Hash Functions
 *============================================================================*/

static uint32_t wm_hash_pointer(const void *key) {
    /* FNV-1a hash for pointer */
    uintptr_t ptr = (uintptr_t)key;
    uint32_t hash = 2166136261u;
    for (size_t i = 0; i < sizeof(ptr); i++) {
        hash ^= (ptr >> (i * 8)) & 0xFF;
        hash *= 16777619u;
    }
    return hash;
}

static uint32_t wm_hash_string(const void *key) {
    const char *str = key;
    uint32_t hash = 2166136261u;
    while (*str) {
        hash ^= (uint8_t)*str++;
        hash *= 16777619u;
    }
    return hash;
}

static bool wm_equal_pointer(const void *a, const void *b) {
    return a == b;
}

static bool wm_equal_string(const void *a, const void *b) {
    return strcmp((const char*)a, (const char*)b) == 0;
}

/*==============================================================================
 * Internal Helpers
 *============================================================================*/

static bool wm_hashmap_resize(WmHashMap *map) {
    size_t old_count = map->bucket_count;
    WmHashMapEntry **old_buckets = map->buckets;
    
    size_t new_count = old_count * 2;
    WmHashMapEntry **new_buckets = calloc(new_count, sizeof(WmHashMapEntry*));
    if (!new_buckets) return false;
    
    map->buckets = new_buckets;
    map->bucket_count = new_count;
    
    /* Rehash all entries */
    for (size_t i = 0; i < old_count; i++) {
        WmHashMapEntry *entry = old_buckets[i];
        while (entry) {
            WmHashMapEntry *next = entry->next;
            size_t new_index = entry->hash % new_count;
            entry->next = new_buckets[new_index];
            new_buckets[new_index] = entry;
            entry = next;
        }
    }
    
    free(old_buckets);
    return true;
}

/*==============================================================================
 * Lifecycle
 *============================================================================*/

WmHashMap* wm_hashmap_new(void) {
    return wm_hashmap_new_full(NULL, NULL, NULL, NULL);
}

WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
                                WmFreeFunc key_free, WmFreeFunc value_free) {
    WmHashMap *map = calloc(1, sizeof(WmHashMap));
    if (!map) return NULL;
    
    map->bucket_count = WM_HASHMAP_INITIAL_CAPACITY;
    map->buckets = calloc(map->bucket_count, sizeof(WmHashMapEntry*));
    if (!map->buckets) {
        free(map);
        return NULL;
    }
    
    map->hash_func = hash_func ? hash_func : wm_hash_pointer;
    map->key_equal = key_equal ? key_equal : wm_equal_pointer;
    map->key_free = key_free;
    map->value_free = value_free;
    
    return map;
}

WmHashMap* wm_hashmap_new_string_key(void) {
    return wm_hashmap_new_full(wm_hash_string, wm_equal_string, free, NULL);
}

void wm_hashmap_destroy(WmHashMap *map) {
    if (!map) return;
    wm_hashmap_clear(map);
    free(map->buckets);
    free(map);
}

WmHashMap* wm_hashmap_clone(const WmHashMap *map) {
    if (!map) return NULL;
    
    WmHashMap *clone = wm_hashmap_new_full(map->hash_func, map->key_equal,
                                            NULL, NULL);
    if (!clone) return NULL;
    
    /* Note: This creates a shallow copy */
    wm_hashmap_foreach(map, hashmap_clone_insert, clone);
    
    clone->key_free = map->key_free;
    clone->value_free = map->value_free;
    
    return clone;
}

/*==============================================================================
 * Capacity
 *============================================================================*/

size_t wm_hashmap_size(const WmHashMap *map) {
    return map ? map->size : 0;
}

bool wm_hashmap_is_empty(const WmHashMap *map) {
    return !map || map->size == 0;
}

void wm_hashmap_clear(WmHashMap *map) {
    if (!map) return;
    
    for (size_t i = 0; i < map->bucket_count; i++) {
        WmHashMapEntry *entry = map->buckets[i];
        while (entry) {
            WmHashMapEntry *next = entry->next;
            if (map->key_free && entry->key) map->key_free(entry->key);
            if (map->value_free && entry->value) map->value_free(entry->value);
            free(entry);
            entry = next;
        }
        map->buckets[i] = NULL;
    }
    map->size = 0;
}

/*==============================================================================
 * Element Access
 *============================================================================*/

void* wm_hashmap_get(const WmHashMap *map, const void *key) {
    if (!map || !key) return NULL;
    
    uint32_t hash = map->hash_func(key);
    size_t index = hash % map->bucket_count;
    
    WmHashMapEntry *entry = map->buckets[index];
    while (entry) {
        if (entry->hash == hash && map->key_equal(entry->key, key)) {
            return entry->value;
        }
        entry = entry->next;
    }
    return NULL;
}

void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val) {
    void *val = wm_hashmap_get(map, key);
    return val ? val : default_val;
}

bool wm_hashmap_contains(const WmHashMap *map, const void *key) {
    return wm_hashmap_get(map, key) != NULL;
}

/*==============================================================================
 * Modifiers
 *============================================================================*/

bool wm_hashmap_insert(WmHashMap *map, void *key, void *value) {
    if (!map || !key) return false;
    
    /* Check if resize needed */
    if ((double)map->size / map->bucket_count > WM_HASHMAP_LOAD_FACTOR) {
        if (!wm_hashmap_resize(map)) return false;
    }
    
    uint32_t hash = map->hash_func(key);
    size_t index = hash % map->bucket_count;
    
    /* Check if key exists */
    WmHashMapEntry *entry = map->buckets[index];
    while (entry) {
        if (entry->hash == hash && map->key_equal(entry->key, key)) {
            /* Update existing entry */
            if (map->value_free) map->value_free(entry->value);
            entry->value = value;
            return true;
        }
        entry = entry->next;
    }
    
    /* Insert new entry */
    entry = malloc(sizeof(WmHashMapEntry));
    if (!entry) return false;
    
    entry->hash = hash;
    entry->key = key;
    entry->value = value;
    entry->next = map->buckets[index];
    map->buckets[index] = entry;
    map->size++;
    
    return true;
}

bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value) {
    if (!map || !key) return false;
    if (wm_hashmap_contains(map, key)) return false;
    return wm_hashmap_insert(map, key, value);
}

void* wm_hashmap_set(WmHashMap *map, void *key, void *value) {
    if (!map || !key) return NULL;
    
    void *old_value = wm_hashmap_get(map, key);
    wm_hashmap_insert(map, key, value);
    return old_value;
}

void* wm_hashmap_remove(WmHashMap *map, const void *key) {
    if (!map || !key) return NULL;
    
    uint32_t hash = map->hash_func(key);
    size_t index = hash % map->bucket_count;
    
    WmHashMapEntry *entry = map->buckets[index];
    WmHashMapEntry *prev = NULL;
    
    while (entry) {
        if (entry->hash == hash && map->key_equal(entry->key, key)) {
            void *value = entry->value;
            
            if (prev) {
                prev->next = entry->next;
            } else {
                map->buckets[index] = entry->next;
            }
            
            if (map->key_free) map->key_free(entry->key);
            free(entry);
            map->size--;
            
            return value;
        }
        prev = entry;
        entry = entry->next;
    }
    
    return NULL;
}

bool wm_hashmap_steal(WmHashMap *map, const void *key) {
    return wm_hashmap_remove(map, key) != NULL;
}

/*==============================================================================
 * Iteration
 *============================================================================*/

typedef struct WmHashMapIterInternal {
    WmHashMap *map;
    size_t bucket;
    WmHashMapEntry *entry;
    WmHashMapEntry *next_entry;
} WmHashMapIterInternal;

void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map) {
    if (!iter) return;
    
    iter->map = map;
    iter->bucket = 0;
    iter->entry = NULL;
    iter->next_entry = NULL;
    
    if (!map) return;
    
    /* Find first bucket with entry */
    for (size_t i = 0; i < map->bucket_count; i++) {
        if (map->buckets[i]) {
            iter->bucket = i;
            iter->entry = map->buckets[i];
            iter->next_entry = ((WmHashMapEntry*)iter->entry)->next;
            break;
        }
    }
}

bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value) {
    if (!iter || !iter->entry) return false;
    
    WmHashMapEntry *entry = (WmHashMapEntry*)iter->entry;
    *key = entry->key;
    *value = entry->value;
    
    /* Move to next entry */
    entry = entry->next;
    if (!entry) {
        /* Find next bucket with entries */
        WmHashMap *map = (WmHashMap*)iter->map;
        for (size_t i = iter->bucket + 1; i < map->bucket_count; i++) {
            if (map->buckets[i]) {
                iter->bucket = i;
                entry = map->buckets[i];
                break;
            }
        }
    }
    iter->entry = entry;
    
    return true;
}

void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data) {
    if (!map || !func) return;
    
    for (size_t i = 0; i < map->bucket_count; i++) {
        WmHashMapEntry *entry = map->buckets[i];
        while (entry) {
            func(entry->key, entry->value, user_data);
            entry = entry->next;
        }
    }
}

/*==============================================================================
 * String Helpers
 *============================================================================*/

bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value) {
    if (!map || !key) return false;
    
    char *key_copy = strdup(key);
    if (!key_copy) return false;
    
    if (!wm_hashmap_insert(map, key_copy, value)) {
        free(key_copy);
        return false;
    }
    return true;
}

void* wm_hashmap_get_string(const WmHashMap *map, const char *key) {
    return wm_hashmap_get(map, key);
}

bool wm_hashmap_contains_string(const WmHashMap *map, const char *key) {
    return wm_hashmap_contains(map, key);
}

void* wm_hashmap_remove_string(WmHashMap *map, const char *key) {
    return wm_hashmap_remove(map, key);
}
