#include "StdAfx.h"
#include "ILog.h"
#include "ITimer.h"
#include "ISystem.h"
#include "IConsole.h"
#include "IRenderer.h"
#include "CrySizerImpl.h"
#include "CrySizerStats.h"
#include "ITextModeConsole.h"


CrySizerStatsBuilder::CrySizerStatsBuilder (CrySizerImpl* pSizer, int nMinSubcomponentBytes):
	m_pSizer (pSizer),
	m_nMinSubcomponentBytes (nMinSubcomponentBytes < 0 || nMinSubcomponentBytes > 0x10000000 ? 0 : nMinSubcomponentBytes)
{

}


// creates the map of names from old (in the sizer Impl) to new (in the Stats)
void CrySizerStatsBuilder::processNames()
{
	size_t numCompNames = m_pSizer->m_arrNames.size();
	m_pStats->m_arrComponents.reserve (numCompNames);
	m_pStats->m_arrComponents.clear();

  m_mapNames.resize (numCompNames, (size_t)-1);

	// add all root objects
	addNameSubtree(0,0);
}


//////////////////////////////////////////////////////////////////////////
// given the name in the old system, adds the subtree of names to the 
// name map and components. In case all the subtree is empty, returns false and 
// adds nothing
size_t CrySizerStatsBuilder::addNameSubtree (unsigned nDepth, size_t nName)
{
	assert (nName < m_pSizer->m_arrNames.size());

	CrySizerImpl::ComponentName& rCompName = m_pSizer->m_arrNames[nName];
	size_t sizeObjectsTotal = rCompName.sizeObjectsTotal;

	if (sizeObjectsTotal <= m_nMinSubcomponentBytes)
		return sizeObjectsTotal; // the subtree didn't pass

	// the index of the component in the stats object (sorted by the depth-first traverse order)
	size_t nNewName = m_pStats->m_arrComponents.size();
	m_pStats->m_arrComponents.resize (nNewName+1);

	Component& rNewComp = m_pStats->m_arrComponents[nNewName];
	rNewComp.strName = rCompName.strName;
	rNewComp.nDepth = nDepth;
	rNewComp.numObjects = rCompName.numObjects;
	rNewComp.sizeBytes = rCompName.sizeObjects;
	rNewComp.sizeBytesTotal = sizeObjectsTotal;
	m_mapNames[nName] = nNewName;

	// find the immediate children and sort them by their total size
	typedef std::map<size_t,size_t> UintUintMap;
	UintUintMap mapSizeName; // total size -> child index (name in old indexation)

	for (size_t i = nName + 1; i < m_pSizer->m_arrNames.size(); ++i)
	{
		CrySizerImpl::ComponentName& rChild = m_pSizer->m_arrNames[i];
		if (rChild.nParent == nName && rChild.sizeObjectsTotal > m_nMinSubcomponentBytes)
			mapSizeName.insert (UintUintMap::value_type(rChild.sizeObjectsTotal,i));
	}

	// add the sorted components
	/*
	for (unsigned i = nName + 1; i < m_pSizer->m_arrNames.size(); ++i)
		if (m_pSizer->m_arrNames[i].nParent == nName)
			addNameSubtree(nDepth+1,i);
	*/

	for (UintUintMap::reverse_iterator it = mapSizeName.rbegin(); it != mapSizeName.rend(); ++it)
	{
		addNameSubtree(nDepth + 1, it->second);
	}

	return sizeObjectsTotal;
}


//////////////////////////////////////////////////////////////////////////
// creates the statistics out of the given CrySizerImpl into the given CrySizerStats
// Maps the old to new names according to the depth-walk tree rule
void CrySizerStatsBuilder::build (CrySizerStats* pStats)
{
	m_pStats = pStats;

	m_mapNames.clear();

	processNames();

	m_pSizer->clear();
	pStats->refresh();
	pStats->m_nAgeFrames = 0;
}


//////////////////////////////////////////////////////////////////////////
// constructs the statistics based on the given cry sizer
CrySizerStats::CrySizerStats (CrySizerImpl* pCrySizer)
{
	CrySizerStatsBuilder builder (pCrySizer);
	builder.build(this);
}

CrySizerStats::CrySizerStats ()
{
}


// if there is already such name in the map, then just returns the index
// of the compoentn in the component array; otherwise adds an entry to themap
// and to the component array nad returns its index
CrySizerStatsBuilder::Component& CrySizerStatsBuilder::mapName (unsigned nName)
{
	assert (m_mapNames[nName] != -1);
	return m_pStats->m_arrComponents[m_mapNames[nName]];
	/*
	IdToIdMap::iterator it = m_mapNames.find (nName);
	if (it == m_mapNames.end())
	{
		unsigned nNewName = m_arrComponents.size();
		m_mapNames.insert (IdToIdMap::value_type(nName, nNewName));
		m_arrComponents.resize(nNewName + 1);
		m_arrComponents[nNewName].strName.swap(m_pSizer->m_arrNames[nName]);
		return m_arrComponents.back();
	}
	else
	{
		assert (it->second < m_arrComponents.size());
		return m_arrComponents[it->second];
	}
	*/
}

