// DJS_Commander.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "../Common/Zockets/net.h"

#define				DJS_COMMANDER_VERSION 1.0f
//#define				DJS_COMMANDER_SERVERNAME "PC051"
//#define				DJS_COMMANDER_SERVERNAME "PC101"
#define				MIN_SHADER_IN_A_LIST	500

char g_DJS_CLIENT_SERVERNAME[256] = "DJS_SERVER";

extern char g_HostNameSuffix[32];

void Log( const char* szFormat,... )
{
	FILE *f;
	fopen_s( &f, "DJS_Commander.log", "at" );
	if( f )
	{
		char timestr[32];
		_strtime_s( timestr,sizeof(timestr) );

		char datestr[128];
		_strdate_s( datestr,sizeof(datestr) );

		va_list arglist;
		va_start(arglist, szFormat);
		char szBuf[1024];
		vsprintf_s( szBuf, 1024, szFormat, arglist);
		fprintf_s( f, "[%s][%s] %s\n",datestr,timestr,szBuf );
		fclose(f);

		printf( "%s\n",szBuf );
	}
}

//------------------------------------------------------------------------------------------------------------------
// Copy files and directories function
//------------------------------------------------------------------------------------------------------------------
int XCopy( const char* szSource, const char* szDestination, const bool bRecursive, const bool bKeepAttributes, const bool bCopyHiddenFile, const bool bOnlyNewFiles, const bool bOneFile, const char *szExclude )
{
	if( NULL == szSource || NULL == szDestination )
		return -1;

	char szCommand[1024];

	strcpy_s( szCommand, 1024, "xcopy ");
	strcat_s( szCommand, 1024, szSource );
	strcat_s( szCommand, 1024, " " );
	strcat_s( szCommand, 1024, szDestination );
	strcat_s( szCommand, 1024, " /Y /C /R" );
	if( !bOneFile )
		strcat_s( szCommand, 1024, " /I" );
	if( szExclude )
	{
		strcat_s( szCommand, 1024, " /exclude:" );
		strcat_s( szCommand, 1024, szExclude );
	}
	if( bRecursive )
		strcat_s( szCommand, 1024, " /E" );
	if( bKeepAttributes )
		strcat_s( szCommand, 1024, " /K" );

	if( bCopyHiddenFile )
		strcat_s( szCommand, 1024, " /H" );
	if( bOnlyNewFiles )
		strcat_s( szCommand, 1024, " /D" );

	Log( szCommand );

	return system( szCommand );
}

//------------------------------------------------------------------------------------------------------------------
// del function
//------------------------------------------------------------------------------------------------------------------
int XDel( const char* szFiles, const bool bRecursive, bool bDelDirectories )
{
	if( NULL == szFiles )
		return -1;

	char szCommand[1024];
	int nRes = -1;

  if (bDelDirectories)
  {
    strcpy_s( szCommand, 1024, "rmdir ");
    strcat_s( szCommand, 1024, szFiles );
    strcat_s( szCommand, 1024, " /S" );
    strcat_s( szCommand, 1024, " /Q" );
		Log( szCommand );
    nRes = system( szCommand );
  }
	else
	{
		strcpy_s( szCommand, 1024, "del ");
		strcat_s( szCommand, 1024, szFiles );
		if( bRecursive )
			strcat_s( szCommand, 1024, " /S" );
		strcat_s( szCommand, 1024, " /Q /F" );		//quiet mode, read-only file too...

		Log( szCommand );
		nRes = system( szCommand );
	}

  return nRes;
}

//////////////////////////////////////////////////////////////////////////
int RoboCopyMove( const char* szSource, const char* szDestination )
{
	if( NULL == szSource || NULL == szDestination )
		return -1;

	char szCommand[1024];

	strcpy_s( szCommand, 1024, "robocopy /MOVE /R:3 /E ");
	strcat_s( szCommand, 1024, szSource );
	strcat_s( szCommand, 1024, " " );
	strcat_s( szCommand, 1024, szDestination );
	
	Log( szCommand );

	int nResult = system( szCommand );
	int nError = (nResult >= 8) ? 1 : 0; // Filter out not important errors.
	if (nResult == -1)
		nError = nResult;
	if (nError != 0)
	{
		Log( "<Error in RoboCopy %d> %s ",nResult,szCommand );
	}

	return nError;
}

