// vim:fileencoding=utf-8:foldmethod=marker

#ifndef ARRAY_H
#define ARRAY_H

#include "../mem/allocator/mem_allocator.h"
#include "../../common/misc/misc_utils.h"
#include "../../common/aliases/aliases.h"
#include "../../common/platform/platform.h"

#ifdef WP_PLATFORM_CPP
BEGIN_C_LINKAGE
#endif // !WP_PLATFORM_CPP

#define WP_ARRAY_MAGIC (u64)0x57415f415252

#define _calcArrayCount(TYPE, ...) wpMiscUtilsVaArgsCount(TYPE, __VA_ARGS__)
#define _calcArrayCapacity(TYPE, ...) wpMiscUtilsU64RoundUpPow2(_calcArrayCount(TYPE, __VA_ARGS__) * 2)

typedef struct WpStr8 WpStr8;

// NOTE (Abdelrahman): Typedefs to distinguish arrays from regular pointers
typedef void   *WpArray;
typedef void  **WpVoidPtrArray;
typedef c8     *WpC8Array;
typedef c16    *WpC16Array;
typedef c32    *WpC32Array;
typedef u8     *WpU8Array;
typedef u16    *WpU16Array;
typedef u32    *WpU32Array;
typedef u64    *WpU64Array;
typedef b8     *WpB8Array;
typedef i8     *WpI8Array;
typedef i16    *WpI16Array;
typedef i32    *WpI32Array;
typedef i64    *WpI64Array;
typedef f32    *WpF32Array;
typedef f64    *WpF64Array;
typedef f128   *WpF128Array;
typedef uptr   *WpUptrArray;
typedef iptr   *WpIptrArray;
typedef WpStr8 *WpStr8Array;

typedef enum {
	WP_ARRAY_INIT_NONE   = 0,
	WP_ARRAY_INIT_FILLED = 1 << 1,
} WpArrayInitFlags;

#ifdef WP_PLATFORM_CPP
#define wpArray(TYPE, ...) ([&]() {                                                                \
	u64 capacity = _calcArrayCapacity(TYPE, __VA_ARGS__);                                            \
	                                                                                                 \
	TYPE items[_calcArrayCapacity(TYPE, __VA_ARGS__)] = {__VA_ARGS__};                               \
	                                                                                                 \
	wp_persist u8 array[                                                                             \
		sizeof(WpArrayHeader) + _calcArrayCapacity(TYPE, __VA_ARGS__) * sizeof(TYPE)                   \
	] = {0};                                                                                         \
	WpArrayHeader *header = (WpArrayHeader *)array;                                                  \
	header->magic       = WP_ARRAY_MAGIC;                                                            \
	header->count       = _calcArrayCount(TYPE, __VA_ARGS__);                                        \
	header->capacity    = _calcArrayCapacity(TYPE, __VA_ARGS__);                                     \
	header->item_size   = sizeof(TYPE);                                                              \
	                                                                                                 \
	u8 *buf = (u8 *)(header + 1);                                                                    \
	memcpy(buf, items, capacity * sizeof(TYPE));                                                     \
	return (TYPE *)buf;                                                                              \
}())
#define wpArrayWithCapacity(TYPE, CAPACITY, FLAGS) ([&]() {                                        \
	wp_persist u8 array[                                                                             \
		sizeof(WpArrayHeader) + CAPACITY * sizeof(TYPE)                                                \
	] = {0};                                                                                         \
	WpArrayHeader *header = (WpArrayHeader *)array;                                                  \
	header->magic       = WP_ARRAY_MAGIC;                                                            \
	header->count       = (FLAGS & WP_ARRAY_INIT_FILLED) ? CAPACITY : 0;                             \
	header->capacity    = CAPACITY;                                                                  \
	header->item_size   = sizeof(TYPE);                                                              \
	                                                                                                 \
	return (TYPE *)(header + 1);                                                                     \
}())
#define wpArrayPop(TYPE, ARRAY) ([&]() {                                                           \
	if (ARRAY == NULL || _arrayCount((WpArray)ARRAY) == 0) {                                         \
		TYPE result{};                                                                                 \
		return result;                                                                                 \
	}                                                                                                \
	                                                                                                 \
	return *((TYPE *)_arrayPop((WpArray)ARRAY, sizeof(TYPE)));                                       \
}())
#else
#define _stackArray(TYPE, SIZE) struct {WpArrayHeader header;                                      \
                                         TYPE items[SIZE];                                         \
                                         wpMiscUtilsReservePadding(sizeof(WpArrayHeader) +         \
                                                                         sizeof(TYPE) * SIZE);}    
