/// ArrayAppend pushes values to the end of an array, resizing if necessary.
/// A pointer to the first value appended will be returned.
///
/// Note: If no allocator has been set, ArrayAppend will use the current context allocator.
/// Note: Calls to Append may invalidate pre-existing pointers.
ArrayAppend :: inline (arr: *[..]$T, values: ..T) -> *T {
   TrySetAllocator(arr,, allocator = ArenaToAllocator(*context.arena));

   if values.count == 0 {
      return basic.array_add(arr);
   }

   count := arr.count;
   basic.array_add(arr, ..values);
   return *arr.data[count];
}

/// ArrayGrow resizes the memory associated with an array to hold new_count values.
///
/// Note: If the array has enough allocated memory to accomodate new_count, ArrayGrow does nothing.
/// Note: ArrayGrow does not guarantee pointer stability.
ArrayGrow :: inline (arr: *[..]$T, new_count: int) {
   if new_count <= arr.allocated
    { return; }

   TrySetAllocator(arr,, allocator = ArenaToAllocator(*context.arena));
   basic.array_reserve(arr, new_count,, allocator = arr.allocator);
}

/// ArraySlice returns a subsection of an array.
ArraySlice :: (arr: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
   AssertCallsite(start_idx >= +0 && start_idx < arr.count, "jc: incorrect slice bounds");
   AssertCallsite(count >= -1 && count < arr.count, "jc: incorrect slice length");

   if count == -1
    { count = arr.count - start_idx; }

   return .{ data = arr.data + start_idx, count = count };
}

/// ArrayReset sets an array's length to 0, but leaves its memory intact.
/// Note: To reset the associated memory as well, see ArrayClear.
ArrayReset :: (arr: *[]$T) {
   arr.count = 0;
}

/// ArrayClear zeroes an array's memory and sets its length to 0.
/// Note: To leave the associated memory intact, see ArrayReset.
ArrayClear :: (arr: *[]$T) {
   MemZero(arr.data, arr.count * size_of(T));
   arr.count = 0;
}

/// ArrayEquals checks equality between two arrays.
ArrayEquals :: (lhs: []$T, rhs: []T) -> bool {
   if lhs.count != rhs.count
    { return false; }
   return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
}

ArrayIndex :: struct(T: Type) {
   value: T = ---;
   index: int;
}

ArrayFindFlags :: enum_flags {
   Last;    // Return the last matching element.
   FromEnd; // Search in reverse.
}

/// ArrayFind searches through an array, returning the first element that matches the given value.
ArrayFind :: (view: []$T, value: T, $flags: ArrayFindFlags = 0) -> (bool, ArrayIndex(T)) {
   found: bool;

   result: ArrayIndex(T);
   result.index = -1;

   REVERSE :: #run (flags & .FromEnd).(bool);
   for #v2 <=REVERSE view if it == value {
      found  = true;
      result.index = it_index;
      result.value = it;
      #if !(flags & .Last) break;
   }

   return found, result;
}

/// ArrayContains checks if the given value exists in an array.
ArrayContains :: (view: []$T, value: T) -> bool {
   return ArrayFind(view, value);
}


ArrayTrimFlags :: enum_flags {
   FromStart;   // ArrayTrim the start of the array.
   FromEnd;     // ArrayTrim the end of the array.
   MatchInFull; // Only trim when the cutset matches exactly.
}

/// ArrayTrim returns a subsection of an array with all leading/trailing values
/// from cutset removed.
ArrayTrim :: (view: []$T, cutset: []T, $flags: ArrayTrimFlags = .FromStart) -> []T {
   result := view;
   if cutset.count == 0 || cutset.count > view.count {
      return result;
   }

   #if flags & .FromStart {
      #if flags & .MatchInFull {
         if ArrayEquals(ArraySlice(view, 0, cutset.count), cutset) {
            result = ArraySlice(view, cutset.count, -1);
         }
      }
      else {
         while result.count > 0 {
            if !ArrayContains(cutset, result[0]) {
               break;
            }

            result.data  += 1;
            result.count -= 1;
         }
      }
   }

   #if flags & .FromEnd {
      #if flags & .MatchInFull {
         if ArrayEquals(ArraySlice(view, view.count - cutset.count), cutset) {
            result.count -= cutset.count;
         }
      }
      else {
         while result.count > 0 {
            if !ArrayContains(cutset, result[result.count - 1]) {
               break;
            }

            result.count -= 1;
         }
      }
   }

   return result;
}


CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
   Message :: "bounds check failed!";
   #if is_constant(index) && is_constant(count) {
      if index < 0 || index >= count {
         CompileError(Message, loc = loc);
      }
   }
   else if index < 0 || index > count {
      Panic(Message, loc = loc);
   }
}


#scope_file

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