//////////////////////////////////////////////////////////////////////////////
//
// Asura_PSP_Profiling.cpp
//
// Created by: Anthony Gregson - 20/09/04
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// First version of PSP profiling. Uses Asura_Profiling_System, and 
// isn't the most efficient of systems at this point. Needs proper work on
// making a system that impacts the running of the system less by using more
// PSP-specific calls.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_PSP_PROFILING

// This file's header
#include "Debugging/Asura_PSP_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 "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

// PSP headers
#include "Debugging/Asura_PSP_GPIO.h"
#include "Debugging/Asura_PSP_Tuner.h"
#include "Rendering/Asura_PSP_Graphics.h"
#include "Rendering/Asura_PSP_TextureManagement.h"
#include "Rendering/Asura_PSP_RenderStates.h"

// Platform headers
#include <thread.h>

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 Asura_Handle g_aiGPUTimerHandles[Asura_PSP_Profiling::ASURA_PSP_SIGNAL_MAX / 2]; // GPU time, two signals (start/end) per bar

static float g_fGPUTimerSignals[Asura_PSP_Profiling::ASURA_PSP_SIGNAL_MAX / 2]; // Testing that we don't loose any interruptions

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

static const u_int g_auGPUColours[Asura_PSP_Profiling::ASURA_PSP_SIGNAL_MAX / 2] =
{
	0xff800000,	// ASURA_PSP_SIGNAL_IDLE (dark blue)
	0xff8022ff, // ASURA_PSP_SIGNAL_CLEAR (violet?)
	0xffff2222, // ASURA_PSP_SIGNAL_SKYBOX (blue)
	0xff22ffff, // ASURA_PSP_SIGNAL_ENVIRONMENT (yellow)
	0xff55dd55,	// ASURA_PSP_SIGNAL_BOTANICALS (project green)
	0xff2222ff, // ASURA_PSP_SIGNAL_DECAL (red)
	0xffff22ff, // ASURA_PSP_SIGNAL_ENTITY (magenta)
	0xff22ff22, // ASURA_PSP_SIGNAL_GLOW (green)
	0xff000000, // ASURA_PSP_SIGNAL_PFX (black)
	0xffffff22, // ASURA_PSP_SIGNAL_OTHERS (cyan)
	0xffff8022, // ASURA_PSP_SIGNAL_MOTIONBLUR (turquoise?)
	0xff2280ff, // ASURA_PSP_SIGNAL_FOG (orange?)
	0xffffffff, // ASURA_PSP_SIGNAL_FILTER (white)
	0xffbbbbbb, // ASURA_PSP_SIGNAL_CORONAS (light grey)
	0xffdd55dd, // ASURA_PSP_SIGNAL_OUTLINE (project magenta)
	0xff55dddd, // ASURA_PSP_SIGNAL_PROFILE (project yellow)
};

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

	for (int i=0; i<ASURA_PSP_SIGNAL_MAX / 2; i++)
	{
		g_aiGPUTimerHandles[i] = ASURA_INVALID_HANDLE;
	}

	Asura_Profiling_System::Initialise();

}

void Asura_PSP_Profiling::SignalHandler(int iSignalID)
{
	if (g_aiGPUTimerHandles[iSignalID/2] == ASURA_INVALID_HANDLE)
	{
		g_aiGPUTimerHandles[iSignalID/2] = Asura_Profiling_System::CreateTimer();
	}

	Asura_Assert(iSignalID <= ASURA_PSP_SIGNAL_SYNC, "Incorrect signal value"); 

	if (iSignalID == ASURA_PSP_SIGNAL_SYNC)
	{
		for (int i=0; i < ASURA_PSP_SIGNAL_MAX / 2; i++)
		{
			if (g_aiGPUTimerHandles[i] != ASURA_INVALID_HANDLE)
			{
				g_fGPUTimerSignals[i] = Asura_Profiling_GetTotalDuration(g_aiGPUTimerHandles[i]);
				Asura_Profiling_ResetTimer(g_aiGPUTimerHandles[i]);
			}

		}
	}
	else if (iSignalID % 2)
	{
		Asura_Profiling_System::StopTiming(g_aiGPUTimerHandles[iSignalID/2]);
	}
	else
	{
		Asura_Profiling_System::StartTiming(g_aiGPUTimerHandles[iSignalID/2]);
	}
	
}

void Asura_PSP_Profiling::InstallSignalHandler()
{
	GU_SetCallback( SCEGU_INT_SIGNAL, SignalHandler ); 
}

void Asura_PSP_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;
		}

	}
	
	for (int i=0; i<ASURA_PSP_SIGNAL_MAX / 2; i++)
	{
		if (g_aiGPUTimerHandles[i] != ASURA_INVALID_HANDLE)
		{
			Asura_Profiling_System::DestroyTimer(g_aiGPUTimerHandles[i]);
			g_aiGPUTimerHandles[i] = ASURA_INVALID_HANDLE;
		}
	}
}

void Asura_PSP_Profiling::RegisterLabel(int iProfileType, const char* szName)
{
	Asura_PSP_Tuner::StartMarker(iProfileType, const_cast<char*>(szName));
}

void Asura_PSP_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]);
}

