package container

import "intrinsics"
_ :: intrinsics;


Map :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
	hash: Array(int),
	entries: Array(Map_Entry(Key, Value)),
}

Map_Entry :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
	hash:  uintptr,
	next:  int,
	key:   Key,
	value: Value,
}


/*
map_init :: proc{
	map_init_none,
	map_init_cap,
}
map_delete

map_has
map_get
map_get_default
map_get_ptr
map_set
map_remove
map_reserve
map_clear

// Multi Map

multi_map_find_first
multi_map_find_next
multi_map_count
multi_map_get :: proc{
	multi_map_get_array,
	multi_map_get_slice,
};
multi_map_get_as_slice
multi_map_insert
multi_map_remove
multi_map_remove_all

*/

map_init :: proc{map_init_none, map_init_cap};

map_init_none :: proc(m: ^$M/Map($Key, $Value), allocator := context.allocator) {
	m.hash.allocator = allocator;
	m.entries.allocator = allocator;
}

map_init_cap :: proc(m: ^$M/Map($Key, $Value), cap: int, allocator := context.allocator) {
	m.hash.allocator = allocator;
	m.entries.allocator = allocator;
	map_reserve(m, cap);
}

map_delete :: proc(m: $M/Map($Key, $Value)) {
	array_delete(m.hash);
	array_delete(m.entries);
}


map_has :: proc(m: $M/Map($Key, $Value), key: Key) -> bool {
	return _map_find_or_fail(m, key) >= 0;
}

map_get :: proc(m: $M/Map($Key, $Value), key: Key) -> (res: Value, ok: bool) #optional_ok {
	i := _map_find_or_fail(m, key);
	if i < 0 {
		return {}, false;
	}
	return array_get(m.entries, i).value, true;
}

map_get_default :: proc(m: $M/Map($Key, $Value), key: Key, default: Value) -> (res: Value, ok: bool) #optional_ok {
	i := _map_find_or_fail(m, key);
	if i < 0 {
		return default, false;
	}
	return array_get(m.entries, i).value, true;
}

map_get_ptr :: proc(m: $M/Map($Key, $Value), key: Key) -> ^Value {
	i := _map_find_or_fail(m, key);
	if i < 0 {
		return nil;
	}
	return array_get_ptr(m.entries, i).value;
}

map_set :: proc(m: ^$M/Map($Key, $Value), key: Key, value: Value) {
	if array_len(m.hash) == 0 {
		_map_grow(m);
	}

	i := _map_find_or_make(m, key);
	array_get_ptr(m.entries, i).value = value;
	if _map_full(m^) {
		_map_grow(m);
	}
}

map_remove :: proc(m: ^$M/Map($Key, $Value), key: Key) {
	fr := _map_find_key(m^, key);
	if fr.entry_index >= 0 {
		_map_erase(m, fr);
	}
}


map_reserve :: proc(m: ^$M/Map($Key, $Value), new_size: int) {
	nm: M;
	map_init(&nm, m.hash.allocator);
	array_resize(&nm.hash, new_size);
	array_reserve(&nm.entries, array_len(m.entries));

	for i in 0..<new_size {
		array_set(&nm.hash, i, -1);
	}
	for i in 0..<array_len(m.entries) {
		e := array_get(m.entries, i);
		multi_map_insert(&nm, e.key, e.value);
	}

	map_delete(m^);
	m^ = nm;
}

map_clear :: proc(m: ^$M/Map($Key, $Value)) {
	array_clear(&m.hash);
	array_clear(&m.entries);
}



multi_map_find_first :: proc(m: $M/Map($Key, $Value), key: Key) -> ^Map_Entry(Key, Value) {
	i := _map_find_or_fail(m, key);
	if i < 0 {
		return nil;
	}
	return array_get_ptr(m.entries, i);
}

multi_map_find_next :: proc(m: $M/Map($Key, $Value), e: ^Map_Entry(Key, Value)) -> ^Map_Entry(Key, Value) {
	i := e.next;
	for i >= 0 {
		it := array_get_ptr(m.entries, i);
		if it.hash == e.hash && it.key == e.key {
			return it;
		}
		i = it.next;
	}
	return nil;
}

multi_map_count :: proc(m: $M/Map($Key, $Value), key: Key) -> int {
	n := 0;
	e := multi_map_find_first(m, key);
	for e != nil {
		n += 1;
		e = multi_map_find_next(m, e);
	}
	return n;
}

multi_map_get :: proc{multi_map_get_array, multi_map_get_slice};

multi_map_get_array :: proc(m: $M/Map($Key, $Value), key: Key, items: ^Array(Value)) {
	if items == nil {
		return;
	}
	e := multi_map_find_first(m, key);
	for e != nil {
		array_append(items, e.value);
		e = multi_map_find_next(m, e);
	}
}

multi_map_get_slice :: proc(m: $M/Map($Key, $Value), key: Key, items: []Value) {
	e := multi_map_find_first(m, key);
	i := 0;
	for e != nil && i < len(items) {
		items[i] = e.value;
		i += 1;
		e = multi_map_find_next(m, e);
	}
}

multi_map_get_as_slice :: proc(m: $M/Map($Key, $Value), key: Key) -> []Value {
	items: Array(Value);
	array_init(&items, 0);

	e := multi_map_find_first(m, key);
	for e != nil {
		array_append(&items, e.value);
		e = multi_map_find_next(m, e);
	}

	return array_slice(items);
}


