// Strongly influenced by Array.jai in Basic module.
#pragma once

// For Arena-Backed arrays use ArenaArray

MSVC_RUNTIME_CHECKS_OFF

template <typename T>
struct Array { // #downcasts to an ArrayView.
  using ValueType = T;
  s64 count;
  T* data;
  s64 allocated;
  Allocator allocator;

  Array() { 
    memset(this, 0, sizeof(*this));
  }

  Array (Allocator _allocator, s64 reserve_count=0, s64 resize_count=0, bool initialize=false) {
    memset(this, 0, sizeof(*this));
    allocator = _allocator;
    array_reserve(*this, reserve_count);
    array_resize(*this, resize_count, initialize);
  }

  Array(s64 new_count, void* new_data, s64 _allocated) {
    count = new_count;
    data = (T*)new_data;
    allocated = _allocated;
    allocator = context_allocator();
  }
  
  Array(Allocator _allocator, s64 new_count, void* new_data, s64 _allocated) {
    count = new_count;
    data = (T*)new_data;
    allocated = _allocated;
    allocator = _allocator;
  }
  
  T& operator[](s64 index) {
#if ARRAY_ENABLE_BOUNDS_CHECKING
    if (index < 0 || index >= count) { debug_break(); } // index out of bounds
#endif
    return static_cast<T*>(data)[index];
  }
};

template <typename T> bool is_resizable (Array<T>& src) {
  // If we have a valid allocator, we assume this is resizeable.
  return src.allocator.proc != nullptr;
}

template <typename T>
bool is_valid (Array<T> src) {
  if (src.count == 0) return true;
  if (src.count < 0) return false;
  if (src.data == nullptr) return false;
  if (src.allocated < src.count) return false;
  return true;
}

template <typename T>
void array_zero (const Array<T>& src) {
  memset(src.data, 0, src.count * sizeof(T));
}

// Here, the copied array uses the same allocator as src. I'm not sure if this is a good idea.
// Applies to `array_copy_zero` and `array_copy`
template <typename T>
Array<T> array_copy (const Array<T>& src) {
  if (!src.data || src.count == 0) {
    return Array<T>(src.allocator);
  }
  
  T* new_data = NewArray<T>(src.count, false);
  memcpy(new_data, src.data, src.count * sizeof(T));
  
  return Array<T>(src.allocator, src.count, new_data, src.allocated);
}

template <typename T>
Array<T> array_copy_zero (const Array<T>& src) {
  if (!src.data || src.count == 0) {
    return Array<T>(src.allocator);
  }
  
  T* new_data = NewArray<T>(src.count, false);
  memset(new_data, 0, src.count * sizeof(T));
  
  return Array<T>(src.allocator, src.count, new_data, src.allocated);
}

template <typename T>
void array_reset_keeping_memory (Array<T>& src) {
  src.count = 0;
}

template <typename T>
void array_free (Array<T>& src) {
  if (!src.data) return;
  if (src.allocated == 0) return;
  if (src.allocator.proc != nullptr) {
    src.allocator.proc(Allocator_Mode::DEALLOCATE, 0, 0, src.data, src.allocator.data);
  } else {
    internal_free(src.data);
  }
  src.count = 0;
  src.data = nullptr;
  src.allocated = 0;
}

template <typename T>
void array_initialize (Array<T>& src, s64 start, s64 end) {
  for (s64 i = start; i < end; i += 1) {
    // Really this can be one ini followed by a bunch of memcpy. 
    // For long arrays we could power-of-two double the copy out, etc.
    src[i] = T(); // `new (&src[i]) T();` also works.
  }
}

template <typename T>
void array_reserve (Array<T>& src, s64 desired_items) {
  if (desired_items <= src.allocated) return;

  if (src.allocator.proc == nullptr) {
    src.allocator = context_allocator();
  }
  
  Assert(src.allocator.proc != nullptr);
  
  src.data = (T*)src.allocator.proc(Allocator_Mode::RESIZE, desired_items * sizeof(T), src.allocated * sizeof(T), src.data, src.allocator.data);
  
  Assert(src.data != nullptr);

  src.allocated = desired_items;
}

