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

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

   HashProc              :: Murmur32;
   InvalidHash           :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
   AllocatedItemsAtStart :: 16;
}

MapGet :: (r: *Map, key: r.Key) -> r.Value, bool {
   slot, ok := FindSlot(r, GetHash(r, key));
   if !ok
    { return zero_of(r.Value), false; }

   return slot.value, true;
}

MapSet :: (r: *Map, key: r.Key, value: r.Value) {
   hash := GetHash(r, key);
   slot, exists := FindSlot(r, hash);
   if !exists {
      slot = CreateOrReuseSlot(r);
      slot.hash = hash;
   }

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

MapHas :: (r: *Map, key: r.Key) -> bool {
   _, exists := FindSlot(r, GetHash(r, key));
   return exists;
}

MapRemove :: (r: *Map, key: r.Key) -> bool, r.Value {
   slot, ok, idx := FindSlot(r, GetHash(r, key));
   if !ok
    { return false, zero_of(r.Value); }

   last_value := slot.value;
   MarkSlotForReuse(r, idx);

   return true, last_value;
}

MapReset :: (t: *Map) {
   t.count            = 0;
   t.slots.count      = 0;
   t.free_slots.count = 0;
}

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


#scope_file

GetHash :: inline (r: *Map, key: r.Key) -> u32 {
   hash := r.HashProc(key);
   Assert(hash != r.InvalidHash, "key collided with invalid hash");
   return hash;
}

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

   return null, false, -1;
}

CreateOrReuseSlot :: (r: *Map) -> *r.Slot {
   inline LazyInit(r);

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

   if r.slots.allocated == 0 {
      ArrayGrow(*r.slots, r.AllocatedItemsAtStart);
   }
   else if r.slots.count >= r.slots.allocated {
      ArrayGrow(*r.slots, NextPowerOfTwo(r.slots.allocated));
   }

   slot := ArrayAppend(*r.slots);
   r.count = r.slots.count;
   return slot;
}

MarkSlotForReuse :: (t: *Map, index: int) {
   inline LazyInit(t);

   t.count -= 1;
   t.slots[index] = .{ hash = t.InvalidHash };

   ArrayAppend(*t.free_slots, index);
}

LazyInit :: inline (t: *Map) {
   TrySetAllocator(t);
   TrySetAllocator(*t.slots);
   TrySetAllocator(*t.free_slots);
}

