//================================================================================================
// Profiling
//
// AUTHOR:  Gabor Nagy
// DATE:    2023-Oct-17 22:06:53
//
// Copyright 2014-2026 Gabor Nagy. All rights reserved.
//================================================================================================
#ifndef _ETiming_h
#define _ETiming_h

#include <EStrings.h>

// We mostly use macros here, so they can be "compiled away" when profiling is disabled
//

#ifndef TIMER_USE_AUTO_SCOPE
	#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
		#define TIMER_USE_AUTO_SCOPE  1
	#endif
#endif

#ifndef TIMER_PRINTOUT_ENABLE
	#define TIMER_PRINTOUT_ENABLE  1
#endif


#define TIMER_MAX_NAMELEN        256
#define TIMER_PAD_DEBUG          32

#define TIMER_INITIAL_ALLOC      256
#define TIMER_ALLOC_INCREMENT    256
#ifndef TIMER_NAME_COLUMN_WIDTH
#define TIMER_NAME_COLUMN_WIDTH  24
#endif

#define TimerPrefix  do { TextColorCyan;printf("[Profiler] ");fflush(stdout);TextColorNormal; } while(0)
#define TimerPrefixF do { TextColorCyan;fprintf(PStream, "[Profiler] ");TextColorNormal; } while(0)

#ifndef ETm_Malloc

#define ETm_Malloc(p)                       malloc(p)

#define ETm_Resize(pType, pCount, pBuffer)  do { pBuffer = (pType*)realloc(pBuffer, sizeof(pType) * (pCount)); } while(0)

#define ETm_New(pType, pDst)                do { pDst = (pType*)ETm_Malloc(sizeof(pType)); } while(0)
#define ETm_NewClr(pType, pDst)             do { const int  lByteSize = sizeof(pType);pDst = (pType*)ETm_Malloc(lByteSize);memset(pDst, 0, lByteSize); } while(0)

#define ETm_NewN(pType, pCount, pDst)       do { pDst = (pType*)ETm_Malloc(sizeof(pType) * (pCount)); } while(0)
#define ETm_NewNClr(pType, pCount, pDst)    do { const int  lByteSize = sizeof(pType) * (pCount);pDst = (pType*)ETm_Malloc(lByteSize);memset(pDst, 0, lByteSize); } while(0)

#define ETm_Free(p)                       free(p)
#define ETm_FreeChkClr(p)                 if(p) { ETm_Free(p);p = NULL; }
#define ETm_DeleteChkClr(p)               if(p) { delete p;p = NULL; }
#define ETm_DeleteChk(p)                  if(p) delete p;

#ifndef TIMER_DRAW_HIERARCHY_LINKS
	#define TIMER_DRAW_HIERARCHY_LINKS  0
#endif

#else  // ETm_Malloc

#define TIMER_DRAW_HIERARCHY_LINKS  0

#endif  // ETm_Malloc


	#ifdef _WIN32
		#define TimingWINDOWS  1
	#else
		#ifdef _WIN64
			#define TimingWINDOWS  1
		#endif
	#endif

	#ifndef TextColorNormal
		#ifdef TimingWINDOWS
			#define  TextColorNormal   do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); } while(0)
			#define  TextColorRed      do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_RED|FOREGROUND_INTENSITY); } while(0)
			#define  TextColorGreen    do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_GREEN|FOREGROUND_INTENSITY); } while(0)
			#define  TextColorBlue     do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_BLUE|FOREGROUND_INTENSITY); } while(0)
			#define  TextColorCyan     do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); } while(0)
			#define  TextColorMagenta  do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_INTENSITY); } while(0)
			#define  TextColorYellow   do { HANDLE  hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut, FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY); } while(0)
		#else
			static const char  NormalCode[]  = { 0x1B, '[' ,'m', 0 };
			static const char  RedCode[]     = { 0x1B, '[', '3', '1', 'm', 0 };
			static const char  GreenCode[]   = { 0x1B, '[', '3', '2', 'm', 0 };
			static const char  YellowCode[]  = { 0x1B, '[', '3', '3', 'm', 0 };
			static const char  BlueCode[]    = { 0x1B, '[', '1', 'm', 0 };
			static const char  MagentaCode[] = { 0x1B, '[', '3', '5', 'm', 0 };
			static const char  CyanCode[]    = { 0x1B, '[', '3', '6', 'm', 0 };

			#define  TextColorNormal   printf("%s", NormalCode)
			#define  TextColorRed      printf("%s", RedCode)
			#define  TextColorGreen    printf("%s", GreenCode)
			#define  TextColorBlue     printf("%s", BlueCode)
			#define  TextColorCyan     printf("%s", CyanCode)
			#define  TextColorMagenta  printf("%s", MagentaCode)
			#define  TextColorYellow   printf("%s", YellowCode)
		#endif
	#endif


typedef uint64_t  TIMERTime;

