//////////////////////////////////////////////////////////////////////////////
//
// Asura_CPUProfile_Renderer.cpp
//
// Created by: Mike Healey 15/08/2005
//
// Copyright (c) 2004-2005 Rebellion. All rights reserved.
//
// Basic profiling bar renderer so new platforms can get up and running 
// quickly with a profiling system that isn't as efficient as it could be, 
// but does the job reasonably well. It is very much based on Anthony 
// Gregson's work for PSP, which never became particularly PSP-specific.
//
// I have added code to time the total frame length, so in platform code, 
// before calling the render function stop the total frame timer, and start it
// again after the VSync.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#if defined( ASURA_USE_PROFILING_SYSTEM)

#if defined( ASURA_USE_PROFILING_SYSTEM_MULTITHREADED)
	#error ASURA_USE_PROFILING_SYSTEM_MULTITHREADED should not be defined with this file. Use "Debugging/Asura_CPUProfile_Multithreaded_Render.cpp"
#endif

#include "Debugging/Asura_CPUProfile_Renderer.h"

#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#ifdef ASURA_USE_DEBUG_TEXT
#include "Debugging/Asura_DebugText.h"
#endif
#include "Debugging/Asura_CPUProfile.h"
#include "Debugging/Asura_Profiling_BarRenderer.h"
#include "Debugging/Asura_Logfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

#ifdef ASURA_ASSERT
#include <string.h>
#endif

// G L O B A L S /////////////////////////////////////////////////////////////
static Asura_TickCount g_auTimerTicks[ASURA_CPU_PROFILE__MAX_TIMERS] = {0};

//Stack of nested timers that are currently active
#define ASURA_CPU_PROFILE__MAX_NESTED_TIMER 8
static Asura_TickCount g_auTimerStartTicks[ASURA_CPU_PROFILE__MAX_NESTED_TIMER] = {0};
static int g_aiInProfile[ASURA_CPU_PROFILE__MAX_NESTED_TIMER] = {0};
static u_int g_uInProfileDepth = 0;


// colours should match those in Asura_CPUProfile.h
static const u_int g_auProfileColours[ASURA_CPU_PROFILE__MAX_TIMERS] =
{
	0xff604020, 0xff2222ff, 0xff22ff22, 0xffff2222, 0xff22ffff, 0xffffff22, 0xffff22ff, 0xff222222,
	0xffffffff, 0xff5555dd, 0xff55dd55, 0xffdd5555, 0xff55dddd, 0xffdddd55, 0xffdd55dd, 0xff555555,
	0xffdddddd, 0xff2222ff, 0xff22ff22, 0xffff2222, 0xff22ffff, 0xffffff22, 0xffff22ff, 0xff222222,
	0xff808080, 0xff222280, 0xff228022, 0xff802222, 0xff55dddd, 0xffdddd55, 0xffdd55dd, 0xff555555,
	0xff303030, 0xff000050, 0xff500000, 0xff005000, 0xff882288, 0xff552255
};

static const char* g_aszProfileNames[ASURA_CPU_PROFILE__MAX_TIMERS];


// S T A T I C   C L A S S   D A T A /////////////////////////////////////////

#if defined( ASURA_TOOLS)
bool Asura_CPUProfile_Renderer::s_bBarsEnabled = false;
#else
bool Asura_CPUProfile_Renderer::s_bBarsEnabled = true;
#endif

#if ASURA_USE_DEBUG_TEXT
#if defined(ASURA_TOOLS)
bool Asura_CPUProfile_Renderer::s_bFPSEnabled	= false;
#else
bool Asura_CPUProfile_Renderer::s_bFPSEnabled	= true;
#endif	//defined(ASURA_TOOLS)
bool Asura_CPUProfile_Renderer::s_bTextEnabled	= false;
#endif

bool Asura_CPUProfile_Renderer::s_bPaused;

const float Asura_CPUProfile_Renderer::fY_OFFSET_SCALE = 10.0f;
float Asura_CPUProfile_Renderer::s_fScaledYOffset = 0.006f * fY_OFFSET_SCALE;

// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////

