#pragma once

struct LocationTrace {
    char *file;
    int line;
};

#if DEBUG_BUILD
thread_local LocationTrace LocationTraceO;
#define LOCATION_TRACE (LocationTraceO.file = __FILE__, LocationTraceO.line = __LINE__)
#else
#define LOCATION_TRACE
#endif

const int AllocatorKind_Allocate   = 1;
const int AllocatorKind_Deallocate = 2;

struct Allocator {
    void *(*proc)(void *object, int kind, void *p, size_t size);
    void *object;
};

#define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type))
#define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count))
#define AllocSize(alo, size) (LOCATION_TRACE, _AllocSize_(alo, size))
API void *_AllocSize_(Allocator alo, size_t size);
inline void Dealloc(Allocator alo, void *p) { if (p) (alo).proc((alo).object, AllocatorKind_Deallocate, (p), 0); }

API Allocator GetSystemAllocator();
API void TrackingAllocatorCheck();
API Allocator GetTrackingAllocator();

#define MemoryZero(x, size) memset(x, 0, size)
#define MemoryZeroStruct(x) memset(x, 0, sizeof(*x))
#define MemoryCopy(dst, src, size) memcpy(dst, src, size)
#define MemoryMove(dst, src, size) memmove(dst, src, size)

///////////////////
// Block Arena
///////////////////
API void *BlockArenaAllocatorProc(void *object, int kind, void *p, size_t size);

struct BlockArenaNode {
    BlockArenaNode *next;
    U8 *end;
    U8 start[0];
};

struct BlockArena {
    U8 *start;
    U8 *end;
    BlockArenaNode *blocks;
    Allocator allocator;

    operator Allocator() { return {BlockArenaAllocatorProc, this}; }
};

API void *PushSize(BlockArena *arena, size_t size);
API void  Release(BlockArena *arena);
API void  Unwind(BlockArena *arena, U8 *pos);

///////////////////
// Virtual Arena
///////////////////

struct VirtualArena {
    uint8_t *data;
    size_t   len;
    size_t   base_len; // to prevent self deleting the arena
    size_t   reserve;
    size_t   commit;
    size_t   align;

    operator Allocator() {
        void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size);
        return {ArenaAllocatorProc, this};
    }
};


inline void SetLen(VirtualArena *arena, size_t len) { arena->len = Clamp(len, arena->base_len, arena->len); }
inline void Pop(VirtualArena *arena, size_t size) { SetLen(arena, arena->len - size); }
inline void Clear(VirtualArena *arena) { SetLen(arena, 0); }

API void *VReserve(size_t size);
API bool  VCommit(void *p, size_t size);
API bool  VRelease(void *p, size_t size);
API bool  VDecommit(void *p, size_t size);

API void          InitArena(VirtualArena *arena, size_t reserve = MiB(256));
API VirtualArena *AllocArena(size_t reserve = MiB(256));
API VirtualArena *AllocArena(Allocator allocator, size_t size);
API void         *PushSize(VirtualArena *arena, size_t size);
API void          Release(VirtualArena *arena);

///////////////////////////////
// Scratch

struct Scratch {
    BlockArena arena = {};
    Scratch() {}
    Scratch(BlockArena *conflict) {}
    Scratch(BlockArena *c1, BlockArena *c2) {}
    Scratch(Allocator conflict) {}
    Scratch(Allocator c1, Allocator c2) {}
    ~Scratch() { Release(&arena); }
    operator BlockArena *() { return &arena; }
    operator Allocator() { return arena; }

  private: // @Note: Disable copy constructors, cause its error prone
    Scratch(Scratch &arena);
    Scratch(Scratch &arena, Scratch &a2);
};

const int PAGE_SIZE         = 4096;
const int DEFAULT_ALIGNMENT = sizeof(void *);