/*
 * Profiler.cpp
 * ------------
 * Purpose: Performance measuring
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "Profiler.h"


OPENMPT_NAMESPACE_BEGIN


#ifdef USE_PROFILER


class Statistics
{
public:
	Profile &profile;
	Profile::Data data;
	double usage;
	double seconds;
	Statistics(Profile &p) : profile(p)
	{
		usage = 0.0;
		seconds = 0.0;
		Update();
	}
	void Update()
	{
		data = profile.GetAndResetData();
		uint64 now = profile.GetTime();
		uint64 timewindow = now - data.Start;
		if(data.Calls > 0 && timewindow > 0)
		{
			usage = (double)data.Sum / (double)timewindow;
		} else
		{
			usage = 0.0;
		}
		seconds += static_cast<double>(data.Sum) / static_cast<double>(profile.GetFrequency());
	}
};


struct ProfileBlock
{
	class Profile * profile;
	const char * name;
	class Statistics * stats;
};

static constexpr std::size_t MAX_PROFILES = 1024;

static ProfileBlock Profiles[ MAX_PROFILES ];

static std::size_t NextProfile = 0;


static void RegisterProfile(Profile *newprofile)
{
	if(NextProfile < MAX_PROFILES)
	{
		Profiles[NextProfile].profile = newprofile;
		Profiles[NextProfile].stats = 0;
		NextProfile++;
	}
}


static void UnregisterProfile(Profile *oldprofile)
{
	for(std::size_t i=0; i<NextProfile; i++) {
		if(Profiles[i].profile == oldprofile) {
			Profiles[i].profile = 0;
			delete Profiles[i].stats;
			Profiles[i].stats = 0;
		}
	}
}


void Profiler::Update()
{
	for(std::size_t i=0; i<NextProfile; i++)
	{
		if(!Profiles[i].stats)
		{
			Profiles[i].stats = new Statistics(*Profiles[i].profile);
		} else
		{
			Profiles[i].stats->Update();
		}
	}
}


std::string Profiler::DumpProfiles()
{
	std::string ret;
	for(std::size_t i=0; i<NextProfile; i++)
	{
		if(Profiles[i].stats)
		{
			Statistics &stats = *Profiles[i].stats;
			std::string cat;
			switch(stats.profile.Category)
			{
			case Profiler::GUI: cat = "GUI"; break;
			case Profiler::Audio: cat = "Audio"; break;
			case Profiler::Notify: cat = "Notify"; break;
			case Profiler::Settings: cat = "Settings"; break;
			}
			ret += cat + " " + std::string(stats.profile.Name) + ": " + mpt::afmt::right(6, mpt::afmt::fix(stats.usage * 100.0, 3)) + "% " + mpt::afmt::fix(stats.seconds, 3) + "s\r\n";
		}
	}
	ret += "\r\n";
	return ret;
}


std::vector<double> Profiler::DumpCategories()
{
	std::vector<double> ret;
	ret.resize(Profiler::CategoriesCount);
	for(std::size_t i=0; i<NextProfile; i++)
	{
		if(Profiles[i].stats)
		{
			ret[Profiles[i].profile->Category] += Profiles[i].stats->usage;
		}
	}
	return ret;
}


uint64 Profile::GetTime() const
{
	LARGE_INTEGER ret;
	ret.QuadPart = 0;
	QueryPerformanceCounter(&ret);
	return ret.QuadPart;
}


uint64 Profile::GetFrequency() const
{
	LARGE_INTEGER ret;
	ret.QuadPart = 0;
	QueryPerformanceFrequency(&ret);
	return ret.QuadPart;
}


Profile::Profile(Profiler::Category category, const char *name) : Category(category), Name(name)
{
	data.Calls = 0;
	data.Sum = 0;
	data.Overhead = 0;
	data.Start = GetTime();
	EnterTime = 0;
	RegisterProfile(this);
}


Profile::~Profile()
{
	UnregisterProfile(this);
}


Profile::Data Profile::GetAndResetData()
{
	Profile::Data ret;
	datamutex.lock();
	ret = data;
	data.Calls = 0;
	data.Sum = 0;
	data.Overhead = 0;
	data.Start = GetTime();
	datamutex.unlock();
	return ret;
}


void Profile::Reset()
{
	datamutex.lock();
	data.Calls = 0;
	data.Sum = 0;
	data.Overhead = 0;
	data.Start = GetTime();
	datamutex.unlock();
}


void Profile::Enter()
{
	EnterTime = GetTime();
}


void Profile::Leave()
{
	uint64 LeaveTime = GetTime();
	datamutex.lock();
	data.Calls += 1;
	data.Sum += LeaveTime - EnterTime;
	datamutex.unlock();
}


#else // !USE_PROFILER

MPT_MSVC_WORKAROUND_LNK4221(Profiler)

#endif // USE_PROFILER


OPENMPT_NAMESPACE_END
