//
// Asura_Wii_Profiling.cpp
//
// Created by KOR - 22 January 2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_WII_PROFILING

// This file's header.
#include "Debugging/Asura_Wii_Profiling.h"

// Asura headers
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Profiling_System.h"
#include "Debugging/Asura_CPUProfile.h"
#include "Debugging/Asura_Logfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_GX.h"
#include <revolution/gx.h>


bool Asura_Wii_Profiling::s_bRenderingEnabled = false;

static Asura_Handle g_aiProfileTimerHandles[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can
static void* g_aiProfileFifoBufs[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can
static u_int g_aiProfileFifoSizes[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can
static OSTick g_aiProfileGPUStarts[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can
static OSTick g_aiProfileGPUDurations[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can
static OSTick g_aiProfileGPUDurations2[ASURA_PROFILING_SYSTEM_MAX_TIMERS]; // I'm assuming we won't try to start more than we possibly can

static const u_int g_auProfileColours[ASURA_PROFILING_SYSTEM_MAX_TIMERS] =
{
	0xff604020, //  0 light grey
	0xff2222ff, //  1 blue
	0xff22ff22, //  2 green
	0xffff2222, //  3 red
	0xff22ffff, //  4 cyan
	0xffffff22, //  5 yellow
	0xffff22ff, //  6 magenta
	0xff222222, //  7 black
	0xffffffff, //  8 white
	0xff5555dd, //  9 project blue
	0xff55dd55, // 10 project green
	0xffdd5555, // 11 project red
	0xff55dddd, // 12 project cyan
	0xffdddd55, // 13 project yellow
	0xffdd55dd, // 14 project magenta
	0xff555555, // 15 project grey
	0xffdddddd, // 16 = 0
	0xff2222ff, // 17 = 1, etc.
	0xff22ff22, // 18
	0xffff2222, // 19
	0xff22ffff, // 20
	0xffffff22, // 21
	0xffff22ff, // 22
	0xff222222, // 23
	0xffffffff, // 24
	0xff5555dd, // 25
	0xff55dd55, // 26
	0xffdd5555, // 27
	0xff55dddd, // 28
	0xffdddd55, // 29
	0xffdd55dd, // 30
	0xff555555, // 31 = 15
	0xff303030, // 32 darker grey
	0xff000050, // 33 darker blue
	0xff500000, // 34 darker red
	0xff005000, // 35 darker green
	0xff882288, // 36 purple
	0xff552255, // 37 dark purple
	0xffff22ff, // 38 = unused
	0xff222222, // 
	0xffffffff, // 
	0xff5555dd, // 
	0xff55dd55, // 
	0xffdd5555, // 
	0xff55dddd, // 
	0xffdddd55, // 
	0xffdd55dd, // 
	0xff555555, // 
	0xffdddddd, // 
	0xff2222ff, // 
	0xff22ff22, // 
	0xffff2222, // 
	0xff22ffff, // 
	0xffffff22, // 
	0xffff22ff, // 
	0xff222222, // 
	0xffffffff, // 
	0xff5555dd, // 
	0xff55dd55, // 
	0xffdd5555, // 
	0xff55dddd, // 
	0xffdddd55, // 
	0xffdd55dd, // 
	0xff555555, // 
};

void Asura_Wii_Profiling::Initialise()
{
	for (int i=0; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
	{
		g_aiProfileTimerHandles[i] = ASURA_INVALID_HANDLE;
	}

	Asura_Profiling_System::Initialise();

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
	Asura_CommandConsole::AddVar(L"Wii.RenderProfileBars", &s_bRenderingEnabled, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
#endif
}

void Asura_Wii_Profiling::Shutdown()
{
	for (int i=0; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
	{
		if (g_aiProfileTimerHandles[i] != ASURA_INVALID_HANDLE)
		{
			Asura_Profiling_System::DestroyTimer(g_aiProfileTimerHandles[i]);
			g_aiProfileTimerHandles[i] = ASURA_INVALID_HANDLE;
		}

	}
}

void Asura_Wii_Profiling::RegisterLabel(int iProfileType, const char* szName)
{
	// Anything to do here?
}

#define ASURA_PROFILE_START_TOKEN_LAST	0xFFFC
#define ASURA_PROFILE_START_TOKEN_FIRST	(ASURA_PROFILE_START_TOKEN_LAST - ASURA_PROFILING_SYSTEM_MAX_TIMERS)
#define ASURA_PROFILE_STOP_TOKEN_LAST	(ASURA_PROFILE_START_TOKEN_FIRST - 1)
#define ASURA_PROFILE_STOP_TOKEN_FIRST	(ASURA_PROFILE_STOP_TOKEN_LAST - ASURA_PROFILING_SYSTEM_MAX_TIMERS)

void Asura_Wii_Profiling::StartGPUProfile(int iProfileType)
{
	g_aiProfileGPUStarts[iProfileType] = OSGetTick();
}

void Asura_Wii_Profiling::StopGPUProfile(int iProfileType)
{
	OSTick uTicks = OSDiffTick(OSGetTick(), g_aiProfileGPUStarts[iProfileType]);
	if (OSTicksToMilliseconds(uTicks) > 1000/30)
		uTicks = 0;

	g_aiProfileGPUDurations2[iProfileType] += uTicks;

	if (iProfileType == 0)
	{
		for (int i=0; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
		{
			g_aiProfileGPUDurations[i] = g_aiProfileGPUDurations2[i];
			g_aiProfileGPUDurations2[i] = 0;
		}
	}
}



void Asura_Wii_Profiling::StartProfile(int iProfileType)
{
	if (g_aiProfileTimerHandles[iProfileType] == ASURA_INVALID_HANDLE)
	{
		g_aiProfileTimerHandles[iProfileType] = Asura_Profiling_System::CreateTimer();
	}

	Asura_Profiling_System::StartTiming(g_aiProfileTimerHandles[iProfileType]);
	GXFlush();
	GXPixModeSync();

	GXFifoObj currFifoObj;
	void* pxReadPtr;
	void* pxWritePtr;
	GXGetCPUFifo( &currFifoObj );
	GXGetFifoPtrs( &currFifoObj, &pxReadPtr, &pxWritePtr );
	g_aiProfileFifoBufs[iProfileType] = pxWritePtr;
	Asura_GX::AddDrawSyncCallback( (Asura_GX::GPU_CALLBACK_TOKEN)(Asura_GX::GPU_CALLBACK_PROFILE_START_FIRST + iProfileType) );


}

void Asura_Wii_Profiling::StopProfile(int iProfileType)
{
	Asura_Profiling_System::StopTiming(g_aiProfileTimerHandles[iProfileType]);

	GXFlush();
	GXPixModeSync();

	GXFifoObj currFifoObj;
	void* pxReadPtr;
	void* pxWritePtr;
	GXGetCPUFifo( &currFifoObj );
	GXGetFifoPtrs( &currFifoObj, &pxReadPtr, &pxWritePtr );

	u_int uSize;
	if ( pxWritePtr > g_aiProfileFifoBufs[iProfileType] )
	{
		uSize	= (u_int)pxWritePtr - (u_int)g_aiProfileFifoBufs[iProfileType];
	}
	else
	{
		void *pFifoBase = GXGetFifoBase(&currFifoObj);
		u_int uFifoSize = GXGetFifoSize(&currFifoObj);
		uSize	= (((u_int)pFifoBase + uFifoSize) - (u_int)g_aiProfileFifoBufs[iProfileType]) + ((u_int)pxWritePtr - (u_int)pFifoBase);
	}
	g_aiProfileFifoSizes[iProfileType] += uSize;
	Asura_GX::AddDrawSyncCallback( (Asura_GX::GPU_CALLBACK_TOKEN)(Asura_GX::GPU_CALLBACK_PROFILE_STOP_FIRST + iProfileType));
}

static const float fASURA_WII_TIMING_BAR_SECTION = 1.0f/60.0f;
static const float fASURA_WII_ONE_OVER_TIMING_BAR_SECTION = 60.0f;
static const int iASURA_WII_TIMING_MAX_DIVISIONS = 12;	// i.e. 5fps
DEBUGVAR float dbg_fProfileBarsX = 48.0f;
DEBUGVAR float dbg_fProfileBarsY = 48.0f;
DEBUGVAR float dbg_fProfileBarsW = 544.0f;
DEBUGVAR float dbg_fProfileBarsH = 4.0f;
DEBUGVAR float dbg_fProfileBarsHSpace = 6.0f;
DEBUGVAR float dbg_fProfileBarsHBorder = 2.0f;
// TODO: max divisions
DEBUGVAR int dbg_iFrameSmoothing = 8;	// num frames to keep rendering more divisions than required.
DEBUGVAR u_int dbg_uProfBackColour0 = 0xff333333;	// colours for alternating background divisions.
DEBUGVAR u_int dbg_uProfBackColour1 = 0xff666666;
DEBUGVAR u_int dbg_uGPUColour = 0xffffffff;



void Asura_Wii_Profiling::Render()
{
	if(!s_bRenderingEnabled) { return; }

	const float fTotalCPU = Asura_Profiling_GetTotalDuration(g_aiProfileTimerHandles[ASURA_CPU_PROFILE__TOTALFRAME]);
	const float fTotalGPU = static_cast<float>(g_aiProfileGPUDurations[0]) / static_cast<float>(OS_TIMER_CLOCK);
	const float fTotal = fTotalCPU > fTotalGPU ? fTotalCPU : fTotalGPU;

	int iDivisions = Asura_Maths::FloatToInt_Chop(fTotal / fASURA_WII_TIMING_BAR_SECTION) + 1;
	static int ls_iLastDivisions = iDivisions;
	static int ls_iClampCount = 0;
	// if we're going to draw fewer divisions than we did last time use the previous value for a few frames
	if (iDivisions < ls_iLastDivisions)
	{
		if (ls_iClampCount++ < dbg_iFrameSmoothing)
		{
			iDivisions = ls_iLastDivisions;
		}
	}
	else
	{
		ls_iClampCount = 0;
	}
	// don't draw more than max (12) divisions.
	iDivisions = Asura_Maths::Min(iDivisions, iASURA_WII_TIMING_MAX_DIVISIONS);
	ls_iLastDivisions = iDivisions;

	const float fOneOverDivisions = 1.0f / static_cast<float>(iDivisions);
	const float fDivisionWidth = dbg_fProfileBarsW * fOneOverDivisions;

	// Use material to set colour of sprites.
	Asura_Render::CurrentStates.RequestTextureIndex( 0, ASURA_TEXTURE_NONE );
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF ); 

	// render background divisions
	{
		for (int i=0; i<iDivisions; i++)
		{
			const float fX = dbg_fProfileBarsX + (fDivisionWidth * static_cast<float>(i));
			Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
			Asura_Render::Overlay.Colour = (i & 1) ? dbg_uProfBackColour0 : dbg_uProfBackColour1;
			Asura_Render::Overlay.SetRectXY(fX, dbg_fProfileBarsY, fDivisionWidth, (dbg_fProfileBarsH + dbg_fProfileBarsHBorder) * 3 + dbg_fProfileBarsHSpace * 2.0f);
			Asura_Render::Overlay.Render();
		}
	}

	// render CPU graph
	{
		float fRunningTotal = 0.0f;

		// Start at 1 because 0 is total frame time.
		for (int i = 1; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
		{
			if (g_aiProfileTimerHandles[i] != ASURA_INVALID_HANDLE)
			{
				const float fValue = Asura_Profiling_GetTotalDuration(g_aiProfileTimerHandles[i]) * fASURA_WII_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions;
				const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
				Asura_Render::Overlay.Colour = g_auProfileColours[i];
				Asura_Render::Overlay.SetRectXY(fX, dbg_fProfileBarsY + dbg_fProfileBarsHBorder, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
				Asura_Render::Overlay.Render();
				fRunningTotal += fValue;
			}
		}
		// KOR - render remaining time for whole frame.
		const float fValue = fTotalCPU * fASURA_WII_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions	- fRunningTotal;
		const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
		Asura_Render::Overlay.Colour = 0xff80ff00;
		Asura_Render::Overlay.SetRectXY(fX, dbg_fProfileBarsY + dbg_fProfileBarsHBorder, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
		Asura_Render::Overlay.Render();
	}
	// render GPU graph
	{
		float fRunningTotal = 0.0f;

		// Start at 1 because 0 is total frame time.
		for (int i = 1; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
		{
			if (g_aiProfileTimerHandles[i] != ASURA_INVALID_HANDLE)
			{
				const float fValue = static_cast<float>(g_aiProfileGPUDurations[i]) / static_cast<float>(OS_TIMER_CLOCK) * fASURA_WII_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions;
				const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
				const float fY = dbg_fProfileBarsY + dbg_fProfileBarsHBorder + dbg_fProfileBarsH + dbg_fProfileBarsHSpace;
				Asura_Render::Overlay.Colour = g_auProfileColours[i];
				Asura_Render::Overlay.SetRectXY(fX, fY, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
				Asura_Render::Overlay.Render();
				fRunningTotal += fValue;
			}
		}
		const float fValue = fTotalGPU * fASURA_WII_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions - fRunningTotal;
		const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
		const float fY = dbg_fProfileBarsY + (dbg_fProfileBarsHBorder + dbg_fProfileBarsH + dbg_fProfileBarsHSpace);
		Asura_Render::Overlay.Colour = 0xff80ff00;
		Asura_Render::Overlay.SetRectXY(fX, fY, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
		Asura_Render::Overlay.Render();

	}

	// render Fifo graph
	{
		float fRunningTotal = 0.0f;
		GXFifoObj currFifoObj;
		GXGetCPUFifo( &currFifoObj );
		float fInverseFifoSize = 1.0f / static_cast<float>(GXGetFifoSize(&currFifoObj));


		// Start at 1 because 0 is total frame time.
		for (int i = 1; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
		{
			if (g_aiProfileFifoSizes[i] != 0)
			{
				const float fValue = static_cast<float>(g_aiProfileFifoSizes[i])  * fInverseFifoSize;
				const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
				const float fY = dbg_fProfileBarsY + (dbg_fProfileBarsHBorder + dbg_fProfileBarsH + dbg_fProfileBarsHSpace) * 2.0f;
				Asura_Render::Overlay.Colour = g_auProfileColours[i];
				Asura_Render::Overlay.SetRectXY(fX, fY, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
				Asura_Render::Overlay.Render();
				fRunningTotal += fValue;
			}
		}
		const float fValue = static_cast<float>(g_aiProfileFifoSizes[0]) * fInverseFifoSize - fRunningTotal;
		const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
		const float fY = dbg_fProfileBarsY + (dbg_fProfileBarsHBorder + dbg_fProfileBarsH + dbg_fProfileBarsHSpace) * 2.0f;
		Asura_Render::Overlay.Colour = 0xff80ff00;
		Asura_Render::Overlay.SetRectXY(fX, fY, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
		Asura_Render::Overlay.Render();
	}

}

void Asura_Wii_Profiling::ClearValues()
{
	for (int i=0; i<ASURA_PROFILING_SYSTEM_MAX_TIMERS; i++)
	{
		if (g_aiProfileTimerHandles[i] != ASURA_INVALID_HANDLE)
		{
			Asura_Profiling_ResetTimer(g_aiProfileTimerHandles[i]);
			g_aiProfileFifoSizes[i] = 0;
		}
	}

}
#endif //#ifdef ASURA_WII_PROFILING