int RoboCopy( const char* szSource, const char* szDestination,const char *szExclude=0 )
{
	if( NULL == szSource || NULL == szDestination )
		return -1;

	char szCommand[1024];

	strcpy_s( szCommand, 1024, "robocopy ");
	strcat_s( szCommand, 1024, szSource );
	strcat_s( szCommand, 1024, " " );
	strcat_s( szCommand, 1024, szDestination );
	strcat_s( szCommand, 1024, " /MIR" ); // Mirror mode.
	strcat_s( szCommand, 1024, " /R:3" ); // Retry at max 10 times.
	strcat_s( szCommand, 1024, " /TEE" ); // Log to console
	strcat_s( szCommand, 1024, " /E" ); // recursive

	if (szExclude)
	{
		strcat_s( szCommand, 1024, " /XF " ); // Exclude some files
		strcat_s( szCommand, 1024, szExclude );
	}

	Log( szCommand );
	int nResult = system( szCommand );
	int nError = (nResult >= 8) ? 1 : 0; // Filter out not important errors.
	if (nResult == -1)
		nError = nResult;
	if (nError != 0)
	{
		Log( "<Error in RoboCopy %d> %s",nResult,szCommand );
	}
	return nError;
}

//////////////////////////////////////////////////////////////////////////
int MakeNTFSJunction( const char* szSourceDir, const char* szDestinationDir )
{
	if( NULL == szSourceDir || NULL == szDestinationDir )
		return -1;

	char szCommand[1024];

	strcpy_s( szCommand, 1024, "junction ");
	strcat_s( szCommand, 1024, szSourceDir );
	strcat_s( szCommand, 1024, " " );
	strcat_s( szCommand, 1024, szDestinationDir );

	Log( szCommand );

	return system( szCommand );
}

void MakeDir( const char *szDirectory )
{
	Log( "Create Directory: %s",szDirectory );
	CreateDirectory( szDirectory,NULL );
}

const char *stristr(const char *pszText, const char *pszSub)
{
 int	nLen = (int)strlen(pszSub);
 const char test = toupper(*pszSub) ;
 while (*pszText)
 {
	 if ( toupper(*pszText)==test )
	 {
		 if ( 0 == _strnicmp( pszText,pszSub,nLen ) )
			 return pszText;
	 }
	 pszText++;
 }
 return NULL;
}

static bool isDirectoryExists(char *szPath)
{
  DWORD dwAttr = GetFileAttributesA(szPath);
  if (dwAttr == INVALID_FILE_ATTRIBUTES)
    return false;
  else
    if (dwAttr == FILE_ATTRIBUTE_DIRECTORY)
      return true;
  return false;
}

//////////////////////////////////////////////////////////////////////////
bool EditorStillRunning()
{
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL,0);
	if( INVALID_HANDLE_VALUE == hSnapshot ) 
		return false;

	PROCESSENTRY32 PE;
	ZeroMemory(&PE, sizeof(PROCESSENTRY32) );
	PE.dwSize = sizeof( PROCESSENTRY32 );

	DWORD dwRet = Process32First(hSnapshot,&PE);
	while( dwRet )
	{		
		if( _stricmp( PE.szExeFile, "Editor.exe" ) == 0 )
		{
			CloseHandle(hSnapshot);
			Sleep(500);
			return true;
		}

		ZeroMemory( &PE,sizeof( PROCESSENTRY32 ) );
		PE.dwSize = sizeof( PROCESSENTRY32 );
		dwRet = Process32Next(hSnapshot,&PE);
	}

	CloseHandle(hSnapshot);
	return false;
}

