//-----------------------------------------------------------------------------------------------
// Profiler.cpp
//
#include "GypsyPrecompiledHeader.hpp"

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/Profiler.hpp"
#include "Engine/Core/Application.hpp"
#include "Engine/Renderer/Renderer.hpp"



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
ProfileCategory::ProfileCategory( const char* name, CaseInsensitiveStringHashValue nameHash )
	: m_name( name )
	, m_nameHash( nameHash )
	, m_nameLength( -1 )
	, m_parentNameLength( -1 )
	, m_parentNameHash( 0 )
	, m_myIndex( PROFILE_CATEGORY_INDEX_UNKNOWN )
	, m_parentIndex( PROFILE_CATEGORY_INDEX_UNKNOWN )
	, m_numChildren( 0 )
	, m_totalSecondsEver( 0.0 )
	, m_totalSecondsThisFrame( 0.0 )
	, m_totalSecondsPreviousFrame( 0.0 )
	, m_slowestSecondsEver( 0.0 )
	, m_slowestSecondsThisFrame( 0.0 )
	, m_slowestSecondsPreviousFrame( 0.0 )
	, m_numCallsEver( 0 )
	, m_numCallsThisFrame( 0 )
	, m_numCallsPreviousFrame( 0 )
	, m_mostCallsPerFrameEver( 0 )
	, m_timeLatestProfilingBegan( 0.0 )
{
	ParseAndProcessNameInformation();
}


//-----------------------------------------------------------------------------------------------
void ProfileCategory::ParseAndProcessNameInformation()
{
	const char* scan = m_name;
	int lastPeriodIndex = 0;
	while( *scan != '\0' )
	{
		if( *scan == '.' )
			lastPeriodIndex = (scan - m_name);

		++ scan;
	}

	m_nameLength = scan - m_name;
	m_parentNameLength = lastPeriodIndex;
	m_parentNameHash = ComputeCaseInsensitiveHashForString( m_name, m_parentNameLength );
}



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
Profiler::Profiler()
	: m_numFrameResets( 0 )
{
}


//-----------------------------------------------------------------------------------------------
Profiler::~Profiler()
{
}


//-----------------------------------------------------------------------------------------------
int Profiler::GetProfileCategoryIndexForName( const char* profileCategoryName, const Rgba& graphColor )
{
	UNUSED( graphColor );

	// Try to find it
	CaseInsensitiveStringHashValue nameHash = ComputeCaseInsensitiveHashForString( profileCategoryName );
	std::vector< ProfileCategory >::iterator profileCategoryIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; profileCategoryIter != profileCategoriesEnd; ++ profileCategoryIter )
	{
		ProfileCategory& profileCategory = *profileCategoryIter;
		if( profileCategory.m_nameHash == nameHash )
		{
			return profileCategory.m_myIndex;
		}
	}

	// Doesn't exist; create it
	ProfileCategory tempNewProfileCategory( profileCategoryName, nameHash );
	tempNewProfileCategory.m_myIndex = (int) m_profileCategories.size();
	m_profileCategories.push_back( tempNewProfileCategory );
	ProfileCategory& permanentNewCategory = m_profileCategories.back();
	LocateAndAssignParentForCategory( permanentNewCategory );
	LocateAndAssignChildrenForCategory( permanentNewCategory );
	return permanentNewCategory.m_myIndex;
}


//-----------------------------------------------------------------------------------------------
void Profiler::EndProfileSection( int profileCategoryIndex )
{
	ProfileCategory& profileCategory = m_profileCategories[ profileCategoryIndex ];
	double elapsedSeconds = GetCurrentTimeSeconds() - profileCategory.m_timeLatestProfilingBegan;

	++ profileCategory.m_numCallsEver;
	++ profileCategory.m_numCallsThisFrame;
	profileCategory.m_totalSecondsThisFrame += elapsedSeconds;
	profileCategory.m_totalSecondsEver += elapsedSeconds;

	if( elapsedSeconds > profileCategory.m_slowestSecondsThisFrame )
		profileCategory.m_slowestSecondsThisFrame = elapsedSeconds;

	if( elapsedSeconds > profileCategory.m_slowestSecondsEver )
		profileCategory.m_slowestSecondsEver = elapsedSeconds;
}


//-----------------------------------------------------------------------------------------------
int GetProfileCategoryIndexForName( const char* profileCategoryName, const Rgba& graphColor )
{
	FATAL_ASSERTION_GENERIC( theApplication != nullptr );
	return theApplication->m_profiler.GetProfileCategoryIndexForName( profileCategoryName, graphColor );
}


//-----------------------------------------------------------------------------------------------
void BeginProfileSection( int profileCategoryIndex )
{
	FATAL_ASSERTION_GENERIC( theApplication != nullptr );
	theApplication->m_profiler.BeginProfileSection( profileCategoryIndex );
}


