#pragma once
/*
// Iterating and removing elements
for (int i = 0; i < array.len; i += 1) {
    auto &it = array[i];
    bool remove_item = false;
    defer {
        if (remove_item) {
            array.ordered_remove(it);
            i -= 1;
        }
    }
}

// Simple delete
IterRemove(arr) {
    IterRemovePrepare(arr);

    remove_item = true;
}

// Deleting backwards
For(arr.reverse_iter()) {
    defer{ arr.unordered_remove(it); };
}
*/
#define IterRemove(a) for (int i = 0; i < (a).len; i += 1)
#define IterRemovePrepare(a)    \
    auto &it          = (a)[i]; \
    bool  remove_item = false;  \
    defer {                     \
        if (remove_item) {      \
            Remove(&(a), it);   \
            i -= 1;             \
        }                       \
    }
#define ForItem(it, array) for (auto &it : (array))
#define For(array) ForItem(it, array)

template <class T>
struct Slice {
    T      *data;
    int64_t len;

    Slice() = default;
    Slice(T *s, int64_t l) : data(s), len(l) {}

    T &operator[](int64_t index) {
        Assert(index < len);
        return data[index];
    }
    T *begin() { return data; }
    T *end() { return data + len; }
};

template <class T>
Slice<T> Copy(Allocator alo, Slice<T> array) {
    Slice<T> result = {};
    result.data     = AllocArray(alo, T, array.len);
    memcpy(result.data, array.data, sizeof(T) * array.len);
    result.len = array.len;
    return result;
}

template <class T>
T Pop(Slice<T> *arr) {
    Assert(arr->len > 0);
    return arr->data[--arr->len];
}

template <class T>
bool Contains(Slice<T> &arr, T item) {
    For(arr) if (it == item) return true;
    return false;
}

template <class T>
int64_t GetIndex(Slice<T> &arr, const T &item) {
    ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
    Assert(index >= 0 && index < arr.len);
    return (int64_t)index;
}

template <class T>
T Get(Slice<T> &arr, int64_t i, T default_value = {}) {
    T result = default_value;
    if (i >= 0 && i < arr.len) result = arr[i];
    return result;
}

template <class T>
bool IsLast(Slice<T> &arr, T &item) {
    bool result = arr.last() == &item;
    return result;
}

template <class T>
bool IsFirst(Slice<T> &arr, T &item) {
    bool result = arr.first() == &item;
    return result;
}

template <class T>
T *GetFirst(Slice<T> &arr) {
    Assert(arr.len > 0);
    return arr.data;
}

template <class T>
T *GetLast(Slice<T> &arr) {
    Assert(arr.len > 0);
    return arr.data + arr.len - 1;
}

template <class T>
Slice<T> Chop(Slice<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data, arr.len - len};
    return result;
}

template <class T>
Slice<T> Skip(Slice<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data + len, arr.len - len};
    return result;
}

template <class T>
Slice<T> GetPostfix(Slice<T> &arr, int64_t len) {
    len                 = ClampTop(len, arr.len);
    int64_t  remain_len = arr.len - len;
    Slice<T> result     = {arr.data + remain_len, len};
    return result;
}

template <class T>
Slice<T> GetPrefix(Slice<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data, len};
    return result;
}

template <class T>
Slice<T> GetSlice(Slice<T> &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) {
    // Negative indexes work in python style, they return you the index counting from end of list
    if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
    if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;

    if (first_index == SLICE_LAST) first_index = arr.len;
    if (first_index < 0) first_index = arr.len + first_index;

    Slice<T> result = {arr.data, arr.len};
    if (arr.len > 0) {
        if (one_past_last_index > first_index) {
            first_index         = ClampTop(first_index, arr.len - 1);
            one_past_last_index = ClampTop(one_past_last_index, arr.len);
            result.data += first_index;
            result.len = one_past_last_index - first_index;
        } else {
            result.len = 0;
        }
    }
    return result;
}

// Make arrays resize on every item
#define ARRAY_DEBUG 0
#if ARRAY_DEBUG
    #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF
#else
    #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) ELSE
#endif

template <class T>
struct Array {
    Allocator allocator;
    int64_t   cap;
    union {
        Slice<T> slice;
        struct {
            T      *data;
            int64_t len;
        };
    };

