// CombinationsMerge.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "FileChangeMonitor.h"
#include <assert.h>								// assert()
#include <string>
#include <set>
#include <vector>

//////////////////////////////////////////////////////////////////////////
bool g_bStayResident = false;
char m_sTargetFileSpec[_MAX_PATH];
char g_szLine[65536];
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
struct CacheFile
{
	CacheFile()
	{
		m_bModified = false;

		// some test cases
		assert(CheckSyntax("<1>watervolume@WaterVolumeOutofPS()()(0)(0)(0)(ps_2_0)")==true);
		assert(CheckSyntax("<1>Blurcloak@BlurCloakPS(%BUMP_MAP)(%_RT_FOG|%_RT_HDR_MODE|%_RT_BUMP)(0)(0)(1)(ps_2_0)")==true);
		assert(CheckSyntax("<1>Burninglayer@BurnPS()(%_RT_ADDBLEND|%_RT_)HDR_MODE|%_RT_BUMP|%_RT_3DC)(0)(0)(0)(ps_2_0)")==false);
		assert(CheckSyntax("<1>Illum@IlluminationVS(%DIFFUSE|%SPECULAR|%BUMP_MAP|%VERTCOLORS|%STAT_BRANCHING)(%_RT_RAE_GEOMTERM)(101)(0)(0)(vs_2_0)")==true);
	}

	bool Load( const char *filename );
	bool Save();
	bool Reload();

	// Merge another cache file into this file.
	// Return true if contents of this file where modified.
	bool Merge( CacheFile &cache );

	//////////////////////////////////////////////////////////////////////////
	bool m_bModified;
	std::string m_filename;
	typedef std::set<std::string> Entries;
	Entries m_entries;

private:

	// Returns
	//   true=syntax is ok, false=syntax is wrong
	static bool CheckSyntax( const char *szLine,const char **sOutStr=NULL );

	// Returns:
	//   true - line was instered, false otherwise
	bool InsertLine( const char *szLine );
};

//////////////////////////////////////////////////////////////////////////
bool CacheFile::Reload()
{
	return Load( m_filename.c_str() );
}

