#include "stdafx.h"
#include "ExcelWorksheetRef.h"			// CExcelWorksheetRef
#include "LevelStatsProcessor.h"		// CLevelStatsProcessor
#include "tinyxml/tinyxml.h"				// TiXmlDocument
#include <io.h>											// __finddata64_t
#include <vector>										// STL vector<>
#include <set>											// STL set<>
#include <algorithm>								// STL sort
#include <windows.h>								// CreateDirectory()	


static std::string g_sDest;										// file or directory, e.g. "c:/temp/Operations_Comparison.xml" or "c:/temp"
static std::string g_sSource;									// file or directory, e.g. "c:/temp/Operations.xml" or "c:/temp"
static std::string g_sPattern;								// e.g. "*.xml" or "Armada.xml"
static uint32 g_dwStartBuild=0;								// e.g. 3380
static uint32 g_dwMaxBuildCount=0;						// e.g. 5
static uint32 g_dwEndBuild=0xffffffff;				// e.g. 3390
bool g_bCombineLevels=false;									// first column is level name, all levels combined on one page



// e.g. "c:\temp\test.xml" -> "c:\temp"
static std::string ExtractPath( const std::string sIn )
{
	std::string sRet = sIn;

	const char *begin = sIn.c_str();
	const char *p = begin;
	const char *start=p;

	while(*p)
	{
		if(*p=='/' || *p=='\\')
			start = p;

		++p;
	}

	sRet.resize(start-begin);

	return sRet;
}

// e.g. "c:\temp\test.xml" -> "test.xml"
static std::string ExtractFilename( const std::string sIn )
{
	const char *p = sIn.c_str();
	const char *start=p;

	while(*p)
	{
		if(*p=='/' || *p=='\\')
			start = p+1;

		++p;
	}

	return start;
}


static const char *GetExtension( const char *p )
{
	while(*p)
	{
		if(*p=='.')
			return p+1;

		++p;
	}

	return p;
}

// Returns
//   0 if not found
static uint32 ExtractBuildNumber( const char *szFilePath )
{
	const char *p = szFilePath;
	uint32 dwBuildNumber = 0;

	while(*p!=0 && *p!='(') 
		++p;

	if(*p)
	{
		++p;
		while(*p>='0' && *p<='9') 
		{
			dwBuildNumber = dwBuildNumber*10 + *p-'0';
			++p;
		}
		if(*p==')')
			return dwBuildNumber;
	}

	assert(0);		// no build number
	return dwBuildNumber;
}


static bool IsBuildInRange( const char *szFilePath )
{
	const char *p = szFilePath;
	uint32 dwBuildNumber = ExtractBuildNumber(szFilePath);

	if(dwBuildNumber<g_dwStartBuild || dwBuildNumber>g_dwEndBuild)
		return false;

	return true;
}


// removes file extension including '.'
// does nothing if there is no '.'
// e.g. "c:\temp\test.xml" -> "c:\temp\test"
static std::string RemoveExtension( const std::string sIn )
{
	std::string sRet = sIn;

	const char *p = sIn.c_str();

	while(*p)
	{
		if(*p=='.')
		{
			sRet.resize(p-sIn.c_str());
			return sRet;
		}

		++p;
	}

	return sRet;
}


//////////////////////////////////////////////////////////////////////////
// the paths must have trailing slash
// 
// Example usage:
//    std::vector<std::string> RefFiles;
//    ScanDirectoryRecursive(std::string(szRefDir)+"/","","*.*",RefFiles,true);
static void ScanDirectoryRecursiveForBuilds( const std::string &root, const std::string &path,
	const std::string &file, std::set<std::string> &builds, const char *szExcludePrefix )
{
	__finddata64_t c_file;
	intptr_t hFile;
	uint32 dwExcludePrefixLen=0;
	
	if(szExcludePrefix)
		dwExcludePrefixLen = (uint32)strlen(szExcludePrefix);

	std::string fullPath = root + path + "*.*";

	if( (hFile = _findfirst64( fullPath.c_str(), &c_file )) != -1L )
	{
		// Find directories.
		do {
			if (c_file.attrib & _A_SUBDIR)
			{
				// If recursive.
				if (c_file.name[0] != '.')
				{
					// only builds in range
					if(!IsBuildInRange(c_file.name))
						continue;

					const std::string sCurrentPath = path + c_file.name + "\\";

					builds.insert(sCurrentPath);

					ScanDirectoryRecursiveForBuilds( root,sCurrentPath,file,builds,szExcludePrefix);
				}
			}
		}	while (_findnext64( hFile, &c_file ) == 0);
		_findclose( hFile );
	}
}