#define wpArray(TYPE, ...)                                                                         \
	((TYPE *)(                                                                                       \
		(_stackArray(TYPE, _calcArrayCapacity(TYPE, __VA_ARGS__))){                                    \
			.header = {                                                                                  \
				.magic     = WP_ARRAY_MAGIC,                                                               \
				.count     = _calcArrayCount(TYPE, __VA_ARGS__),                                           \
				.capacity  = _calcArrayCapacity(TYPE, __VA_ARGS__),                                        \
				.item_size = sizeof(TYPE),                                                                 \
			},                                                                                           \
			.items  = {__VA_ARGS__},                                                                     \
		}.items                                                                                        \
	))
#define wpArrayWithCapacity(TYPE, CAPACITY, FLAGS)                                                 \
	((TYPE *)(                                                                                       \
		(_stackArray(TYPE, CAPACITY)){                                                                 \
			.header = {                                                                                  \
				.magic     = WP_ARRAY_MAGIC,                                                               \
				.count     = (FLAGS & WP_ARRAY_INIT_FILLED) ? CAPACITY : 0,                                \
				.capacity  = CAPACITY,                                                                     \
				.item_size = sizeof(TYPE),                                                                 \
			},                                                                                           \
			.items  = {0},                                                                               \
		}.items                                                                                        \
	))
#define wpArrayPop(TYPE, ARRAY)                                                                    \
	(ARRAY == NULL || _arrayCount((WpArray)ARRAY) == 0 ?                                             \
		(TYPE){0} :                                                                                    \
		*((TYPE *)_arrayPop((WpArray)ARRAY, sizeof(TYPE)))                                             \
	)
#endif // !WP_PLATFORM_CPP

#define wpArrayCount(ARRAY)                                                                        \
	(_arrayCount((WpArray)ARRAY))
#define wpArrayCapacity(ARRAY)                                                                     \
	(_arrayCapacity((WpArray)ARRAY))
#define wpArrayItemSize(ARRAY)                                                                     \
	(_arrayItemSize((WpArray)ARRAY))
#define wpArraySetCount(ARRAY, COUNT)                                                              \
	(_arraySetCount((WpArray)ARRAY, COUNT))
#define wpArrayGet(TYPE, ARRAY, INDEX)                                                             \
	((TYPE *)_arrayGet((WpArray)ARRAY,                                                               \
	                    INDEX,                                                                       \
	                    sizeof(TYPE)))
#define wpArraySet(TYPE, ARRAY, INDEX, VALUE_PTR)                                                  \
	(_arraySet((WpArray)ARRAY,                                                                       \
	            INDEX,                                                                               \
	            (u8 *)VALUE_PTR,                                                                     \
	            sizeof(TYPE)))
#define wpArrayAppendCapped(TYPE, ARRAY, VALUE_PTR)                                                \
	(_arrayAppendCapped((WpArray)ARRAY,                                                              \
	                      (u8 *)VALUE_PTR,                                                           \
	                      sizeof(TYPE)))
#define wpArrayExtendCapped(TYPE, DST_ARRAY, SRC_ARRAY)                                            \
	(_arrayExtendCappend((WpArray)DST_ARRAY,                                                         \
	                      (WpArray)SRC_ARRAY,                                                        \
	                      sizeof(TYPE)))
#define wpArrayCopyCapped(TYPE, DST_ARRAY, SRC_ARRAY)                                              \
	(_arrayCopyCapped((WpArray)DST_ARRAY,                                                            \
	                    (WpArray)SRC_ARRAY,                                                          \
	                    sizeof(TYPE)))