void Asura_PSP_Profiling::StopProfile(int iProfileType)
{
	Asura_Profiling_System::StopTiming(g_aiProfileTimerHandles[iProfileType]);
	Asura_PSP_Tuner::StopMarker(iProfileType);
}

static const float fASURA_PSP_TIMING_BAR_SECTION = 1.0f/60.0f;
static const float fASURA_PSP_ONE_OVER_TIMING_BAR_SECTION = 60.0f;
static const int iASURA_PSP_TIMING_MAX_DIVISIONS = 12;	// i.e. 5fps
DEBUGVAR float dbg_fProfileBarsX = 48.0f;
DEBUGVAR float dbg_fProfileBarsY = 0.0f;
DEBUGVAR float dbg_fProfileBarsW = 384.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;

void Asura_PSP_Profiling::Render()
{
	if(!Asura_PSP_GPIO::GetGPI(ASURA_PSP_GPI_PROFILING))
	{
		return;
	}

	// HACKY rendering of profiling data (by AJG)
	// MH 28.11.05 -- now that we also have a GPU profile bar, use the same divisions for both

	const float fTotalCPU = Asura_Profiling_GetTotalDuration(g_aiProfileTimerHandles[ASURA_CPU_PROFILE__TOTALFRAME]);
	const float fTotal = fTotalCPU;
	// KOR - 18/01/2006 - CPU is always bigger than GPU, as it has to wait for the GPU to finish.
/*	{
		float fTotalGPU = 0.0f;
		for (int i=0; i<ASURA_PSP_SIGNAL_MAX / 2; i++)
		{
			if (g_aiGPUTimerHandles[i] != ASURA_INVALID_HANDLE)
			{
				fTotalGPU += g_fGPUTimerSignals[i];
			}
		}

		if (fTotalGPU <= 0.0f) { return; }

		fTotal = Asura_Maths::Max(fTotalCPU, fTotalGPU);
	}*/

	int iDivisions = Asura_Maths::FloatToInt_Chop(fTotal / fASURA_PSP_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_PSP_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_PSP_RenderStates::RequestColourMaterial(0);
	Asura_PSP_TextureManagement::RequestTextureIndex( 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));
			GU_Material(SCEGU_AMBIENT, (i & 1) ? dbg_uProfBackColour0 : dbg_uProfBackColour1);
			GU_SpriteMode( 1, 1, fDivisionWidth, ((dbg_fProfileBarsH + dbg_fProfileBarsHBorder) * 2.0f) + dbg_fProfileBarsHSpace);
			GU_DrawSprite(fX, dbg_fProfileBarsY, 0, 0, 0, 0, 0);
		}
	}

	// 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_PSP_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions;
				const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
				GU_Material(SCEGU_AMBIENT, g_auProfileColours[i]);
				GU_SpriteMode( 1, 1, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
				GU_DrawSprite(fX, dbg_fProfileBarsY + dbg_fProfileBarsHBorder, 0, 0, 0, 0, 0);
				fRunningTotal += fValue;
			}
		}
		// KOR - render remaining time for whole frame.
		const float fValue = fTotalCPU * fASURA_PSP_ONE_OVER_TIMING_BAR_SECTION * fOneOverDivisions	- fRunningTotal;
		const float fX = dbg_fProfileBarsX + (dbg_fProfileBarsW * fRunningTotal);
		GU_Material(SCEGU_AMBIENT, 0xff00ff80);
		GU_SpriteMode( 1, 1, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
		GU_DrawSprite(fX, dbg_fProfileBarsY + dbg_fProfileBarsHBorder, 0, 0, 0, 0, 0);
	}

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

		for (int i=0; i<ASURA_PSP_SIGNAL_MAX / 2; i++)
		{
			if (g_aiGPUTimerHandles[i] != ASURA_INVALID_HANDLE)
			{
				const float fValue = g_fGPUTimerSignals[i] * fASURA_PSP_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;
				GU_Material(SCEGU_AMBIENT, g_auGPUColours[i]);
				GU_SpriteMode( 1, 1, dbg_fProfileBarsW * fValue, dbg_fProfileBarsH);
				GU_DrawSprite(fX, fY, 0, 0, 0, 0, 0);
				fRunningTotal += fValue;
			}
		}
	}

	// (MH - 19.12.2006) tell Asura_PSP_Graphics that its cached material type is invalid due to GU_Material calls.
	Asura_PSP_Graphics::InvalidateMaterialType();
}

void Asura_PSP_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]);
		}
	}

}


void Asura_PSP_Profiling::Signal(int iSignalID)
{
	GU_Signal(SCEGU_SIGNAL_WAIT, iSignalID); 
}

void Asura_PSP_Profiling::LogEventTime(const char* szEvent)
{
	SceKernelSysClock xNewTickCount;
	sceKernelGetSystemTime(&xNewTickCount);
	u_int64 uCurrentTickCount = xNewTickCount.hi;
	uCurrentTickCount = uCurrentTickCount << 32;
	uCurrentTickCount += xNewTickCount.low;
	Asura_OutputToDebugger(("%s\tat time: %u\n", szEvent, uCurrentTickCount));
}
#endif //#ifdef ASURA_PSP_PROFILING
