Static_Array :: struct(capacity: int, T: Type) {
   items: [capacity]T;
   count: int;

   Default :: #run mem.default_of(T);
}

append :: inline (a: *Static_Array, item: a.T) -> *a.T #no_abc {
   ensure_array_has_room(a, 1);
   ptr := *a.items[a.count];
   ptr.* = item;
   a.count += 1;
   return ptr;
}

append :: inline (a: *Static_Array) -> *a.T #no_abc {
   ensure_array_has_room(a, 1);
   ptr := *a.items[a.count];
   a.count += 1;
   return ptr;
}

append :: inline (a: *Static_Array, items: ..a.T) -> *a.T #no_abc {
   ensure_array_has_room(a, items.count);
   first := *a.items[a.count];
   memcpy(a.items.data + a.count, items.data, items.count * size_of(a.T));
   a.count += items.count;
   return first;
}

remove_ordered :: inline (a: *Static_Array, index: int, loc := #caller_location) #no_abc {
   meta.check_bounds(index, a.count, loc = loc);
   memcpy(a.items.data + index, a.items.data + index + 1, (a.count - index - 1) * size_of(a.T));
   a.count -= 1;
}

remove_unordered :: inline (a: *Static_Array, index: int, loc := #caller_location) #no_abc {
   meta.check_bounds(index, a.count, loc = loc);
   a.items[index] = a.items[a.count - 1];
   a.count -= 1;
}

reset :: inline (a: *Static_Array) #no_abc {
   for 0..a.count - 1 a.items[it] = a.Default;
   a.count = 0;
}

find :: (a: Static_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: *Static_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 [] :: inline (a: Static_Array, $$index: int, loc := #caller_location) -> a.T #no_abc {
   meta.check_bounds(index, a.count, loc = loc);
   return a.items[index];
}

operator *[] :: inline (a: *Static_Array, $$index: int, loc := #caller_location) -> *a.T #no_abc {
   meta.check_bounds(index, a.count, loc = loc);
   return *a.items[index];
}

operator []= :: inline (a: *Static_Array, $$index: int, value: a.T, loc := #caller_location) #no_abc {
   meta.check_bounds(index, a.count, loc = loc);
   a.items[index] = value;
}

for_expansion :: (a: *Static_Array, body: Code, flags: For_Flags) #expand {
   view := make_view(a);
   for *=(flags & .POINTER == .POINTER) <=(flags & .REVERSE == .REVERSE) `it, `it_index: view {
      #insert,scope(body)(break = break it) body;
   }
}

make_view :: (a: Static_Array) -> []a.T {
   view := a.items.([]a.T);
   view.count = a.count;
   return view;
}

make_dynamic :: (a: *Static_Array) -> [..]a.T {
   res: [..]a.T;
   res.count     = a.count;
   res.allocated = a.count;
   res.data      = basic.alloc(a.count * size_of(a.T));
   memcpy(res.data, a.items.data, a.count * size_of(a.T));
}


#scope_file;

ensure_array_has_room :: (array: *Static_Array, count: int, loc := #caller_location) #expand {
   basic.assert(array.count + count <= array.capacity, "attempt to add too many elements! want: %, max: %", array.count + count, array.capacity, loc = loc);
}

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

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


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

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

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

      test.expect(t, a.count    == 0);
      test.expect(t, a.capacity == 10);

      append(*a, 10, 20, 30);
      test.expect(t, a.count == 3, "count: %", a.count);

      _, ok := find(a, v => v == 10);
      test.expect(t, ok);

      _, ok = find_pointer(*a, v => v == 20);
      test.expect(t, ok);
   });

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

      remove_ordered(*a, 1);
      test.expect(t, a.count == 2);
      test.expect(t, a.items[0] == 10);

      remove_ordered(*a, 0);
      test.expect(t, a.count == 1);
      test.expect(t, a.items[0] == 30);

      remove_ordered(*a, 0);
      test.expect(t, a.count == 0);

      append(*a, 10);
      test.expect(t, a.count == 1);
      test.expect(t, a.items[0] == 10);
   });

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

      remove_unordered(*a, 1);
      test.expect(t, a.count == 2);
      test.expect(t, a.items[1] == 30);

      remove_unordered(*a, 0);
      test.expect(t, a.count == 1);
      test.expect(t, a.items[0] == 30);

      remove_unordered(*a, 0);
      test.expect(t, a.count == 0);

      append(*a, 10);
      test.expect(t, a.count == 1);
      test.expect(t, a.items[0] == 10);
   });
}