void GenerateShaderPrecacheList( char* szDirectory )
{
	char szCommand[1024];
	std::vector<std::string> vBigShaderList;
	//std::vector<std::string> vShaderList;
	//std::vector<std::string>::iterator itShaderList;
	//std::vector<std::string>::iterator itShaderEnd;
	std::vector<std::string>::iterator itBigShaderList;
	std::vector<std::string>::iterator itBigShaderEnd;

	printf("COM   Loading the ShaderList.txt\n" );

  SHELLEXECUTEINFOA ExecInfo;

  ZeroMemory(&ExecInfo,sizeof(ExecInfo));
  ExecInfo.cbSize = sizeof(ExecInfo);
  ExecInfo.hwnd = NULL;

	FILE* f;
	sprintf_s( szCommand,1024, "%s\\ShaderList.txt", szDirectory );
	fopen_s( &f, szCommand, "rt" );
	if( NULL == f )
	{
		printf("%s not found.\n", szCommand );
		return;
	}

	char pBuffer[2048];
  char szFullPath[1024];
	char szMergePath[1024];

	while( fgets( pBuffer, 2048, f ) )
	{
		vBigShaderList.push_back( pBuffer );
/*
		char* pEndOfName = strchr( pBuffer, '(' );
		if( pEndOfName )
			*pEndOfName = 0;

		itShaderEnd = vShaderList.end();
		for( itShaderList = vShaderList.begin(); itShaderList < itShaderEnd; ++itShaderList )
		{
			if( 0 == _stricmp( pBuffer, itShaderList->c_str() ) )
				break;
		}

		if( itShaderList == itShaderEnd )
			vShaderList.push_back( pBuffer );
			*/
	}
	fclose(f);
	f = NULL;


	SetNetBusy(  true );
	printf("NET   Commander inited.  HostName: %s \n", Client_GetClientName());
	Log( "Connecting to server: %s",g_DJS_CLIENT_SERVERNAME );
	if( false == NetInit( g_DJS_CLIENT_SERVERNAME, true ) )
	{
		Log("NET>  Can't connect to the '%s' server! Terminate the program. <<<<\n", g_DJS_CLIENT_SERVERNAME );
		return;
	}
	Client_SendStatusToServer("REQUESTSENDING",true);

	//////////////////////////////////////////////////////////////////////////
	// Prepare directories.
	//////////////////////////////////////////////////////////////////////////
	sprintf_s(szFullPath, 1024, "%s\\Temp", szDirectory );
	XDel( szFullPath,true,true );
	MakeDir( szFullPath );
	NetFrame();
	//////////////////////////////////////////////////////////////////////////
	sprintf_s(szFullPath, 1024, "%s\\ShaderCache", szDirectory );
	XDel( szFullPath,true,true );
	MakeDir( szFullPath );
	NetFrame();
	//////////////////////////////////////////////////////////////////////////
	sprintf_s(szMergePath, 1024, "%s\\SPC_Merged", szDirectory );
	XDel( szMergePath,true,true );
	MakeDir( szMergePath );
	//////////////////////////////////////////////////////////////////////////
	Sleep(1000);

	NetFrame();
	NetFrame();

	// Previous operations can take too long, and server can drop us, so make sure we are still connected.
	if (!Client_IsLoggedIn())
		NetInit( g_DJS_CLIENT_SERVERNAME, true );

	Client_SendStatusToServer("REQUESTSENDING",true);

	f = NULL;
	int nNumberOfShaderInTheList = 0;
	char szFName[MAX_PATH];
	int nID = 0;
	//generate shaderlist / shader 

	itBigShaderEnd = vBigShaderList.end();

	bool bFirstTimeEntry = true;

  int nFirstMerge = 1;
	for( itBigShaderList = vBigShaderList.begin(); itBigShaderList != itBigShaderEnd; ++itBigShaderList )
	{
		if (nNumberOfShaderInTheList > MIN_SHADER_IN_A_LIST || bFirstTimeEntry)
		{
			bFirstTimeEntry = false;
			if( f )
			{
				fclose(f);
				sprintf_s(szCommand, 1024, "DJS_ShaderPrecacheJobServer %s %03d", szDirectory, nID );
				Log( "Send Command to server: %s",szCommand );
				Client_RequestCommand(szCommand, 0);
				Client_SendStatusToServer("REQUESTSENDING",true);
				NetFrame();
				Sleep(300);// Let server some time to process command.
			}
			nID++;
			sprintf_s( szFName, MAX_PATH, "%s\\Temp\\ShaderList_%03d.txt", szDirectory, nID );
			Log( "Save File %s",szFName );
			fopen_s( &f, szFName, "wt" );
			if( NULL == f )
			{
				Log( "Failed to open file %s",szFName );
				continue;
			}
			nNumberOfShaderInTheList = 0;
		}

		fputs( itBigShaderList->c_str(), f );
		++nNumberOfShaderInTheList;
	}

	if( f )
	{
		fclose(f);

		sprintf_s(szCommand, 1024, "DJS_ShaderPrecacheJobServer %s %03d", szDirectory, nID );
		Log( "Send Command to server: %s",szCommand );
		Client_RequestCommand(szCommand, 0);
		Client_SendStatusToServer("REQUESTSENDING",true);
		NetFrame();
	}

	Log( "Send %d commands to server",nID );

	// Wait 10 seconds.
	for (int i = 0; i < 10; i++)
	{
		Sleep(1000);
		NetFrame();
	}

  int nLastMerge = nID;
  {
    int i = 0;
    char szDst[1024];
		ServerStatus serverStatus;
    while (true)
    {
			Sleep(1000);
			NetFrame();

			if (Client_GetServerStatus(serverStatus))
			{
				if (serverStatus.nJobsLeft == 0)
				{
					Log( "Server have no jobs left" );
					break;
				}
			}
    }
    Sleep(5000);

		//////////////////////////////////////////////////////////////////////////
		Log( "Copy Latest Build for merging" );
		//////////////////////////////////////////////////////////////////////////

    // Copy build
    sprintf_s( szFullPath, 1024, "%s\\SPC", szDirectory );
    if( -1 == XCopy( szFullPath, "SPC\\SPC_Merged", true, true, true, true, false, NULL ) )
      return;

    {
			char szSrc[1024];

			sprintf_s(szSrc, 1024, "SPC\\Temp",szDirectory );
      sprintf_s(szDst, 1024, "SPC\\SPC_Merged\\game\\shaders\\MergeCache",szMergePath);
			if (RoboCopyMove(szSrc,szDst) != 0)
				return;
      //if (-1 == MakeNTFSJunction( szDst,szSrc ))
        //return;
    }
		sprintf_s(szDst, 1024, "%s\\game\\shaders\\cache\\*.*",szMergePath);
		XDel(szDst, true, false);

		//////////////////////////////////////////////////////////////////////////
		Log( "Starting Shaders Merging" );
		//////////////////////////////////////////////////////////////////////////

		/*
    //run the editor for merging Job
		
		sprintf_s( szFullPath, 1024, "%s\\bin32\\editor.exe", szMergePath );
    ExecInfo.lpFile = "c:\\DJS_CLIENT\\SPC\\bin32\\editor.exe";
    ExecInfo.lpParameters = "/MergeShaders";
    ExecInfo.lpDirectory ="c:\\DJS_CLIENT\\SPC\\bin32";
    ExecInfo.nShow = SW_HIDE;
    ExecInfo.hInstApp = 0;
    ExecInfo.fMask = SEE_MASK_CONNECTNETDRV | SEE_MASK_NOCLOSEPROCESS;

    if( false == ShellExecuteExA( &ExecInfo ) )
		{
			Log( "Failed to execute Editor.exe to Merge Shaders" );
      return;
		}
		
		//wait the editor
		while( EditorStillRunning() ) {}

    CloseHandle( ExecInfo.hProcess );
		*/

		STARTUPINFOA sinfo;
		PROCESS_INFORMATION procInfo;
		memset( &procInfo,0,sizeof(procInfo) );
		memset( &sinfo,0,sizeof(sinfo) );

		char sCommandLine[1024];
		sprintf_s( sCommandLine, 1024, "SPC\\SPC_Merged\\bin32\\editor.exe /MergeShaders", szMergePath );
		Log( "%s",sCommandLine );

		int nFlags = 0;
		if (!CreateProcessA( NULL,sCommandLine,NULL,NULL,TRUE,nFlags,NULL,NULL,&sinfo,&procInfo ))
		{
			Log( "<ERROR> Failed to create Editor process" );
			return;
		}

		//////////////////////////////////////////////////////////////////////////
		// Wait for editor process to finish.
		//////////////////////////////////////////////////////////////////////////
		DWORD dwExitCode = 0;
		while (true)
		{
			GetExitCodeProcess( procInfo.hProcess,&dwExitCode );
			if (dwExitCode == STILL_ACTIVE)
			{
				Sleep(500);
			}
			else
			{
				break;
			}
		}
		CloseHandle(procInfo.hProcess);

		//////////////////////////////////////////////////////////////////////////
		Log( "Shaders Merging done, Starting final copy" );
		//////////////////////////////////////////////////////////////////////////

		char szMergedShadersPath[1024];
		sprintf_s( szFullPath, 1024, "%s\\ShaderCache", szDirectory );
		sprintf_s( szMergedShadersPath, 1024, "%s\\game\\shaders\\cache\\*.*",szMergePath );
		MakeDir(szFullPath);
		XCopy( szMergedShadersPath,szFullPath, true, true, true, true, false, NULL );
  }

	printf("NET   Commander shutdown.\n");
	NetShutdown();
}