// refreshes the statistics built after the component array is built
void CrySizerStats::refresh()
{
	m_nMaxNameLength = 0; 
	for (size_t i = 0; i < m_arrComponents.size(); ++i)
	{
		size_t nLength = m_arrComponents[i].strName.length()+m_arrComponents[i].nDepth;
		if (nLength > m_nMaxNameLength)
			m_nMaxNameLength = nLength;
	}
}


bool CrySizerStats::Component::GenericOrder::operator () (const Component& left, const Component& right)const
{
	return left.strName < right.strName;
}


CrySizerStatsRenderer::CrySizerStatsRenderer (ISystem* pSystem, CrySizerStats* pStats, unsigned nMaxSubcomponentDepth, int nMinSubcomponentBytes):
	m_pStats(pStats),
	m_pRenderer(pSystem->GetIRenderer()),
	m_pLog (pSystem->GetILog()),
	m_pTextModeConsole(pSystem->GetITextModeConsole()),
	m_nMinSubcomponentBytes (nMinSubcomponentBytes < 0 || nMinSubcomponentBytes > 0x10000000 ? 0x8000 : nMinSubcomponentBytes),
	m_nMaxSubcomponentDepth (nMaxSubcomponentDepth)
{

}

void CrySizerStatsRenderer::render(bool bRefreshMark)
{
	if (!m_pStats->size())
		return;

	int x,y,dx,dy;
	m_pRenderer->GetViewport(&x,&y,&dx,&dy);

	// left coordinate of the text
	unsigned nNameWidth = (unsigned)(m_pStats->getMaxNameLength()+1);
	if (nNameWidth < 25)
		nNameWidth = 25;
	float fCharScaleX = 0.8f, fCharScaleY = 0.8f;
	float fCharSizeX = fCharScaleX, fCharSizeY = fCharScaleY;
	float fLeft = 0;
	float fTop  = 8;
	float fVStep = 6;

#ifdef _DEBUG
	char *szCountStr1="count";
	char *szCountStr2="_____";
#else // _DEBUG
	char *szCountStr1="", *szCountStr2="";
#endif // _DEBUG

	m_pRenderer->WriteXY((int)fLeft, (int)(fTop), fCharScaleX, fCharScaleY, 0.9f,0.85f,1,0.85f,
		"%-*s   total  partial  %s",nNameWidth,bRefreshMark?"Memory usage (refresh*)":"Memory usage (refresh )",szCountStr1);
	m_pRenderer->WriteXY((int)fLeft, (int)(fTop + fVStep*0.25f), fCharScaleX, fCharScaleY, 0.85f,0.9f,1,0.85f,
		"%*s   _____   _______  %s",nNameWidth,"",szCountStr2);

	unsigned nSubgroupDepth = 1;

	// different colors used to paint the statistics subgroups
	// a new statistic subgroup starts with a new subtree of depth <= specified
	float fGray = 0;//0.45f;
	float fLightGray = 0.5f;//0.8f;
	float fColors[] =
	{
		fLightGray,fLightGray,fGray, 1,
		1,1,1,1,
		fGray,1,1,1,
		1,fGray,1,1,
		1,1,fGray,1,
		fGray,fLightGray,1,1,
		fGray,1,fGray,1,
		1,fGray,fGray,1
	};
	float*pColor = fColors;

	for (unsigned i = 0, r = 0; i < m_pStats->size(); ++i)
	{
		const Component& rComp = (*m_pStats)[i];

		if (rComp.nDepth <= nSubgroupDepth)
		{
			//switch the color
			pColor += 4;
			if (pColor >= fColors + sizeof(fColors)/sizeof(fColors[0]))
				pColor = fColors;

			fTop += fVStep*(0.333333f + (nSubgroupDepth - rComp.nDepth) * 0.15f);
		}

		if (rComp.sizeBytesTotal <= m_nMinSubcomponentBytes || rComp.nDepth > m_nMaxSubcomponentDepth)
			continue;

		fTop += fVStep;

		char szDepth[32] = " ..............................";
		if (rComp.nDepth < sizeof(szDepth))
			szDepth[rComp.nDepth] = '\0';

		char szSize[32];
		if (rComp.sizeBytes > 0)
		{
			if (rComp.sizeBytesTotal > rComp.sizeBytes)
				sprintf (szSize, "%7.3f  %7.3f", rComp.getTotalSizeMBytes(), rComp.getSizeMBytes());
			else
				sprintf (szSize, "         %7.3f", rComp.getSizeMBytes());
		}
		else
		{
			assert (rComp.sizeBytesTotal > 0);
			sprintf (szSize, "%7.3f         ", rComp.getTotalSizeMBytes());
		}
		char szCount[16];
#ifdef _DEBUG
		if (rComp.numObjects)
			sprintf (szCount, "%8u", rComp.numObjects);
		else
#endif
			szCount[0] = '\0';

		m_pRenderer->WriteXY((int)fLeft, (int)(fTop), fCharScaleX, fCharScaleY, pColor[0],pColor[1],pColor[2],pColor[3],
			"%s%-*s:%s%s",szDepth, nNameWidth-rComp.nDepth,rComp.strName.c_str(), szSize, szCount);

		if (m_pTextModeConsole)
		{
			string text;
			text.Format("%s%-*s:%s%s",szDepth, nNameWidth-rComp.nDepth,rComp.strName.c_str(), szSize, szCount);
			m_pTextModeConsole->PutText(0, r++, text.c_str());
		}
	}

	fTop += 0.25f*fVStep;
	m_pRenderer->WriteXY((int)fLeft, (int)(fTop), fCharScaleX, fCharScaleY, fLightGray,fLightGray,fLightGray,1,
			"%-*s %s",nNameWidth,"___________________________", "________________");
	fTop += fVStep;

	const char* szOverheadNames[CrySizerStats::g_numTimers] = 
	{
		".Collection",
		".Transformation",
		".Cleanup"
	};
	bool bOverheadsHeaderPrinted = false;
	for (unsigned i = 0; i < CrySizerStats::g_numTimers; ++i)
	{
		float fTime = m_pStats->getTime(i);
		if (fTime < 20)
			continue;
		// print the header
		if (!bOverheadsHeaderPrinted)
		{
			m_pRenderer->WriteXY((int)fLeft, (int)(fTop), fCharScaleX, fCharScaleY, fLightGray,fLightGray,fLightGray,1,
				"%-*s",nNameWidth,"Overheads");
			fTop += fVStep;
			bOverheadsHeaderPrinted = true;
		}

		m_pRenderer->WriteXY((int)fLeft, (int)(fTop), fCharScaleX, fCharScaleY, fLightGray,fLightGray,fLightGray,1,
				"%-*s:%7.1f ms",nNameWidth,szOverheadNames[i], fTime);
		fTop += fVStep;
	}
}