template <typename T>
void array_resize (Array<T>& src, s64 new_count, bool initialize=true) {
  if (src.count == new_count) return;
  
  s64 old_count = src.count;
  
  array_reserve(src, new_count);
  src.count = new_count;
  
  if (initialize) { array_initialize(src, old_count, new_count); }
}

template <typename T>
force_inline void array_maybe_grow (Array<T>& src) {
  if (src.count >= src.allocated) {
    // Replace with Basic.max(8, 2 * src.count).
    s64 reserve = 8;
    if (src.count * 2 > reserve) { reserve = src.count * 2; }
    array_reserve(src, reserve);
  }
}

template <typename T>
T pop (Array<T>& src) {
  auto result = src[src.count-1];
  src.count -= 1;
  return result;
}

template <typename T>
T* array_add (Array<T>& src) {
  array_maybe_grow(src);
  
  T* result = &src.data[src.count];
  (*result) = T(); // initialize
  
  src.count += 1;
  return result;
}

template <typename T>
void array_add (Array<T>& src, T new_item) {
  array_maybe_grow(src);
  
  src.data[src.count] = new_item;
  
  src.count += 1;
}

template <typename T>
s64 array_find (Array<T>& src, T item) {
  for (s64 i = 0; i < src.count; i += 1) {
    if (src[i] == item) return i;
  }
  return -1;
}

template <typename T>
bool array_add_if_unique (Array<T>& src, T new_item) {
  if (array_find(src, new_item) == -1) {
    array_add(src, new_item);
    return true;
  }
  
  return false;
}

template <typename T>
void array_ordered_remove_by_index (Array<T>& src, s64 index) {
  Assert(index >= 0); Assert(index < src.count);
  
  for (s64 i = index; i < src.count-1; i += 1) {
    src[i] = src[i + 1];
  }
  
  src.count -= 1;
}

template <typename T>
void array_ordered_remove_by_value (Array<T>& src, T item) {
  auto index = array_find(src, item);
  if (index != -1) { array_ordered_remove_by_index(src, index); }
}

template <typename T>
void array_unordered_remove_by_index (Array<T>& src, s64 index) {
  Assert(index >= 0); Assert(index < src.count);
  
  auto last_index = src.count - 1;
  
  if (index != last_index) { // Copy back item:
    memcpy(&src[index], &src[last_index], sizeof(T));
  }
  
  src.count -= 1;
}

template <typename T>
s64 array_unordered_remove_by_value (Array<T>& src, T item, s64 max_count_to_remove) {
  s64 removed_count = 0;
  for (s64 i = 0; i < src.count; i += 1) {
    if (src[i] == item) {
      removed_count += 1;
      array_unordered_remove_by_index(src, i);

      i -= 1; // check this element index again
      if (max_count_to_remove == removed_count) { break; }
    }
  }
  
  return removed_count;  
}

template <typename T>
struct ArrayView {
  using ValueType = T;
  s64 count;
  T* data;
  
  ArrayView(Array<T> array) { // auto-#downcast from Array<T>
    count = array.count;
    data  = array.data;
  }
  
  // Unfortunately we need ArenaArray to be declared ahead. God, I hate C++.
  // We have to use to_view(), and cannot auto-#downcast.
  // ArrayView(ArenaArray<T> array) { // auto-#downcast from ArenaArray<T>
  //   count = array.count;
  //   data  = array.data; 
  // }
  
  ArrayView() { count = 0; data = nullptr; }
  
  ArrayView(s64 new_count, bool initialize=true) {
    count = new_count;
    data = NewArray<T>(new_count, initialize);
  }
  
  ArrayView(Allocator allocator, s64 new_count, bool initialize=true) {
    count = new_count;
    data = NewArray<T>(allocator, new_count, initialize);
  }
  
