//---This Script will get the latest build, precache shaders and copy them to a Server---
//---Written by Denis Barth(Denis@crytek.de)

// If debug is true then some important steps won't be executed
var DEBUG = false;

// Create Shell objects.
var wshShell = WScript.CreateObject("WScript.Shell");
var FSO = WScript.CreateObject("Scripting.FileSystemObject");
var wshNetwork = WScript.CreateObject("WScript.Network");

var forReading = 1, forWriting = 2, forAppending = 8;
var tristateUseDefault = -2, tristateTrue = -1, tristateFalse = 0;

// Folder where the buildscript is located  
var buildScriptLocation = wshShell.CurrentDirectory + "\\";

// Create a Log file
var logFile = buildScriptLocation  + "PrecacheShaders.log";
var openedLogFile = FSO.CreateTextFile( logFile ,true );
var numLogs = 0;

// Defintion of used variables
var copyDestDir = "D:\\PrecacheShadersBuild";
var localShaderDir  = copyDestDir + "\\Game\\Shaders";
var shadersPak  = copyDestDir + "\\Game\\Shaders.pak";
var d3d9ShadersDir = localShaderDir + "\\Cache\\d3d9";
// Old paths
//var cgpShadersDir = localShaderDir + "\\Cache\\CGPShaders"; 
//var cgvShadersDir = localShaderDir + "\\Cache\\CGVShaders";
var editorExe = copyDestDir + "\\Bin32\\Editor.exe";
var isNewestBuildFile = copyDestDir + "\\BuildName.txt";
var buildServerDir = "\\\\storage\\Builds\\procedurally_generated_builds"; 
var serverMapDir = "\\\\Mastercd\\ShaderCache";
// Regular Expression for check wheter the drive is already mapped, must be the same as "serverMapDir"
var regExpNetworkPath = /\\\\Mastercd\\ShaderCache/i;
var serverMapDrive = "M:";
var scbnDest = serverMapDrive;
var a_networkDrives = null;
var nightlyBuildPath = "";
var newestBuildSourceDir = "";
var newestBuildName = "";
var startCopying = true;
var result = 1;
var reBuild = false;

// vars for mailing
var mailServer= "mail2.INTERN.CRYTEK.DE";

if(DEBUG)
{
var a_failRecipient = new Array("Denis@Crytek.de");
var a_sucRecipient = new Array("Denis@Crytek.de");
}
else
{
	var a_failRecipient = new Array("FP_Build@crytek.de","Denis@Crytek.de");
	var a_sucRecipient = new Array("FP_Build@crytek.de");
}

var a_emptyFile = new Array();
var a_logFiles = new Array(logFile);

// Catch option Rebuild
if (WScript.Arguments.Named.Exists("rebuild"))
{
	reBuild = true;
}



///////////////////////////////////////////////////////////////////////////////
// Main.
///////////////////////////////////////////////////////////////////////////////

Log("Automated shader precaching system");
Log("*******************************************");
Log("For bugs and comments: Denis@Crytek.de\r\n\r\n");

if(DEBUG)
	Log("\r\n---You are in DEBUG mode!!!!---\r\n");


var startScriptTime = new Date();
Log("Started at " + startScriptTime.toString());

// Delete build dir if rebuild
if(reBuild)
{
	if(!DEBUG)
		Cleanup();
		
	nightlyBuildPath = FindTodaysNightlyBuild(buildServerDir);
}

// Get the Name of the latest build and create a complete link to it
Log("\r\n--Figuring out what is the newest build and whether it's already on the hard drive--");
newestBuildName = FindNewestBuild(buildServerDir);
newestBuildSourceDir = buildServerDir + "\\" + newestBuildName;	

// Figure out whether the newest build is already on the hard drive
startCopying = IsNewestBuild();

if(startCopying==true)
{
	Log("\r\n--Copying of the build--");
	if(!DEBUG)
	{
		if(reBuild)
		{
			Copy( newestBuildSourceDir, copyDestDir +"\\", true , true , true , "" );	
			
			// Delte the local shaders folder
			fsoDelete(d3d9ShadersDir, true, true );	
		}
		else
		{
			// Copy the build from the server
			Copy ( newestBuildSourceDir + "\\Bin32", copyDestDir +"\\Bin32\\", true , true , true , "" );	
			Copy ( newestBuildSourceDir + "\\Game\\shaders.pak", copyDestDir +"\\Game\\", false , true , true , "" );	
		}
		// Update flag with latest build name
		IsNewestBuild(newestBuildName);
	}
}
else if(startCopying==false)
{
	Log("Newest build is already on the hard drive, no need for update");
}