void CrySizerStatsRenderer::dump(bool bUseKB)
{
	if (!m_pStats->size())
		return;

	unsigned nNameWidth = (unsigned)(m_pStats->getMaxNameLength()+1);

	// left coordinate of the text
	m_pLog->LogToFile ("Memory Statistics: %s", bUseKB ? "KB" : "MB");
	m_pLog->LogToFile("%-*s   TOTAL   partial  count",nNameWidth,"");

	unsigned nSubgroupDepth = 1;

	// different colors used to paint the statistics subgroups
	// a new statistic subgroup starts with a new subtree of depth <= specified

	for (unsigned i = 0; i < m_pStats->size(); ++i)
	{
		const Component& rComp = (*m_pStats)[i];

		if (rComp.sizeBytesTotal <= m_nMinSubcomponentBytes || rComp.nDepth > m_nMaxSubcomponentDepth)
			continue;

		char szDepth[32] = " ..............................";
		if (rComp.nDepth < sizeof(szDepth))
			szDepth[rComp.nDepth] = '\0';

		char szSize[32];
		if (rComp.sizeBytes > 0)
		{
			if (rComp.sizeBytesTotal > rComp.sizeBytes)
				sprintf (szSize, bUseKB ? "%7.2f  %7.2f" : "%7.3f  %7.3f", bUseKB ? rComp.getTotalSizeKBytes() : rComp.getTotalSizeMBytes(), bUseKB ? rComp.getSizeKBytes() : rComp.getSizeMBytes());
			else
				sprintf (szSize, bUseKB ? "         %7.2f" : "         %7.3f", bUseKB ? rComp.getSizeKBytes() : rComp.getSizeMBytes());
		}
		else
		{
			assert (rComp.sizeBytesTotal > 0);
			sprintf (szSize, bUseKB ? "%7.2f         " : "%7.3f         ", bUseKB ? rComp.getTotalSizeKBytes() : rComp.getTotalSizeMBytes());
		}
		char szCount[16];

		if (rComp.numObjects)
			sprintf (szCount, "%8u", rComp.numObjects);
		else
			szCount[0] = '\0';

		m_pLog->LogToFile ("%s%-*s:%s%s",szDepth, nNameWidth-rComp.nDepth,rComp.strName.c_str(), szSize, szCount);
	}
}


void CrySizerStats::startTimer(unsigned nTimer, ITimer* pTimer)
{
	assert (nTimer < g_numTimers);
	m_fTime[nTimer] = pTimer->GetAsyncCurTime();
}

void CrySizerStats::stopTimer(unsigned nTimer, ITimer* pTimer)
{
	assert (nTimer < g_numTimers);
	m_fTime[nTimer] = 1000*(pTimer->GetAsyncCurTime() - m_fTime[nTimer]);
}