//
// A ETmTimerEntry is an individual instance of a timed code segment
//
typedef struct
{
	char            Name[TIMER_MAX_NAMELEN];  // We use static buffers here to avoid the CPU overhead of malloc/free (we're trying to measure performance after all)
  const char*     FunctionName;const char*  FileName;int  LineNumber;  // Location of invocation
	char            Bracket;                  // Opening character for bracketing when printing the name (e.g. '[' means  [name])

	const char*     FWName;                   // Fly-weight name : pointer to the Name member of the first ETmTimerEntry that has this name. After a capture is done, we set and use these for quick (pointer) comparisons.
	EBool           AddedToUnit;

	TIMERTime       Start, End, Tare;

	int             ParentIndex;              // The parent is set as we build the hierarchy. We use and index because Entries may be reallocated during the build.
	int             ChildIndex;               // Child and RightSibling are computed after the profile stack is built

	int             Level;
} ETmTimerEntry;

//
// A ETmProfilingUnit is a collection of ETmTimerEntry elements that share the same name
//
typedef struct
{
	const char*     Name;
	int*            EntryIndices;int  EntryIndices_Count;
	TIMERTime       TotalTime, SelfTime;  // Total time spent in unit. Time spent in unit, excluding time in children
} ETmProfilingUnit;


typedef struct
{
	int             Verbosity;

	ETmTimerEntry*  Entries;
	int             Entries_Count, Entries_CountAllocated;

	int             CurrentParentIndex;
	int             CurrentLevel;

	TIMERTime       PrevTime;


// Profiling info
//
	ETmProfilingUnit*  ProfilingUnits;int  ProfilingUnits_Count, ProfilingUnits_CountAllocated;

	uint32_t           Enabled : 1, Initialized : 1;
} ETmTimerStack;


#define _GetTime() E_GetMicroseconds()

