#module_parameters(RUN_TESTS := false);

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

Default_Align :: #run 2 * align_of(*void);

align_of :: ($T: Type) -> int #expand {
   return #run -> int {
      info := type_info(struct{ p: u8; t: T; });
      return info.members[1].offset_in_bytes.(int);
   };
}

default_of :: ($T: Type) -> T #expand {
   default: T;
   return default;
}

undefined_of :: ($T: Type) -> T #expand {
   uninit: T = ---;
   return uninit;
}

zero_of :: ($T: Type) -> T #expand {
   zero := undefined_of(T);
   memset(*zero, 0, size_of(T));
   return zero;
}

bitcast :: ($T: Type, expr: Code) -> T #expand {
   value := expr;
   return (*value).(*T).*;
}

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

next_power_of_two :: (x: int) -> int #no_aoc {
   basic.assert(power_of_two(x), "value (%) must be a power of two", x);

   // Bit twiddling hacks next power of two
   x |= x >> 1;
   x |= x >> 2;
   x |= x >> 4;
   x |= x >> 8;
   x |= x >> 16;
   x |= x >> 32;

   return x + 1;
}

align_to :: (ptr: int, align: int = Default_Align) -> int {
   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) {
   if custom_init != null {
      custom_init(ptr);
   }

   initializer :: initializer_of(T);
   #if initializer {
      inline initializer(ptr);
   }
   else {
      memset(ptr, 0, size_of(T));
   }
}

allocator_reset :: () {
   allocator := context.allocator;
   allocator.proc(xx Extended_Allocator_Mode.reset_state, 0, 0, null, allocator.data);
}

allocator_save :: () -> int {
   allocator := context.allocator;
   return allocator.proc(xx Extended_Allocator_Mode.save_point, 0, 0, null, allocator.data).(*int).*;
}

allocator_restore :: (save_point: int) {
   allocator := context.allocator;
   allocator.proc(xx Extended_Allocator_Mode.restore_save_point, 0, save_point, null, allocator.data);
}

request_memory :: (size: int, align := Default_Align) -> *void {
   allocator := context.allocator;
   aligned_size := align_to(size, align);
   return allocator.proc(xx Extended_Allocator_Mode.request_memory, aligned_size.(int), 0, null, allocator.data);
}

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

   return false;
}
{
   size := size_of(T) * basic.max(reserved, 0);
   data := request_memory(size, align_of(T)).(*T);
   #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;
}

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

release_memory :: inline (ptr: *void) {
   allocator := context.allocator;
   allocator.proc(xx Extended_Allocator_Mode.release_memory, 0, 0, ptr, allocator.data);
}

release_memory :: inline (arr: []$T) {
   release_memory(arr.data.(*void));
}

release_memory :: inline (arr: [..]$T) {
   release_memory(arr.data.(*void),, allocator = arr.allocator);
}

release_memory :: inline (str: string) {
   release_memory(str.data.(*void));
}

#load "allocators.jai";

#scope_file;

meta :: #import "jc/meta";

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


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

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

   #run {
      test.run("request_memory:dynamic arrays", (t) => {
         a1 := request_memory([..]int);
         defer release_memory(a1);

         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 := request_memory([..]int, 8);
         defer release_memory(a2);

         test.expect(t, a2.count     == 0);
         test.expect(t, a2.allocated == 8);
      });

      test.run("request_memory:values", (t) => {
         v1 := request_memory(int);
         defer release_memory(v1);

         test.expect(t, v1.* == 0);
      });
   }
}