    T &operator[](int64_t index) {
        Assert(index >= 0 && index < len);
        return data[index];
    }
    T *begin() { return data; }
    T *end() { return data + len; }
};

template <class T>
T *GetFirst(Array<T> &arr) {
    Assert(arr.len > 0);
    return arr.data;
}

template <class T>
T *GetLast(Array<T> &arr) {
    Assert(arr.len > 0);
    return arr.data + arr.len - 1;
}

template <class T>
void Reserve(Array<T> *arr, int64_t size) {
    if (size > arr->cap) {
        if (!arr->allocator.proc) arr->allocator = GetSystemAllocator();

        T *new_data = AllocArray(arr->allocator, T, size);
        Assert(new_data);
        memcpy(new_data, arr->data, arr->len * sizeof(T));
        Dealloc(arr->allocator, arr->data);

        arr->data = new_data;
        arr->cap  = size;
    }
}

template <class T>
void TryGrowing(Array<T> *arr) {
    if (arr->len + 1 > arr->cap) {
        int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16);
        int64_t new_size     = ClampBottom(initial_size, arr->cap ARRAY_IF_DEBUG_ELSE(+1, *2));
        Reserve(arr, new_size);
    }
}

template <class T>
void TryGrowing(Array<T> *arr, int64_t item_count) {
    if (arr->len + item_count > arr->cap) {
        int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16);
        int64_t new_size     = ClampBottom(initial_size, (arr->cap + item_count) ARRAY_IF_DEBUG_ELSE(+1, *2));
        Reserve(arr, new_size);
    }
}

template <class T>
void Add(Array<T> *arr, T item) {
    TryGrowing(arr);
    arr->data[arr->len++] = item;
}

template <class T>
void Add(Array<T> *arr, Array<T> &another) {
    For(another) Add(arr, it);
}

template <class T>
void Add(Array<T> *arr, T *items, int64_t item_count) {
    for (int64_t i = 0; i < item_count; i += 1) Add(arr, items[i]);
}

template <class T>
void BoundedAdd(Array<T> *arr, T item) {
    if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item;
}

template <class T>
void BoundedAddError(Array<T> *arr, T item) {
    Assert(arr->len + 1 <= arr->cap);
    if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item;
}

template <class T>
void Insert(Array<T> *arr, T item, int64_t index) {
    if (index == arr->len) {
        Add(arr, item);
        return;
    }

    Assert(index < arr->len);
    Assert(index >= 0);
    TryGrowing(arr);
    int64_t right_len = arr->len - index;
    memmove(arr->data + index + 1, arr->data + index, sizeof(T) * right_len);
    arr->data[index] = item;
    arr->len += 1;
}

template <class T>
Array<T> Copy(Allocator alo, Array<T> array) {
    Array<T> result = {alo};
    Reserve(&result, array.cap);
    memcpy(result.data, array.data, sizeof(T) * array.len);
    result.len = array.len;
    return result;
}

template <class T>
Array<T> TightCopy(Allocator alo, Array<T> array) {
    Array<T> result = {alo};
    Reserve(&result, array.len);
    memcpy(result.data, array.data, sizeof(T) * array.len);
    result.len = array.len;
    return result;
}

template <class T>
T Pop(Array<T> *arr) {
    Assert(arr->len > 0);
    return arr->data[--arr->len];
}

template <class T>
bool Contains(Array<T> &arr, T item) {
    For(arr) if (it == item) return true;
    return false;
}

template <class T>
int64_t GetIndex(Array<T> &arr, const T &item) {
    ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
    Assert(index >= 0 && index < arr.len);
    return (int64_t)index;
}

template <class T>
void RemoveByIndex(Array<T> *arr, int64_t index) {
    Assert(index >= 0 && index < arr->len);
    int64_t right_len = arr->len - index - 1;
    memmove(arr->data + index, arr->data + index + 1, right_len * sizeof(T));
    arr->len -= 1;
}

template <class T>
void RemoveManyByIndex(Array<T> *arr, int64_t index, int64_t count) {
    if (count == 0) return;
    Assert(index >= 0 && index < arr->len);
    Assert((index + count) > 0 && (index + count) <= arr->len);
    int64_t right_len = arr->len - index - count;
    memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T));
    arr->len -= count;
}