void GenerateRAM( const char* szLevelName, const char* szUniqueDirectoryName, int nPriority )
{
	char szText[1024];
	SHELLEXECUTEINFOA ExecInfo;

	if( NULL == szLevelName || NULL == szUniqueDirectoryName )
		return;

	//del the old files
	XDel( "..\\*.DM", false, false );
	XDel( "..\\Octree.dat", false, false );

	//run the editor to create the files
	ZeroMemory(&ExecInfo,sizeof(ExecInfo));
	ExecInfo.cbSize = sizeof(ExecInfo);
	ExecInfo.hwnd = NULL;
	ExecInfo.lpFile = "editor.exe";
	sprintf_s( szText, 1024, "%s /ExportLM", szLevelName );
	ExecInfo.lpParameters = szText;
	ExecInfo.lpDirectory ="";
	ExecInfo.nShow = SW_HIDE;
	ExecInfo.hInstApp = 0;
	ExecInfo.fMask = SEE_MASK_CONNECTNETDRV | SEE_MASK_NOCLOSEPROCESS;

	if( false == ShellExecuteExA( &ExecInfo ) )
		return;

	//wait the editor
	while( EditorStillRunning() ) {}

	//copy the datas to the DJS Server
	sprintf_s( szText, 1024, "\\\\DJS_SERVER\\DJS\\RAM\\%s\\*.*", szUniqueDirectoryName );
	XCopy( "..\\*.DM", szText, false, true, true, false, true, NULL );
	XCopy( "..\\Octree.*", szText, false, true, true, false, true, NULL );

	int nNumberOfDM = 100;

	SetNetBusy(  true );
	printf("NET   Commander inited.  HostName: %s \n", Client_GetClientName());
	if( false == NetInit( g_DJS_CLIENT_SERVERNAME, true ) )
	{
		printf("NET>  Can't connect to the '%s' server! Terminate the program. <<<<\n", g_DJS_CLIENT_SERVERNAME );
		return;
	}
	Client_SendStatusToServer("REQUESTSENDING",true);


	//send the jobs to the server
	for( int i = 0; i < nNumberOfDM; ++i )
	{
		sprintf_s(szText, 1024, "DJS_RAMJobServer_v2 \"%s\" %d", szUniqueDirectoryName, i );
		Client_RequestCommand(szText, nPriority);
		Client_SendStatusToServer("REQUESTSENDING",true);
		Sleep(100);
	}

	printf("NET   Commander shutdown.\n");
	NetShutdown();
}