Log("\r\n--Drive mapping--")
a_networkDrives = wshNetwork.EnumNetworkDrives();

if(FSO.DriveExists(serverMapDrive))
{
	var regExpDrive = new RegExp(serverMapDrive,"i");
	
	for(i = 0; i < a_networkDrives.length; i += 2)
	{
		// If the right drive is mapped wiht another networkpath
		if(a_networkDrives.Item(i).search(regExpDrive) != "-1" && a_networkDrives.Item(i + 1).search(regExpNetworkPath) == "-1")
		{
			Log(serverMapDrive + " drive is already mapped with another network path. Removing it...");
			wshNetwork.RemoveNetworkDrive (serverMapDrive);
			Log("Mapping " + serverMapDrive + " to " + serverMapDir);
			wshNetwork.MapNetworkDrive( serverMapDrive , serverMapDir);
			break;
		}
		else
		{
			Log("Networkdrive is already mapped.");
			break;
		}
	}
}
else
{
	Log(serverMapDrive + " drive does not exists. Mapping " + serverMapDir + " to it");
	wshNetwork.MapNetworkDrive( serverMapDrive , serverMapDir);
}

Log("\r\n--Get latest shaderlist and compile shaders--");
// Copy the combinations.txt from the server to hard drive
Copy ( serverMapDrive + "\\ShaderList.txt"  , localShaderDir +"\\Cache\\", false , true , true , "" );	

var startPrecachingTime = new Date();
// Start Precaching Shaders
Log("Start of Precaching Shaders");
if(!DEBUG)
	result = wshShell.Run( editorExe + " /PrecacheShaderList",1,true);
else
	result = 0;
	
var endPrecachingTime = new Date();

// If precaching was successful, copy compiled shaders to server
if(result==0)
{
	var updatedShadersInBuild = true;
	var clearedServerShaders = true;
	var updatedShadersOnServer = true;
	
	Log("NO Error while running Editor to Precache Shaders");
	Log( "\r\n--Starting to copy the compiled shaders back to server.--");
	
	if(reBuild)
	{
		var newestBuildSCPath = newestBuildSourceDir + "\\Game\\Shaders\\Cache";
		
		if(!fsoCopy(d3d9ShadersDir, newestBuildSCPath + "\\d3d9" , true, true ))
			updatedShadersInBuild = false;
			
		if(!fsoCopy(isNewestBuildFile, newestBuildSourceDir + "\\ShaderCacheBuildNumber.txt" , false, true ))
			updatedShadersInBuild = false;
			
		
		// Delte the server shaders folder;
		if(!fsoDelete(serverMapDrive + "\\d3d9" , true, true ))
			clearedServerShaders = false;
			

		// Create them again
		if(!fsoCreateFolder( serverMapDrive + "\\d3d9" ))
			clearedServerShaders = false;
	}

	if(!fsoCopy(shadersPak, scbnDest + "\\Shaders.pak" , false, true ))
		updatedShadersOnServer = false;
		
	if(!fsoCopy(d3d9ShadersDir, serverMapDrive + "\\d3d9" , true, true ))
		updatedShadersOnServer = false;
		
	if(!fsoCopy(isNewestBuildFile, scbnDest + "\\ShaderCacheBuildNumber.txt" , false, true ))
		updatedShadersOnServer = false;
		
	var endScriptTime = new Date();
	
	SendSuccessMail();
}
else
{
	ErrorExit("Error while running Editor to Precache Shaders", a_failRecipient , a_logFiles );
}

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


///////////////////////////////////////////////////////////////////////////////
// Functions.
///////////////////////////////////////////////////////////////////////////////

//+++++++++++++++++++++++++Log,Create and catch error+++++++++++++++++++++++++
function fsoCreateFolder(path)
{
	if(!DEBUG)
	{
		Log("Creating: " + path );		
		try
		{	
			FSO.CreateFolder( path );
			return true;
		}
		catch(e)
		{
			//SendMail( a_failRecipient,"Error: Couldn't create: " + path + " because of: " + e.description,"Automated shader compilation warning!",a_emptyFile	,1, false );
			Log("Error: Couldn't create: " + path + " because of: " + e.description);
			return false;
		}
	}
	else
	{
		Log("(Creatings_test_logging: " + path );	
		return true;
	}	
}

