// A dynamic array whose values will never move in memory.
//
// This means it is safe to take a pointer to a value within the array
// while continuing to append to it.
Stable_Array :: struct(T: Type, items_per_chunk := 32) {
   allocator: Allocator;
   chunks:    [..]*Chunk;
   count:     int;

   Chunk :: Static_Array(items_per_chunk, T);
}

init :: (a: *Stable_Array, allocator: Allocator) {
   a.allocator        = allocator;
   a.chunks.allocator = allocator;
}

append :: (a: *Stable_Array) -> *a.T {
   chunk := find_or_create_chunk(a, 1);
   a.count += 1;
   return append(chunk);
}

append :: (a: *Stable_Array, value: a.T) -> *a.T {
   chunk := find_or_create_chunk(a, 1);
   item  := append(chunk, value);
   a.count += 1;
   return item;
}

append :: (a: *Stable_Array, values: ..a.T) -> *a.T {
   // @todo(judah): this should look for chunks where can just copy values directly
   // rather than calling append for each one.

   first: *a.T;
   for values {
      if first == null {
         first = inline append(a, it);
      }
      else {
         inline append(a, it);
      }
   }

   return first;
}

reset :: (a: *Stable_Array) {
   for a.chunks it.count = 0;
   a.count = 0;
   a.chunks.count = 0;
}

find :: (a: Stable_Array, $predicate: (a.T) -> bool) -> a.T, bool, int {
   for a if inline predicate(it) return it, true, it_index;
   return mem.undefined_of(a.T), false, -1;
}

find_pointer :: (a: *Stable_Array, $predicate: (a.T) -> bool) -> *a.T, bool, int {
   for * a if inline predicate(it.*) return it, true, it_index;
   return null, false, -1;
}

operator [] :: (a: Stable_Array, index: int, loc := #caller_location) -> a.T #no_abc {
   cidx := index / a.items_per_chunk;
   iidx := index % a.items_per_chunk;
   meta.check_bounds(cidx, a.chunks.count, loc = loc);
   meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
   return a.chunks[cidx].items[iidx];
}

operator *[] :: (a: *Stable_Array, index: int, loc := #caller_location) -> *a.T #no_abc {
   cidx := index / a.items_per_chunk;
   iidx := index % a.items_per_chunk;
   meta.check_bounds(cidx, a.chunks.count, loc = loc);
   meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
   return *a.chunks[cidx].items[iidx];
}

operator []= :: (a: *Stable_Array, index: int, value: a.T, loc := #caller_location) #no_abc {
   cidx := index / a.items_per_chunk;
   iidx := index % a.items_per_chunk;
   meta.check_bounds(cidx, a.chunks.count, loc = loc);
   meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
   a.chunks[cidx].items[iidx] = value;
}

for_expansion :: (a: Stable_Array, body: Code, flags: For_Flags) #expand {
   for #v2 <=(flags & .REVERSE == .REVERSE) i: 0..a.count - 1 {
      `it_index := i;
      #if flags & .POINTER == .POINTER {
         `it := *a[i];
      }
      else {
         `it := a[i];
      }

      #insert,scope(body) body;
   }
}


#scope_file;

mem  :: #import "jc/memory";
meta :: #import "jc/meta";

find_or_create_chunk :: (a: *Stable_Array, amount: int) -> *a.Chunk {
   if a.chunks.count == 0 {
      return create_chunk(a);
   }

   last := a.chunks[a.chunks.count - 1];
   if amount > a.items_per_chunk - last.count {
      last = create_chunk(a);
   }

   return last;
}

create_chunk :: (a: *Stable_Array) -> *a.Chunk {
   inline try_lazy_init(a);

   chunk := mem.request_memory(a.Chunk,, allocator = a.allocator);
   append(*a.chunks, chunk);
   return chunk;
}

try_lazy_init :: (a: *Stable_Array) {
   if a.allocator.proc == null {
      init(a, context.allocator);
   }
}


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

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

   test.run("basic operations", t => {
      a: Stable_Array(int, 4);

      append(*a, 10, 20, 30, 40);
      test.expect(t, a.count == 4);
      test.expect(t, a.chunks.count == 1, "chunk count was %", a.chunks.count);

      append(*a, 50);
      test.expect(t, a.count == 5);
      test.expect(t, a.chunks.count == 2, "chunk count was %", a.chunks.count);

      append(*a, 60, 70, 80, 90, 100, 110, 120);
      test.expect(t, a.count == 12);
      test.expect(t, a.chunks.count == 3, "chunk count was %", a.chunks.count);

      for a {
         test.expect(t, it == (it_index + 1) * 10, "% was %", it, (it_index + 1) * 10);
      }
   });

   test.run("iteration", t => {
      a: Stable_Array(int);
      append(*a, 10, 20, 30, 40);

      last := 999;
      for < a {
         test.expect(t, it == (it_index + 1) * 10);
         test.expect(t, it < last);

         last = it;
      }

      for * a it.* = 1;
      for   a test.expect(t, it == 1);

      ptr, ok, idx := find_pointer(*a, v => v == 1);
      test.expect(t, ok);
      test.expect(t, idx == 0);
      test.expect(t, ptr == *a[0]);

      a[a.count - 1] = -1;

      _, ok, idx = find(a, v => v == -1);
      test.expect(t, ok);
      test.expect(t, idx == a.count - 1);
   });

   test.run("stability", t => {
      a: Stable_Array(int, 1);

      first := append(*a, 10);
      addr  := first.(u64);
      for 0..10 append(*a, it * 10);

      test.expect(t, first.(u64) == addr);
      test.expect(t, first.* == 10);
   });
}