void GenerateTest( const char* szUniqueDirectoryName, int nJobNum)
{
	if( NULL == szUniqueDirectoryName )
		return;

	SetNetBusy(  true );
	printf("NET   Commander inited.  HostName: %s \n", Client_GetClientName());
	if( false == NetInit( g_DJS_CLIENT_SERVERNAME, true ) )
	{
		printf("NET>  Can't connect to the '%s' server! Terminate the program. <<<<\n", g_DJS_CLIENT_SERVERNAME );
		return;
	}
	Client_SendStatusToServer("REQUESTSENDING",true);

	char szText[1024];
	for( int i = 0; i < nJobNum; ++i )
	{
//		sprintf_s(szText, 1024, "DJS_ExecJobServer NET STOP DJS_Client" );
		sprintf_s(szText, 1024, "DJS_RAMJobServer_v2 \"%s\" %d", szUniqueDirectoryName, i );
		Client_RequestCommand(szText, 0);
		Client_SendStatusToServer("REQUESTSENDING",true);
		Sleep(100);
	}

	printf("NET   Commander shutdown.\n");
	NetShutdown();
}

//////////////////////////////////////////////////////////////////////////
#include "../Common/SimpleIni.h"
void ReadIniFile()
{
	CSimpleIni ini(false,false);
	if (0 == ini.LoadFile("DJS.ini"))
	{
		const char *pszVal;
		bool bHasMulti;
		pszVal = ini.GetValue( "", "server", 0, &bHasMulti );
		if (pszVal)
		{
			strcpy_s( g_DJS_CLIENT_SERVERNAME,sizeof(g_DJS_CLIENT_SERVERNAME),pszVal );
		}
		/*
		pszVal = ini.GetValue( "", "server_directory", 0, &bHasMulti );
		if (pszVal)
		{
			strcpy_s( g_WINDOWS_JOBSERVER_DIRECTORY,sizeof(g_WINDOWS_JOBSERVER_DIRECTORY),pszVal );
		}
		*/
	}
}


