////////////////////////////////
//- sixten: Common Memory Functions

static void Copy(void *Dest, void *Source, umm Count)
{
	if(Count <= S32_Max)
	{
		u8 *Dest8 = (u8 *)Dest;
		u8 *Source8 = (u8 *)Source;
		
		while(Count--)
		{
			*Dest8++ = *Source8++;
		}
	}
}

static void CopyReverse(void *Dest, void *Source, umm Count)
{
	if(Count <= S32_Max)
	{
		u8 *Dest8 = (u8 *)Dest + Count;
		u8 *Source8 = (u8 *)Source + Count;
		
		while(Count--)
		{
			*--Dest8 = *--Source8;
		}
	}
}

static void Fill(void *Dest, u8 Value, umm Count)
{
	if(Count <= S32_Max)
	{
		u8 *Dest8 = (u8 *)Dest;
		
		while(Count--)
		{
			*Dest8++ = Value;
		}
	}
}

#if VN_ASAN_ENABLED
#include <sanitizer/asan_interface.h>
#endif

////////////////////////////////
//- sixten: Memory Arena Functions

static arena *ArenaAlloc(u64 Size, b32 Chaining, char *Format, ...)
{
	arena *Arena = 0;
	if(Chaining)
	{
		void *Memory = Platform.Allocate(Size);
		Arena = (arena *)Memory;
		
#if VN_ASAN_ENABLED
		ASAN_POISON_MEMORY_REGION(Arena, Size);
		ASAN_UNPOISON_MEMORY_REGION(Arena, sizeof(arena));
#endif
		
		Arena->Chaining = true;
		SenDLLInit(Arena);
		
	}
	else
	{
		u64 RoundedSize = Size+Megabytes(64)-1;
		RoundedSize -= RoundedSize&Megabytes(64);
		void *Memory = Platform.Reserve(RoundedSize);
		u64 InitialCommitSize = MEMORY_ARENA_COMMIT_SIZE;
		Platform.Commit(Memory, InitialCommitSize);
		Arena = (arena *)Memory;
	}
	Arena->Position = sizeof(arena);
	Arena->Size = Size;
	Arena->Align = 8;
	return(Arena);
}

static void ArenaRelease(arena *Arena)
{
	if(Arena->Chaining)
	{
		arena *Node = Arena->Prev;
		for(;Node != Arena;)
		{
			arena *Next = Node->Prev;
			Platform.Deallocate(Node, Node->Size);
			Node = Next;
		}
#if VN_ASAN_ENABLED
		ASAN_POISON_MEMORY_REGION(Arena, Arena->Size);
#endif
		Platform.Deallocate(Arena, Arena->Size);
	}
	else
	{
		Platform.Release(Arena);
	}
}

static void *ArenaPushNoClear(arena *Arena, u64 Size)
{
	void *Result = 0;
	if(Arena->Chaining)
	{
		if(Size <= Arena->Size)
		{
			arena *Target = Arena->Prev; // sixten: We always append to the end of the list.
			if(Target->Position + Size + Arena->Align > Target->Size)
			{
				arena *New = ArenaAlloc(Arena->Size, true, "Chain");
				New->NotFirst = true;
				SenDLLInsertLast(Arena, New);
				Target = New;
			}
			u8 *Base = (u8 *)Target;
			u64 PostAlignPos = (Target->Position+Target->Align-1);
			PostAlignPos -= PostAlignPos%Target->Align;
			Result = Base + PostAlignPos;
			Target->Position = PostAlignPos+Size;
			
#if VN_ASAN_ENABLED
			ASAN_UNPOISON_MEMORY_REGION(Result, Size);
#endif
		}
		else
		{
			// sixten(NOTE): We can't really do anything in this situation.
		}
	}
	else
	{
		if(Arena->Position + Size <= Arena->Size)
		{
			u8 *Base = (u8 *)Arena;
			u64 PostAlignPos = (Arena->Position+Arena->Align-1);
			PostAlignPos -= PostAlignPos%Arena->Align;
			u64 Align = PostAlignPos - Arena->Position;
			Result = Base + Arena->Position + Align;
			Arena->Position += Size + Align;
			if(Arena->CommitPosition < Arena->Position)
			{
				u64 ToCommit = Arena->Position - Arena->CommitPosition;
				ToCommit += MEMORY_ARENA_COMMIT_SIZE - 1;
				ToCommit -= ToCommit%MEMORY_ARENA_COMMIT_SIZE;
				Platform.Commit(Base + Arena->CommitPosition, ToCommit);
				Arena->CommitPosition += ToCommit;
			}
		}
		else
		{
			InvalidCodepath;
		}
	}
	return(Result);
}

static void *ArenaPush(arena *Arena, u64 Size)
{
	void *Result = ArenaPushNoClear(Arena, Size);
	Fill(Result, 0, Size);
	return(Result);
}

static void ArenaPopTo(arena *Arena, u64 Position)
{
	if(Arena->Chaining)
	{
		// sixten: find the relevant arena
		arena *Target = Arena;
		for(int ArenaIndex = 0; ArenaIndex < Position/Arena->Size; ++ArenaIndex)
		{
			Target = Target->Next;
		}
		
		// sixten: deallocate all arenas that are no longer needed
		{
			arena *Node = Arena->Prev;
			while(Node != Target)
			{
				arena *Prev = Node->Prev;
				SenDLLRemove(Node);
#if VN_ASAN_ENABLED
				ASAN_POISON_MEMORY_REGION(Node, Node->Size);
#endif
				Platform.Deallocate(Node, Node->Size);
				Node = Prev;
			}
		}
		
		Target->Position = Position%Arena->Size;
#if VN_ASAN_ENABLED
		u64 UnpoisonPosition = Target->Position;
		ASAN_POISON_MEMORY_REGION(Target, Target->Size);
		ASAN_UNPOISON_MEMORY_REGION(Target, UnpoisonPosition);
#endif
	}
	else
	{
		u64 MinPosition = sizeof(*Arena);
		u64 NewPosition = Maximum(MinPosition, Position);
		Arena->Position = NewPosition;
		u64 CommitAlignedPosition = Arena->Position+MEMORY_ARENA_COMMIT_SIZE-1;
		CommitAlignedPosition -= CommitAlignedPosition%MEMORY_ARENA_COMMIT_SIZE;
		if(CommitAlignedPosition + MEMORY_ARENA_DECOMMIT_THRESHOLD <= Arena->CommitPosition)
		{
			u8 *Base = (u8 *)Arena;
			u64 ToDecommit = Arena->CommitPosition-CommitAlignedPosition;
			Platform.Decommit(Base+CommitAlignedPosition, ToDecommit);
			Arena->CommitPosition -= ToDecommit;
		}
	}
}

static void ArenaPop(arena *Arena, u64 Amount)
{
	ArenaPopTo(Arena, Max(Arena->Position-Amount, (s64)sizeof(arena)));
}

static void ArenaClear(arena *Arena)
{
	ArenaPopTo(Arena, sizeof(*Arena));
}

static void ArenaSetAlign(arena *Arena, u64 Align)
{
	Arena->Align = Align;
}

////////////////////////////////
//- sixten: Temporary Memory Functions

static temp BeginTemp(arena *Arena)
{
	temp Temp;
	Temp.Arena = Arena;
	Temp.Position = Arena->Position;
	return(Temp);
}

static void EndTemp(temp Temp)
{
	ArenaPopTo(Temp.Arena, Temp.Position);
}