multi_map_insert :: proc(m: ^$M/Map($Key, $Value), key: Key, value: Value) {
	if array_len(m.hash) == 0 {
		_map_grow(m);
	}

	i := _map_make(m, key);
	array_get_ptr(m.entries, i).value = value;
	if _map_full(m^) {
		_map_grow(m);
	}
}

multi_map_remove :: proc(m: ^$M/Map($Key, $Value), e: ^Map_Entry(Key, Value)) {
	fr := _map_find_entry(m, e);
	if fr.entry_index >= 0 {
		_map_erase(m, fr);
	}
}

multi_map_remove_all :: proc(m: ^$M/Map($Key, $Value), key: Key) {
	for map_exist(m^, key) {
		map_remove(m, key);
	}
}


/// Internal


Map_Find_Result :: struct {
	hash_index:  int,
	entry_prev:  int,
	entry_index: int,
}

_map_add_entry :: proc(m: ^$M/Map($Key, $Value), key: Key) -> int where intrinsics.type_is_valid_map_key(Key) {
	hasher := intrinsics.type_hasher_proc(Key);

	e: Map_Entry(Key, Value);
	e.key = key;
	e.hash = hasher(&e.key, 0);
	e.next = -1;
	idx := array_len(m.entries);
	array_push(&m.entries, e);
	return idx;
}

_map_erase :: proc(m: ^$M/Map, fr: Map_Find_Result) {
	if fr.entry_prev < 0 {
		array_set(&m.hash, fr.hash_index, array_get(m.entries, fr.entry_index).next);
	} else {
		array_get_ptr(m.entries, fr.entry_prev).next = array_get(m.entries, fr.entry_index).next;
	}

	if fr.entry_index == array_len(m.entries)-1 {
		array_pop_back(&m.entries);
		return;
	}

	array_set(&m.entries, fr.entry_index, array_get(m.entries, array_len(m.entries)-1));
	last := _map_find_key(m^, array_get(m.entries, fr.entry_index).key);

	if last.entry_prev < 0 {
		array_get_ptr(m.entries, last.entry_prev).next = fr.entry_index;
	} else {
		array_set(&m.hash, last.hash_index, fr.entry_index);
	}
}


_map_find_key :: proc(m: $M/Map($Key, $Value), key: Key) -> Map_Find_Result where intrinsics.type_is_valid_map_key(Key) {
	fr: Map_Find_Result;
	fr.hash_index = -1;
	fr.entry_prev = -1;
	fr.entry_index = -1;

	if array_len(m.hash) == 0 {
		return fr;
	}

	hasher := intrinsics.type_hasher_proc(Key);

	key := key;
	hash := hasher(&key, 0);

	fr.hash_index = int(hash % uintptr(array_len(m.hash)));
	fr.entry_index = array_get(m.hash, fr.hash_index);
	for fr.entry_index >= 0 {
		it := array_get_ptr(m.entries, fr.entry_index);
		if it.hash == hash && it.key == key {
			return fr;
		}
		fr.entry_prev = fr.entry_index;
		fr.entry_index = it.next;
	}
	return fr;
}

_map_find_entry :: proc(m: ^$M/Map($Key, $Value), e: ^Map_Entry(Key, Value)) -> Map_Find_Result {
	fr: Map_Find_Result;
	fr.hash_index = -1;
	fr.entry_prev = -1;
	fr.entry_index = -1;

	if array_len(m.hash) == 0 {
		return fr;
	}

	fr.hash_index = int(e.hash % uintptr(array_len(m.hash)));
	fr.entry_index = array_get(m.hash, fr.hash_index);
	for fr.entry_index >= 0 {
		it := array_get_ptr(m.entries, fr.entry_index);
		if it == e {
			return fr;
		}
		fr.entry_prev = fr.entry_index;
		fr.entry_index = it.next;
	}
	return fr;
}

_map_find_or_fail :: proc(m: $M/Map($Key, $Value), key: Key) -> int {
	return _map_find_key(m, key).entry_index;
}
_map_find_or_make :: proc(m: ^$M/Map($Key, $Value), key: Key) -> int {
	fr := _map_find_key(m^, key);
	if fr.entry_index >= 0 {
		return fr.entry_index;
	}

	i := _map_add_entry(m, key);
	if fr.entry_prev < 0 {
		array_set(&m.hash, fr.hash_index, i);
	} else {
		array_get_ptr(m.entries, fr.entry_prev).next = i;
	}
	return i;
}


_map_make :: proc(m: ^$M/Map($Key, $Value), key: Key) -> int {
	fr := _map_find_key(m^, key);
	i := _map_add_entry(m, key);

	if fr.entry_prev < 0 {
		array_set(&m.hash, fr.hash_index, i);
	} else {
		array_get_ptr(m.entries, fr.entry_prev).next = i;
	}

	array_get_ptr(m.entries, i).next = fr.entry_index;

	return i;
}


_map_full :: proc(m: $M/Map($Key, $Value)) -> bool {
	// TODO(bill): Determine good max load factor
	return array_len(m.entries) >= (array_len(m.hash) / 4)*3;
}

_map_grow :: proc(m: ^$M/Map($Key, $Value)) {
	new_size := array_len(m.entries) * 4 + 7; // TODO(bill): Determine good grow rate
	map_reserve(m, new_size);
}