//////////////////////////////////////////////////////////////////////////
bool CacheFile::Load( const char *filename )
{
	FILE *f = fopen( filename,"rt" );
	if (!f)
		return false;

	int nNumLines = 0;
	m_entries.clear();
	m_filename = filename;
	char str[65535];
	while (fgets(str,sizeof(str),f) != NULL)
	{
		if(InsertLine(str))
			++nNumLines;
	}
	fclose(f);
	if (nNumLines == m_entries.size())
		m_bModified = false;
	else
		m_bModified = true;
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CacheFile::Save()
{
	if (m_filename.empty())
		return false;


	FILE *f = fopen( m_filename.c_str(),"wt" );
	for (Entries::iterator it = m_entries.begin(); it != m_entries.end(); ++it)
	{
		const char *str = it->c_str();
		fputs( str,f );
	}
	fclose(f);
	m_bModified = false;
	return true;
}

//////////////////////////////////////////////////////////////////////////
inline bool IsHexNumberCharacter( const char c )
{
	return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F');
}

//////////////////////////////////////////////////////////////////////////
inline bool IsNameCharacter( const char c )
{
	return (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') || c=='@'  || c=='%' || c=='_';
}

int shGetHex(const char *buf)
{
	if (!buf)
		return 0;
	int i = 0;

	sscanf(buf, "%x", &i);

	return i;
}

//////////////////////////////////////////////////////////////////////////
bool CacheFile::CheckSyntax( const char *szLine,const char **sOutStr )
{
	assert(szLine);
	char *t = g_szLine;

	if (sOutStr)
		*sOutStr = 0;

	// e.g. Blurcloak@BlurCloakPS(%BUMP_MAP|%SPECULAR)(%_RT_FOG|%_RT_HDR_MODE|%_RT_BUMP)(0)(0)(0)(ps_2_0)

	const char *p=szLine;

	if (strlen(szLine) < 4)
		return false;

	if (szLine[0] != '<' || szLine[1] != '1' || szLine[2] != '>')
		return false;

	*t++ = *p++; // Copy <
	*t++ = *p++; // Copy 1
	*t++ = *p++; // Copy >

	// e.g. "Blurcloak@BlurCloakPS"
	while(IsNameCharacter(*p)) *t++ = *p++;

	// e.g. "(%BUMP_MAP|%SPECULAR)(%_RT_FOG|%_RT_HDR_MODE|%_RT_BUMP)"
	for(int i=0;i<2;++i)
	{
		if(*p!='(')	return false;					*t++ = *p++;
		while(true)
		{
			while(IsNameCharacter(*p))			*t++ = *p++;
			if(*p!='|')
				break;
			*t++ = *p++;
		}
		if(*p!=')')	return false;					*t++ = *p++;
	}

	// e.g. "(0)(0)(0)"
	for(int i=0;i<3;++i)
	{
		if(*p!='(')	return false;				*t++ = *p++;
		if (i == 0)
		{
			int n = shGetHex(p);
			while(IsHexNumberCharacter(*p))	p++;
			if (n)
				*t++ = '1';
			else
				*t++ = '0';
		}
		else
		{
			while(IsHexNumberCharacter(*p))	*t++ = *p++;
		}
		if(*p!=')')	return false;				*t++ = *p++;
	}

	// e.g. "(ps_2_0)"
	if(*p!='(')	return false;					*t++ = *p++;
	while(IsNameCharacter(*p))				*t++ = *p++;
	if(*p!=')')	return false;					*t++ = *p++;
	
	// Copy rest of the line.
	while (*p)
		*t++ = *p++;
	*t++ = '\0'; // end of line.

	if (sOutStr)
		*sOutStr = szLine;

	return true;
}


//////////////////////////////////////////////////////////////////////////
bool CacheFile::InsertLine( const char *szLine )
{
	const char *szCorrectedLine = 0;
	if(CheckSyntax(szLine,&szCorrectedLine))
	{
		if (szCorrectedLine)
			m_entries.insert(szCorrectedLine);
		return true;
	}
	else
	{
		if (strncmp(szLine,"// BadLine:",11) == 0)
			return false;

		assert(0);		// should not happen - seems client produced bad input file

		std::string str = std::string("// BadLine: ") + szLine;
		if (m_entries.find(str) == m_entries.end())
			m_entries.insert(str);
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CacheFile::Merge( CacheFile &cache )
{
	bool bModified = false;
	Entries::iterator srcit,trgit;
	for (srcit = cache.m_entries.begin(); srcit != cache.m_entries.end(); ++srcit)
	{
		const std::string &str = *srcit;
		if (m_entries.find(str) == m_entries.end())
		{
			if(InsertLine(str.c_str()))		// passing it as "const char *" causes a temp string creation - can be optimized
				bModified = true;
		}
	}
	if (bModified)
		m_bModified = true;
	return bModified;
}

//////////////////////////////////////////////////////////////////////////

CacheFile g_mainCombinations;


bool MergeAdditionaCombinations( const char *filespec,bool bDeleteMegedFiles )
{
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];

	char sMainFilename[_MAX_PATH];
	char sNewFilename[_MAX_PATH];
	char sFullPath[_MAX_PATH];
	char sSearchPath[_MAX_PATH];

	_splitpath(g_mainCombinations.m_filename.c_str(),NULL,NULL,sMainFilename,NULL );
	
	_splitpath(filespec,drive,dir,NULL,NULL );
	_makepath(sSearchPath,drive,dir,NULL,NULL );

	std::vector<std::string> filesToDelete;

	bool bAnyMerged = false;
	_finddata_t fd;
	intptr_t fhandle = _findfirst( filespec,&fd );
	if (fhandle != -1)
	{
		do
		{
			if (fd.attrib & _A_SUBDIR)
				continue;

			// Skip the main file, if it is also enumerated.
			_splitpath(fd.name,NULL,NULL,sNewFilename,NULL );
			if (stricmp(sNewFilename,sMainFilename) == 0)
				continue;

			_makepath( sFullPath,NULL,sSearchPath,fd.name,NULL );
			CacheFile cf;
			if (cf.Load( sFullPath ))
			{
				filesToDelete.push_back(sFullPath);
				if (g_mainCombinations.Merge( cf ))
					bAnyMerged = true;
			}
		}
		while (_findnext(fhandle,&fd) == 0);
		_findclose(fhandle);
	}

	if (bDeleteMegedFiles)
	{
		// Delete merged files.
		for (unsigned int i = 0; i < filesToDelete.size(); i++)
		{
			remove( filesToDelete[i].c_str() );
		}
	}
	
	return bAnyMerged;
}

//////////////////////////////////////////////////////////////////////////
void StayResident()
{
	char path_buffer[_MAX_PATH];
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];

	_fullpath( path_buffer,g_mainCombinations.m_filename.c_str(),_MAX_PATH );
	_splitpath( path_buffer,drive,dir,fname,ext );
	_makepath( path_buffer,drive,dir,NULL,NULL );

	int len = (int)strlen(path_buffer);
	if (len < 1)
		return;
	if (path_buffer[len-1] == '\\')
		path_buffer[len-1] = 0;

	std::vector<std::string> dirs;
	dirs.push_back( path_buffer );
	
	CFileChangeMonitor fm;
	fm.MonitorDirectories( dirs );

	while (true)
	{
		Sleep(5000);
		if (fm.HaveModifiedFiles())
		{
			g_mainCombinations.Reload();
			MergeAdditionaCombinations( m_sTargetFileSpec,true );
			if (g_mainCombinations.m_bModified)
				g_mainCombinations.Save();
		}
	}
}

//////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[])
{
	if (argc < 3)
	{
		printf( "sc_merge <filename> <mergefiles> [-wait]\n\tfilename - shaderlist.txt file path\
						\n\t\tmergefiles - Wildcard for file to merge into the main file (shaderlist*.txt)\
						\n\t\t-wait Makes program resident" );
		return 0;
	}

	for (int i = 0; i < argc; i++)
	{
		if (stricmp(argv[i],"-wait") == 0)
		{
			g_bStayResident = true;
		}
	}

	const char *filename = argv[1];
	if (!g_mainCombinations.Load(filename))
		return 1;

	_fullpath( m_sTargetFileSpec,argv[2],_MAX_PATH );
	MergeAdditionaCombinations( m_sTargetFileSpec,true );

	if (g_mainCombinations.m_bModified)
		g_mainCombinations.Save();

	if (g_bStayResident)
		StayResident();

/*
	CacheFile c,c1;

	c1.Load( "c:/temp/1.txt" );
	c.Load( "c:/temp/shaderlist.txt" );
	c.Merge( c1 );
	c.Save();
*/
	return 0;
}