//////////////////////////////////////////////////////////////////////////
// the paths must have trailing slash
// 
// Example usage:
//    std::vector<std::string> RefFiles;
//    ScanDirectoryRecursive(std::string(szRefDir)+"/","","*.*",RefFiles,true);
static void ScanDirectoryForFiles( const std::string &root, const std::string &path,
	const std::string &file, std::vector<std::string> &files, const char *szExcludePrefix )
{
	__finddata64_t c_file;
	intptr_t hFile;
	uint32 dwExcludePrefixLen=0;

	if(szExcludePrefix)
		dwExcludePrefixLen = (uint32)strlen(szExcludePrefix);

	std::string fullPath = root + path + file;
	if ( (hFile = _findfirst64( fullPath.c_str(), &c_file )) != -1L )
	{
		// Find the rest of the files.
		do {
			if (!(c_file.attrib & _A_SUBDIR))
			{
				if(stricmp(c_file.name,"Thumbs.db")==0)					// ignore this
					continue;

				if(szExcludePrefix && strnicmp(c_file.name,szExcludePrefix,dwExcludePrefixLen)==0)
					continue;

				files.push_back( path + c_file.name );
			}
		}	while (_findnext64( hFile, &c_file ) == 0);
		_findclose( hFile );
	}
}



static std::string ExtractBuildName( const char *szFilePath )
{
	const char *p = szFilePath,*pBehindPath=szFilePath;

	while(*p!=0) 
	{
		if(*p=='\\' ||  *p=='/')
			pBehindPath=p;

		++p;
	}

	std::string ret=szFilePath;
	ret.resize(pBehindPath-szFilePath);		// a bit inefficient

	return ret;
}






#include <time.h>

void ProcessAllLevels( std::vector<std::string> &SrcFiles, uint32 &dwProgress )
{
	struct tm *newtime;
	__time64_t long_time;

	_time64( &long_time );           // Get time as 64-bit integer.
	newtime = _localtime64( &long_time );	// Convert to local time.

	char datebuff[30];
	asctime_s( datebuff, sizeof(datebuff), newtime );
	datebuff[24]=0;		// remove "\n"

	// to get rid of ":"
	char *p=datebuff;
	while(*p)
	{
		if(*p==':')
			*p='_';

		++p;
	}

	std::string sDestFilename = g_sDest + "/" + "LevelOverview " + (datebuff+4) + ".xml";		// +4 to get rid of day

	printf(" ProcessAllLevels : '%s'\n",sDestFilename.c_str());

	CLevelStatsProcessor processor(sDestFilename.c_str());

	uint32 dwCurrentBuildNo = 0;

	std::vector<std::string>::iterator it, end = SrcFiles.end();
	for(it=SrcFiles.begin();it!=end;++it)
	{
		std::string &sFilePath = *it;
		std::string sFilename = ExtractFilename(*it);

		uint32 dwLocalBuildNo = ExtractBuildNumber(sFilePath.c_str());

		if(dwLocalBuildNo!=dwCurrentBuildNo && it!=SrcFiles.begin())
			processor.IncreaseDestX();

		dwCurrentBuildNo=dwLocalBuildNo;

		printf("  %d/%d %s\n",dwProgress+1,SrcFiles.size(),sFilePath.c_str());

		++dwProgress;

		std::string sSourceFilename = g_sSource + "/" + *it;

		processor.ProcessFile(sSourceFilename.c_str());
	}

	printf("\n");
}