//+++++++++++++++++++++++++Log,Delete and catch error+++++++++++++++++++++++++
function fsoDelete(path , isDir, overwrite )
{
	if(!DEBUG)
	{
		Log("Deleting: " + path );		
		try
		{	
			if(isDir)
				FSO.DeleteFolder( path, overwrite );
			else
				FSO.DeleteFile( path, overwrite );
				
			return true;
		}
		catch(e)
		{
			//SendMail( a_failRecipient,"Error: Couldn't delete: " + path + " because of: " + e.description,"Automated shader compilation warning!",a_emptyFile	,1,false );
			Log("Error: Couldn't delete: " + path + " because of: " + e.description);
			return false;
		}
	}
	else
	{
		Log("(Deleting_test_logging: " + path );
		return true;
	}
}

//+++++++++++++++++++++++++Log,Copy and catch error+++++++++++++++++++++++++
function fsoCopy(source, destination , isDir, overwrite )
{
	if(!DEBUG)
	{
		Log("Copying: " + source + " to: " + destination );		
		try
		{	
			if(isDir)
				FSO.CopyFolder( source, destination, overwrite );
			else
				FSO.CopyFile( source, destination, overwrite );
				
			return true;
		}
		catch(e)
		{
			//SendMail( a_failRecipient,"Error: Couldn't copy: " + source + " to: " + destination + " because of: " + e.description,"Automated shader compilation warning!", a_emptyFile, 1, false );
			Log("Error: Couldn't copy: " + source + " to: " + destination + " because of: " + e.description);
			return false;
		}
	}
	else
	{
		Log("(Copying_test_logging: " + source + " to: " + destination );	
		return true;
	}
}

//+++++++++++++++++++++++++Copying folders and files with arguments+++++++++++++++++++++++++
function Copy(Source,Destination,Recrusive,KeepAttributes,CopyHiddenFiles,Exclude)
{
	if(!DEBUG)
	{
		var cmdLine = "xcopy ";
		cmdLine += Source + " " + Destination;
		cmdLine +=" /I /Y /C /R";
		if (Exclude != "") cmdLine +=" /exclude:"+Exclude;
		if (Recrusive) cmdLine +=" /E";
		if (KeepAttributes) cmdLine +=" /K";
		if (CopyHiddenFiles) cmdLine +=" /H";
		Log( "Start copying Folder "+Source+" to "+Destination);
		Log("with command line: " + cmdLine);
		var result = wshShell.Run( cmdLine,1,true );
		if(result != 0)
			ErrorExit( "Error: Couldn't copy from " + Source + " to " + Destination , a_failRecipient , a_logFiles );
	}
	else
	{
		Log("(Copying_test_logging: " + Source + " to: " + Destination );	
		return true;
	}
}

//+++++++++++++++++++++++++Clean up the test build directory+++++++++++++++++++++++++
function Cleanup()
{
	Log("\r\n--Started a cleanup of the local build files. The local build will be deleted and a new one will be copied over again.--");
		fsoDelete(copyDestDir, true, true );	
		fsoCreateFolder(copyDestDir);
}

//+++++++++++++++++++++++++Handling with newest build is already on the hard drive informations+++++++++++++++++++++++++
function IsNewestBuild(saveBuildName)
{
	if(saveBuildName)
	{
		Log("Writing copied build name to file: "+isNewestBuildFile);
		FSO.CreateTextFile( isNewestBuildFile,true );  
		var LoadedisNewestBuildFile = FSO.GetFile( isNewestBuildFile );   
		var Stream = LoadedisNewestBuildFile.OpenAsTextStream( forWriting,tristateUseDefault );
		Stream.Write(newestBuildName);
		Stream.Close();
	}
	else if(!saveBuildName)
	{
		if (FSO.FileExists( isNewestBuildFile ))
		{
			try
			{
				Log("Loading isNewestBuildFile file from: " + isNewestBuildFile);
				var loadedIsNewestBuildFile = FSO.OpenTextFile( isNewestBuildFile , forReading);
				var Output = loadedIsNewestBuildFile.ReadAll();
				loadedIsNewestBuildFile.Close();
				if( Output == newestBuildName )
				return(false);
				else
				return(true);				
			}
			catch(e)
			{
				Log("WARNING: This problem \"" + e.description + "\" occured during accessing the file \"" + isNewestBuildFile + "\"");
				return(true);
			}
			
		}
		else
		{
			Log("WARNING:Could not find flag file which says whether copying of build is needed from:"+isNewestBuildFile);
			return(true);
		}
	}
	else
		Log("ERROR: Wrong parameter was given to function IsNewestBuild.");
		return(true);	
}