void Asura_CPUProfile_Renderer::Initialise()
{
#if defined( ASURA_SUPPORT_COMMAND_CONSOLE )

	Asura_CommandConsole::AddVar( L"Debug.Profile.CPUBars", &s_bBarsEnabled, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#ifdef ASURA_USE_DEBUG_TEXT
	Asura_CommandConsole::AddVar( L"Debug.Profile.FPS", &s_bFPSEnabled, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( L"Debug.Profile.CPUText", &s_bTextEnabled, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
	Asura_CommandConsole::AddVar( L"Debug.Profile.YOffset", &s_fScaledYOffset, -0.1f * fY_OFFSET_SCALE, 0.1f * fY_OFFSET_SCALE, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

#endif // #if defined( ASURA_USE_COMMANDCONSOLE )
}

void Asura_CPUProfile_Renderer::Shutdown()
{
}

void Asura_CPUProfile_Renderer::StartProfile(int iProfileType)
{
	if (s_bPaused)
	{
		return;
	}

	if(g_uInProfileDepth >= ASURA_CPU_PROFILE__MAX_NESTED_TIMER)
	{
		Asura_Assert(0,"Too many nested CPU timers");
		return;
	}

	#ifdef ASURA_ASSERT
	Asura_Assert(iProfileType >= 0 && iProfileType < ASURA_CPU_PROFILE__MAX_TIMERS, "bad timer index");
	if(iProfileType == ASURA_CPU_PROFILE__TOTALFRAME)
	{
		Asura_Assert(g_uInProfileDepth == 0, "Starting the total frame timer while other timers are already active");
	}
	else
	{
		Asura_Assert(g_uInProfileDepth != 0, "Starting a timer before the total frame timer has been started");
	}
	#endif

	g_aiInProfile[g_uInProfileDepth] = iProfileType;
	g_auTimerStartTicks[g_uInProfileDepth] = Asura_Timers::GetCurTicks();
	g_uInProfileDepth++;
}

void Asura_CPUProfile_Renderer::StopProfile(int iProfileType)
{
	if (s_bPaused)
	{
		return;
	}

	if(!g_uInProfileDepth)
	{
		Asura_Assert(0,"Not currently profiling anything , so can't stop");
		return;
	}
	if(g_aiInProfile[g_uInProfileDepth-1] != iProfileType)
	{
		Asura_Assertf(0,("Attempting to stop profile %d , expecting to stop profile %d",iProfileType,g_aiInProfile[g_uInProfileDepth-1]));
		return;
	}

#ifdef ASURA_ASSERT
	Asura_Assert(iProfileType >= 0 && iProfileType < ASURA_CPU_PROFILE__MAX_TIMERS, "bad timer index");
#endif

	g_uInProfileDepth--;
	Asura_TickCount uEndTicks = Asura_Timers::GetCurTicks();
	Asura_TickCount uElapsedTicks = uEndTicks - g_auTimerStartTicks[g_uInProfileDepth];
	g_auTimerTicks[iProfileType] += uElapsedTicks;

	if(g_uInProfileDepth > 1)
	{
		//Increase the start time for any other active timers so as to subtract the time used up by this nested timer.
		//(Except for the very first timer , which represents the total frame time)
		//RAR 19/12/2008
		for(int i=g_uInProfileDepth-1;i;i--)
		{
			g_auTimerStartTicks[i] += uElapsedTicks;
		}
	}
}

void Asura_CPUProfile_Renderer::SetName(int iProfileType, const char* szName)
{
#ifdef ASURA_ASSERT
	Asura_Assert(iProfileType >= 0 && iProfileType < ASURA_CPU_PROFILE__MAX_TIMERS, "bad timer index");
	if (g_aszProfileNames[iProfileType])
	{
		Asura_Assertf(!strcmp(g_aszProfileNames[iProfileType], szName), ("same timer, different name. From '%s' to '%s'", g_aszProfileNames[iProfileType], szName));
	}
#endif
	g_aszProfileNames[iProfileType] = szName;
}


DEBUGVAR u_int dbg_uProfTextX = 8;
// JAT 27/02/07 try to put the profile text below the DVS render overlay
#if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
DEBUGVAR u_int dbg_uProfTextY = 15;
#else // #if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
DEBUGVAR u_int dbg_uProfTextY = 2;
#endif // #if defined( ASURA_DEBUG_VARIABLE_SYSTEM )


void Asura_CPUProfile_Renderer::Render(bool bIncludeText)
{
	if (s_bPaused)
	{
		return;
	}

	if (
		false == s_bBarsEnabled
#if defined( ASURA_USE_DEBUG_TEXT)
	&&
		false == s_bTextEnabled
	&&
		false == s_bFPSEnabled
#endif
	)
	{
		return;
	}

	Asura_Render::FlushDisplay();

    // (MH - 16.08.2005) assume that timer 0 is used for total frame. 
	Asura_Assert(g_auTimerTicks[ ASURA_CPU_PROFILE__TOTALFRAME], "didn't set total frame?");
	Asura_Assert(g_uInProfileDepth==0,"Expecting all timers to have been stopped");
#ifdef ASURA_USE_DEBUG_TEXT
	float fStart = Asura_Timers::GetTimeInFrame();
	float fTotalTime = Asura_Timers::TicksToSeconds(g_auTimerTicks[ ASURA_CPU_PROFILE__TOTALFRAME]);
#endif

	Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
	Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;

	float fRunningTotalTime = 0.0f;
	
	// Pre-sum the total time (for bars and text _both_ require it)
	if (s_bBarsEnabled
#if ASURA_USE_DEBUG_TEXT
		|| s_bTextEnabled)
#else
		)
#endif
	{
		for (int i = 1; i<ASURA_CPU_PROFILE__MAX_TIMERS; i++)
		{
			fRunningTotalTime += Asura_Timers::TicksToSeconds(g_auTimerTicks[i]);
		}
	}

	if (s_bBarsEnabled)
	{
        static Asura_Profiling_BarRenderer_Data xData;

        xData.SetUseDefaultBackgroundColours();
        xData.SetBarColours( &g_auProfileColours[1], ASURA_CPU_PROFILE__MAX_TIMERS - 1, g_auProfileColours[0] );
        xData.SetYOffset( s_fScaledYOffset / fY_OFFSET_SCALE );

        float afTimers[ASURA_CPU_PROFILE__MAX_TIMERS];
        for( u_int u = 0; u < ASURA_CPU_PROFILE__MAX_TIMERS; ++u )
        {
            afTimers[u] = Asura_Timers::TicksToSeconds( g_auTimerTicks[u] );
        }

        xData.SetNewFrameData( afTimers[0], &afTimers[1], ASURA_CPU_PROFILE__MAX_TIMERS - 1 );
        Asura_Profiling_BarRenderer::Render( xData );
    }

#ifdef ASURA_USE_DEBUG_TEXT
	if(s_bFPSEnabled)
	{
		const float	fFPS	= Asura_Timers::GetFrameRate();
		const float fMaxFPS	= Asura_Timers::GetMaxFrameRate();
		const int	iFormat	= 0;

		if(fMaxFPS > 0.0f)
		{
			const float	fTwoThirdsMaxFPS	= fMaxFPS * 0.666f;
			const float	fHalfMaxFPS			= fMaxFPS * 0.500f;
			const float	fOneThirdMaxFPS		= fMaxFPS * 0.333f;

			const u_int	uColour				= (fFPS >= fTwoThirdsMaxFPS) ? 0xFFFFFFFF : ((fFPS >= fHalfMaxFPS) ? 0xFFFFFF00 : ((fFPS >= fOneThirdMaxFPS) ? 0xFFFF8000 : 0xFFFF0000));

			Asura_DebugText::AddDefaultTextBlock(uColour, iFormat, "FPS: %.1f (Max: %.1f)", fFPS, fMaxFPS);
		}
		else
		{
			Asura_DebugText::AddDefaultTextBlock(0xFFFFFFFF, iFormat, "FPS: %.1f", fFPS);
		}
		Asura_DebugText::IncrementDefaultTextY();
	}

	if (s_bTextEnabled)
	{
		u_int uNumRendered = 0;
		for (int i = 1; i<ASURA_CPU_PROFILE__MAX_TIMERS; i++)
		{
			if (g_auTimerTicks[i])
			{
				float fTime = Asura_Timers::TicksToSeconds(g_auTimerTicks[i]);
				if (fTime > 0.0f)
				{
					Asura_Assert(fTime < fTotalTime, "timed event took longer than entire frame?");
					const char* szName = g_aszProfileNames[i] ? g_aszProfileNames[i] : "???";
					Asura_DebugText::AddTextBlock( dbg_uProfTextX, dbg_uProfTextY + uNumRendered, 0xffffffff, ASURA_DEBUGTEXT_FORMAT_LEFTJUSTIFIED, 
						"%2d %5.02f%% %s: (%d us)", i, Asura_Maths::FtoD(100.0f * fTime / fTotalTime), szName, Asura_Maths::FloatToInt(fTime * 1000000.0f));

					// render a colour coded square
					float fX = (static_cast<float>(dbg_uProfTextX - 1) * ASURA_DEBUGTEXT_OUTPUT_FONT_WIDTH);
					float fY = 1.0f + (static_cast<float>(dbg_uProfTextY + uNumRendered) * ASURA_DEBUGTEXT_OUTPUT_FONT_HEIGHT);
					float fW = ASURA_DEBUGTEXT_OUTPUT_FONT_WIDTH - 2.0f;
					float fH = ASURA_DEBUGTEXT_OUTPUT_FONT_HEIGHT - 2.0f;
					Asura_Render::Overlay.SetRectXY(fX, fY, fW, fH);
					Asura_Render::Overlay.Colour = g_auProfileColours[i];
					Asura_Render::Overlay.Render();
					
					++uNumRendered;
				}
			}
		}

		{
			// render a colour coded square for the untimed section
			float fX = (static_cast<float>(dbg_uProfTextX - 1) * ASURA_DEBUGTEXT_OUTPUT_FONT_WIDTH);
			float fY = 1.0f + (static_cast<float>(dbg_uProfTextY + uNumRendered) * ASURA_DEBUGTEXT_OUTPUT_FONT_HEIGHT);
			float fW = ASURA_DEBUGTEXT_OUTPUT_FONT_WIDTH - 2.0f;
			float fH = ASURA_DEBUGTEXT_OUTPUT_FONT_HEIGHT - 2.0f;
			Asura_Render::Overlay.SetRectXY(fX, fY, fW, fH);
			Asura_Render::Overlay.Colour = g_auProfileColours[0];
			Asura_Render::Overlay.Render();
			// and the text...
			const float fTime = fTotalTime - fRunningTotalTime;
			Asura_DebugText::AddTextBlock( dbg_uProfTextX, dbg_uProfTextY + uNumRendered, 0xffffffff, ASURA_DEBUGTEXT_FORMAT_LEFTJUSTIFIED, 
				"   %5.02f%% unprofiled: (%d us)", Asura_Maths::FtoD(100.0f * fTime / fTotalTime), Asura_Maths::FloatToInt(fTime * 1000000.0f));

			Asura_DebugText::AddTextBlock( dbg_uProfTextX, dbg_uProfTextY + uNumRendered + 1, 0xffffffff, ASURA_DEBUGTEXT_FORMAT_LEFTJUSTIFIED, 
				"rendering this lot: (%d us)", Asura_Maths::FloatToInt((Asura_Timers::GetTimeInFrame() - fStart) * 1000000.0f));
		}
	}
#endif // #ifdef ASURA_USE_DEBUG_TEXT

	Asura_Render::FlushDisplay();
}

void Asura_CPUProfile_Renderer::ClearValues()
{
	if (s_bPaused)
	{
		return;
	}
	for (int i=0; i<ASURA_CPU_PROFILE__MAX_TIMERS; i++)
	{
		g_auTimerTicks[i] = 0;
	}
	Asura_Assert(g_uInProfileDepth==0,"Expecting all timers to have been stopped");
	g_uInProfileDepth = 0;

}

void Asura_CPUProfile_Renderer::SetPaused( bool bPaused )
{
	Asura_Assert( bPaused != s_bPaused, "unmatched pause/unpause calls?");
	s_bPaused = bPaused;
}

void Asura_CPUProfile_Renderer::Reset()
{
	// reset all the names to avoid asserts.
	for (int iProfileType = 0; iProfileType < ASURA_CPU_PROFILE__MAX_TIMERS; ++iProfileType)
	{
		g_aszProfileNames[iProfileType] = NULL;
	}
}

#endif //#ifdef ASURA_USE_PROFILING_SYSTEM