//-----------------------------------------------------------------------------------------------
void EndProfileSection( int profileCategoryIndex )
{
	FATAL_ASSERTION_GENERIC( theApplication != nullptr );
	theApplication->m_profiler.EndProfileSection( profileCategoryIndex );
}


//-----------------------------------------------------------------------------------------------
double Profiler::CalcChildrenSecondsPreviousFrame( const ProfileCategory& profileCategory ) const
{
	double totalChildrenSeconds = 0.0;
	for( int childIndex = 0; childIndex < profileCategory.m_numChildren; ++ childIndex )
	{
		int globalChildIndex = profileCategory.m_childrenGlobalIndexes[ childIndex ];
		const ProfileCategory& child = m_profileCategories[ globalChildIndex ];
		totalChildrenSeconds += child.m_totalSecondsPreviousFrame;
	}
	
	return totalChildrenSeconds;
}


//-----------------------------------------------------------------------------------------------
void Profiler::DebugDumpPreviousFrameStats()
{
	DebuggerPrintf( "----------------------------------------------------------------------------\n" );
	std::vector< ProfileCategory >::iterator profileCategoryIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; profileCategoryIter != profileCategoriesEnd; ++ profileCategoryIter )
	{
		ProfileCategory& profileCategory = *profileCategoryIter;
		if( profileCategory.m_parentIndex != PROFILE_CATEGORY_INDEX_NONE )
			continue; // Only start with root-level profile categories (they will recurse to their children)

		DebugDumpCategoryAndChildrenPreviousFrame( profileCategory, 0 );
	}
	DebuggerPrintf( "\n" );
}


//-----------------------------------------------------------------------------------------------
void Profiler::DebugDumpCategoryAndChildrenPreviousFrame( const ProfileCategory& profileCategory, int indentationLevel )
{
	int numSpaces = 2 * indentationLevel;
	const char* spaces = "                                                                          ";
	double totalSeconds = profileCategory.m_totalSecondsPreviousFrame;
	double childrenSeconds = CalcChildrenSecondsPreviousFrame( profileCategory );
	double selfSeconds = totalSeconds - childrenSeconds;
//	if( selfSeconds == 999.5 )
	DebuggerPrintf( "%*.*s%s : %.02f ms total, %.02f ms self\n", numSpaces, numSpaces, spaces, profileCategory.m_name, 1000.0 * totalSeconds, 1000.0 * selfSeconds );

	for( int myChildrenIndex = 0; myChildrenIndex < profileCategory.m_numChildren; ++ myChildrenIndex )
	{
		int indexOfChild = profileCategory.m_childrenGlobalIndexes[ myChildrenIndex ];
		ProfileCategory& child = m_profileCategories[ indexOfChild ];
		DebugDumpCategoryAndChildrenPreviousFrame( child, 1 + indentationLevel );
	}
}


//-----------------------------------------------------------------------------------------------
void Profiler::RenderPreviousFrameStats()
{
	static int pcIndex = GetProfileCategoryIndexForName( "EndFrame.ProfileRender", Rgba::SILVER );
	ScopedProfileSection profileSection( pcIndex );

// 	if( g_debugDumpProfileStats )
// 	{
// 		g_debugDumpProfileStats = false;
//		DebugDumpPreviousFrameStats();
//	}

#if defined( GYPSY_ENABLE_RENDERER )
	return; // TEMP HACK
/*

	float virtualScreenAspect = theApplication->GetPhysicalWindowClientAreaAspectRatio();
	Vector2 virtualScreenSize( 1600.f, 1600.f / virtualScreenAspect );
	theRenderer->PushMatrix();
	theRenderer->SetScreenVirtualOrtho( virtualScreenSize );

	const float padding = 10.f;
//	const float indentSize = 20.f;
	const float textHeight = 20.f;
	Vector2 startDrawPos( padding, 1600.f - padding - textHeight );
	Vector2 drawPos = startDrawPos;

	DebuggerPrintf( "----------------------------------------------------------------------------\n" );
	std::vector< ProfileCategory >::iterator profileCategoryIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; profileCategoryIter != profileCategoriesEnd; ++ profileCategoryIter )
	{
		ProfileCategory& profileCategory = *profileCategoryIter;
		if( profileCategory.m_parentIndex != PROFILE_CATEGORY_INDEX_NONE )
			continue; // Only start with root-level profile categories (they will recurse to their children)

		DebugDumpCategoryAndChildrenPreviousFrame( profileCategory, 0 );
	}
	DebuggerPrintf( "\n" );


// 	struct ColoredTextLine
// 	{
// 		Vector2			position;
// 		Rgba			color;
// 		std::string		text;
// 	};
// 
// 	std::vector< ColoredTextLine > textLines;

	theRenderer->PopMatrix();
*/

#endif
}