//+++++++++++++++++++++++++Figure out what is the name of the newest build+++++++++++++++++++++++++
function FindNewestBuild(dir)
{
	Log( "Start figuring out what is the newest bulid");
	
	var regExp = /game..\((\d+)\)_\d+_\d+_*/i;
	
	// Create the folderobject and enumerator with the subfolders
	var oFolder = FSO.GetFolder(dir);
  var enumSubFolders = new Enumerator(oFolder.SubFolders);
  	
	var aFinalBuildPaths = new Array();
	
	// Fill the array with the matching builds
  for (; !enumSubFolders.atEnd(); enumSubFolders.moveNext())
  {
     var tempBuildPath = enumSubFolders.item();
     tempBuildPath += "";
     if(tempBuildPath.search(regExp) != "-1")
     {
     	var tempArray = new Array(tempBuildPath, parseInt(RegExp.$1));
     	aFinalBuildPaths.push(tempArray);
     }
  }
  
  // Sort the list ascending to the buildnumbers
	var done = false;
	do
  {
  	done = true;
    for (var i=0; i<aFinalBuildPaths.length-1; i++)
    {
      if(aFinalBuildPaths[i][1] < aFinalBuildPaths[i+1][1])
      {      		
        var tmp = aFinalBuildPaths[i];
        aFinalBuildPaths[i] = aFinalBuildPaths[i+1];
        aFinalBuildPaths[i+1] = tmp;
        done = false;
      }
    }
   }while (!done);
   
 regExp = /(game..\(\d+\)_\d+_\d+.*)/i;
 if(aFinalBuildPaths[0][0].search(regExp) != "-1")
 {
 	// return the name of the newest build
 	return(RegExp.$1);
 }
 else
 {
 	Log("Could not find a build");
 	WScript.Quit(1);
 }
}

//+++++++++++++++++++++++++Figure out what is the name of the newest build+++++++++++++++++++++++++
function FindTodaysNightlyBuild ( searchingDir) 
{
	// get all subfolders
	var buildsFolder = FSO.GetFolder(searchingDir);
  var buildsSubFolders = new Enumerator(buildsFolder.SubFolders);
  
  // Get the date and extract the day,month
  var curDate = new Date();
  var dayOfMonth = curDate.getDate();
  var monthOfYear = curDate.getMonth() + 1;
  
  // Add a zero in from of the number if it has only one digit
  if(monthOfYear <= 9)
  {
  	var temp = "0" + monthOfYear;
  	monthOfYear = temp;
  }
  
  // Add a zero in from of the number if it has only one digit
  if(dayOfMonth <= 9)
  {
  	var temp = "0" + dayOfMonth;
  	dayOfMonth = temp;
  }
  	
  // Generate the Date-ID for today
  var idForToday = monthOfYear + "_" + dayOfMonth;
  
  var finalBuildPath = "";
  // go through all the subfolders
  for (; !buildsSubFolders.atEnd(); buildsSubFolders.moveNext())
  {
  	// Copy the current path into a temporary variable
     var buildPath = buildsSubFolders.item();
     
   	 // Convert the variable to a string
     buildPath += "";
     
     // Check if the directoy is a nightly build
     if(buildPath.indexOf(idForToday,buildPath.indexOf( "\)_" )) != "-1" && buildPath.search("FastBuild") == "-1" && buildPath.search("Copying") == "-1")
     {
     	// Save the found path
     	finalBuildPath = buildPath;  	
     	break;
     }
   }
   // Clear the memory
   buildsFolder = null;
   buildsSubFolders = null;
   
   // return a string
   	return finalBuildPath;
}  


//+++++++++++++++++++++++++Function for writing to the log+++++++++++++++++++++++++
function Log(str) 
{
	openedLogFile.WriteLine( str );
	WScript.StdOut.WriteLine( str );
	//WScript.Echo(str);

	numLogs = numLogs + 1;
}