#define wpArrayAppendAlloc(TYPE, ALLOCATOR_PTR, ARRAY, VALUE_PTR, FLAGS)                           \
	((TYPE *)_arrayAppendAlloc(ALLOCATOR_PTR,                                                        \
	                             (WpArray)ARRAY,                                                     \
	                             (u8 *)VALUE_PTR,                                                    \
	                             FLAGS,                                                              \
	                             sizeof(TYPE)))
#define wpArrayExtendAlloc(TYPE, ALLOCATOR_PTR, DST_ARRAY, SRC_ARRAY, FLAGS)                       \
	((TYPE *)_arrayExtendAlloc(ALLOCATOR_PTR,                                                        \
	                             (WpArray)DST_ARRAY,                                                 \
	                             (WpArray)SRC_ARRAY,                                                 \
	                             FLAGS,                                                              \
	                             sizeof(TYPE)))
#define wpArrayCopyAlloc(TYPE, ALLOCATOR_PTR, DST_ARRAY, SRC_ARRAY, FLAGS)                         \
	((TYPE *)_arrayCopyAlloc(ALLOCATOR_PTR,                                                          \
	                           (WpArray)DST_ARRAY,                                                   \
	                           (WpArray)SRC_ARRAY,                                                   \
	                           FLAGS,                                                                \
	                           sizeof(TYPE)))
#define wpArrayClear(TYPE, ARRAY)                                                                  \
	(_arrayClear((WpArray)ARRAY,                                                                     \
	             sizeof(TYPE)))
#define wpArrayCalcAllocSize(TYPE, CAPACITY) _arrayCalcAllocSize(CAPACITY, sizeof(TYPE))
#define wpArrayAllocCapacity(TYPE, ALLOCATOR_PTR, CAPACITY, FLAGS)                                 \
	((TYPE *)_arrayAllocCapacity(ALLOCATOR_PTR, CAPACITY, FLAGS, sizeof(TYPE)))
#define wpArrayFromPreallcatedBuffer(TYPE, BUFFER, BUFFER_SIZE)                                    \
	((TYPE *)_array_from_preallcated_buffer(BUFFER, BUFFER_SIZE, sizeof(TYPE)))
// Only needed for allocators like malloc where each allocation has to be freed on its own.
// No need to use it for allocators like Arena.
#define wpArrayDealloc(TYPE, ALLOCATOR_PTR, ARRAY_DPTR)                                            \
	(_arrayDealloc(ALLOCATOR_PTR, (WpArray *)ARRAY_DPTR, sizeof(TYPE)))


typedef struct WpArrayHeader WpArrayHeader;
struct WpArrayHeader {
  u64 magic;
  u64 count;
  u64 capacity;
  u64 item_size;
};

u64     _arrayCount(WpArray array);
u64     _arrayCapacity(WpArray array);
u64     _arrayItemSize(WpArray array);
void    _arraySetCount(WpArray array, u64 count);
void   *_arrayGet(WpArray array, u64 index, u64 item_size);
void    _arraySet(WpArray array, u64 index, void *value, u64 item_size);
void    _arrayAppendCapped(WpArray array, void *value, u64 item_size);
void    _arrayExtendCappend(WpArray dst, const WpArray src, u64 item_size);
void    _arrayCopyCapped(WpArray dst, const WpArray src, u64 item_size);
WpArray _arrayAppendAlloc(const WpAllocator *allocator, WpArray array, void *value,
                          WpArrayInitFlags flags, u64 item_size);
WpArray _arrayExtendAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
                          WpArrayInitFlags flags, u64 item_size);
WpArray _arrayCopyAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
                        WpArrayInitFlags flags, u64 item_size);
void   *_arrayPop(WpArray array, u64 item_size);
void    _arrayClear(WpArray array, u64 item_size);
u64     _arrayCalcAllocSize(u64 capacity, u64 item_size);
WpArray _arrayAllocCapacity(const WpAllocator *allocator, u64 capacity, WpArrayInitFlags flags,
                            u64 item_size);
WpArray _arrayFromPreallocatedBuffer(void *buffer, u64 buffer_size, WpArrayInitFlags flags,
                                     u64 item_size);
void    _arrayDealloc(const WpAllocator *allocator, WpArray *array, u64 item_size);

#ifdef WP_PLATFORM_CPP
END_C_LINKAGE
#endif // !WP_PLATFORM_CPP

#endif // !ARRAY_H
