#module_parameters(RUN_TESTS := false);

// Dead simple key-value pair type (aka. hash table or hash map)
Kv :: struct(Key: Type, Value: Type) {
   allocator:  Allocator;
   slots:      [..]Slot;
   free_slots: [..]int;
   count:      int;

   Slot :: struct {
      hash:  u32   = invalid_hash;
      key:   Key   = ---;
      value: Value = ---;
   }

   hash_proc    :: hash.murmur32;
   invalid_hash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
   number_of_items_to_allocate_initially :: 16; // @note(judah): must be a power of two
}

init :: (kv: *Kv, allocator: Allocator) {
   kv.allocator            = allocator;
   kv.slots.allocator      = allocator;
   kv.free_slots.allocator = allocator;
}

get :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
   slot, ok := find_slot(kv, get_hash(kv, key));
   if !ok {
      return mem.zero_of(kv.Value), false;
   }

   return slot.value, true;
}

set :: (kv: *Kv, key: kv.Key, value: kv.Value) {
   hash := get_hash(kv, key);
   slot, exists := find_slot(kv, hash);
   if !exists {
      slot = create_or_reuse_slot(kv);
      slot.hash = hash;
   }

   slot.key   = key;
   slot.value = value;
}

exists :: (kv: *Kv, key: kv.Key) -> bool {
   _, exists := find_slot(kv, get_hash(kv, key));
   return exists;
}

// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
delete :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
   slot, ok, idx := find_slot(kv, get_hash(kv, key));
   if !ok return mem.zero_of(kv.Value), false;

   last_value := slot.value;
   mark_slot_for_reuse(kv, idx);

   return last_value, true;
}

reset :: (kv: *Kv) {
   kv.count            = 0;
   kv.slots.count      = 0;
   kv.free_slots.count = 0;
}

for_expansion :: (kv: *Kv, body: Code, flags: For_Flags) #expand {
   #assert (flags & .POINTER == 0) "cannot iterate by pointer";
   for   <=(flags & .REVERSE == .REVERSE) slot: kv.slots if slot.hash != kv.invalid_hash {
      `it       := slot.value;
      `it_index := slot.key;
      #insert,scope(body)(break = break slot) body;
   }
}


#scope_file;

get_hash :: inline (kv: *Kv, key: kv.Key) -> u32 {
   hash := kv.hash_proc(key);
   basic.assert(hash != kv.invalid_hash, "key % collided with invalid hash marker (%)", key, kv.invalid_hash);
   return hash;
}

find_slot :: (kv: *Kv, hash: u32) -> *kv.Slot, bool, int {
   for * kv.slots if it.hash == hash {
      return it, true, it_index;
   }

   return null, false, -1;
}

create_or_reuse_slot :: (kv: *Kv) -> *kv.Slot {
   inline try_lazy_init(kv);

   if kv.free_slots.count > 0 {
      slot_idx := kv.free_slots[kv.free_slots.count - 1];
      kv.free_slots.count -= 1;
      return *kv.slots[slot_idx];
   }

   if kv.slots.allocated == 0 {
      array.resize(*kv.slots, kv.number_of_items_to_allocate_initially);
   }
   else if kv.slots.count >= kv.slots.allocated {
      array.resize(*kv.slots, mem.next_power_of_two(kv.slots.allocated));
   }

   slot := array.append(*kv.slots);
   kv.count = kv.slots.count;
   return slot;
}

mark_slot_for_reuse :: (kv: *Kv, index: int) {
   inline try_lazy_init(kv);

   kv.count -= 1;
   kv.slots[index] = .{ hash = kv.invalid_hash };

   array.append(*kv.free_slots, index);
}

try_lazy_init :: inline (kv: *Kv) {
   if kv.allocator.proc == null {
      init(kv, context.allocator);
   }
}

mem   :: #import "jc/memory";
array :: #import "jc/array";
hash  :: #import "jc/hash";

basic :: #import "Basic"; // @future


// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------

#if RUN_TESTS {
   test :: #import "jc/test";

   #run {
      test.run("basic operations", t => {
         ITERATIONS :: 64;

         values: Kv(int, int);
         for 0..ITERATIONS {
            set(*values, it, it * it);
         }

         for 0..ITERATIONS {
            v, ok := get(*values, it);
            test.expect(t, v == it * it);
         }

         for 0..ITERATIONS if it % 2 == 0 {
            _, ok := delete(*values, it);
            test.expect(t, ok);
         }

         for 0..ITERATIONS if it % 2 == 0 {
            _, ok := get(*values, it);
            test.expect(t, !ok);
         }
      });

      test.run("free slots", t => {
         values: Kv(int, int);

         set(*values, 1, 100);
         set(*values, 2, 200);
         set(*values, 3, 300);
         test.expect(t, values.count == 3);
         test.expect(t, values.slots.allocated == values.number_of_items_to_allocate_initially);

         // deleting something that doesn't exist should do nothing
         _, ok := delete(*values, 0);
         test.expect(t, !ok);
         test.expect(t, values.count == 3);

         delete(*values, 2);
         test.expect(t, values.count == 2);
      });

      test.run("iteration", t => {
         values: Kv(int, int);

         for 0..10 set(*values, it, it * it);
         test.expect(t, values.count == 11);

         for   v, k: values test.expect(t, v == k * k);
         for < v, k: values test.expect(t, v == k * k);
      });
   }
}
