Kilobyte :: 1024;
Megabyte :: 1024 * Kilobyte;
Gigabyte :: 1024 * Megabyte;

power_of_two :: (x: uint) -> bool {
   if x == 0 return false;
   return x & (x - 1) == 0;
}

align_forward :: (ptr: uint, align: uint = Default_Align) -> uint {
   basic.assert(power_of_two(align), "alignment must be a power of two");

   p   := ptr;
   mod := p & (align - 1);
   if mod != 0 then p += align - mod;
   return p;
}

init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) {
   init :: initializer_of(T);
   if custom_init != null {
      custom_init(ptr);
   }

   #if init != null {
      inline init(ptr);
   }
   else {
      memset(ptr, 0, size_of(T));
   }
}

make :: ($T: Type, reserved := 0, $init := true) -> [..]T
#modify {
   ok, info := type_is_array(T);
   if ok && info.array_type == .RESIZABLE {
      T = compiler.get_type(info.element_type);
      return true;
   }

   return false;
}
{
   size := align_forward(size_of(T) * basic.max(reserved, 0).(uint)).(sint);
   data := basic.alloc(size);

   #if init if size != 0 {
      memset(data, 0, size);
   }

   arr: [..]T;
   arr.data      = data;
   arr.count     = 0;
   arr.allocated = size / size_of(T);
   arr.allocator = context.allocator;
   return arr;
}

make :: ($T: Type, $init := true) -> *T
#modify { return !type_is_array(T); }
{
   ptr := basic.alloc(size_of(T)).(*T);
   #if init init_or_zero(ptr);
   return ptr;
}

Crash_Allocator :: Allocator.{ proc = crash_allocator_proc };

crash_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
   message: string;
   if mode == {
      case .ALLOCATE;
         message = basic.tprint("Attempt to allocate % byte(s) using the crash allocator!", size);
      case .RESIZE;
         message = basic.tprint("Attempt to resize (from % to % byte(s)) using the crash allocator!", old_size, size);
      case .FREE;
         message = basic.tprint("Attempt to free % byte(s) using the crash allocator!", size);
   }

   basic.assert(false, message);
   debug_break();
   return null;
}

Arena :: struct {
   memory:      *void;
   memory_size: u64;
   offset:      u64;
}

init_arena :: (a: *Arena, memory: *void, size: u64) {
   a.memory      = memory;
   a.memory_size = size;
   a.offset      = 0;
}

arena_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
   arena := allocator_data.(*Arena);
   if mode == {
      case .ALLOCATE;
         return arena_alloc(arena, size);

      case .RESIZE;
         if old_memory == null {
            return arena_alloc(arena, size);
         }

         if size == 0 {
            return null;
         }

         if size == old_size {
            return old_memory;
         }

         new_memory := arena_alloc(arena, size);
         memcpy(new_memory, old_memory, old_size);
         return new_memory;
      case;
   }

   return null;
}

arena_alloc :: (a: *Arena, count: int, alignment := Default_Align, loc := #caller_location) -> *void {
   basic.assert(a.memory != null, "arena: not initialized", loc = loc);
   basic.assert(power_of_two(alignment));

   end := a.memory.(*u8) + a.offset;
   ptr := align_forward(end.(uint), alignment);
   total_size := (count + ptr.(*u8) - end.(*u8)).(u64);

   basic.assert(a.offset + total_size <= a.memory_size, "arena: out of memory", loc = loc);
   a.offset += total_size;

   return ptr.(*void);
}


#scope_file;

#if RUN_TESTS #run {
   test :: #import,file "./test/module.jai";

   test.run("make:dynamic arrays", (t) => {
      a1 := make([..]int);
      test.expect(t, a1.count     == 0);
      test.expect(t, a1.allocated == 0);

      basic.array_add(*a1, 10, 20, 30);
      test.expect(t, a1.count     == 3);
      test.expect(t, a1.allocated != 0, "%", a1.allocated);

      a2 := make([..]int, 8);
      test.expect(t, a2.count     == 0);
      test.expect(t, a2.allocated == 8);
   });

   test.run("make:values", (t) => {
      v1 := make(int);
      test.expect(t, v1.* == 0);
   });
}