//-----------------------------------------------------------------------------------------------
// Find the profile category which has the exact (case-insensitive) name of our name, but
//	with the trailing ".Something" stripped off.  For example, if we are "Game.Render.World",
//	we are looking for a parent called "Game.Render" (but NOT "Game").
//
// String comparisons are eschewed in favor of 32-bit name hash comparisons.  The code
//	assumes that hash collisions do not occur (so far we've had one instance in 7 years).
//
void Profiler::LocateAndAssignParentForCategory( ProfileCategory& possibleChild )
{
	possibleChild.m_parentIndex = PROFILE_CATEGORY_INDEX_NONE;

	static CaseInsensitiveStringHashValue frameNameHash = ComputeCaseInsensitiveHashForString( "Frame" );
	if( possibleChild.m_nameHash == frameNameHash )
		return; // "Frame" is hardcoded internal umbrella profile category name for per-frame stats

	std::vector< ProfileCategory >::iterator possibleParentIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; possibleParentIter != profileCategoriesEnd; ++ possibleParentIter )
	{
		ProfileCategory& possibleParent = *possibleParentIter;
		if( &possibleParent == &possibleChild )
			continue;

		if( possibleParent.m_nameHash == possibleChild.m_parentNameHash || (possibleParent.m_nameHash == frameNameHash && possibleChild.m_parentNameHash == 0) )
		{
			HookUpParentage( possibleParent, possibleChild );
			return; // Only one parent per child
		}
	}
}


//-----------------------------------------------------------------------------------------------
// Find all profile categories which, when their trailing ".Something" is stripped off, would
//	result in our name.  For example, if we are "Game.Render", we are looking for anything
//	named "Game.Render.*" (but NOT "Game.Render.*.*").
//
// String comparisons are eschewed in favor of 32-bit name hash comparisons.  The code
//	assumes that hash collisions do not occur (so far we've had one instance in 7 years).
//
void Profiler::LocateAndAssignChildrenForCategory( ProfileCategory& possibleParent )
{
	std::vector< ProfileCategory >::iterator possibleChildIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; possibleChildIter != profileCategoriesEnd; ++ possibleChildIter )
	{
		ProfileCategory& possibleChild = *possibleChildIter;
		if( &possibleChild == &possibleParent )
			continue;

		if( possibleChild.m_parentNameHash == possibleParent.m_nameHash )
		{
			HookUpParentage( possibleParent, possibleChild );
		}
	}
}


//-----------------------------------------------------------------------------------------------
void Profiler::HookUpParentage( ProfileCategory& parent, ProfileCategory& child )
{
	FATAL_ASSERTION( parent.m_numChildren < MAX_CHILDREN_PER_PROFILE_CATEGORY, Stringf( "Profiling error: could not assign parent (\"%s\") / child (\"%s\") relationship in profiling hierarchy because parent \"%s\" is already full at %i children!", parent.m_name, child.m_name, parent.m_name, parent.m_numChildren ) );

	child.m_parentIndex = parent.m_myIndex;
	int newChildIndexInListOfParent = parent.m_numChildren;
	parent.m_childrenGlobalIndexes[ newChildIndexInListOfParent ] = child.m_myIndex;
	++ parent.m_numChildren;
}


//-----------------------------------------------------------------------------------------------
void Profiler::AdvanceToNextFrame()
{
	static int pcIndex = GetProfileCategoryIndexForName( "EndFrame.ProfileAdvance", Rgba::SILVER );
	ScopedProfileSection profileSection( pcIndex );

	++ m_numFrameResets;
	std::vector< ProfileCategory >::iterator profileCategoryIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; profileCategoryIter != profileCategoriesEnd; ++ profileCategoryIter )
	{
		ProfileCategory& profileCategory = *profileCategoryIter;

		if( profileCategory.m_numCallsThisFrame > profileCategory.m_mostCallsPerFrameEver )
			profileCategory.m_mostCallsPerFrameEver = profileCategory.m_numCallsThisFrame;

		if( profileCategory.m_totalSecondsThisFrame > profileCategory.m_mostSecondsPerFrameEver )
			profileCategory.m_mostSecondsPerFrameEver = profileCategory.m_totalSecondsThisFrame;

		profileCategory.m_numCallsPreviousFrame			= profileCategory.m_numCallsThisFrame;
		profileCategory.m_slowestSecondsPreviousFrame	= profileCategory.m_slowestSecondsThisFrame;
		profileCategory.m_totalSecondsPreviousFrame		= profileCategory.m_totalSecondsThisFrame;

		profileCategory.m_numCallsThisFrame = 0;
		profileCategory.m_slowestSecondsThisFrame = 0.0;
		profileCategory.m_totalSecondsThisFrame = 0.0;
	}
}