void ProcessOneLevelAtATime( std::string &sLevelFilename, std::vector<std::string> &SrcFiles, uint32 &dwProgress )
{
	printf("Level: %s\n",sLevelFilename.c_str());

	std::string sDestFilename = g_sDest + "/" + RemoveExtension(sLevelFilename) + "_Comparison.xml";

	CLevelStatsProcessor processor(sDestFilename.c_str());

	std::vector<std::string>::iterator it, end = SrcFiles.end();
	for(it=SrcFiles.begin();it!=end;++it)
	{
		std::string &sFilePath = *it;
		std::string sFilename = ExtractFilename(*it);

		if(stricmp(sLevelFilename.c_str(),sFilename.c_str())!=0)
			continue;

		printf("  %d/%d %s\n",dwProgress+1,SrcFiles.size(),sFilePath.c_str());

		++dwProgress;

		std::string sSourceFilename = g_sSource + "/" + *it;

		processor.ProcessFile(sSourceFilename.c_str());
		
		processor.IncreaseDestX();

		sFilePath.clear();		// mark used
	}

	printf("\n");
}




void Work()
{
	{
		printf("      Dest = %s\n",g_sDest.c_str());
		printf("    Source = %s\n",g_sSource.c_str());
		printf("   Pattern = %s\n",g_sPattern.c_str());
		
		if(g_dwStartBuild==0)
			printf("    StartBuild = <not defined>\n");
		 else
			printf("    StartBuild = %d\n",g_dwStartBuild);

		if(g_dwEndBuild==0xffffffff)
			printf("      EndBuild = <not defined>\n");
		 else
			printf("      EndBuild = %d\n",g_dwEndBuild);

		if(g_dwMaxBuildCount)
			printf(" MaxBuildCount = %d\n",g_dwMaxBuildCount);

		 printf("\n");
	}

	// ------------------------------------

	FILE *test = fopen(g_sSource.c_str(),"rb");

	if(test)
	{
		// one file processing
		fclose(test);
		CLevelStatsProcessor processor(g_sDest.c_str());
		processor.ProcessFile(g_sSource.c_str());
		return;
	}
	else
	{
		// directory processing
		CreateDirectory(g_sDest.c_str(),0);

		printf("... scanning directory:\n\n");
		printf("    %s ...",g_sSource.c_str());

		const char *szExtension = GetExtension(g_sSource.c_str());
		const char *szExcludePrefix="depends_";
		std::string sRoot = g_sSource+"/"; 

		std::set<std::string> BuildSet;

		ScanDirectoryRecursiveForBuilds(sRoot,"",g_sPattern,BuildSet,szExcludePrefix);

		printf("\n\nbuilds in range found: %d\n",BuildSet.size());

		// limit build count
		if(g_dwMaxBuildCount)
		{
			while(BuildSet.size()>g_dwMaxBuildCount)
				BuildSet.erase(BuildSet.begin());
		}

		printf("builds limited to: %d\n",BuildSet.size());

		std::vector<std::string> SrcFiles;

		{
			std::set<std::string>::const_iterator it, end = BuildSet.end();

			for(it=BuildSet.begin();it!=end;++it)
			{
				const std::string sBuildPath = *it;

				ScanDirectoryForFiles(sRoot,sBuildPath,g_sPattern,SrcFiles,szExcludePrefix);
			}
		}


		printf("\n\nfiles in range: %d\n",SrcFiles.size());

		std::sort(SrcFiles.begin(),SrcFiles.end());

		printf("\n... processing directory ...\n\n");

		uint32 dwProgress=0;

		if(g_bCombineLevels)
			ProcessAllLevels(SrcFiles,dwProgress);
		else
		{
			std::vector<std::string>::const_iterator it, end = SrcFiles.end();

			for(it=SrcFiles.begin();it!=end;++it)
			{
				const std::string &sFilePath = *it;

				if(sFilePath.empty())		//  marked used 
					continue;

				std::string sFilename = ExtractFilename(*it);

				// following function will mark used entries in SrcFiles
				ProcessOneLevelAtATime(sFilename,SrcFiles,dwProgress);
			}
		}
	}
}




