// -------------------------- Metadata Generation Functions-/-------------------------------------
// For diff build system
// See library interface functions at end of file for docs

// Notes:
// Could get rid of cygPath conversions in recursion by using Folder.name property

var checkTool = "check";

//+++++++++++++++++++++++++ Convert slashes in path for UNIX-style format +++++++++++++++++++++++
function GetPathDOS2UNIX(path)
{
	return path.replace(/\\/g,"/");
}


//+++++++++++++++++++++++++ Write to console +++++++++++++++++++++++
function Verbiage(message)
{
	WScript.Echo(message);
}



//+++++++++++++++++++++++++ Reformat multiline string and enter each into hash table +++++++++++++++++++++++
function AddLinesToHashTable(text, table, excludeREArray)
{
	// Split into array of lines
	var lines = text.split("\n");
	
	// Regexp to match data from output
	var re = /(\S*)\s+CRC-32 = ([a-f0-9]+), size = ([0-9]+)/i;

	var lineEnum = new Enumerator(lines);
		
	LineLoop: for (; !lineEnum.atEnd(); lineEnum.moveNext())
	{
		//WScript.Echo(lineEnum.item());
		
		// Extract data
		lineEnum.item().match(re);
		var path = RegExp.$1;   // This is still a UNIX path (for efficiency)
		var crc32 = RegExp.$2;
		var size = RegExp.$3;
		
		// May be invalid if it tried to check a directory
		if (!path) continue;	// Skip over

		
		//Check path against exclude array
		for (index in excludeREArray) 
		{
			if (path.match(excludeREArray[index])) continue LineLoop;	// Skip on to next line
		}
		
		//WScript.Echo(path+","+crc32+","+size);
		
		// Insert into hash table
		table[path] = crc32+","+size;
	}
}

//+++++++++++++++++++++++++ Create output file and dump hash table +++++++++++++++++++++++
function DumpMetadata(table,fileName)
{
	// Create file, overwriting if need be
	var metaDataFile = FSO.CreateTextFile( fileName,true );

	// Dump to output file
	for(x in table) 
	{
		metaDataFile.WriteLine(x+","+table[x]);
	}
}


//+++++++++++++++++++++++++ Parse metadata file into hash table +++++++++++++++++++++++
function ReadMetadata(table,fileName)
{	 
	//WScript.Echo("Reading "+fileName);
  
  var ForReading = 1;
  var metadataFile = FSO.OpenTextFile(fileName, ForReading);
  var text = metadataFile.ReadAll();
  metadataFile.Close();
  
  // Split into array of lines
	lines = text.split("\n");
	
	// Regexp to match data from output
	var re = /(\S*),([a-f0-9]+),([0-9]+)/i;

	var lineEnum = new Enumerator(lines);
	for (; !lineEnum.atEnd(); lineEnum.moveNext())
	{
		// Extract data
		lineEnum.item().match(re);
		var path = RegExp.$1;   // This is still a UNIX path (for efficiency)
		var crc32 = RegExp.$2;
		var size = RegExp.$3;
		
		// Validity check
		if (!path) {
			WScript.Echo("Metadata file read failed");
			WScript.Quit(1);
		}
		
		// Insert into hash table
		table[path] = crc32+","+size;
	}
} 
   

//+++++++++++++++++++++++++ Recursively generate metadata over a file tree +++++++++++++++++++++++
function GenerateTreeMetadata(genPath, table, level, excludeREArray) {
	// If argument undefined, default
	if (!level)	level = 1000;
	
	genPath = FSO.GetFolder(genPath).Path;
	
	genPath = genPath+"/";
	var baseCygwinPath = GetPathDOS2UNIX(genPath);

	//Create regular expression object for path removal
	var basePathRE = new RegExp("^.{"+baseCygwinPath.length+"}","mg");  // Just removes x chars from each line

	// Call the recursive routine
	RecurseGTM(basePathRE,genPath,table,level, excludeREArray);
}


function RecurseGTM(basePathRE,genPath, table, level, excludeREArray)
{
	//WScript.Echo("Level:"+level+" Searching:"+genPath);
	
	// Convert slashes in path for UNIX-style format
	cygwinPath = GetPathDOS2UNIX(genPath);
	// Form command-line, calling Adler's "check" tool
	var cmdline = checkTool+" "+cygwinPath+'*.*';
	// Execute, grab output
	try {
		var output = WSHSHELL.Exec( cmdline ).StdOut.ReadAll();
	} catch(exception) {
		var message = "Command line failed from Metadata Functions script: "+cmdline+"  Message:" + WSHSHELL.Exec( cmdline ).StdErr.ReadAll();
		throw(message);
	}
	
	//WScript.Echo("output:"+output+" cmdline:"+cmdline);

	
	// Cut down to relative paths
	output = output.replace(basePathRE,"");
	// Add to hash table
	AddLinesToHashTable(output, table, excludeREArray);
	
	// Now recurse through subfolders
	var Folder = FSO.GetFolder(genPath);
	var FolderEnum = new Enumerator(Folder.SubFolders);
  if (level == 0) return;
	level--;
	
	for (; !FolderEnum.atEnd(); FolderEnum.moveNext())
	{
		// Recurse
		RecurseGTM(basePathRE,FolderEnum.item()+"\\", table, level, excludeREArray);
	}

}
	
	
	