#define ETmList_IntAdd(pList, pValue) do\
{\
	ETm_Resize(int, pList##_Count + 1, pList);\
	pList[pList##_Count] = pValue;\
	pList##_Count += 1;\
} while(0)

#define ETmList_StructAddAClr(pType, pList, pAllocIncrement,  pDst) do\
{\
	if(pList##_Count >= pList##_CountAllocated)\
	{\
		const int  lNewAllocCount = pList##_Count + pAllocIncrement;\
		ETm_Resize(pType, lNewAllocCount, pList);\
		pList##_CountAllocated = lNewAllocCount;\
	}\
	memset(pList + pList##_Count, 0, sizeof(pType));\
	pDst = pList + pList##_Count;\
	pList##_Count += 1;\
} while(0)


static inline void ETimerStack_Init(ETmTimerStack* PStack)
{
	if(!PStack->Initialized)
	{
		EClr_p(PStack);
		PStack->Initialized = TRUE;
	}
	PStack->PrevTime = _GetTime();PStack->Entries_Count = 0;
	PStack->ProfilingUnits_Count = 0;
	PStack->CurrentLevel = 0;
	PStack->CurrentParentIndex = -1;

	PStack->Enabled = TRUE;
}

static inline void ETimerStack_FreeContents(ETmTimerStack* PStack)
{
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
}

static void	_UnitsClear(ETmTimerStack* PTimerStack)
{
	if(PTimerStack->ProfilingUnits)
	{
		int  LC = 0;const int  LN = PTimerStack->ProfilingUnits_Count;ETmProfilingUnit*  LUnit = PTimerStack->ProfilingUnits;for(;LC < LN;LC++)
		{
			EFreeChk(LUnit->EntryIndices);
			memset(LUnit, 0, sizeof(ETmProfilingUnit));
		}
		PTimerStack->ProfilingUnits_Count = 0;
	}
}


static inline void _UnitsFree(ETmTimerStack* PTimerStack)
{
	if(PTimerStack->ProfilingUnits)
	{
		int  LC = 0;const int  LN = PTimerStack->ProfilingUnits_Count;ETmProfilingUnit*  LUnit = PTimerStack->ProfilingUnits;for(;LC < LN;LC++)
		{
			EFreeChk(LUnit->EntryIndices);
		}
		EFreeClr(PTimerStack->ProfilingUnits);PTimerStack->ProfilingUnits_Count = PTimerStack->ProfilingUnits_CountAllocated = 0;
	}
}


//================================================================================================
// Set FWName fields to point to the Name field of the first Entry that has the same name.
// This will allow us to replace string comparisons with pointer comparisons.
//================================================================================================
static inline void _SetFWNames(ETmTimerStack* PTimerStack)
{
	{
		EIndex  LC = 0;const EIndex  LN = PTimerStack->Entries_Count;ETmTimerEntry*  LEntry = PTimerStack->Entries;for(;LC < LN;LC++, LEntry++)
		{
			if(LEntry->FWName == NULL)
			{
				const char*  LName = LEntry->Name;
				LEntry->FWName = LName;

// Find other Entries with the same name and set their FWName as well
//
				EIndex  LC1 = LC + 1;ETmTimerEntry*  LEntry1 = PTimerStack->Entries + LC1;for(;LC1 < LN;LC1++, LEntry1++)
				{
					if(LEntry1->FWName == NULL && EStr_Equal(LName, LEntry1->Name)) LEntry1->FWName = LName;
				}
			}
		}
	}

/*
	{
		EIndex  LC = 0;const EIndex  LN = PTimerStack->Entries_Count;ETmTimerEntry*  LEntry = PTimerStack->Entries;for(;LC < LN;LC++, LEntry++)
		{
printf("%s() %s:%d  %s%p:%s%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, LEntry->FWName, LEntry->FWName, ECodeNormal);fflush(stdout);
			if(LEntry->FWName == NULL)
			{
printf("%s() %s:%d  %5lld [%s] %sNo FWName%s\n", __FUNCTION__, __FILE__, __LINE__,  (long long)LC, LEntry->Name,  ECodeYellow, ECodeNormal);fflush(stdout);
			}
		}
	}
*/
}


//================================================================================================================================
// Process timing information in a TimerStack.
//
// Arguments
//  PTimerStack      Pointer to the TimerStack
//  PExpandLevels    Expand units with the same name, if they have children (e.g. from recursive calls)
//
// Description
//  Call this function before calling ETimerStack_OutputProfilingInfo().
//  To make timings more accurate and minimize interference with the code being profiled,
//  data collection overhead during processing is kept to an absolute minimum.
//  You can shift that processing to a "safe" time, by calling this function, for example at the
//  end of a frame in a video game, or after data for several frames are collected.
//
// See also
//  ETimerStack_Init, ETimerStack_FreeContents, TimeStamp_PUSH, TimeStamp_PUSH_D, TimeStamp_POP, ETimerStack_OutputProfilingInfo
//================================================================================================================================
static inline int ETimerStack_CollectProfilingInfo(ETmTimerStack* PTimerStack, const EBool PExpandLevels)
{
	if(!PTimerStack->Enabled) return(E_DISABLED);

// Fill out hierarchy links : Child and RightSibling.
// This will allow us to more efficiently parse the hierarchy.
//
	{
		int  LC = 0;const int  LN = PTimerStack->Entries_Count;ETmTimerEntry*  LEntry = PTimerStack->Entries;for(;LC < LN;LC++, LEntry++)
		{
			const int  LParentIndex = LEntry->ParentIndex;if(LParentIndex >= 0)
			{
				ETmTimerEntry*  LParent = PTimerStack->Entries + LParentIndex;
				if(LParent->ChildIndex == -1) LParent->ChildIndex = LC;
			}
		}
	}

	_UnitsClear(PTimerStack);

	_SetFWNames(PTimerStack);

	const ETmTimerEntry*  LTimerStackEntries = PTimerStack->Entries;

	{
		EIndex  LEntryI = 0;const EIndex  LEntryN = PTimerStack->Entries_Count;ETmTimerEntry*  LEntry = PTimerStack->Entries;for(;LEntryI < LEntryN;LEntryI++, LEntry++)
		{
//printf("%s() %s:%d  Entry % 3lld [%s]\n", __FUNCTION__, __FILE__, __LINE__, (long long)LEntryI, LEntry->Name);fflush(stdout);
			if(!LEntry->AddedToUnit)
			{
				ETmProfilingUnit*  LNewProfilingUnit;

				ETmList_StructAddAClr(ETmProfilingUnit, PTimerStack->ProfilingUnits, TIMER_ALLOC_INCREMENT,  LNewProfilingUnit);
				LEntry->AddedToUnit = TRUE;

				ETmList_IntAdd(LNewProfilingUnit->EntryIndices, LEntryI);

				const char*  LEntryFWName = LEntry->FWName;
				const int    LLevel = LEntry->Level;
				LNewProfilingUnit->Name = LEntryFWName;

// Find other Entries with the same name and add them to LNewProfilingUnit 
//
				if(PExpandLevels)
				{
					int  LC1 = LEntryI + 1;ETmTimerEntry*  LEntry1 = PTimerStack->Entries + LC1;for(;LC1 < LEntryN;LC1++, LEntry1++)
					{
						if(!LEntry1->AddedToUnit && LEntryFWName == LEntry1->FWName)
						{
							EBool  LRecursive = FALSE;const ETmTimerEntry*  LEntryT = LEntry1;while(LEntryT->ParentIndex >= 0) { LEntryT = LTimerStackEntries + LEntryT->ParentIndex;if(LEntryT->FWName == LEntryFWName) { LRecursive = TRUE;break; } }

							if(LEntry1->Level == LLevel || LRecursive) { ETmList_IntAdd(LNewProfilingUnit->EntryIndices, LC1);LEntry1->AddedToUnit = TRUE; }
//								ETmList_IntAdd(LNewProfilingUnit->EntryIndices, LC1);LEntry1->AddedToUnit = TRUE;
						}
					}
				}
				else
				{
					int  LC1 = LEntryI + 1;ETmTimerEntry*  LEntry1 = PTimerStack->Entries + LC1;for(;LC1 < LEntryN;LC1++, LEntry1++)
					{
						if(!LEntry1->AddedToUnit && LEntryFWName == LEntry1->FWName)
						{
							LEntry1->AddedToUnit = TRUE;
							ETmList_IntAdd(LNewProfilingUnit->EntryIndices, LC1);
						}
					}
				}
			}
		}
	}

//printf("%s() %s:%d  PTimerStack->Entries_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)PTimerStack->Entries_Count);fflush(stdout);

// Recursively add up total time for each ProfilingUnit
//
	{
		EIndex  LUnitI = 0;const EIndex  LUnitN = PTimerStack->ProfilingUnits_Count;ETmProfilingUnit*  LUnit = PTimerStack->ProfilingUnits;for(;LUnitI < LUnitN;LUnitI++, LUnit++)
		{
			TIMERTime  LUnitTotalTime = 0;

/*
			if(PExpandLevels)
			{
				EIndex  LC1 = 0;const int  LN1 = LUnit->EntryIndices_Count;for(;LC1 < LN1;LC1++)
				{
					const ETmTimerEntry*  LEntry = LTimerStackEntries + LUnit->EntryIndices[LC1];
					const TIMERTime       LEntryTime = LEntry->End - LEntry->Start - LEntry->Tare;
					LUnitTotalTime += LEntryTime;
				}
			}
			else
*/
			{
//printf("%s() %s:%d  Unit % 3lld [%s]\n", __FUNCTION__, __FILE__, __LINE__, (long long)LUnitI, LUnit->Name);fflush(stdout);
				EIndex  LEntryI = 0;const int  LEntryN = LUnit->EntryIndices_Count;for(;LEntryI < LEntryN;LEntryI++)
				{
					const ETmTimerEntry*  LEntry = LTimerStackEntries + LUnit->EntryIndices[LEntryI];
					const char*           LEntryFWName = LEntry->FWName;
//printf("%s() %s:%d    <Entry %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LEntryI);fflush(stdout);
//						EBool  LRecursive = FALSE;const ETmTimerEntry*  LEntryT = LEntry;while(LEntryT->ParentIndex >= 0) { LEntryT = LTimerStackEntries + LEntryT->ParentIndex;if(LEntryT->FWName == LEntryFWName) { LRecursive = TRUE;break; } }

					EBool  LRecursive = FALSE;const ETmTimerEntry*  LEntryT = LEntry;EIndex  LLevel = 0;const EIndex  LFirstParentIndex = LEntryT->ParentIndex;EBool  LLoop = FALSE;for(;LEntryT->ParentIndex >= 0;LLevel++)
					{
//printf("%s() %s:%d      [%s] ParentIndex %lld\n", __FUNCTION__, __FILE__, __LINE__, LEntryT->Name, (long long)LEntryT->ParentIndex);fflush(stdout);
						LEntryT = LTimerStackEntries + LEntryT->ParentIndex;if(LEntryT->FWName == LEntryFWName) { LRecursive = TRUE;break; }
						if(LEntryT->ParentIndex == LFirstParentIndex) { LLoop = TRUE;break; }
					}

					if(LLoop)
					{
						printf("%s() %s:%d  %sLOOP%s (%lld nodes) at %s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, (long long)LLevel, LEntry->Name);fflush(stdout);
					}

//printf("%s() %s:%d    %lld>\n", __FUNCTION__, __FILE__, __LINE__, (long long)LEntryI);fflush(stdout);

					if(!LRecursive)
					{
						const TIMERTime  LEntryTime = LEntry->End - LEntry->Start - LEntry->Tare;
						LUnitTotalTime += LEntryTime;
					}
				}
			}

			LUnit->TotalTime = LUnitTotalTime;
		}
	}
	PTimerStack->Enabled = FALSE;  // Disable to avoid side-effects. ETimerStack_Init() calls will enable it.
	return(E_SUCCESS);
}

static inline void _PadString(const char* PStr, const int PLengthToPadTo)
{
	const int  LL = (int)strlen(PStr);
	if(LL < PLengthToPadTo)
	{
		int  LC = 0;const int  LN = PLengthToPadTo - LL;for(;LC < LN;LC++) printf(" ");
	}
}

#define TimerPrefixf  do { if(LPrintPrefix) TimerPrefixF; } while(0)

// PFlags  EStrCOLORS|EStrALIGN|EStrHEADER
static inline void ETimerStack_OutputProfilingInfo(ETmTimerStack* PTimerStack, const uint16_t PFlags,  FILE* PStream)
{
	const double  LTimeDivider = (double)1000000.0;  // We're using microseconds
	const EBool   LPrintPrefix = PStream == stdout;

//printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)PTimerStack->ProfilingUnits_Count);fflush(stdout);

	const EIndex  LN = PTimerStack->ProfilingUnits_Count;

	int  LColumnSizesX[5];EClr(LColumnSizesX);
	int  LLevelMax = 0;

	if(PFlags & EStrALIGN)
	{
		EIndex  LC = 0;ETmProfilingUnit*  LUnit = PTimerStack->ProfilingUnits;for(;LC < LN;LC++, LUnit++)
		{
			ETmTimerEntry*  LEntry = PTimerStack->Entries + LUnit->EntryIndices[0];
			int  LColumn = 0;

// Name column
			{
				const size_t  LL = 1 + EStr_Len(LUnit->Name) + 1;  // Brackets
				if(LColumnSizesX[LColumn] < LL) LColumnSizesX[LColumn] = LL;

				if(LLevelMax < LEntry->Level) LLevelMax = LEntry->Level;
				LColumn++;
			}

// Calls column
			LColumnSizesX[LColumn] = 7;


// Total time column
			 LColumnSizesX[LColumn] = 7;


// Source code location column
			 LColumnSizesX[LColumn] = 16;
		}
		LColumnSizesX[0] += LLevelMax;
	}


	const size_t  LL0 = LColumnSizesX[0];
	char  LPaddingStr[LL0];memset(LPaddingStr, ' ', LL0);

	TimerPrefixf;
	if(PFlags & EStrHEADER)
	{
		const int  LL = fprintf(PStream, "Entries : %lld        Name", (long long)PTimerStack->Entries_Count);
		const int  LPadding = LColumnSizesX[0] > LL ? LColumnSizesX[0] - LL : 0;
		_PadString("Name", LPadding);
		fprintf(PStream, "         Calls      Time       Location\n");
	}
	else fprintf(PStream, "Entries : %lld\n", (long long)PTimerStack->Entries_Count);
	fflush(stdout);

	{
		EIndex  LC = 0;ETmProfilingUnit*  LUnit = PTimerStack->ProfilingUnits;for(;LC < LN;LC++, LUnit++)
		{
			ETmTimerEntry*  LEntry = PTimerStack->Entries + LUnit->EntryIndices[0];

			TimerPrefixf;fprintf(PStream, " ");

			if(LEntry->Level > 0)
			{
#if TIMER_DRAW_HIERARCHY_LINKS
				{ int  LCI = 0;const int  LNI = LEntry->Level - 1;for(;LCI < LNI;LCI++) printf(" "); }

				TextColorGreen;
				const ETmProfilingUnit*  LNextUnit = LC <  LN - 1 ? LUnit + 1 : NULL;

				if(LNextUnit)
				{
					if(LNextUnit->NextSiblingIndex >= 0) printf("Ã");
					else fprintf(PStream, "À");
				}
				else fprintf(PStream, "À");
				TextColorNormal;
#else
				{ int  LCI = 0;const int  LNI = LEntry->Level;for(;LCI < LNI;LCI++) printf(" "); }
#endif

			}
//			printf("[%s]", LUnit->Name);_PadString(LUnit->Name, TIMER_NAME_COLUMN_WIDTH - LEntry->Level);

			char  LClosingBracket = ' ';
			switch(LEntry->Bracket)
			{
				case '[':  LClosingBracket = ']';break;
				case '{':  LClosingBracket = '}';break;
				case '|':  LClosingBracket = '|';break;
				case ':':  LClosingBracket = ':';break;
				case '#':  LClosingBracket = '#';break;
				case '%':  LClosingBracket = '%';break;
				case '*':  LClosingBracket = '*';break;
				case '-':  LClosingBracket = '-';break;
				case '=':  LClosingBracket = '=';break;
				case '<':  LClosingBracket = '>';break;
				case '(':  LClosingBracket = ')';break;
				case '"':  LClosingBracket = '"';break;
				case '/':  LClosingBracket = '/';break;
				case '\'':  LClosingBracket = '\'';break;
			}

			if(LColumnSizesX[0] > 0) { fprintf(PStream, "%c%s%c", LEntry->Bracket, LUnit->Name, LClosingBracket);_PadString(LUnit->Name, LColumnSizesX[0] - LEntry->Level); }
			else { fprintf(PStream, "%c%s%c", LEntry->Bracket, LUnit->Name, LClosingBracket);_PadString(LUnit->Name, TIMER_NAME_COLUMN_WIDTH); }

			if(PFlags & EStrHEADER)
			{
				fprintf(PStream, " %7d  % 8.2lfms", LUnit->EntryIndices_Count, (double)LUnit->TotalTime / LTimeDivider * 1000.0);
				fprintf(PStream, "   %s() %s:%d\n", LEntry->FunctionName, LEntry->FileName, LEntry->LineNumber);fflush(stdout);
			}
			else
			{
				fprintf(PStream, "  Calls:%5d  total time: % 5.2lfms", LUnit->EntryIndices_Count, (double)LUnit->TotalTime / LTimeDivider * 1000.0);
				fprintf(PStream, "   From: %s() %s:%d\n", LEntry->FunctionName, LEntry->FileName, LEntry->LineNumber);fflush(stdout);
			}

			fflush(PStream);
		}
	}
}


#ifndef ETimerENABLE
	#define ETimerENABLE  0
#endif


#if ETimerENABLE

	#ifndef TIMER_IGNORE_BELOW_THRESHOLD
	#define TIMER_IGNORE_BELOW_THRESHOLD 1
	#endif

	#ifndef TIMER_WARNING_THRESHOLD
	#define TIMER_WARNING_THRESHOLD  0.001  // In seconds (1ms) - yellow messages
	#endif

	#ifndef TIMER_ALERT_THRESHOLD
	#define TIMER_ALERT_THRESHOLD    0.005  // In seconds (5ms) - red messages
	#endif

#define ETm_PointerAdd(pList, pPointer) do\
{\
	ETm_Resize(typeof((pList)[0]), pList##_Count + 1, pList);\
	pList[pList##_Count] = pPointer;\
	pList##_Count += 1;\
} while(0)


#define ETmList_StructAdd(pType, pList, pAllocIncrement,  pDst) do\
{\
	if(pList##_Count >= pList##_CountAllocated)\
	{\
		const int  lNewAllocCount = pList##_Count + pAllocIncrement;\
		ETm_Resize(pType, lNewAllocCount, pList);\
		pList##_CountAllocated = lNewAllocCount;\
	}\
	pDst = (pType*)pList + pList##_Count;\
} while(0)


	static inline ETmTimerEntry* _EntrySet(ETmTimerStack* PTimerStack, const char* PName, const TIMERTime PNow)
	{
		if(PTimerStack->Entries_Count >= PTimerStack->Entries_CountAllocated)
		{
			int  LNewAllocCount = PTimerStack->Entries_Count * 2;
			if(LNewAllocCount == 0)	LNewAllocCount = TIMER_INITIAL_ALLOC;
			ETm_Resize(ETmTimerEntry, LNewAllocCount, PTimerStack->Entries);
			PTimerStack->Entries_CountAllocated = LNewAllocCount;
		}
		ETmTimerEntry*  LEntry = PTimerStack->Entries + PTimerStack->Entries_Count;

		LEntry->FunctionName = NULL;
		LEntry->FileName = NULL;
		LEntry->FWName = NULL;
		LEntry->AddedToUnit = FALSE;
		LEntry->LineNumber = 0;
 		LEntry->Start = PTimerStack->PrevTime;
		LEntry->End = PNow;
		LEntry->ParentIndex = PTimerStack->CurrentParentIndex;
		LEntry->Level = PTimerStack->CurrentLevel;
		LEntry->ChildIndex = -1;
		LEntry->Bracket = '[';

		const EIndex  LL = PName ? strlen(PName) : 0;if(LL > 0)
		{
			if(LL >= TIMER_MAX_NAMELEN) { const EIndex  LLT = TIMER_MAX_NAMELEN - 1;memcpy(LEntry->Name, PName, LLT);LEntry->Name[LLT] = 0; }
			else memcpy(LEntry->Name, PName, LL + 1);
		}
		else LEntry->Name[0] = 0;
		PTimerStack->Entries_Count++;
		return(LEntry);
	}

	static inline void  ETimerStack_Reset(ETmTimerStack* PStack)
	{
		PStack->PrevTime = _GetTime();
	}


	#if TIMER_IGNORE_BELOW_THRESHOLD

		#define ETimerStack_Stamp(pTimerStack, pName) do\
		{\
			const TIMERTime  lNow = _GetTime(), lTimePassed = lNow - pTimerStack.PrevTime;\
			if(lTimePassed >= TIMER_ALERT_THRESHOLD)\
			{\
				_EntrySet(pTimerStack, pName);\
\
				if(TIMER_PRINTOUT_ENABLE)\
				{\
					TimerPrefix;printf("%s() %s:%d  ", __FUNCTION__, __FILE__, __LINE__);\
					TextColorRed;\
					printf("[%s] % 7.3lf ms\n", pName, lTimePassed * 1000.0);\
					TextColorNormal;\
					fflush(stdout);\
				}\
			}\
			else if(lTimePassed >= TIMER_WARNING_THRESHOLD)\
			{\
				_EntrySet(pTimerStack, pName);\
\
				if(TIMER_PRINTOUT_ENABLE)\
				{\
					TimerPrefix;printf("%s() %s:%d  ", __FUNCTION__, __FILE__, __LINE__);\
					TextColorYellow;\
					printf("[%s] % 7.3lf ms\n", pName, lTimePassed * 1000.0);\
					TextColorNormal;\
					fflush(stdout);\
				}\
			}\
			pTimerStack.PrevTime = lNow;\
		} while(0)

	#else

		#define ETimerStack_Stamp(pTimerStack, pName) do\
		{\
			const TIMERTime  lNow = _GetTime(), lTimePassed = lNow - pTimerStack.PrevTime;\
			if(lTimePassed >= TIMER_ALERT_THRESHOLD)\
			{\
				_EntrySet(pTimerStack, pName);\
\
				if(TIMER_PRINTOUT_ENABLE)\
				{\
					TimerPrefix;printf("%s() %s:%d  ", __FUNCTION__, __FILE__, __LINE__);\
					TextColorRed;\
					printf("[%s] % 7.3lf ms\n", pName, lTimePassed * 1000.0);\
					TextColorNormal;\
					fflush(stdout);\
				}\
			}\
			else if(lTimePassed >= TIMER_WARNING_THRESHOLD)\
			{\
				_EntrySet(pTimerStack, pName);\
\
				if(TIMER_PRINTOUT_ENABLE)\
				{\
					TimerPrefix;printf("%s() %s:%d  ", __FUNCTION__, __FILE__, __LINE__);\
					TextColorYellow;\
					printf("[%s] % 7.3lf ms\n", pName, lTimePassed * 1000.0);\
					TextColorNormal;\
					fflush(stdout);\
				}\
			}\
			else\
			{\
				_EntrySet(pTimerStack, pName);\
\
				if(TIMER_PRINTOUT_ENABLE)\
				{\
					TimerPrefix;printf("%s() %s:%d  [%s] % 7.3lf ms\n", __FUNCTION__, __FILE__, __LINE__, #pName, lTimePassed * 1000.0);fflush(stdout);\
				}\
			}\
			pTimerStack.PrevTime = lNow;\
		} while(0)

	#endif


	static inline void TimeStamp_PUSH(ETmTimerStack* PTimerStack, const char* PName, const char* PFunctionName, const char* PFileName, const int PLine,  const char PBracket)
	{
		if(!PTimerStack->Enabled) return;

		const TIMERTime  LNow = _GetTime();

		PTimerStack->PrevTime = LNow;

		if(PTimerStack->Verbosity)
		{
			TimerPrefix;int  LL = printf("%s:%d ", PFileName, PLine);for(;LL < TIMER_PAD_DEBUG;LL++) printf(" ");
			int  LC = 0;const int  LN = PTimerStack->CurrentLevel;for(;LC < LN;LC++) printf(" ");
			printf("{% 3d PUSH [%s]\n", PTimerStack->CurrentLevel, PName);
			fflush(stdout);
		}

		{
			ETmTimerEntry*  LEntry = _EntrySet(PTimerStack, PName, LNow);
			LEntry->FunctionName = PFunctionName;
			LEntry->FileName = PFileName;
			LEntry->LineNumber = PLine;
			LEntry->Bracket = PBracket;

//printf("%s() %s:%d  %s%p%s  PTimerStack->Entries_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, PTimerStack, ECodeNormal, (long long)PTimerStack->Entries_Count);fflush(stdout);

			PTimerStack->CurrentParentIndex = PTimerStack->Entries_Count - 1;
			PTimerStack->CurrentLevel += 1;

			LEntry->Start = LNow;
			LEntry->End = LNow;  // The last child that "pops" back to this entry will set this

			const TIMERTime  LEnd = _GetTime(), LTare = LEnd - LNow;
			LEntry->Tare = LTare;
		}
	}

	static inline void TimeStamp_PUSH_D(ETmTimerStack* PTimerStack, const char* PName, const EIndex PDepth, const char* PFunctionName, const char* PFileName, const int PLine,  const char PBracket)
	{
		if(!PTimerStack->Enabled) return;

		const TIMERTime  LNow = _GetTime();

		PTimerStack->PrevTime = LNow;

		if(PTimerStack->Verbosity)
		{
			TimerPrefix;int  LL = printf("%s:%d", PFileName, PLine);for(;LL < TIMER_PAD_DEBUG;LL++) printf(" ");
			int  LC = 0;const int  LN = PTimerStack->CurrentLevel;for(;LC < LN;LC++) printf(" ");
			printf("{PUSH % 3d [%s-%lld]\n", PTimerStack->CurrentLevel, PName, (long long)PDepth);
			fflush(stdout);
		}

		{
			char  LName[1024];snprintf(LName, 1024, "%s-%lld", PName, (long long)PDepth);

			ETmTimerEntry*  LEntry = _EntrySet(PTimerStack, LName, LNow);
			LEntry->FunctionName = PFunctionName;
			LEntry->FileName = PFileName;
			LEntry->LineNumber = PLine;
			LEntry->Bracket = PBracket;

			PTimerStack->CurrentParentIndex = PTimerStack->Entries_Count - 1;
			PTimerStack->CurrentLevel += 1;

			LEntry->Start = LNow;
			LEntry->End = LNow;  // The last child that "pops" back to this entry will set this

			const TIMERTime  LEnd = _GetTime(), LTare = LEnd - LNow;
			LEntry->Tare = LTare;
		}
	}


	#define ETimerStack_Push(pTimerStack, pName) do\
	{\
		TimeStamp_PUSH(pTimerStack, pName, __FUNCTION__, __FILE__, __LINE__, '[');\
	} while(0)

	#define ETimerStack_PushD(pTimerStack, pName, pDepth) do\
	{\
		TimeStamp_PUSH_D(pTimerStack, pName, pDepth, __FUNCTION__, __FILE__, __LINE__, '[');\
	} while(0)

	#define ETimerStack_Push_WITH_OPTIONS(pTimerStack, pName, mBracket) do\
	{\
		TimeStamp_PUSH(pTimerStack, pName, __FUNCTION__, __FILE__, __LINE__, mBracket);\
	} while(0)


//========================================================================================
// Append string to the current timer Entry's Name
// This is very useful if you want to start a timer, then
// use a conditional or a loop to determine a name suffix
// For example:
//  ETimerStack_Push(My_TimerStack, "Picking fruit:")   // The timer starts here
//  switch(MyStruct->FruitType)
//  {
//   case MyBANANA:  TimeStamp_NAME_APPEND("Banana");/* Do some timed stuff */;break;
//   case MyORANGE:  TimeStamp_NAME_APPEND("Orange");/* Do some timed stuff */;break;
//  }
//  ETimerStack_Pop(My_TimerStack)   // The timer ends here
//========================================================================================
	static inline void TimeStamp_NAME_APPEND(ETmTimerStack* PTimerStack, const char* PString)
	{
		if(!PTimerStack->Enabled) return;

		ETmTimerEntry*  lEntry = PTimerStack->Entries + PTimerStack->Entries_Count - 1;
		const int  LL = (int)strlen(lEntry->Name);
		const int  LL1 = (int)strlen(PString);
		const int  LL2 = LL + LL1 > TIMER_MAX_NAMELEN - 1 ? LL1 : TIMER_MAX_NAMELEN - 1 - LL;
		memcpy(lEntry->Name + LL, PString, LL2);lEntry->Name[LL + LL2] = 0;
	}

	static inline void TimeStamp_POP(ETmTimerStack* PTimerStack, const char* PFileName, const int PLine)
	{
		if(!PTimerStack->Enabled) return;

		const TIMERTime  LNow = _GetTime();

		if(PTimerStack->CurrentLevel > 0)
		{
			PTimerStack->CurrentLevel -= 1;
			const ETmTimerEntry*  LEntry = PTimerStack->Entries + PTimerStack->Entries_Count - 1;
			ETmTimerEntry*        LParent = PTimerStack->Entries + PTimerStack->CurrentParentIndex;
			if(LEntry != LParent)    // PTimerStack->CurrentParent has descendants : update its End time
			{
				LParent->End = LNow;
			}
			else LParent->End = LNow;

			if(PTimerStack->Verbosity)
			{
				TimerPrefix;int  LL = printf("%s:%d ", PFileName, PLine);for(;LL < TIMER_PAD_DEBUG;LL++) printf(" ");
				int  LC = 0;const int  LN = PTimerStack->CurrentLevel;for(;LC < LN;LC++) printf(" ");
//				printf("POP [%s -> %s]\n", lParent->Name, lParent->ParentIndex >= 0 ? PTimerStack->Entries[lParent->ParentIndex].Name : "-");
				printf("}POP % 3d\n", PTimerStack->CurrentLevel);
				fflush(stdout);
			}

			PTimerStack->CurrentParentIndex = LParent->ParentIndex;
		}
		else
		{
			TimerPrefix;printf("%s:%d  ", PFileName, PLine);
			TextColorRed;printf("TIMER STACK UNDERFLOW %d\n", PTimerStack->CurrentLevel);
			TextColorNormal;
			fflush(stdout);
		}
	}

	#define ETimerStack_Pop(pTimerStack) do\
	{\
		TimeStamp_POP(pTimerStack, __FILE__, __LINE__);\
	} while(0)


#if TIMER_USE_AUTO_SCOPE
namespace Timer
{
	struct Scope
	{
		ETmTimerStack*  TimerStack;
		const char*     Name;

		Scope(ETmTimerStack* PTimerStack, const char* PName,  const char* PFunctionName, const char* PFileName, const int PLine) : TimerStack(PTimerStack), Name(PName)
		{
			TimeStamp_PUSH(PTimerStack, PName, PFunctionName, PFileName, PLine);
//			ETimerStack_Push(PTimerStack, PName);
		};

		~Scope(void) { TimeStamp_POP(TimerStack); };
	};

};

	#define ETimerStack_Scope(pTimerStack, pName) Timer::Scope  TimerScopeStart_##__LINE__(&(pTimerStack), pName, __FUNCTION__, __FILE__, __LINE__)

#else

	#define ETimerStack_Scope(pTimerStack, pName) do { printf("%s() %s:%d profiler auto-scoping is disabled (TIMER_USE_AUTO_SCOPE = 0)\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout); } while(0)

#endif


#else  // ETimerENABLE

	#define ETimerStack_Reset(pTimerStack)                               do { } while(0)
	#define ETimerStack_Stamp(pTimerStack, pName)                        do { } while(0)
	#define ETimerStack_Push(pTimerStack, pName)                         do { } while(0)
	#define ETimerStack_PushD(pTimerStack, pName, pDepth)                do { } while(0)
	#define ETimerStack_Push_WITH_OPTIONS(pTimerStack, pName, pBracket)  do { } while(0)
	#define ETimerStack_Pop(pTimerStack)                                 do { } while(0)
	#define ETimerStack_Scope(pTimerStack, pName)                        do { } while(0)
	#define TimeStamp_PUSH(pTimerStack, pName, pFunctionName, pFileName, pLine,  pBracket)
	#define TimeStamp_PUSH_D(pTimerStack, pName, pDepth, pFunctionName, pFileName, pLine,  pBracket)
	#define TimeStamp_POP(pTimerStack)

#endif  // ETimerENABLE


#endif  // _ETiming_h