template <class T>
void RemoveMany(Array<T> *arr, T &item, int64_t count) {
    Assert(arr->len > 0);
    Assert(&item >= arr->begin() && &item < arr->end());
    int64_t index = GetIndex(*arr, item);
    RemoveManyByIndex(arr, index, count);
}

template <class T>
void Remove(Array<T> *arr, T &item) {
    Assert(arr->len > 0);
    Assert(&item >= arr->begin() && &item < arr->end());
    int64_t index = GetIndex(*arr, item);
    RemoveByIndex(arr, index);
}

template <class T>
void UnorderedRemove(Array<T> *arr, T &item) {
    Assert(arr->len > 0);
    Assert((&item >= arr->begin()) && (&item < arr->end()));
    item = arr->data[--arr->len];
}

template <class T>
void UnorderedRemoveByIndex(Array<T> *arr, int64_t index) {
    Assert(arr->len > 0);
    Assert(index >= 0 && index < arr->len);
    arr->data[index] = arr->data[--arr->len];
}

template <class T>
void InsertArray(Array<T> *arr, T *items, int64_t count, int64_t index) {
    if (index == arr->len) {
        Add(arr, items, count);
        return;
    }
    Assert(index < arr->len);

    TryGrowing(arr, count);
    T      *gap_begin  = arr->data + index;
    T      *gap_end    = gap_begin + count;
    int64_t item_count = arr->len - index;
    memmove(gap_end, gap_begin, item_count * sizeof(T));
    for (int64_t i = 0; i < count; i += 1) arr->data[index + i] = items[i];
    arr->len += count;
}

template <class T>
T Get(Array<T> &arr, int64_t i, T default_value = {}) {
    T result = default_value;
    if (i >= 0 && i < arr.len) result = arr[i];
    return result;
}

template <class T>
void Dealloc(Array<T> *arr) {
    if (arr->data) {
        Dealloc(arr->allocator, arr->data);
        arr->data = NULL;
    }
    arr->len = arr->cap = 0;
}

template <class T>
bool IsLast(Array<T> &arr, T &item) {
    bool result = GetLast(arr) == &item;
    return result;
}

template <class T>
bool IsFirst(Array<T> &arr, T &item) {
    bool result = GetFirst(arr) == &item;
    return result;
}

template <class T>
Slice<T> Chop(Array<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data, arr.len - len};
    return result;
}

template <class T>
Slice<T> Skip(Array<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data + len, arr.len - len};
    return result;
}

template <class T>
Slice<T> GetPostfix(Array<T> &arr, int64_t len) {
    len                 = ClampTop(len, arr.len);
    int64_t  remain_len = arr.len - len;
    Slice<T> result     = {arr.data + remain_len, len};
    return result;
}

template <class T>
Slice<T> GetPrefix(Array<T> &arr, int64_t len) {
    len             = ClampTop(len, arr.len);
    Slice<T> result = {arr.data, len};
    return result;
}

template <class T>
Slice<T> GetSlice(Array<T> &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) {
    // Negative indexes work in python style, they return you the index counting from end of list
    if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
    if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;

    if (first_index == SLICE_LAST) first_index = arr.len;
    if (first_index < 0) first_index = arr.len + first_index;

    Slice<T> result = {arr.data, arr.len};
    if (arr.len > 0) {
        if (one_past_last_index > first_index) {
            first_index         = ClampTop(first_index, arr.len - 1);
            one_past_last_index = ClampTop(one_past_last_index, arr.len);
            result.data += first_index;
            result.len = one_past_last_index - first_index;
        } else {
            result.len = 0;
        }
    }
    return result;
}

template <class T>
struct ReverseIter {
    T        *data;
    Slice<T> *arr;

    ReverseIter operator++(int) {
        ReverseIter ret = *this;
        data -= 1;
        return ret;
    }
    ReverseIter &operator++() {
        data -= 1;
        return *this;
    }

    T &operator*() { return data[0]; }
    T *operator->() { return data; }

    friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
    friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };

    ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; }
    ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; }
};

template <class T>
ReverseIter<T> IterateInReverse(Array<T> *arr) {
    return {arr->end() - 1, &arr->slice};
}
template <class T>
ReverseIter<T> IterateInReverse(Slice<T> *slice) {
    return {slice->end() - 1, slice};
}