//+++++++++++++++++++++++++Logs an error and send a mail if something goes wrong+++++++++++++++++++++++++
function ErrorExit( errStr , a_errRecipients , a_errLogs )
{
	var errorString = errStr;
	Log ( errorString );
	wshShell.LogEvent( 1,errorString );
	SendMail( a_errRecipients,errorString,"Shader precaching failed!",a_errLogs,1 , true );
	WScript.Quit(1);
}

function SendSuccessMail()
{
	var regExp = new RegExp(newestBuildName,"i");
	
	var a_mailRecipient = a_failRecipient;
	var mailMsg = "";
	//var mailSub = "Automated update of shader cache on the server";
	var mailSub = "Automated update of shader cache ";
	
//	if(reBuild)
//		mailSub += " and in build " + newestBuildName;
		
	if(updatedShadersInBuild && updatedShadersOnServer)
	{
		mailSub += " was successful";
		a_mailRecipient = a_sucRecipient;
	}
	else if(!updatedShadersInBuild && !updatedShadersOnServer)
	{
		mailSub += " failed";
	}
	else
	{
		mailSub += " was partially successful";
	}
	
	if(nightlyBuildPath == "" && reBuild)
	{
		Log("Could not find any nightly build which was made today");	
		mailMsg = "WARNING! Could not find any nightly build which was made today!\r\n Because of this the build " + newestBuildName + " will be updated.";
		a_mailRecipient.push("Denis@Crytek.de");
	}
	else
	{
		mailMsg = mailSub;
	}
	
	if(!updatedShadersInBuild)
		 mailMsg += "\n\n The update of the shaders in the build failed";
	
	if(!clearedServerShaders)
		mailMsg += "\n\n Could not cleanup the sever shader directory";
	
	if(!updatedShadersOnServer)
		mailMsg += "\n\n The update of the shaders on the server failed";
		
	var tmpShaderCompilingTimeMsg = "Shader compiling time: " + GetTimeDiff(startPrecachingTime,endPrecachingTime);
	var tmpScriptTimeMsg = "Script running time : " + GetTimeDiff(startScriptTime,endScriptTime);
	
	Log(tmpShaderCompilingTimeMsg);
	Log(tmpScriptTimeMsg);
	
	mailMsg += "\r\n\r\n" + tmpShaderCompilingTimeMsg + "\r\n\r\n" + tmpScriptTimeMsg;
	
	SendMail( a_mailRecipient, mailMsg, mailSub , a_logFiles, 1, true );
}

//+++++++++++++++++++++++++Get difference between Time1 and Time2+++++++++++++++++++++++++
function GetTimeDiff( t0,t1 )
{
	var timeDiff = t1.getTime() - t0.getTime();
	var secMilli = 1000;
	var minMilli = 1000 * 60;
	var hrMilli = minMilli * 60;
	var dyMilli = hrMilli * 24;
	
	var t,h,m,s;
	t = timeDiff;
	h = Math.floor( t / hrMilli);
	t = timeDiff % hrMilli;
	m = Math.floor( t / minMilli );
	t = t % minMilli;
	s = Math.floor( t / secMilli );
	var str = ""+h+"h:"+m+"m:"+s+"s";
	return str;
}


//+++++++++++++++++++++++++Mainfunction for sending mails+++++++++++++++++++++++++
function SendMail( recipient,msg,subject,attachmentsArray,importance, closeLogFile)
{
	Log( "Sending Mail to "+recipient+", With subject: "+subject );	
	
	if(closeLogFile)
		openedLogFile.Close();

	var cdoConfig = new ActiveXObject("CDO.Configuration");
	cdoConfig.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2; //cdoSendUsingPort
	cdoConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1; //cdoBasic
	cdoConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = mailServer;
	cdoConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25;
	cdoConfig.Fields.Update();

	var cdoMessage = new ActiveXObject( "CDO.Message" );
	cdoMessage.Configuration = cdoConfig;

	cdoMessage.Subject = subject;
	cdoMessage.From = "Build@crytek.de";
	cdoMessage.To = recipient;
	cdoMessage.TextBody = msg;

	var i;
	for (i = 0; i < attachmentsArray.length; i++)
	{
		if (FSO.FileExists( attachmentsArray[i] ))
		{
			cdoMessage.AddAttachment( attachmentsArray[i] );
		}
	}

	cdoMessage.Send();
	cdoConfig = null;
	cdoMessage = null;
}

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