int _tmain( int argc, char *argv[] )
{
	bool bWait=false;
	bool bWork=true;

	uint32 dwStart = timeGetTime();

	for(int i=1;i<argc;++i)
	{
		const char *szArg = argv[i];

		if(*szArg=='-')
		{
			if(_stricmp(szArg+1,"wait")==0)	
				bWait=true;
			else if(_stricmp(szArg+1,"CombineLevels")==0)	
			{
				g_bCombineLevels=true;
			}
			else if(_strnicmp(szArg+1,"maxbuildcount=",14)==0)	
			{
				if(sscanf(szArg+1+14,"%d",&g_dwMaxBuildCount)!=1)
				{
					printf("ERROR: max build count not recognized");
					bWork=false;
				}
			}
			else if(_strnicmp(szArg+1,"startbuild=",11)==0)	
			{
				if(sscanf(szArg+1+11,"%d",&g_dwStartBuild)!=1)
				{
					printf("ERROR: start build id not recognized");
					bWork=false;
				}
			}
			else if(_strnicmp(szArg+1,"endbuild=",9)==0)	
			{
				if(sscanf(szArg+1+9,"%d",&g_dwEndBuild)!=1)
				{
					printf("ERROR: end build id not recognized");
					bWork=false;
				}
			}
			else
			{
				printf("ERROR: unknown argument %s\n",szArg);
				bWork=false;
			}
		}
		else
		{
			if(g_sDest.empty())
				g_sDest = szArg;
			else if(g_sSource.empty())
				g_sSource = szArg;
			else if(g_sPattern.empty())
				g_sPattern = szArg;
			else
			{
				printf("ERROR: unknown parameter %s\n",szArg);
				bWork=false;
			}
		}
	}

	if(g_sPattern.empty())
		g_sPattern="*.xml";

	if(g_sDest.empty())
	{
		printf("\n");
		printf("LevelStatsProcessor V1.2   Copyright by Crytek, written by Martin Mittring\n");
		printf("===================\n");
		printf("\n");
		printf(" LevelStatsProcessor [-wait] [-CombineLevels]\n");
		printf("                     [-startbuild=<buidno>] [-endbuild=<buidno>]\n");
		printf("                     [-maxbuildcount=<number>]\n");
		printf("                     <destdirectory> <sourcefilename> [<pattern>]\n");
		printf("\n");
		printf("    Build number is extracted from directory name (within brackets).\n");
		printf("\n");
		printf("    The destination file is loaded (if not existing - it becomes created as a\n");
		printf("    clean copy of the source). A new column is added.");
		printf("\n");
		printf("    Examples:\n");
		printf("\n");
		printf("    LevelStatsProcessor dest \"X:\\QA\\AutoTest Results\\HighSpec\" -maxbuildcount=5 -wait\n");
		printf("    LevelStatsProcessor dest \"X:\\QA\\AutoTest Results\\HighSpec\" -startbuild=5074 -wait\n");
		printf("    LevelStatsProcessor dest \"X:\\QA\\AutoTest Results\\HighSpec\" Armada.xml -startbuild=5074 -wait\n");
		printf("    LevelStatsProcessor dest \"X:\\QA\\AutoTest Results\\HighSpec\" Armada.xml -startbuild=5074 -wait\n");
		printf("\n\n");
		bWork=false;
	}

	if(bWork)
	if(g_sDest.empty() || g_sSource.empty())
	{
		printf("ERROR: Filename[s] missing\n");
		bWork=false;
	}

	if(bWork)
	{
		Work();

		uint32 dwEnd = timeGetTime();

		printf("  .. %d Seconds\n",(dwEnd-dwStart)/1000);
	}

	if(bWait)
	{
		printf("\n");
		printf("\n                                           <RETURN>");
		getchar();
	}
	return 0;
}