//+++++++++++++++++++++++++ Compare two tables to determine changes  +++++++++++++++++++++++
function CompareTablesForChanges(oldTable, newTable, changesFilename)
{
	// Create changes file
	Verbiage("Creating changes file "+changesFilename);
	var changesFile = FSO.CreateTextFile( changesFilename,true );
	
	// RegExp for extracting CRC32
	var reCRC = /^[a-f0-9]+/i;

	// Dump to output file
	for(newEntry in newTable) 
	{
		var oldData = oldTable[newEntry];
		//WScript.Echo("Old: "+newEntry+" "+oldData);
		if (oldData == null) 
		{
			// File was added
			WScript.Echo("+ "+newEntry);
			changesFile.WriteLine(newEntry);		
		} else {
			// Did file change?
			var newData = newTable[newEntry];
			var newCRC = newData.match(reCRC)[0];
			var oldCRC = oldData.match(reCRC)[0];
			if (newCRC != oldCRC) {
			  WScript.Echo("# "+newEntry);
			  changesFile.WriteLine(newEntry);		
			}
		}
	}
}
	
	
	
//+++++++++++++++++++++++++ Library interface functions +++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//+++++++++++++++++++++++++ Generate metadata for a tree and save out a metadata.cvs file
//++++ Example:
//++++   GenerateMetadata("C:\Builds\NewBuild")
//++++
//++++ Will generate data for NewBuild and its subfolders, producing a C:\Builds\NewBuild\metadata.csv file
//++++ Second parameter specifies the metadata filename (can be null for default)
//++++ Third parameter optionally specifies an array of regexps describing files that should be skipped

function GenerateMetadata(genPath, metaFile, excludeREArray)
{
	if (!metaFile) metaFile = genPath+"\\metadata.csv";
	Verbiage("Checking if "+metaFile+" already exists");
	// Check if file already exists
	if (FSO.FileExists(metaFile)) return;
	// Hash table of (UNIX) filepaths to metadata strings
  var metadataTable = new Object;
	// Generate
	Verbiage("Generating "+metaFile);
	GenerateTreeMetadata(genPath, metadataTable, 1000, excludeREArray);
	// Dump to file
	Verbiage("Dumping "+metaFile);
	DumpMetadata( metadataTable, metaFile );
}



//+++++++++++++++++++++++++ Compare two metadata.cvs files and produce changes file +++++++++++++++++++++++
//++++ Example:
//++++   CompareMetadata(oldMetaFile, newMetaFile, changesFile)
//++++
//++++ Will compare two metafiles for changes and output a changes file

function CompareMetadataFiles(oldMetaFile, newMetaFile, changesFile)
{
	// Hash tables of (UNIX) filepaths to metadata strings
  var t1 = new Object;
	var t2 = new Object;

  Verbiage("Reading old metadata "+oldMetaFile);
  ReadMetadata(t1, oldMetaFile );	
  
	// Read the new metadata
  Verbiage("Reading new metadata "+newMetaFile);
  ReadMetadata(t2, newMetaFile );	
  
  // Compare and output
	CompareTablesForChanges(t1, t2, changesFile);	
}





//++++++++++++ Take metadata file and new tree and generate a changes files +++++++
//++++ Example:
//++++   CompareMetadata("C:\Builds\lastMetadata.csv", "C:\Builds\NewBuild", ""C:\Builds\changedFiles.txt")
//++++
//++++ Will parse lastMetadata.csv, generate metadata NewBuild, and output the changes to changedFiles.txt

function CompareMetadata(oldMetaFile, newMetaFile, newPath, changesFile)
{
	if (!newMetaFile) newMetaFile = newPath+"\\metadata.csv";
	
	// Hash tables of (UNIX) filepaths to metadata strings
  var t1 = new Object;
	var t2 = new Object;
	
	// Read the old metadata
  Verbiage("Reading old metadata "+oldMetaFile);
  ReadMetadata(t1, oldMetaFile );	
  
	// Generate new metadata
	Verbiage("Generating new metadata");
	GenerateTreeMetadata(newPath, t2, 1000);
	// Dump new metadata to file
	Verbiage("Dumping new metadata to "+newMetaFile);
	DumpMetadata( t2, newMetaFile );

	// Generate a changes file
	Verbiage("Comparing metadata");
	CompareTablesForChanges(t1, t2, changesFile);	
}



//++++++++++++ Take metadata file and new tree and generate a changes files +++++++
//++++ Example:
//++++   CompareMetadataAndTree("C:\Builds\lastMetadata.csv", null, "C:\Builds\NewBuild", ""C:\Builds\changedFiles.txt")
//++++
//++++ Will parse lastMetadata.csv, generate metadata for NewBuild, and output the changes to changedFiles.txt
//++++ If newMetaFile is null, will use default

function CompareMetadataAndTree(oldMetaFile, newMetaFile, newPath, changesFile, excludeREArray)
{
	if (!newMetaFile) newMetaFile = newPath+"\\metadata.csv";
	
	// Hash tables of (UNIX) filepaths to metadata strings
  var t1 = new Object;
	var t2 = new Object;
	
	// Read the old metadata
  Verbiage("Reading old metadata "+oldMetaFile);
  ReadMetadata(t1, oldMetaFile );	
  
	// Check if new metadata file already exists
	Verbiage("Checking if "+newMetaFile+" already exists");
	if (FSO.FileExists(newMetaFile)) 
	{
		// Read it in
		Verbiage("Reading "+newMetaFile);
		ReadMetadata(t2, newMetaFile );	
	} else {
		// Generate new metadata
		Verbiage("Generating new metadata");
		GenerateTreeMetadata(newPath, t2, 1000, excludeREArray);
		// Dump new metadata to file
		Verbiage("Dumping new metadata to "+newMetaFile);
		DumpMetadata( t2, newMetaFile );
	}
	// Generate a changes file
	Verbiage("Comparing metadata");
	CompareTablesForChanges(t1, t2, changesFile);	
}