int main(int argc, char* argv[])
{
	ReadIniFile();
	strcpy_s( g_HostNameSuffix," (Commander)" );

	printf("###############################################################################\n");
	printf("### Crytek's distributed job service - Commander Version %2.2f               ###\n", DJS_COMMANDER_VERSION );
	printf("###############################################################################\n\n");

	if( argc < 2 )
	{
		printf("Usage: DJS_Commander RAM levelname uniquename\n");
		printf("Usage: DJS_Commander SP Path_Of_the_ShaderList.txt\n");
		NetShutdown();
		return -1;
	}

	if( 0 == _stricmp( argv[1], "RAM" ) )
	{
		if( argc < 4 )
		{
			printf("Usage: DJS_Commander RAM levelname uniquename\n");
			NetShutdown();
			return -1;
		}

		//////////////////////////////////////////////////////////////////////////
		Log( "RAM generation started" );
		//////////////////////////////////////////////////////////////////////////

		GenerateRAM( argv[2], argv[3], 0 );

		//////////////////////////////////////////////////////////////////////////
		Log( "RAM generation finished" );
		//////////////////////////////////////////////////////////////////////////
	}
	else
		if( 0 == _stricmp( argv[1], "SP" ) )
		{
			if( argc < 3 )
			{
				printf("Usage: DJS_Commander SP Path_Of_the_ShaderList.txt\n");
				NetShutdown();
				return -1;
			}

			Log( "-------------------------------------------------------------------");
			Log( "Distributed Shader Precaching started");

			GenerateShaderPrecacheList(argv[2]);

			Log( "Distributed Shader Precaching finished" );
		}
		else
			if( 0 == _stricmp( argv[1], "TEST" ) )
			{
				if( argc < 4 )
				{
					printf("Usage: DJS_Commander TEST directory jobnumber\n");
					NetShutdown();
					return -1;
				}

				GenerateTest( argv[2], atoi( argv[3]) );
			}

	return 1;
}

