//-----------------------------------------------------------------------------------------------
// Profiler.hpp
//
#pragma once
#ifndef included_Profiler
#define included_Profiler

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/EngineBase.hpp"


//-----------------------------------------------------------------------------------------------
const int PROFILE_CATEGORY_INDEX_NONE			= -2;
const int PROFILE_CATEGORY_INDEX_UNKNOWN		= -1;
const int MAX_PROFILE_CATEGORY_HIERARCHY_DEPTH	= 20;
const int MAX_CHILDREN_PER_PROFILE_CATEGORY		= 100;


//-----------------------------------------------------------------------------------------------
class Profiler;
class Application;


/////////////////////////////////////////////////////////////////////////////////////////////////
// Instantiate a temp (stack-local) variable of type ScopedProfileSection, and it will
//	automagically call BeginProfileSection() for you in its constructor, and EndProfileSection()
//	in its destructor (when it goes out of scope) and report the elapsed real time in-between.
//
// This is useful primarily for two reasons:
//	first, so one can easily profile an entire function (or body-scope) without having to edit
//		code both at the top and bottom of the function; and
//	second, so that early-return flow control paths don't accidentally cause you to skip your
//	call to EndProfileSection().
//
// For best results, call GetProfileCategoryIndexForName() once (as the first-time-use
//	initialization of a static local variable), then use the returned profile category index
//	thereafter to gain O(1) access to the relevant ProfileCategory that the timing data results
//	will be stored under.  For example:
//	
//	void Game::Render()
//	{
//		static int pcIndex = GetProfileCategoryIndexForName( "Game.Render" );
//		ScopedProfileSection profileSection( pcIndex );
//
//		CodeIWishToProfileHere1();
//		CodeIWishToProfileHere2();
//		CodeIWishToProfileHere3();
//	}
//
// For more explicit control - profiling an arbitrary set of code lines, profiling across
//	multiple frames, non-nested overlapping profile sections, etc. - you can instead simply
//	call BeginProfileSection() and EndProfileSection() manually.
//
class ScopedProfileSection
{
public:
	ScopedProfileSection( int profileCategoryIndex );
	~ScopedProfileSection();

private:
	int			m_profileCategoryIndex;
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class ProfileCategory
{
	friend class Profiler;

public:
	~ProfileCategory();

private:
	ProfileCategory( const char* name, CaseInsensitiveStringHashValue nameHash );
//	int GetNameLengthLazyCached() const;
	void ParseAndProcessNameInformation();

private:
public: // TEMP HACK FIXME TODO REMOVE THIS
	const char*								m_name;
	CaseInsensitiveStringHashValue			m_nameHash;
	int										m_nameLength;
	int										m_parentNameLength;
	CaseInsensitiveStringHashValue			m_parentNameHash;
	int										m_myIndex;
	int										m_parentIndex;
	int										m_numChildren;
	int										m_childrenGlobalIndexes[ MAX_CHILDREN_PER_PROFILE_CATEGORY ];
	int										m_numCallsEver;
	int										m_numCallsThisFrame;
	int										m_numCallsPreviousFrame;
	int										m_mostCallsPerFrameEver;
	double									m_totalSecondsEver;
	double									m_totalSecondsThisFrame;
	double									m_totalSecondsPreviousFrame;
	double									m_mostSecondsPerFrameEver;
	double									m_slowestSecondsEver;
	double									m_slowestSecondsThisFrame;
	double									m_slowestSecondsPreviousFrame;
	double									m_timeLatestProfilingBegan;
};



/////////////////////////////////////////////////////////////////////////////////////////////////
// Use BeginProfileSection() and EndProfileSection() to bookend a section of code you wish to
//	profile, or instantiate a temp stack-local ScopedProfileSection (see above).
//
// For best results, call GetProfileCategoryIndexForName() once (as the first-time-use
//	initialization of a static local variable), then use the returned profile category index
//	thereafter to gain fast O(1) access to the relevant ProfileCategory that the timing data
//	results will be stored under.  For example:
//
//	static int pcIndex = GetProfileCategoryIndexForName( "Game.Render" );
//	BeginProfileSection( pcIndex );
//	CodeIWishToProfileHere1();
//	CodeIWishToProfileHere2();
//	CodeIWishToProfileHere3();
//	EndProfileSection( pcIndex );
//
// Note that ProfileCategories are automatically hierarchical based on identical leading
//	substring match, period-delimited; i.e. "Game.Render" will automatically be considered
//	a child of "Game", for purposes of accounting for % of time spent in self vs. children, etc.
//
class Profiler
{
	friend class Application;

public:
	int GetProfileCategoryIndexForName( const char* profileCategoryName, const Rgba& graphColor );
	void BeginProfileSection( int profileCategoryIndex );
	void EndProfileSection( int profileCategoryIndex );
	void ResetLifetimeProfilingStats();
	void AdvanceToNextFrame();
	double CalcChildrenSecondsPreviousFrame( const ProfileCategory& profileCategory ) const;
	void DebugDumpPreviousFrameStats();
	void RenderPreviousFrameStats();
	~Profiler();

	// External convenience functions
	friend int GetProfileCategoryIndexForName( const char* profileCategoryName, const Rgba& graphColor=Rgba::GREY );
	friend void BeginProfileSection( int profileCategoryIndex );
	friend void EndProfileSection( int profileCategoryIndex );

private:
	Profiler();
	void DebugDumpCategoryAndChildrenPreviousFrame( const ProfileCategory& profileCategory, int indentationLevel );
	void LocateAndAssignParentForCategory( ProfileCategory& orphan );
	void LocateAndAssignChildrenForCategory( ProfileCategory& possibleParent );
	void HookUpParentage( ProfileCategory& parent, ProfileCategory& child );

private:
public: // TEMP HACK: TODO: FIXME: REMOVE THIS
	int								m_numFrameResets;
	std::vector< ProfileCategory >	m_profileCategories;
};



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
inline ScopedProfileSection::ScopedProfileSection( int profileCategoryIndex )
	: m_profileCategoryIndex( profileCategoryIndex )
{
	BeginProfileSection( m_profileCategoryIndex );
}


//-----------------------------------------------------------------------------------------------
inline ScopedProfileSection::~ScopedProfileSection()
{
	EndProfileSection( m_profileCategoryIndex );
}



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
inline ProfileCategory::~ProfileCategory()
{
}



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
inline void Profiler::BeginProfileSection( int profileCategoryIndex )
{
	m_profileCategories[ profileCategoryIndex ].m_timeLatestProfilingBegan = GetCurrentTimeSeconds();
}


//-----------------------------------------------------------------------------------------------
inline void Profiler::ResetLifetimeProfilingStats()
{
	std::vector< ProfileCategory >::iterator profileCategoryIter = m_profileCategories.begin();
	std::vector< ProfileCategory >::iterator profileCategoriesEnd = m_profileCategories.end();
	for( ; profileCategoryIter != profileCategoriesEnd; ++ profileCategoryIter )
	{
		ProfileCategory& profileCategory = *profileCategoryIter;
		profileCategory.m_numCallsEver = 0;
		profileCategory.m_slowestSecondsEver = 0.0;
		profileCategory.m_totalSecondsEver = 0.0;
		profileCategory.m_mostCallsPerFrameEver = 0;
		profileCategory.m_mostSecondsPerFrameEver = 0.0;
	}
}



#endif // included_Profiler