  // #Note: use array_view to create slices or to downcast to ArrayView!
  ArrayView(s64 _count, T* _data) {
    count = _count;
    data = _data;
  }
  T& operator[](s64 index) {
#if ARRAY_ENABLE_BOUNDS_CHECKING
    if (index < 0 || index >= count) { debug_break(); } // index out of bounds
#endif
    return static_cast<T*>(data)[index];
  }
};

template <typename T> ArrayView<u8> to_byte_view (ArrayView<T> src) {
  ArrayView<u8> byte_view = { src.count * sizeof(T), src.data };
  return byte_view;
}

template <typename T>
bool is_empty (ArrayView<T> src) {
  if (src.count == 0) return true;
  return false;
}

// #NOTE: procedures should be robust to arrays with count of zero!
// Whether or not this is an error is procedure specific, but for most
// things, there is a default behavior that is expected. 
template <typename T>
bool is_valid (ArrayView<T> src) {
  if (src.count < 0) return false;
  if (src.count == 0) return true;
  if (src.data == nullptr) return false;
  
  return true;
}

// can also use ArrayView<T>(count, data) for initialization!
template <typename T>
ArrayView<T> to_view (Array<T> array) {
  ArrayView<T> av;
  av.count = array.count;
  av.data = array.data;
  return av;
}

template <typename T>
ArrayView<T> array_view (ArrayView<T> array, s64 start_index, s64 view_count) {
  ArrayView<T> av;
  av.count = view_count; // check if count exceeds 
  Assert(start_index + view_count <= array.count);
  av.data = &array[start_index];
  return av;
}

template <typename T>
ArrayView<T> array_view (Array<T> array, s64 start_index, s64 view_count) {
  ArrayView<T> av;
  av.count = view_count; // check if count exceeds 
  Assert(start_index + view_count <= array.count);
  av.data = &array[start_index];
  return av;
}

template <typename T>
void array_zero (const ArrayView<T>& src) {
  memset(src.data, 0, src.count * sizeof(T));
}

template <typename T>
void array_reset_keeping_memory (ArrayView<T>& src) {
  src.count = 0;
}


template <typename T>
ArrayView<T> array_copy (const ArrayView<T>& src) {
  if (!src.data || src.count == 0) {
    return ArrayView<T>(); // Return an empty array
  }
  
  T* new_data = NewArray<T>(src.count);
  memcpy(new_data, src.data, src.count * sizeof(T));
  
  return ArrayView<T>(src.count, (T*)new_data);
}

template <typename T>
ArrayView<T> array_copy (Allocator allocator, const ArrayView<T>& src) {
  push_allocator(allocator);
  return array_copy(src);
}

template <typename T>
void array_free (ArrayView<T>& src) {
  if (!src.data || src.count == 0) { return; }
  // Use with caution!
  internal_free(src.data); // we just have to trust that the context.allocator is correct for this guy!
  
  src.count = 0;
  src.data  = nullptr;
}

// Usage: `auto array = array_from_values<s32>(6,7,8,9,10,51);`
template <typename T, typename... ArgValues>
Array<T> array_from_values (ArgValues... args) {
  constexpr s64 N = sizeof...(ArgValues);
  auto array = Array<T>(N, /*initialize:*/false);
  T values[] = {args...};
  for (s64 i = 0; i < N; i += 1) {
    array[i] = values[i];
  }
  
  return array;
}


// Usage `auto view = array_view_from_values<s32>(1,2,3,4,5);`
template <typename T, typename... ArgValues>
ArrayView<T> array_view_from_values (ArgValues... args) {
  constexpr s64 N = sizeof...(ArgValues);
  auto array = ArrayView<T>(N, /*initialize:*/false);
  T values[] = {args...}; 
  for (s64 i = 0; i < N; i += 1) {
    array[i] = values[i];
  }
  
  return array;
}

MSVC_RUNTIME_CHECKS_RESTORE
