/**
@file	utils.js
File that contains utility methods for DOM-style operations
*/


///////////////////////////////////////////////////////////////////////
// BEGIN AdobeProperty
/**
A property class that allows you to Get() and Set() a value and Bind()
that value to DOM elements or arbitrary functions to automagically
have things happen when the property changes.
*/

function AdobeProperty(inValue)
{
	this.value = inValue;
}


AdobeProperty.prototype.Get = function()
{
	return this.value;
}


AdobeProperty.prototype.Set = function(inValue)
{
	if (this.value != inValue)
	{
		this.value = inValue;
		if (this.objects)
		{
			for (var n = 0; n < this.objects.length; n++)
			{
				this._BindNotify(this.objects[n]);
			}
		}
	}
}


AdobeProperty.prototype.Bind = function(inObject, inMethod)
{
	var bRet = false;

	if (inObject)
	{
		var bindRecord = { obj: inObject, method: inMethod };
		if (this._BindNotify(bindRecord))
		{
			if (!this.objects)
				this.objects = new Array();
			this.objects.push(bindRecord);
			
			// Input elements get bi-directional
			if (inObject.nodeType == 1 && (inObject.nodeName == "INPUT" || inObject.nodeName == "SELECT"))
			{
				var thisCB = this;
				// Should preserve any existing onchange...
				inObject.onchange = function() { thisCB.Set(this.value); };
			}
			bRet = true;
		}
	}

	return bRet;
}


AdobeProperty.prototype._BindNotify = function(inBindRecord)
{
	var bRet = false;

	if (!inBindRecord || !inBindRecord.obj)
		return bRet;

	// If the bound method is a function, use it as a filter for the value.
	var targetValue = this.value;
	if ("function" == typeof inBindRecord.method)
		targetValue = inBindRecord.method(this.value);

	switch (typeof inBindRecord.obj)
	{
		case "function":
			// Bound to a function, just call it with the property value
			inBindRecord.obj(targetValue);
			bRet = true;
			break;
		case "string":
			// Bound to a string, set the value
			inBindRecord.obj = targetValue;
			bRet = true;
			break;
		case "object":
			// Bound to an object, then we try a few different things
			// 0. An object with a valid method bound
			if ("string" == typeof inBindRecord.method && "function" == typeof inBindRecord.obj[inBindRecord.method])
			{
				inBindRecord.obj[inBindRecord.method](targetValue);
				bRet = true;
			}
			// 1. An element
			else if (1 == inBindRecord.obj.nodeType)
			{
				// For form fields, set the value
				if ("INPUT" == inBindRecord.obj.nodeName || "SELECT" == inBindRecord.obj.nodeName)
				{
					inBindRecord.obj.value = targetValue;
				}
				// Other elements, replace the content
				else
				{
					while (inBindRecord.obj.lastChild)
						inBindRecord.obj.removeChild(inBindRecord.obj.lastChild);
					inBindRecord.obj.appendChild(document.createTextNode(targetValue));
				}
				bRet = true;
			}
			// 2. An attribute or text node
			else if (2 == inBindRecord.obj.nodeType || 3 == inBindRecord.obj.nodeType)
			{
				inBindRecord.obj.nodeValue = targetValue;
				bRet = true;
			}
			// 3. An object with a Set() method
			else if (inBindRecord.obj.Set && "function" == typeof inBindRecord.obj.Set)
			{
				inBindRecord.obj.Set(targetValue);
				bRet = true;
			}
			// 4. An object with a value property
			else if (undefined != inBindRecord.obj.value && (typeof inBindRecord.obj.value == typeof targetValue))
			{
				inBindRecord.obj.value = targetValue;
				bRet = true;
			}
			// 5. Don't know what to do
			break;
		default:
			// Unhandled type
	}
	return bRet;
}
// END AdobeProperty
///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////
// BEGIN WizardAlert

/**
Modal alert widget.
*/
function WizardAlert(inContainerProxy)
{
	this.session = inContainerProxy;
	this.title = null;
	this.body = null;
	this.buttons = new Array();

	if (!this.alertTemplate)
	{
		var alertPathArray = new Array(this.session.GetResourcesPath(), "/common/alert/alert.html");
		var alertPath = _concatPaths(alertPathArray, this.session.GetDefaultProperties().platform);
		
		var alertContents = this.session.LoadFile(alertPath);
		this.alertTemplate = alertContents.data;
		
		var cssFileName = "alert";
		var currentLang = getUserInterfaceLanguage(inContainerProxy);
		
		if (null != currentLang)
		{
			if (isLanguageRTL(currentLang)) 
				cssFileName += "_rtl";
		}
		
		// doctor up the root path to the CSS
		var cssURIArray =  new Array(this.session.GetResourcesPath(), "common", "alert", cssFileName);
		var cssURI = "file://" + _concatPaths(cssURIArray, this.session.GetDefaultProperties().platform);

		this.alertTemplate = ExpandTokens(this.alertTemplate, { CSSRoot: cssURI });		
	}

	if (!this.alertTemplate || this.alertTemplate.length <= 0)
	{
		throw "Unable to load alert.html";
	}
}


/**
Run (show) the alert.
*/
WizardAlert.prototype.Run = function()
{
	// Formulate a token map to fill in the alert template.
	var repMap = new Object();

	// Title and body
	if (this.session.localization)
		repMap.AlertWindowTitle = this.session.localization.GetString("locAlertWindowTitle", "Installer Alert");
	else
		repMap.AlertWindowTitle = "Installer Alert";
	repMap.AlertTitle = this.title;
	repMap.AlertBody = this.body;

	// Button text padding
	var buttonBuffer = ""; // they look like spaces, but they are Special

	// Buttons
	repMap.AlertButtons = "";
	for (var bi = 0; bi < this.buttons.length; bi++)
	{
		// Setup standard button attributes
		var attributes = {
			type: "button",
			value: buttonBuffer + this.buttons[bi].label + buttonBuffer,
			onclick: "window.external.UIExitDialog('" + this.buttons[bi].returnCode + "');",
			onfocus: "setFocus(this);"
		};

		var html = "<input";
		var attr = null;

		// Standard attributs
		for (attr in attributes)
		{
			html += " " + attr + "=\"" + attributes[attr] + "\"";
		}

		// User supplied attributes augment or override
		for (attr in this.buttons[bi].attributes)
		{
			html += " " + attr + "=\"" + this.buttons[bi].attributes[attr] + "\"";
		}

		html += "/>";
		repMap.AlertButtons += html;
	}

	return this.session.UIShowModalAlert(ExpandTokens(this.alertTemplate, repMap));
}


/**
Set the title.  This will be automatically wrapped in an <h1> but may contain other HTML markup.
*/
WizardAlert.prototype.SetTitle = function(inTitle)
{
	this.title = inTitle;
}


/**
Set the body.  This will automatically be wrapped in a <div> and should contain appropriate markup, for example <p> tags.
*/
WizardAlert.prototype.SetBody = function(inBody)
{
	this.body = inBody;
}


/**
Add an alert button.  Buttons are displayed left to right in the order added.  An optional map can be supplied to augment or override the attributes in the generated <input type="button" ... /> fragment.
*/
WizardAlert.prototype.AddButton = function(inButtonLabel, inReturnCode, inOptAttributes)
{
	this.buttons.push({ label: inButtonLabel, returnCode: inReturnCode, attributes: inOptAttributes });
}


/**
A class to generate standard alerts used in a couple different places.
*/
function StandardAlert(inContainerProxy, inLocalizationObj)
{
	this.containerProxy = inContainerProxy;
	this.localizationObj = inLocalizationObj;
}


/**
Utility string lookup method to simplify handling the case where we don't have a Localization object
handy.
*/
StandardAlert.prototype._LoadText = function(inStringID, inDefaultText, inPropertyMap)
{
	var locText = inDefaultText;
	if (null == inPropertyMap)
	{
		inPropertyMap = this.containerProxy.GetDefaultProperties();	
	}
	if (null != this.localizationObj)
	{
		locText = this.localizationObj.GetString(inStringID, inDefaultText, inPropertyMap);
	}
	return locText;
	
}

/**
Standard invalid user credentials alert.
*/
StandardAlert.prototype.InvalidUserCredentials = function()
{
	/*var a = new WizardAlert(this.containerProxy);
	a.SetTitle(this._LoadText("locInvalidUserCredentialsTitle", "Invalid User Credentials"));
	a.SetBody("<p>" + this._LoadText("locInvalidUserCredentialsBody", "You do not have sufficient security credentials to install this software.") + "<p>");
	a.AddButton(this._LoadText("locBtnQuit", "Quit"), "2");
	return a.Run();
	*/
	//WizardAlert1(inSession, inTitle, inBody, buttonsArray, subTitle)
	var button1 = {label:this._LoadText("locBtnQuit", "Quit"), left:"256px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"7"};
	var buttonsArray = new Array(button1);
	new WizardAlert1(this.containerProxy, this._LoadText("locInvalidUserCredentialsTitle", "Invalid User Credentials"), "<p>" + this._LoadText("locInvalidUserCredentialsBody", "You do not have sufficient security credentials to install this software.") + "</p>", buttonsArray, "");
}


/**
Standard Setup already running alert.
*/
StandardAlert.prototype.SetupAlreadyRunning = function()
{
	/*var a = new WizardAlert(this.containerProxy);
	a.SetTitle(this._LoadText("locSetupRunningTitle", "Setup Already Running"));
	a.SetBody("<p>" + this._LoadText("locSetupRunningBody", "You can only install one Adobe product at a time.  Please complete the other installation before attempting to install this product." + "</p>"));
	a.AddButton(this._LoadText("locBtnQuit", "Quit"), "2");
	return a.Run();*/
	//WizardAlert1(inSession, inTitle, inBody, buttonsArray, subTitle)
	var button1 = {label:this._LoadText("locBootStrapQuit", "Quit"), left:"256px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"7"};
	var buttonsArray = new Array(button1);
	new WizardAlert1(this.containerProxy, this._LoadText("locSetupRunningTitle", "Setup Already Running"), "<p>" + this._LoadText("locSetupRunningBody", "You can only install one Adobe product at a time.  Please complete the other installation before attempting to install this product." + "</p>"), buttonsArray, "");
}

StandardAlert.prototype.CAPSDBLocked = function()
{
	var button1 = {label:this._LoadText("locBootStrapQuit", "Quit"), left:"256px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"7"};
	var buttonsArray = new Array(button1);
	new WizardAlert1(this.containerProxy, this._LoadText("locInstallationError", "Installation Error"), "<p>" + this._LoadText("locDBLockedBody", "The Installer databases are locked. Please restart the machine and then run this installer." + "</p>"), buttonsArray, "");
}

/**
Standard Setup Windows update running alert.
*/
StandardAlert.prototype.WindowsUpdateRunning = function()
{
	var button1 = {label:this._LoadText("locBootStrapQuit", "Quit"), left:"187px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"7"};
	var button2 = {label:this._LoadText("locBootStrapContinue", "Continue"), left:"325px", top:"106px", returnCode:"2", hotkey:"C", defaultOption:"0"};
	var buttonsArray = new Array(button1, button2);
	var WA1 = new WizardAlert1(this.containerProxy, this._LoadText("locAlertWindowTitle", "Installer Alert"),
		"<p>"
		+ "<span>"
		+ this._LoadText("systemPageWindowsUpdatesRunning1",
			"Setup has detected that Windows Updates are being installed. It is recommended that you continue installing the updates, reboot and then run this setup again.")
		+ "</span><br/><span>"
		+ this._LoadText("systemPageWindowsUpdatesRunning2",
			"Running this setup while Windows Updates are being installed may result in an invalid installation.")
		+ "</span><br/><span>"
		+ this._LoadText("systemPageWindowsUpdatesRunning3",
			"Click Quit to exit the installer or click continue to proceed with the installation.")
		+"</span></p>",
		buttonsArray, this._LoadText("locWindowUpdateRunningTitle", "Windows Updates Running"));
	return WA1;
}

/**
Standard bootstrap failure alert.
*/
StandardAlert.prototype.BootstrapInstallFailure = function(inExitCode, inErrorArgs)
{
	var propMap = this.containerProxy.GetDefaultProperties();
	var errorText = "";
	if (inErrorArgs["MSIErrorText"])
		errorText += inErrorArgs["MSIErrorText"];
	if (inErrorArgs["ARKErrorText"])
		errorText += inErrorArgs["ARKErrorText"];
	
	var locTitle = this._LoadText("locBootstrapFailedTitle", "Setup Failed");
	var locBody = "<p>" + this._LoadText("locBootstrapFailedBody", "Setup failed to bootstrap.") + "</p>";
	if (errorText && errorText.length > 0)
	{
		var errorTextNode = document.createElement("p");
		errorTextNode.id = "alertErrorText";
		var errorText = document.createTextNode(errorText);
		errorTextNode.appendChild(errorText);
		locBody += errorTextNode.innerHTML;
	}

	/*var a = new WizardAlert(this.containerProxy);
	a.SetTitle(locTitle);
	a.SetBody(locBody);
	a.AddButton(this._LoadText("locBtnQuit", "Quit"), "2");
	return a.Run();
	*/
	//WizardAlert1(inSession, inTitle, inBody, buttonsArray, subTitle)
	var button1 = {label:this._LoadText("locBootStrapQuit", "Quit"), left:"256px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"7"};
	var buttonsArray = new Array(button1);
	var _WA1=new WizardAlert1(this.containerProxy, locTitle, locBody, buttonsArray, "");
	return _WA1;
}

// END WizardAlert
///////////////////////////////////////////////////////////////////////


function FormatLogHeader(inHeaderText)
{
    return "______ " + inHeaderText + " ______"
}


/**
Compare two versions that are in a.b.c.d format.
@retval <0 	iff version1 < version2
@retval 0 	iff version1 == version2
@retval >0	iff version1 > version2
*/
function compareVersions(version1, version2)
{
	var versionDelimsRE = /[,.]/;
	var versionArray1 = version1.split(versionDelimsRE);
	var versionArray2 = version2.split(versionDelimsRE);
	
	var returnVal = 0;
	var maxComponents = (versionArray1.length < versionArray2.length) ? versionArray2.length : versionArray1.length;
	
	for (var i=0; i < maxComponents; ++i)
	{
		if (versionArray1.length < i)
		{
			versionArray1.push(0);	
		}
		if (versionArray2.length < i)
		{
			versionArray2.push(0);	
		}
		if (Number(versionArray1[i]) > Number(versionArray2[i]))
		{
			returnVal = 1;
		}
		else if (Number(versionArray1[i]) < Number(versionArray2[i]))
		{
			returnVal = -1;
		}			
		
		if (returnVal != 0)
		{
			break;	
		}
	}
	return returnVal;
}


function _getSupportedLanguagesArray(inContainerProxy)
{
	var sessionSupportedLanguages = new Array();
	var payloadMap = inContainerProxy.GetSessionData().payloadMap;
	
	var mapFamilyNameToLangs = new Object();
	for (var adobeCode in payloadMap)
	{
		var payload = payloadMap[adobeCode];
		var satisfiesObj = payloadMap[adobeCode].Satisfies;

		var keyPair = satisfiesObj.family + satisfiesObj.productName;
		var langs = mapFamilyNameToLangs[keyPair];
		if (langs == null)
		{
			langs = new Object();
		}
		// Update the langs object
		if (payload.isLanguageIndependent != null &&
			true == payload.isLanguageIndependent)
		{
			var allLangs = getAllSupportedLanguagesArray();
			for (var langIndex = 0; langIndex < allLangs.length; ++langIndex)
			{
				langs[allLangs[langIndex]] = 1;
			}
		}
		else
		{
			for (var langIndex = 0; langIndex < payload.Languages.length; ++langIndex)
			{
				langs[payload.Languages[langIndex]] = 1;
			}
		}
		mapFamilyNameToLangs[keyPair] = langs;
	}	

	var firstIteration = true;
	for (var keyPair in mapFamilyNameToLangs)
	{
		// Get all the languages and intersect that with the existing set
		if (firstIteration)
		{
			var supportedLangMap = mapFamilyNameToLangs[keyPair];
			for (var eachSupportedLang in supportedLangMap)
			{
				sessionSupportedLanguages.push(eachSupportedLang);
			}
			firstIteration = false;	
		}
		else
		{
			if (sessionSupportedLanguages.length <= 0)
			{
				break;	
			}
			// Remove any element that doesn't exist
			var updatedSupportedLangs = new Array();
			for (var supportedIndex = 0; supportedIndex < sessionSupportedLanguages.length; ++supportedIndex)
			{
				if (mapFamilyNameToLangs[keyPair][sessionSupportedLanguages[supportedIndex]] != null)
				{
					updatedSupportedLangs.push(sessionSupportedLanguages[supportedIndex]);
				}
			}
			sessionSupportedLanguages = updatedSupportedLangs;
		}		
	}
	
	return sessionSupportedLanguages;
}

/**
Test to see if this install supports only a single language
@retval "" 		the installer supports more locales or none
@retval string	the installer supports locale <string>
*/
function _extendScriptGetSingleLanguage()
{	
	var returnVal = "";
	
	try
	{
		var sendContainerProxy = new ContainerProxy;
		
		if (sendContainerProxy)
		{
			var langArray = _getSupportedLanguagesArray(sendContainerProxy);
	
			if (1 == langArray.length)
				returnVal = langArray[0];
		}
	}
	catch (ex)
	{
	} 
	
	return returnVal;
}

/**
Given an InstallerSession instance, traverse the payloads and determine how much disk space is required on each volume.
*/
function _calculateRequiredSpace(inInstallerSession)
{
	var mapRootToSize = inInstallerSession.OperationSize().roots;

	var mapVolumeToSize = new Object();
	var dirTokenMap = inInstallerSession.GetDirectoryTokenMap();
	dirTokenMap["[INSTALLDIR]"] = inInstallerSession.properties["INSTALLDIR"];
	for (var eachRoot in mapRootToSize)
	{
		var fullPath = dirTokenMap[eachRoot];
		if (null == fullPath)
		{
			throw "Unsupported directory token: " + eachRoot;
		}
		var volumeInfo = inInstallerSession.GetVolumeStatisticsFromPath(fullPath);
		if (!volumeInfo || !volumeInfo.rootPath)
		{
			throw "Unable to get volume statistics for path: " + fullPath;
		}
		var currentSize = mapVolumeToSize[volumeInfo.rootPath];
		if (null == currentSize)
		{
			currentSize = 0;
		}
		currentSize += mapRootToSize[eachRoot];
		mapVolumeToSize[volumeInfo.rootPath] = currentSize;
	}
		
	return mapVolumeToSize;
}


/**
Object that contains the status information for the current install operation.
*/
function InstallOperationStatus(inInstallerSession)
{
	/**  Current session */
	this.installerSession = inInstallerSession;
	/** The InstallerPayload that we're currently operating on */
	this.currentPayloadObj = null;
	/** Total number of operations that are going to be performed */
	this.totalOperations = 0;
	/** Normalized progress 0 <= n <= 100 */
	this.totalProgress = 0;
	/** Current operation index.  [0, totalOperations) */
	this.currentOperation = 0;
	/** Current relative disk index, from the set of media we are installing right now */
	this.currentRelativeDiskIndex = 1;
	/** Current media name, if any */	
	this.currentMediaName = null;
	/** Number of payloads for this disk that have been installed */	
	this.mediaPayloadsInstalled = 0;
	/** Total media count */
	this.mediaCount = 0;
	/** System Info */
	this.systemInfo = inInstallerSession.systemInfo;	
	/** Last known media path **/
	this.lastMediaPath = null;
	/** Boolean to see if we can move on in media check loop **/
	this.foundOrCancelled = false;
	
	/** Current status object.  The status object has the following form:
	obj.message		
	--- All operation types --
	obj.message.code							Integer.  0 == success.  Other values are context sensitive.  See PayloadOperationConstants.h 
	--- InstallPayload Data ---
	obj.isRunning							Integer [0, 1]
	obj.percentComplete						Integer [0 - 100]
	obj.message.args						Array of arguments that are associated with the current obj.message.code value	
	--- Simulation Data ---
	obj.message.simulateResults						Anonymous object scoping payload results from CAPS simulation test
	obj.message.simulationResults.conflicting		Array of AdobeCodes that would be in conflict were this operation successful
	obj.message.simulationResults.upgraded			Array of AdobeCodes that would be upgraded were this operation successful
	obj.message.simulationResults.corrupted			Array of AdobeCodes that would be orphaned if this operation were successful.  Bootstrapper specific
	*/
	this.operationStatus = null;
	/** Flag that's set if the last operation encountered an error and
		we need to release from the progress since the operation
		won't necessarily have a percentComplete value */
	this.operationsComplete = false;
	this.percentPayload = null;
	/** Flag indicating that we should exit the workflow because the user selected cancel */
	this.exitOperationsLoop = false;
	
	this.updateProgress = function()
	{
		this.operationStatus = this.installerSession.GetInstallStatus();
		if (this.operationStatus != null)
		{			
			if (this.operationStatus.percentComplete != null)
			{
				this.percentPayload = this.operationStatus.percentComplete;
				if (this.totalOperations > 0)
				{
					if (100 == this.operationStatus.percentComplete &&
						this.currentOperation == (this.totalOperations-1))
					{
						this.totalProgress = 100;
					}
					else
					{
						var intervalSize = 1;
						if (this.totalOperations != 0)
							intervalSize = 100/this.totalOperations;
						if (intervalSize < 1)
							intervalSize = 1;

						var offset = (this.currentOperation)*intervalSize;
						offset += ((this.operationStatus.percentComplete * intervalSize)/100);
						this.totalProgress = offset;
					}
				}
			}
		}
		//this.installerSession.LogDebug("totalProgress: " + this.totalProgress);
	}
	this.isCurrentOperationComplete = function()
	{
		var isComplete = false;
		if (this.operationStatus &&
			this.operationStatus.isRunning == 0)
		{
			isComplete = true;
		}
		return isComplete;
	}
	
	this.areAllOperationsComplete = function()
	{
		var allComplete = this.operationsComplete;
		if (!allComplete)
		{
			if (this.totalOperations == (this.currentOperation+1))
			{
				allComplete = this.isCurrentOperationComplete();
			}
		}			
		return allComplete;
	}

}
	
function createFailedOperationResult()
{
	var opResult = new Object();
	opResult["isRunning"] = 0;
	opResult["percentComplete"] = 100;
	
	opResult.message = new Object();
	opResult.message["code"] = gConstants.kORFailed;
	return opResult;
}

function InstallOperationsQueue(inInstallerSession, inStatusCallback, inCancelCallback)
{
	this.installerSession = inInstallerSession;
	this.callbackMethod = inStatusCallback;
	this.cancelCallback = inCancelCallback;
	this.installOperationStatus = new InstallOperationStatus(inInstallerSession);
	this._operationsQueue = null;
	this.estimatedSize = 0;
	this.reverse = false; // True if we are uninstalling.

	this.Open = function()
	{
		var didOpen = false;
		if (null == this._operationsQueue)
		{
			this._operationsQueue = new Array();


			// Walk through the session payloads collecting ones with an interesting action.
			// But we don't allow mixed installs and removes as they are a little too interesting.
			var installCount = 0;
			var removeCount = 0;
			LogPayloadSet(this.installerSession, "InstallOperationsQueue Unordered operations", this.installerSession.sessionPayloads,
				function(p) { return "with operation " + p.GetInstallerAction(); });
			
			var commandLineArgs = this.installerSession.commandLineArgs;
			var bExtensionsOnly = false;
			if (commandLineArgs && commandLineArgs.Properties && commandLineArgs.Properties.extensionsOnly && commandLineArgs.Properties.extensionsOnly == "1")		
			{
			    bExtensionsOnly = true;
			}			
			
			for (var adobeCode in this.installerSession.sessionPayloads)
			{
				var payload = this.installerSession.sessionPayloads[adobeCode];
				payload.SetOperationResult(null);
				var payloadAction = payload.GetInstallerAction();
				
				if(!bExtensionsOnly)
				{
				    // Align the action of extension to parent only when it is being installed from same media
				    if((payloadAction != kInstallerActionNone) && (payload.isExtensionPayload()))
				    {
					    if(!(payload.parentPayload))
						    payloadAction = kInstallerActionNone;
					    else 
					    {
					        if(payload.parentPayload.GetInstallerAction() == kInstallerActionNone)
							    payloadAction = kInstallerActionNone;
							    
							//If the parent is repairing itself and we are patch, then we should uninstall
							if(payload.isPatchPayload() && payload.parentPayload.GetInstallerAction() == kInstallerActionRepair)
							{
							    //check if the patch is really installed on the system
							    //It is possible that the patch has been removed earlier
							    if ( 2 /*kCapsInstallStateInstalled*/ == payload.GetInstallStateForCollection(this.installerSession.sessionCollection))
                                {
							        this.installerSession.LogWarning("Payload:" + payload.parentPayload.LogID() + " has been updated and has been selected for repair. The patcher will be uninstalled now.");
							        payload.SetInstallerAction(kInstallerActionRemove);
							        payload.patchRollbackOrdering = true;
    				                payloadAction = kInstallerActionRemove;
    				            }
    				            else
    				            {
    				                payload.SetInstallerAction(kInstallerActionNone);
    				                payloadAction = kInstallerActionNone;
    				            }
							}
							
					    }
				    }
				}
				else
				{
				    if(payload.beingUpgraded && payload.beingUpgraded == true)
				    {
				        if(payload.GetPhysicalInstallState() && payload.GetPhysicalInstallState().physicallyInstalled == true)
				        {
				            this.installerSession.LogWarning("Payload:" + payload.LogID() + " has newer update. The patch will be uninstalled now.");
				            payload.SetInstallerAction(kInstallerActionRemove);
				            payload.patchRollbackOrdering = true;
		                    payloadAction = kInstallerActionRemove;
				        }
				        else
				        {
				            this.installerSession.LogWarning("Payload:" + payload.LogID() + " has newer update. The patch will not be installed.");
			                payload.SetInstallerAction(kInstallerActionNone);			                
	                        payloadAction = kInstallerActionNone;
				        }				        
				    }
				}			
				
				if(payload.policyNode && payload.policyNode.mode == kWorkflowModeUninstall && payloadAction != kInstallerActionRemove)
				{		
				    var uiselec = payload.GetUISelectabilityForSession(this.installerSession);
				    if(uiselec == false)
				    {		
				         var driver =this.installerSession.GetDriverPayload() ; 
				        if (driver && driver.AdobeCode && driver.AdobeCode != payload.GetAdobeCode())
				        {				        
				            var driverPayload = this.installerSession.allPayloads[driver.AdobeCode];
				            if(driverPayload)
				            {
    				            var d_action = driverPayload.GetInstallerAction();
    				            if(d_action == kInstallerActionRemove)
    				            {
    				                if ( 2 /*kCapsInstallStateInstalled*/ == payload.GetInstallStateForCollection(this.installerSession.sessionCollection))
    				                {
    				                    payload.SetInstallerAction(kInstallerActionRemove);
    				                    payloadAction = kInstallerActionRemove;
    				                }
    				            }
    				        }    				
				        }
				    }
				}
				switch (payloadAction)
				{
					case kInstallerActionInstall:
					case kInstallerActionRepair:
						installCount++;
						this._operationsQueue.push(payload);
						
						var opSize = payload.OperationSize(true);
						if (opSize && opSize.totalBytes)
						{
							var addSize = (opSize.totalBytes/1024);
							if (addSize < 1)
								addSize = 1;
							this.estimatedSize += Math.ceil(addSize);
						}
						break;
					case kInstallerActionRemove:
					    {
					        if(!(payload.patchRollbackOrdering && payload.patchRollbackOrdering==1))
					            removeCount++;
						    this._operationsQueue.push(payload);
						}
						break;
				}
			}
			
			if (installCount > 0 && removeCount > 0)
			{
				this.installerSession.LogError("InstallOperationsQueue: Installing and removing payloads in the same run is not supported");
			}
			else if (this._operationsQueue.length > 0)
			{
				// We have somthing to do, sort the queue
				//this._operationsQueue.sort(PayloadSortOperationOrder);
				
				//We need to call PayloadDependencySort for ordering the extension with their parent
				this._operationsQueue = PayloadDependencySort(this._operationsQueue, false);
				
				if (removeCount > 0)
				{
					this.reverse = true;
					this._operationsQueue.reverse();
				}

				LogPayloadSet(this.installerSession, "InstallOperationsQueue Ordered operations", this._operationsQueue,
					function(p) { return "with operation " + p.GetInstallerAction(); });

				// Negative one so we can PopInstruction and start with 0
				this.installOperationStatus.currentOperation = -1;
				this.installOperationStatus.totalOperations = this._operationsQueue.length;				

				didOpen = true;
			}
			else
			{
				this.installerSession.LogError("InstallOperationsQueue: Instruction set is empty");
			}
		}
		return didOpen;
	};

	/**
	Arrange the session media properties for this payload, possibly prompting the user to insert the media.
	May set this.installOperationStatus.exitOperationsLoop to true as a side effect when prompting
	the user for media.
	*/
	this.GetMediaForPayload = function(inPayload)
	{
		var payloadObject = this.installerSession.payloadMap[inPayload.GetAdobeCode()];
		
		if (payloadObject && payloadObject.MediaInfo)
		{
			this.installOperationStatus.lastMediaPath = this.installerSession.properties["mediaPath"];
			this.installerSession.properties["mediaType"] = payloadObject.MediaInfo.type;
			this.installerSession.properties["mediaVolumeIndex"] = payloadObject.MediaInfo.volumeIndex; 
			this.installerSession.properties["mediaPath"] = payloadObject.MediaInfo.path; 
			this.installerSession.properties["mediaName"] = payloadObject.MediaInfo.mediaName;
			
			if (this.installOperationStatus.currentMediaName != payloadObject.MediaInfo.mediaName)
			{
				this.installOperationStatus.mediaPayloadsInstalled = 0;
				
				if (this.installOperationStatus.currentRelativeDiskIndex < this.installOperationStatus.mediaCount)
					this.installOperationStatus.currentRelativeDiskIndex += 1;
			}
			else
			{
				this.installOperationStatus.mediaPayloadsInstalled += 1;
			}
			
			// See if we need to swap and prompt if we do, but only if have a status callback.
			// If we don't we are not interacting with the user.  This is mainly to cover simulation
			// mode.
			if (this.callbackMethod && "function" == typeof this.callbackMethod)
			{
				DetectSwap(this.installerSession, this.installOperationStatus, inPayload);
			}
			
			this.installOperationStatus.currentMediaName = payloadObject.MediaInfo.mediaName; 
		} 
		else
		{
			this.installerSession.properties["mediaType"] = "0"; 
			this.installerSession.properties["mediaVolumeIndex"] = "1";
			this.installerSession.properties["mediaPath"] = ""; 
			this.installerSession.properties["mediaName"] = "";					
		}
	};
	
	this.PopInstruction = function(inSimulation)
	{
		var retValue = null;
		while (retValue == null 
				&& (this.installOperationStatus.currentOperation+1)  < this.installOperationStatus.totalOperations)
		{
			this.installOperationStatus.currentOperation += 1;
			retValue = this._operationsQueue[this.installOperationStatus.currentOperation];
			
			// If all the dependencies are satisfied, then return it, otherwise
			// propagate the error and skip to the next one.
			this.installerSession.LogDebug("Creating instruction for payload " + retValue.LogID());
			
			// Test if the user canceled.  Note: this may also be
			// set via this.callbackMethod which is used in the operation status polling to pickup
			// a cancel in the middle of an operation.  Both methods have a site effect of setting
			// the exitOperationsLoop property of the installOperationStatus object.
			if (this.cancelCallback && "function" == typeof this.cancelCallback)
			{
				this.cancelCallback(this.installOperationStatus);
			}
			
			// Make sure the media ducks are in a row for this payload.  Done here
			// because it can be effectively equivalent to a user cancel if media prompts
			// necessary.
			if (this.installOperationStatus.exitOperationsLoop != true && !inSimulation)
			{
				this.GetMediaForPayload(retValue);
			}

			// If the user canceled then we need to short circuit
			if (this.installOperationStatus.exitOperationsLoop == true)
			{
				// Set the install status for all remaining payloads to complete
				this.installerSession.LogDebug("User exited workflow.");
				if (retValue)
				{				
					this.installerSession.LogDebug("Marking payload " + retValue.LogID() + " as canceled");
					this.installOperationStatus.operationStatus = new Object();
					this.installOperationStatus.operationStatus.isRunning = false;
					this.installOperationStatus.operationStatus.percentComplete = 100;
					this.installOperationStatus.operationStatus.message = new Object();
					this.installOperationStatus.operationStatus.message.code = 1; // OR_UserCancel
										
					this.installerSession.LogDebug("Status object");
					this.installerSession.LogDebug(this.installOperationStatus.operationStatus);
					
					var aPayload = this.installerSession.sessionPayloads[retValue.GetAdobeCode()];
					if (aPayload != null)
					{
						aPayload.SetOperationResult(this.installOperationStatus.operationStatus);
						this.installerSession.LogDebug("Set payload canceled status");				
					}
					else
					{
						this.installerSession.LogDebug("Unable to get payload to set status");
					}
				}
				else
				{
					this.installerSession.LogDebug("Current operation is null");
				}
				retValue = null;				
			}
			else
			{
			    //If this payload is not language pack, and the current language is new to be installed
               //and this payload has already been installed, we skip this payload. Only when the payload is repairing
               if(retValue && retValue.policyNode && (retValue.GetInstallerAction() == kInstallerActionRepair)  && retValue.policyNode.installedForThisSession &&!( retValue.isExtensionPayload() && (retValue.GetSessionData().Extends.type=="langPack")))
                {                    
                    var installedLangs = this.installerSession.GetInstalledLangaugesForSession();
                    if(installedLangs[this.installerSession.properties[gConstants.kPropInstallLanguage]] != true)
                    {
                        
		                var currentPlatform = this.installerSession.systemInfo.Macintosh ? "Mac" : "Win";
			            var actuallyExists = null;
			            var payloadExists = false;
			            if(currentPlatform=="Mac") 
			            {
				            // If it not third party payload then check whether it is actually installed.
				            if (!retValue.GetSessionData().ThirdPartyCapabilities)
				            {
				                actuallyExists = retValue.policyNode.session.IfRIBSPayloadInstallationActuallyExists(retValue._adobeCode);
				                payloadExists = (actuallyExists != null) && (actuallyExists.isSuccessful == 1) && (actuallyExists["installDir"] != null);
				            }
			            }
                        if(currentPlatform=="Win" || (currentPlatform=="Mac" && !retValue.GetSessionData().ThirdPartyCapabilities && payloadExists))
                        {
                            this.installerSession.LogWarning("Warning: " + retValue.LogID() + " will not be repaired. Only language packs will be installed for it.");		        
    				        
                            this.installOperationStatus.operationStatus = new Object();
                            this.installOperationStatus.operationStatus.isRunning = false;
                            this.installOperationStatus.operationStatus.percentComplete = 100;
                            this.installOperationStatus.operationStatus.message = new Object();
                            this.installOperationStatus.operationStatus.message["code"] = 0; // OR_Success
                            					
                            var aPayload = this.installerSession.sessionPayloads[retValue.GetAdobeCode()];
                            if (aPayload != null)
                            {
                                aPayload.SetOperationResult(this.installOperationStatus.operationStatus);                            
                            }
                            else
                            {
                                this.installerSession.LogDebug("Unable to get payload to set status");
                            }
                            
    					    retValue = null;
				            continue;
				        }
                    }   
                }
                
                if(this.installerSession.isSimulating == false)
                {
                    //Make a call to custom code for pre-payload actions
                    this.installerSession.LogInfo("Calling the custom action code for pre-" + retValue.GetInstallerAction() + " for payload " + retValue.GetAdobeCode());
                    var caRet = this.installerSession.CallCustomActionCode(retValue.GetAdobeCode(), retValue.GetInstallerAction(), retValue.GetPayloadOverrideProperties(this.installerSession.properties), "pre", 0);
                    //Process the return code
                    this.installerSession.LogDebug("Custom action code for pre-" + retValue.GetInstallerAction() + " for payload " + retValue.GetAdobeCode() + " returned : " + caRet);
                    
                    if(caRet && (caRet.returnCode != null ) && (caRet.returnCode != gConstants.kORSuccess))
				    {				
				        this.installerSession.LogError("Custom Action for payload " + retValue.LogID() + " returned error. Failing this payload.");
				        var aPayload = this.installerSession.sessionPayloads[retValue.GetAdobeCode()];
                        if (aPayload != null)
                        {
                            aPayload.SetOperationResult(createFailedOperationResult()); 
                            aPayload.PropogateOperationResult(this.installerSession);                          
                        }
				        retValue = null;
				        continue;
				    }			
				}
                
				// Make sure that whatever we needed has already happened...
				// But restrect checks to payloads in our operation queue.  The full dependency
				// set should have been validated upstream.
				var requiredOpsArray = this.reverse ? retValue.GetSatisfiedArray() : retValue.GetDependentsArray();
				requiredOpsArray = PayloadIntersect(requiredOpsArray, this._operationsQueue);

				
				//If the payload is extension, make sure that the parent is already installed.
				if(retValue && retValue.isExtensionPayload())
				{
				    //Check if this is a patch payload, and its action is repair
				    if(retValue && retValue.isPatchPayload() && retValue.GetInstallerAction() == kInstallerActionRepair)
				    {
				        this.installerSession.LogWarning("Patcher: " + retValue.LogID() + " has already been applied. Patchers can not be repaired/reinstalled, skipping this patcher");
				        retValue = null;
				        continue;
				    }
				    
				    if(retValue.GetInstallerAction() != kInstallerActionRemove)
				    {
				        var parentPayload = retValue.parentPayload;
				        if(parentPayload)
				        {
							var installState = -1;
							//First check if the current session has installed the parent
							for(var index = 0; index < this._operationsQueue.length; ++index)
							{
							    if(this._operationsQueue[index].GetAdobeCode() == parentPayload.GetAdobeCode())
							    {
							        var operationResult = this._operationsQueue[index].GetOperationResult();
							        if(operationResult)
							        {
							            if(operationResult._error == null &&
						                        this.installerSession.IsOperationCodeSuccess(operationResult.message.code))
                                        {
                                            installState = 2 /*kCapsInstallStateInstalled*/; 
                                        }
                                    }
							        break;
							    }
							}
							
							if(installState == -1)
							{
							    try
							    {
								    var collMap = null;
								    if(this.installerSession.capsMap && this.installerSession.capsMap.Payloads)
									    collMap = this.installerSession.capsMap.Payloads[parentPayload.GetAdobeCode()].Collections;
    								
								    if(collMap)
								    {
									    for(var index=0; index < collMap.length; ++index)
									    {
										    var collection = collMap[index];
										    if(collection && (collection.collectionID == this.installerSession.sessionCollection.collectionID))
										    {
											    installState = collection.installState;
											    break;
										    }
									    }
								    }
							    }
							    catch(ex)
							    {
							        this.installerSession.LogInfo("Exception occurred while checking the install state of parentPayload " + ex);
							    }	
							}
							
				            if ( "2" /*kCapsInstallStateInstalled*/ != installState)
				            {
					            this.installerSession.LogInfo("Payload " + retValue.GetAdobeCode() + " can not be installed due to dependent operation failure.");    
					            if(retValue._criticalPayloadFailures == null)
					            {
					                retValue._criticalPayloadFailures = new Array();
					            }
					            retValue._criticalPayloadFailures.push(parentPayload);
					        }					
				        }
				    }				    
				}
				
				// Fix for bug 1631544 Check if any dependent of driver has failed, in that propogate that info to the current payload as well.
				
				driver = this.installerSession.sessionCollection.driverPayload;
				if (driver)
				{		
					var requiredOpsArrayForDriver = this.reverse ? driver.GetSatisfiedArray() : driver.GetDependentsArray();
					requiredOpsArrayForDriver = PayloadIntersect(requiredOpsArrayForDriver, this._operationsQueue);

					LogPayloadSet(this.installerSession, "Dependent operations for DRIVER " + driver.LogID(), requiredOpsArrayForDriver);

					for (var i =0; i < requiredOpsArrayForDriver.length; ++i)
					{
						var aPayload = requiredOpsArrayForDriver[i];
						
						this.installerSession.LogDebug("Checking operation result for " + aPayload.LogID());
					
						var operationResult = aPayload.GetOperationResult();
					
						// Dependency type awareness.  Tell the current payload about this result iff it was a failure
						if (null != operationResult &&
							!this.installerSession.IsOperationCodeSuccess(operationResult.message.code))
						{
							this.installerSession.LogDebug("Recording dependent operation failure for payload: " + retValue.LogID());
							try
							{
								retValue.SetDependentOperationFailureForDriver(aPayload, this.installerSession , driver);						
							}
							catch (ex)
							{
								this.installerSession.LogError("Failed to set dependent operation failure.  Inserting automatically created failure result.");
								retValue.SetOperationResult(createFailedOperationResult());
								//retValue = null;
							}
						}
					}	
				}			


				if (!retValue || false == retValue.CanOperationBeAttempted(this.installerSession))
				{
					this.installerSession.LogDebug("Payload cannot be installed due to dependent operation failure");
					// Create an operation result and assign it
					if (retValue) {
							var opResult = this.installOperationStatus.operationStatus
								? this.installOperationStatus.operationStatus : createFailedOperationResult();
							opResult.message["code"] = gConstants.kORCriticalDependentFailure;
							retValue.SetOperationResult(opResult); 
					}
					
					// If this is the last operation we need to set the special flag
					if (this.installOperationStatus.currentOperation+1 == this.installOperationStatus.totalOperations)
					{											
						this.installOperationStatus.operationsComplete = true;	
					}								
					retValue = null;
				}
				else
				{
					this.installerSession.LogDebug("Operation can be attempted");

				}
			}
						
			this.installOperationStatus.currentPayloadObj = retValue;
		}
		return retValue;
	};
}

function _simulatePayloadOperations(inInstallerSession)
{
    inInstallerSession.isSimulating = true;    
	var didVerify = false;
	var preflightQueue = new InstallOperationsQueue(inInstallerSession);	
	if (preflightQueue.Open())
	{
		try
		{		
			var openSession = inInstallerSession.OpenCAPSSimulation();
			if (openSession.success)
			{
				var poppedInstruction = null;
				while (null != (poppedInstruction = preflightQueue.PopInstruction()))
				{
					// We just do this to the information that
					// is available in the GetOperationResult is the same between the simulation
					// and the thread-based model
					var installOperationStatus = new InstallOperationStatus(inInstallerSession);
					installOperationStatus.currentPayloadObj = poppedInstruction;
					installOperationStatus.operationStatus = inInstallerSession.SimulateInstallPayload(poppedInstruction.GetAdobeCode(), poppedInstruction.GetInstallerAction(), inInstallerSession.properties);
					poppedInstruction.SetOperationResult(installOperationStatus.operationStatus);
				}
				inInstallerSession.CloseCAPSSimulation();
			}	
		}
		catch (ex)
		{
			didVerify = false;
		}		
	}
	return didVerify;	
}



/**
Method to sequence and execute the individual payloads in the current session.
@param	inInstallerSession		Existing installer session instance
@param	inStatusCallback 		Callback method to invoke with status object
@param	inCancelCallback		Callback method returning true to continue operations, false to stop
*/
var _gInstallOperationsQueue = null;
var _gPollingIntervalName = null;

function _doPayloadOperations(inInstallerSession, inStatusCallback, inCancelCallback)
{
    inInstallerSession.isSimulating = false;
    // Get the total number of operations.
	try
	{
		_gInstallOperationsQueue = new InstallOperationsQueue(inInstallerSession, inStatusCallback, inCancelCallback);
		if (_gInstallOperationsQueue.Open())
		{
			inInstallerSession.LogDebug("Opened installation queue");
			var poppedInstruction = _gInstallOperationsQueue.PopInstruction();
			
			if (poppedInstruction)
			{
				// Get all the payloads with an install operation and sum them.  This is the "AddRemoveEstimatedSize" value
				inInstallerSession.properties["AddRemoveInfoEstimatedSize"] = _gInstallOperationsQueue.estimatedSize;
				
				//Check if this is extension payload. If so, set it's parent Adobe code
				inInstallerSession.properties["parentAdobeCode"]="";
				
				var curAdobeCode = poppedInstruction.GetAdobeCode();
				var curPayload = inInstallerSession.allPayloads[curAdobeCode];
				
				var bExtensionOnly = false;
				var commandLineArgs = inInstallerSession.commandLineArgs;
                if (commandLineArgs && commandLineArgs.Properties && commandLineArgs.Properties.extensionsOnly && commandLineArgs.Properties.extensionsOnly == "1")		
                {
                    bExtensionOnly = true;
                    //Set the media path 
                    if(commandLineArgs.Properties.extensionMediaPath)
                        inInstallerSession.properties["mediaPath"] = commandLineArgs.Properties.extensionMediaPath;        		        
                }
	            if(curPayload.isExtensionPayload())
				{
					if(curPayload.parentPayload)
					{
					    inInstallerSession.properties["parentAdobeCode"] = curPayload.parentPayload.GetAdobeCode();
					}
					
					var commandLineArgs = inInstallerSession.commandLineArgs;
	                if (commandLineArgs && commandLineArgs.Properties && commandLineArgs.Properties.extensionsOnly && commandLineArgs.Properties.extensionsOnly == "1")		
	                {
	                    //Set the media path 
	                    if(commandLineArgs.Properties.extensionMediaPath)
	                        inInstallerSession.properties["mediaPath"] = commandLineArgs.Properties.extensionMediaPath;        		        
	                }
				}	
				
				var CLEArray = new Array();
				for(var le in curPayload.GetSessionData().LicensingEntities)
			    {
			       CLEArray.push(le);		        
			    }	
				inInstallerSession.properties["CLE"] = CLEArray.join("|");
				
				var uninstallAliasNameTemplate = inInstallerSession.localization.GetString("UninstallProduct", "Uninstall Product Template");
				// We'll get the rest of it in the callback method
				inInstallerSession.properties["ARK_UninstallAliasName"] = ExpandTokens(uninstallAliasNameTemplate, { "curProductName": curPayload.GetSessionData().ProductName });
				// We will need to return success for the current payload provided the following conditions are met
				// 1. It is a patch payload
				// 2. it is being repaired/reinstalled.
				
				inInstallerSession.InstallPayload(poppedInstruction.GetAdobeCode(), poppedInstruction.GetInstallerAction(), inInstallerSession.properties);											
				
				if(inInstallerSession.UIHosted())
				{	
					_gInstallOperationsQueue.installerSession.LogInfo("Setting interval");		
					_gPollingIntervalName = window.setTimeout("_pollOperationStatus()", 200);
				}
				else
				{
					_gInstallOperationsQueue.installerSession.LogInfo("Setting callback for silent");		
					_gPollingIntervalName = "_pollOperationStatus()";
					eval("_pollOperationStatus()");
				}
			}
			else
			{
				// Couldn't pop. Exit gracefully.
				_gInstallOperationsQueue.installerSession.LogInfo("No operation.  We're done:");
				_gInstallOperationsQueue.installOperationStatus.operationsComplete = true;
				
				// Invoke the callback
				_gInstallOperationsQueue.callbackMethod(_gInstallOperationsQueue.installOperationStatus);
			}
		}
		else
		{
		    throw "Unable to open operation queue";			
		}	
	}
	catch (ex)
	{
		if (inInstallerSession)
			inInstallerSession.LogError("error creating instructions: " + ex);
	}
}

function _pollOperationStatus()
{	
	try
	{
		if (!_gInstallOperationsQueue)
		{
			return;
		}
		
		if (_gPollingIntervalName != null && _gInstallOperationsQueue.installerSession.UIHosted())
		{	
			//_gInstallOperationsQueue.installerSession.LogDebug("Destroying interval: " + _gPollingIntervalName);
			//clearInterval(_gPollingIntervalName);
			_gPollingIntervalName = null;
		}
		else if (_gPollingIntervalName)
		{
			_gInstallOperationsQueue.installerSession.LogDebug("Destroying interval: " + _gPollingIntervalName);
			_gPollingIntervalName = null;
		}
	
		//_gInstallOperationsQueue.installerSession.LogDebug("updating progress");
		_gInstallOperationsQueue.installOperationStatus.updateProgress();
		try
		{
		    if(_gInstallOperationsQueue.installOperationStatus.isCurrentOperationComplete()==true)
		    {
		        //Operation is completed. Call the custom action code for either pre-payload action with rollback or post-payload action depending on the return code
				if(!_gInstallOperationsQueue.installerSession.IsOperationCodeSuccess(_gInstallOperationsQueue.installOperationStatus.operationStatus.message.code))
				{
                    var propResultRet = _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.PropogateOperationResult(_gInstallOperationsQueue.installerSession);
                    _gInstallOperationsQueue.installerSession.LogInfo("Calling the ROLLBACK custom action code for pre-" + _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetInstallerAction() + " for payload " + _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetAdobeCode());                				    
                    var caRet = _gInstallOperationsQueue.installerSession.CallCustomActionCode(_gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetAdobeCode(), _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetInstallerAction(), _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetPayloadOverrideProperties(_gInstallOperationsQueue.installerSession.properties), "pre", 1);			    

                    // Short circuit exit if the user has decided to quit
                    if(propResultRet == 0)
                        _gInstallOperationsQueue.installOperationStatus.exitOperationsLoop = true; 
				}
				else
				{			
				    _gInstallOperationsQueue.installerSession.LogInfo("Calling the custom action code for post-" + _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetInstallerAction() + " for payload " + _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetAdobeCode());                
				    var caRet = _gInstallOperationsQueue.installerSession.CallCustomActionCode(_gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetAdobeCode(), _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetInstallerAction(), _gInstallOperationsQueue.installOperationStatus.currentPayloadObj.GetPayloadOverrideProperties(_gInstallOperationsQueue.installerSession.properties), "post", 0);			    
				}
		    }
		    _gInstallOperationsQueue.callbackMethod(_gInstallOperationsQueue.installOperationStatus);
		}
		catch (ex)
		{
			_gInstallOperationsQueue.installerSession.LogDebug("callbackMethod failed: " + ex);
		}
		
		if (_gInstallOperationsQueue.installOperationStatus.isCurrentOperationComplete() == true)
		{
			// If the current instruction has completed, we can pop the next ones
			// Save the current status
			if (_gInstallOperationsQueue.installOperationStatus.currentPayloadObj != null)
			{
			    _gInstallOperationsQueue.installerSession.LogDebug("Operation complete. Setting status: " + _gInstallOperationsQueue.installOperationStatus.operationStatus.message.code);
				_gInstallOperationsQueue.installOperationStatus.currentPayloadObj.SetOperationResult(_gInstallOperationsQueue.installOperationStatus.operationStatus);			
				
			}
			else
			{
				_gInstallOperationsQueue.installerSession.LogDebug("No status available for payload");				
			}
			_gInstallOperationsQueue.installerSession.LogDebug("Popping new operation");
			var poppedInstruction = _gInstallOperationsQueue.PopInstruction();
			_gInstallOperationsQueue.installerSession.LogDebug("Popped");
			if (poppedInstruction)
			{
				// We'll ge the rest of it in the callback method
				if (_gInstallOperationsQueue.installOperationStatus.exitOperationsLoop != true)
				{
					_gInstallOperationsQueue.installerSession.properties["parentAdobeCode"]="";
					//Check if this is extension payload. If so, set it's parent Adobe code
					var curAdobeCode = poppedInstruction.GetAdobeCode();
					var bExtensionsOnly = false;
					var commandLineArgs = _gInstallOperationsQueue.installerSession.commandLineArgs;
	                if (commandLineArgs && commandLineArgs.Properties && commandLineArgs.Properties.extensionsOnly && commandLineArgs.Properties.extensionsOnly == "1")		
	                {
    	                bExtensionsOnly = true;
	                    //Set the media path 
	                    if(commandLineArgs.Properties.extensionMediaPath)
	                        _gInstallOperationsQueue.installerSession.properties["mediaPath"] = commandLineArgs.Properties.extensionMediaPath;        		        
	                }
	                
					var curPayload = _gInstallOperationsQueue.installerSession.allPayloads[curAdobeCode];
					if(curPayload.isExtensionPayload())
					{
					    if(curPayload.parentPayload)
						    _gInstallOperationsQueue.installerSession.properties["parentAdobeCode"]=curPayload.parentPayload.GetAdobeCode();                        
					}		            
					
					var CLEArray = new Array();
				    for(var le in curPayload.GetSessionData().LicensingEntities)
			        {
			           CLEArray.push(le);		        
			        }	
				    _gInstallOperationsQueue.installerSession.properties["CLE"] = CLEArray.join("|");

					var uninstallAliasNameTemplate = _gInstallOperationsQueue.installerSession.localization.GetString("UninstallProduct", "Uninstall Product Template");
				
				    _gInstallOperationsQueue.installerSession.properties["ARK_UninstallAliasName"] = ExpandTokens(uninstallAliasNameTemplate, { "curProductName": curPayload.GetSessionData().ProductName });
					_gInstallOperationsQueue.installerSession.InstallPayload(poppedInstruction.GetAdobeCode(), poppedInstruction.GetInstallerAction(), _gInstallOperationsQueue.installerSession.properties)
				}
				
				if (_gInstallOperationsQueue.installerSession.UIHosted())
				{
					_gInstallOperationsQueue.installerSession.LogInfo("setting timeout");
					_gPollingIntervalName = window.setTimeout("_pollOperationStatus()", 200);
					//_gInstallOperationsQueue.installerSession.LogDebug("Created interval name:" + _gPollingIntervalName);
				}
				else
				{
					_gInstallOperationsQueue.installerSession.LogInfo("Setting callback for silent");
					_gPollingIntervalName = "_pollOperationStatus()";
					eval("_pollOperationStatus()");
				}
			}
			else
			{
			    // Couldn't pop.  Are we done?
				// _gInstallOperationsQueue.installOperationStatus.updateProgress();
				_gInstallOperationsQueue.installerSession.LogInfo("No operation.  We're done:");
				_gInstallOperationsQueue.installOperationStatus.operationsComplete = true;
				
				// Attempt to eject the media if we are installing from removable media
				if (_gInstallOperationsQueue.installOperationStatus && _gInstallOperationsQueue.installOperationStatus.lastMediaPath)
					_gInstallOperationsQueue.installerSession.UIEjectRemovableMedia(_gInstallOperationsQueue.installOperationStatus.lastMediaPath);
				
				// Invoke the callback
				_gInstallOperationsQueue.callbackMethod(_gInstallOperationsQueue.installOperationStatus);
			}
		}
		else
		{
			// Come around for another query
			if (_gInstallOperationsQueue.installerSession.UIHosted())
			{
				_gPollingIntervalName = window.setTimeout("_pollOperationStatus()", 200);
			}
			else
			{
				_gPollingIntervalName = "_pollOperationStatus()";
				eval("_pollOperationStatus()");
			}
		}	

	}
	catch (ex)
	{
		_gInstallOperationsQueue.installerSession.LogError("Polling error:" + ex);		
	}
}

/**
Property turn JSON string into JS object by encapsulating in parens.
@param	inJSONText	JSON text
@returns	Re-hydrated object, null if JSON invalid.
*/
function _jsonToObject(inJSONText)
{
	var retObj = null;
	try
	{
		retObj = eval('(' + inJSONText + ')');
	}
	catch (e)
	{
		retObj = null;
	}
	return retObj;	
}

/**
Concatenate two strings given the current platform string
@param	inPathComp		Array of paths to concatenate
@param	inPlatName		Platform name
@returns Concat'd paths
*/
function _concatPaths(inPathComp, inPlatName)
{
	
	var retString = "";
	var pathComp = "";
	var pathSep = '/';
	
	// If the first path component starts with a UNC marker then
	// we need to preserve this after normalization
	// 1442989
	var uncPath = false;

	if (inPathComp[0]) 
	{
		if (inPathComp[0].length > 1)
		{
			uncPath = (inPathComp[0].charAt(0) == '\\' && inPathComp[0].charAt(1) == '\\');
		}
		
		for (var i = 0; i < inPathComp.length; i++)
		{
			retString += (inPathComp[i] + "/");
		}
		
		// Convert all backslashes to forward slashes
		retString = retString.replace(/\\+/g, "/");
		if ("Win32" == inPlatName)
		{
			retString = retString.replace(/\/+/g, "\\"); 
			if (uncPath)
			{
				retString = '\\' + retString;
			}
		}
		else
		{
			retString = retString.replace(/[\/\:]+/g, "/"); 
		}
		if (retString.charAt(retString.length-1) == '\\' ||
			retString.charAt(retString.length-1) == '/')
		{
			retString = retString.slice(0, retString.length-1);
		}
	} 
	else 
	{
		throw "Invalid paths provided to _concatPaths";
	}

	return retString;
}

/**
Compares two paths for equality
@param	iPath1		one path
@param	iPath2		Other path
@returns true if path are equal
*/
function _equalPaths(iPath1, iPath2)
{
	var inPath1 = iPath1 + "";
	var inPath2 = iPath2 + "";
	
	if (inPath1.charAt(inPath1.length-1) == '\\' ||
		inPath1.charAt(inPath1.length-1) == '/')
	{
		inPath1 = inPath1.slice(0, inPath1.length-1);
	}
	if (inPath2.charAt(inPath2.length-1) == '\\' ||
		inPath2.charAt(inPath2.length-1) == '/')
	{
		inPath2 = inPath2.slice(0, inPath2.length-1);
	}

	if (inPath1 == inPath2)
		return true;
	else
		return false;
}


/**
Return Width of the given text in pixels
@param	text			text whose width is needed to be determined
@param	className		Any CSS class name (optional) to apply
@param	fontSize		font size (optional) to apply
@returns				Width of the text in numeric value
*/
function GetTextWidth(container, text, className, fontSize)
{
	var element = document.createElement("span");
	element.innerText = text;
	if (className)
		element.className = className;
	if (fontSize)
		element.style.fontSize = fontSize;
	element.style.fontFamily = '"Lucida Grande", "Lucida Sans", Tahoma, Helvetica, Arial, sans-serif';
	if (container == null)
		container = document.body;
	container.appendChild(element);

	var width = GetElementWidth(element);

	container.removeChild(element);
	delete element;

	return width;
}

/**
Return Width of the given element in pixels
@param	inElement		Element in which text is shown
@returns				Width of the element in numeric value
*/
function GetElementWidth(inElement)
{
	var oldStyleWidth = inElement.style.width;
	var oldStylePosition = inElement.style.position;
	inElement.style.width = "auto";
	inElement.style.position = "absolute";
	var width = inElement.offsetWidth;
	inElement.style.position = oldStylePosition;
	inElement.style.width = oldStyleWidth;
	return width;
}

/**
Return height of line(s) ocupied by the given element in pixels
@param	inElement		Element in which text is shown
@param	inbSingle		Optional - Need single line height
@returns				Height of the line(s) in numeric value
*/
function GetLineHeight(inElement, inbSingle)
{
	var retString = inElement.innerText;
	if (inbSingle)
		inElement.innerText = "W";
	var height = inElement.offsetHeight;
	if (inbSingle)
		inElement.innerText = retString;
	return height;
}

/**
Return string with elipsis (...) at the end if widht of the element is short
@param	inElement		Element in which text is shown
@param	inWidth			Widht of the element
@param	inLines			Number of lines that can be shown
@returns				string with added elipsis if required.
*/
function GetDotDotDotString(inElement, inWidth, inLines)
{
	var retString = inElement.innerText;
	var lineHeight = GetLineHeight(inElement, true);
	
	if (!inLines)
		inLines = 1;

	if (parseInt(GetLineHeight(inElement) / lineHeight) > inLines || inElement.offsetWidth > inWidth) {
		var charCount = retString.length;
		var i = 3;

		inElement.innerText = retString.substring(0, i);
		while (i < charCount) {
			i++;
			inElement.innerText = retString.substring(0, i);
			if (parseInt(GetLineHeight(inElement) / lineHeight) > inLines || inElement.offsetWidth > inWidth) {
				inElement.innerText = inElement.innerText.substring(0, i-3) + "...";
				break;
			}
		}
		var tmp = retString;
		retString = inElement.innerText;
		inElement.innerText = tmp;
	}
	return retString;
}

/**
The GetCurrentTime method returns an integer value representing the number of milliseconds between midnight, January 1, 1970
See getTime of Time()
@returns				Number of milliseconds.
*/
function GetCurrentTime()
{
	var currentDate = new Date();
	var callingTime = currentDate.getTime();

	delete currentDate;

	return callingTime;
}
/**
Returns an array of DOM elements under a parent with a specified classname
@param	oElm		Parent element
@param	strTagName		Tag name - span, div, *, ...
@param	strClassName		Class name
*/

function getElementsByClassName(oElm, strTagName, strClassName)
{
	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
	var arrReturnElements = new Array();
	strClassName = strClassName.replace(/\-/g, "\\-");
	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
	var oElement;
	for(var i=0; i<arrElements.length; i++){
		oElement = arrElements[i];
		if(oRegExp.test(oElement.className)){
			arrReturnElements.push(oElement);
		}
	}
	return (arrReturnElements)
}
/**
Apply inFunction to inNode as well as all of inNode's children
@param	inNode 	DOM element
@param	inFunction Unary function taking DOMNode to apply to all elements
*/
function applyToDOMFragment(inNode, inFunction)
{
	inFunction(inNode);
	
	if (inNode.all)
	{
		for (var i = 0; i < inNode.all.length; ++i)
			inFunction(inNode.all[i]);
	}
	else if (null != inNode.getElementsByTagName)
	{
		var childNodes = inNode.getElementsByTagName('*');
		for (var i = 0; i < childNodes.length; ++i)
			inFunction(childNodes[i]);
	}
	else
	{
		var childNodes;
		
		if (inNode.childNodes != null)
			childNodes = inNode.childNodes;
		else if (inNode.children != null)
			childNodes = inNode.children;
		
		if (childNodes != null)
		{
			// make a copy of the node list so that manipulations of
			// the list will not affect the traversal
			var childNodesArray = new Array;
			for (var nodeIndex = 0; nodeIndex < childNodes.length; ++nodeIndex)
				childNodesArray.push(childNodes[nodeIndex]);
			
			// iterate node list copy
			for (var i = 0; i < childNodesArray.length; ++i)
				applyToDOMFragment(childNodesArray[i], inFunction);
		}
	}
}

function listProperties(obj, objName)
{
	var result = "";
	for (var i in obj)
	{
		result += objName + "." + i + "=" + obj[i] + "\n";
	}
	return result;
}


function printNodeTree(inNode, depth)
{
	rv = "";
	for (var i = 0; i < depth; i++)
		rv = rv + "  ";
	
	if (1 == inNode.nodeType)
	{
		rv = rv + inNode.nodeName;
		if (inNode.attributes)
		{
			rv = rv + ": ";
			for (var a = 0; a < inNode.attributes.length; a++)
			{
				rv = rv + inNode.attributes.item(n).nodeName + ": " + inNode.attributes.item(n).nodeValue;
			}
		}
	}
	else
	{
		rv = rv + inNode.nodeName + ": " + inNode.nodeValue;
	}

	rv = rv + "\n";

	for (var n = 0; inNode.childNodes && n < inNode.childNodes.length; n++)
	{
		rv = rv + printNodeTree(inNode.childNodes[n], depth + 1);
	}

	return rv;
}


function bytesToText(inBytes)
{
	var session = gSession;
	var units = [ 
		{ id: "locSizeKilobytes", format: "[size] KB" },
		{ id: "locSizeMegabytes", format: "[size] MB" },
		{ id: "locSizeGigabytes", format: "[size] GB" },
		{ id: "locSizeTerabytes", format: "[size] TB" }
	];
		
	var signStr = inBytes < 0 ? "-" : "";
	inBytes = Math.abs(inBytes);
	var unit = 0;
	while (unit < units.length)
	{
		var size = inBytes / Math.pow(1024, (unit + 1));
		if (size < 1000)
			break;
		unit++;
	}
	
	var decimal = ".";
	if (session && session.localization && session.UIHosted())
		decimal = session.localization.GetString("locDecimal", decimal);

	var sizeStr = new Number(Math.round(size * 10)).toString();
	if (sizeStr.length == 1)
		sizeStr = "0" + sizeStr;
	sizeStr = signStr + sizeStr.slice(0, -1) + decimal + sizeStr.slice(-1);

	var ls = new LocalizedString(units[unit].id, units[unit].format, { size: sizeStr });
	if (session && session.localization && session.UIHosted())
		return ls.Translate(session.localization);
	return ls.Translate(null);
}


//! Deletes all the descendants of the elementName.
//! \returns, the node of the with the Id elementName.
function RemoveAllChildren(element)
{
	if (element)
	{
		while (element && element.childNodes[0]) {
			element.removeChild(element.childNodes[0]);
		}
	}
	return element;
}

//! Sets the display of the element to none.
//! \returns, the node of the with the Id elementName.
function UIHide(element)
{
	if (element)
	{
		element.style["display"] = "none";
	}
	return element;
}

function IsVisible(element)
{
	if (element)
	{
		if(element.style["display"] != "none")
		    return true;
	}
	return false;
}

//! Sets the display of the element to displayStly (block).
//! \returns, the node of the with the Id elementName.
function UISetDisplay(element, displayStyle)
{
	if (element)
	{
        var displayToSet = "block";
        if(displayStyle)
            displayToSet = displayStyle;
	    element.style["display"] = displayToSet;
	}
	return element;
}


//! Deals with detecting if a disk swap is needed
function DetectSwap(inSession, ioOperationStatus, inPayload)
{
	if (!inSession || !ioOperationStatus || !inPayload)
	{
		gSession.LogDebug("DetectSwap: bad arguments");
		return;
	}

	var payloadObject = inPayload.GetSessionData();//inSession.payloadMap[inPayload.GetAdobeCode()];

	// If removable, we prompt for the disk swap if we don't already see the disk
	if (1 == payloadObject.MediaInfo.type 
			&& inSession.UIHosted() 
			&& (kInstallerActionRemove != inPayload.GetInstallerAction())
			&& (inPayload.policyNode.updated != true))
	{
		ioOperationStatus.foundOrCancelled = false;
		var mediaPathInfo = inSession.GetPathInformation(payloadObject.MediaInfo.path);
		
		// Test to see if the disk needed is there, if not, prompt
		if (mediaPathInfo && mediaPathInfo.isValidPath && mediaPathInfo.isValidPath == 1 && mediaPathInfo.pathExists && mediaPathInfo.pathExists == 1)
		{
			ioOperationStatus.foundOrCancelled = true;
		}

		while (!ioOperationStatus.foundOrCancelled && gWizardControl && gWizardControl.pageUniverse && gWizardControl.pageUniverse[0])
		{
			// Attempt to eject the media in question
			if (ioOperationStatus.lastMediaPath)
				inSession.UIEjectRemovableMedia(ioOperationStatus.lastMediaPath);
			
			var diskSwapStringID = ioOperationStatus.systemInfo.Windows ? "locAlertDiskSwapBodyWindows" : "locAlertDiskSwapBody";
			/*var alertObj = new WizardAlert(inSession);
			// Load the platform specific alert text
			
			alertObj.SetTitle(inSession.localization.GetString("locProductName", "[productName]"));
			alertObj.SetBody("<p>" + inSession.localization.GetString(diskSwapStringID, "Please insert \"[mediaName]\" to continue installation.", { mediaName: payloadObject.MediaInfo.mediaName, driveLetter: mediaPathInfo.volumeInfo.rootPath}) + "</p>");

			alertObj.AddButton(inSession.localization.GetString("locBtnQuit", "Cancel"), "1", { accessKey: "c" } );
			alertObj.AddButton(inSession.localization.GetString("locAlertDiskSwapOk", "OK"), "2", { accessKey: "o" } );
			
			var resultDialog = alertObj.Run();
			*/
			//WizardAlert1(inSession, inTitle, inBody, buttonsArray, subTitle)
			var button1 = {label:inSession.localization.GetString("locBtnQuit", "Cancel"), left:"187px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"0"};
			var button2 = {label:inSession.localization.GetString("locAlertDiskSwapOk", "OK"), left:"325px", top:"106px", returnCode:"2", hotkey:"O", defaultOption:"3"};
			var buttonsArray = new Array(button1,button2);
			var resultDialog = new WizardAlert1(inSession, inSession.localization.GetString("locProductName", "[productName]"), "<p>" + inSession.localization.GetString(diskSwapStringID, "Please insert \"[mediaName]\" to continue installation.", { mediaName: payloadObject.MediaInfo.mediaName, driveLetter: mediaPathInfo.volumeInfo.rootPath}) + "</p>", buttonsArray, "");

			if (resultDialog && resultDialog.returnValue && resultDialog.returnValue == "2")
			{
				// Test to see if the disc is on the default removable drive 
				mediaPathInfo = inSession.GetPathInformation(payloadObject.MediaInfo.path);
				if (mediaPathInfo && mediaPathInfo.isValidPath && mediaPathInfo.isValidPath == 1 && mediaPathInfo.pathExists && mediaPathInfo.pathExists == 1)
				{
					ioOperationStatus.foundOrCancelled = true;
				}
				
				// Test to see if the disc is in any removable drive (Win)
				// Repair must be from source installation location for current session
				/*
				if (!ioOperationStatus.foundOrCancelled && ioOperationStatus.systemInfo && ioOperationStatus.systemInfo.RemovableDrives)
				{
					var removableDriveList = ioOperationStatus.systemInfo.RemovableDrives;
					var secondChoicePath = payloadObject.MediaInfo.path;
					var secondChoicePathInfo = null;
					
					if (removableDriveList)
					{
						for (var i = 0; i < removableDriveList.length; i++)
						{
							secondChoicePath = removableDriveList[i].root + secondChoicePath.slice(3, secondChoicePath.length);
							secondChoicePathInfo  = inSession.GetPathInformation(secondChoicePath);
							
							if (secondChoicePathInfo && secondChoicePathInfo.isValidPath && secondChoicePathInfo.isValidPath == 1 && secondChoicePathInfo.pathExists && secondChoicePathInfo.pathExists == 1)
							{ 
								inSession.properties["mediaPath"] = secondChoicePath;
								ioOperationStatus.foundOrCancelled = true;
								break;
							}
						}
					}
				}
				*/
			}
			else
			{
				//z_WizardAlert1(inSession, inTitle, inBody, buttonsArray, subTitle)
				var z_button1 = {label:inSession.localization.GetString("locBtnCancel", "Cancel"), left:"187px", top:"106px", returnCode:"1", hotkey:"Q", defaultOption:"0"};
				var z_button2 = {label:inSession.localization.GetString("locAlertDiskSwapOk", "OK"), left:"325px", top:"106px", returnCode:"2", hotkey:"O", defaultOption:"3"};
				var z_buttonsArray = new Array(z_button1,z_button2);
				var z_resultDialog = new WizardAlert1(inSession,inSession.localization.GetString("locAlertCancelTitle","[productName] Installer"), "<p>" + inSession.localization.GetString("locAlertCancelTitle","[productName] Installer")+ "</p>",z_buttonsArray, inSession.localization.GetString("locAlertCancelSubTitle","Are you sure you want to quit the installer?"));
			
				if(z_resultDialog && z_resultDialog.returnValue && z_resultDialog.returnValue == "2")
				{
					ioOperationStatus.exitOperationsLoop = true;
					ioOperationStatus.foundOrCancelled = true;
				}
				else
				{
				}
			}
		}
	}
}


/**
Payload ordering according to dependency.

Order is calculated from the dependency graph:

 1. Minimum incoming (dependency) edges so those with no
    dependencies go down first.
 2. Maximum outgoing (satisfying) edges so that those who
    satisfy a lot of payloads go down first.
 3. Media number, to minimize disk swapping.
 4. By AdobeCode just to make sure the order has no wiggle room.

*/
function PayloadSortOperationOrder(inInstallerPayloadA, inInstallerPayloadB)
{
	
	var a = inInstallerPayloadA;
	var b = inInstallerPayloadB;

	// If nobody depends on either a or b, favor hidden payloads
	// In principle this should only ever play a role when comparing the driver and a visible
	// payload with no dependents.  Since a visible payload can never be a dependent of
	// the driver, this non-dependency based primary ordering should never violate dependencies.

	/*
	// This causes obvious sort instability with extendscript and subtle instability with WebKit and/or IE
	if (a.policyNode && a.policyNode.isSessionDriver && b.policyNode && b.policyNode.visible)
	{
		return -1;
	}
	if (b.policyNode && b.policyNode.isSessionDriver && a.policyNode && a.policyNode.visible)
	{
		return 1;
	}
	*/
	
	if((a.isExtensionPayload()) && !(b.isExtensionPayload()))
	    return 1;
	if((b.isExtensionPayload()) && !(a.isExtensionPayload()))
	    return -1;
	
	var aDependentOnB = false;
	var bDependentOnA = false;
	var aDepends = a.GetDependentsArray();
	var bDepends = b.GetDependentsArray();
	for (var i = 0 ; i < aDepends.length ; i++)
	{
	    if(aDepends[i]._adobeCode == b._adobeCode)
	    {
	        aDependentOnB = true;
	        break;
	    }
	
	}
	for ( var i = 0 ; i<bDepends.length ; i++)
	{
	    if(bDepends[i]._adobeCode == a._adobeCode)
	    {
	        bDependentOnA = true;
	        break;
	    }
	
	}
	
	
	if ((a.GetDominantDependencyType() == "critical" ) && (b.GetDominantDependencyType() == "non-critical") && (aDependentOnB == false))
		return -1;
	if ((a.GetDominantDependencyType() == "non-critical" ) && (b.GetDominantDependencyType() == "critical") && (bDependentOnA == false))
		return 1;
    /* First sorting on dependent count */
	// High dependents first
	var outgoingDelta = b.GetSatisfiedArray().length - a.GetSatisfiedArray().length;
	if (outgoingDelta != 0)
	{
		return outgoingDelta;
	}
	
	/* Second sorting on dependency count */
	// Low dependencies first
	var incomingDelta = aDepends.length - bDepends.length;
	if (incomingDelta != 0)
	{   
	
		return incomingDelta;
	}

	// Low media first
	var mediaWeight1 = 0;
	var mediaWeight2 = 0;
	if ((a.GetSessionData()).MediaInfo && (a.GetSessionData()).MediaInfo.volumeIndex)
		mediaWeight1 = (a.GetSessionData()).MediaInfo.volumeIndex;
	if ((b.GetSessionData()).MediaInfo && (b.GetSessionData()).MediaInfo.volumeIndex)
		mediaWeight2 = (b.GetSessionData()).MediaInfo.volumeIndex;
	var mediaDelta = mediaWeight1 - mediaWeight2;
	if (mediaDelta != 0)
	{
		return mediaDelta;
	}
	
	if(a.isExtensionPayload() && b.isExtensionPayload())
    {
        return a.GetSessionData().tsInstalled < b.GetSessionData().tsInstalled ? -1 : 1;
    }
    
    // Names
	if (a.GetProductName() < b.GetProductName())
	{
		return -1;
	}
	if (a.GetProductName() > b.GetProductName())
	{
		return 1;
	}
       
	// Low AdobeCodes first
	if (a.GetAdobeCode() == b.GetAdobeCode())
		return 0;
	return a.GetAdobeCode() < b.GetAdobeCode() ? -1 : 1;
}


/**
Sort InstallerPayload objects in dependency order for operations.

@param inPayloads an array or map (object) of InstallerPayload objects.
@param inOptReverse optional parameter, if true the sort order is reversed.
*/
function PayloadDependencySort(inPayloads, inOptReverse)
{
	// Transport to an array
	var result = new Array();
	for (var p in inPayloads)
		result.push(inPayloads[p]);

	// Sort-o-matic
	result.sort(PayloadSortOperationOrder);
		
	//Move all extension payloads just after their respective parents.
	var result1 = new Array();
	//temporarily copying the result array into result1
	for(var i= 0 ; i < result.length ; ++i)
	{
	    var payloadA = result[i];
	    var pushback = true;
	    for(var k=0 ; k <result1.length ; k++)
	    {
	        var payloadnew = result1[k];
	        if(payloadnew.GetAdobeCode() == payloadA.GetAdobeCode())
	        {
	            pushback = false;
	            break;
	        }
	    }
	    
	    
	    var pre_array = new Array();
	    var post_array = new Array();
	    
	    for (var j = i+1 ; j < result.length ; ++j)
	    {
	        var payloadB = result[j];
	        if(payloadB.isExtensionPayload() && (payloadB.parentPayload.GetAdobeCode() == payloadA.GetAdobeCode()))
	        {
	            if(payloadB.patchRollbackOrdering && payloadB.patchRollbackOrdering == 1)
	            {
	                pre_array.push(payloadB);
	            }
	            else
	            {
	                post_array.push(payloadB);
	            }
	        }
	    }	   
	    if(pre_array.length > 0)
	        result1 = result1.concat(pre_array);
	    if(pushback == true)
	    {
	        result1.push(payloadA);
	    }
	    if(post_array.length > 0)
	        result1 = result1.concat(post_array); 	    
	}	
	
	// Reverse-o-matic?
	if (inOptReverse)
		result1.reverse();

	//LogPayloadSet(gSession, "PayloadDependencySort result", result);

	return result1;
}


/**
Sort InstallerPayload objects in UI order.

@param inPayloads an array or map (object) of InstallerPayload objects.
@param inSession a session object to determine if a payload is a driver.
*/
function PayloadUISort(inPayloads, inSession)
{
	// Transport to an array
	var result = new Array();
	for (var p in inPayloads)
		result.push(inPayloads[p]);

	var sortpredicate = function(a, b)
	{
		if (inSession)
		{
			if (a.IsDriverForSession(inSession))
				return -1;
			if (b.IsDriverForSession(inSession))
				return 1;
		}
		if (a.GetProductName() == b.GetProductName())
		{
			return 0;
		}
		return a.GetProductName() < b.GetProductName() ? -1 : 1;
	};

	// Sort-o-matic
	result.sort(sortpredicate);
	
	return result;
}


/**
Convenience function to accumulate a map or array of InstallerPayloads into a map.
*/
function AccumulatePayloads(ioMap, inPayloads)
{
	if (inPayloads)
	{
		for (var p in inPayloads)
		{
			ioMap[inPayloads[p].GetAdobeCode()] = inPayloads[p];
		}
	}
	return ioMap;
}


/**
Dumb payload set intersection.
*/
function PayloadIntersect(inSet1, inSet2)
{
	var result = new Array();
	for (var p1 in inSet1)
	{
		for (var p2 in inSet2)
		{
			if (inSet1[p1].GetAdobeCode() == inSet2[p2].GetAdobeCode())
			{
				result.push(inSet1[p1]);
			}
		}
	}
	return result;
}


/**
Dump a set of payloads to log.

@param inSession a session object for access to the LogDebug method.
@param inHeader text written to the log before and after the payload list
@param inPayloads a collection (list or map) of InstallerPayload objects
@param inOptAppend static text or a function to append to each payload logged.
If a function is supplied, the function is passed the InstallerPayload object.
*/
function LogPayloadSet(inSession, inHeader, inPayloads, inOptAppend)
{
	var messages = new Array();
	for (var p in inPayloads)
	{
		var text = "  " + inPayloads[p].LogID();
		if (inOptAppend)
		{
			text += ": ";

			if ("function" == typeof inOptAppend)
			{
				text += inOptAppend(inPayloads[p]);
			}
			else
			{
				text += inOptAppend;
			}
		}
		messages.push(text);
	}
	if (messages.length > 0)
	{
		inSession.LogDebug("BEGIN " + inHeader);
		for (var m in messages)
		{
			inSession.LogDebug(messages[m]);
		}
		inSession.LogDebug("END " + inHeader);
	}
	else
	{
		inSession.LogDebug("EMPTY SET " + inHeader);
	}
}


/**
Clone an object in a way that is not safe for object graphs with cycles.
*/
function CloneAcyclicObject(inSource, inTarget)
{
	for (var eachProp in inSource)
	{
		if (null == inSource[eachProp])
		{
			inTarget[eachProp] = null;
		}
		else if (typeof(inSource[eachProp]) == 'object')
		{
			inTarget[eachProp] = new Object();
			CloneAcyclicObject(inSource[eachProp], inTarget[eachProp]);
		}
		else
		{
			inTarget[eachProp] = inSource[eachProp].valueOf();
		}					
	}		
}


/**
Determine if an object has enumerable properties.
*/
var ContainerNotEmpty = function(inObj)
{
	if (inObj)
	{
		for (var p in inObj)
		{
			return true;
		}
	}
	return false;
};


/**
Given a collection of InstallerPayload objects, return an array
of session names (driver.GetProductName()) associated with the payload(s).
if inOptSession is provided, the driver of that session is excluded
from the list.  Useful for the common case where you want to list
OTHER sessions in which a payload is installed.
*/
function SessionNamesFromPayloads(inPayloadList, inOptSession)
{
	var sessionMap = new Object();
	var addAdobeSetup = false;

	// For each payload
	for (var oldAdobeCode in inPayloadList)
	{
		// find the collections
		var collectionList = inPayloadList[oldAdobeCode].GetCollectionRecords();
		for (var oldCollection in collectionList)
		{
			// Grab the driver's name
			var collectionDriver = collectionList[oldCollection].collection.driverPayload;
			if (collectionDriver)
			{
				if (inOptSession)
				{
					if (!collectionDriver.IsDriverForSession(inOptSession))
					{
						sessionMap[collectionDriver.GetAdobeCode()] = collectionDriver.GetProductName();
					}
				}
				else
				{
					sessionMap[collectionDriver.GetAdobeCode()] = collectionDriver.GetProductName();
				}
			}

			// Or flag to add "Adobe Setup" for collections sans-driver
			else
			{
				addAdobeSetup = true;
			}
		}
	}

	// Assemble an ordered array
	var sessionList = new Array();
	for (var adobeCode in sessionMap)
		sessionList.push(sessionMap[adobeCode]);
	if (addAdobeSetup)
		sessionList.push("Adobe Setup");
	sessionList.sort();

	return sessionList;
};

/*************************************************************************
ColoredLine and System Requirements Checks
**************************************************************************/

// Constructs an InclusiveRange instance.
// null can be used as an endpoint, in which
// case the range is considered to extend to
// infinity in the direction of that endpoint.
// An exception will be thrown if inStart comes after inEnd.
function InclusiveRange(inStart, inEnd)
{
	if ((null != inStart) && (null != inEnd) && (Number(inStart) > Number(inEnd)))
	{
		throw "InclusiveRange cannot be created with endpoints " + Number(inStart) + "and " + Number(inEnd);
	}
	
	this.start = (null != inStart) ? Number(inStart) : null;
	this.end = (null != inEnd) ? Number(inEnd) : null;
}


// Returns the maximum of two range end values, with null being the maximum end
InclusiveRange.prototype.GetMaxEnd = function(inRange)
{
	var result = null;
	
	if ((null != this.end) && (null != inRange.end))
	{
		result = (this.end > inRange.end) ? this.end : inRange.end;
	}
	else
	{
		result = null;
	}
	
	return result;
}


// Returns the minimum of two range end values, with null being the maximum end
InclusiveRange.prototype.GetMinEnd = function(inRange)
{
	var result = null;
	
	if (null != this.end)
	{
		if (null != inRange.end)
		{
			result = (this.end < inRange.end) ? this.end : inRange.end;
		}
		else
		{
			result = this.end;
		}
	}
	else
	{
		result = inRange.end;
	}
	
	return result;
}


// Returns the maximum of two range start values, with null being the minimal start
InclusiveRange.prototype.GetMaxStart = function(inRange)
{
	var result = null;
	
	if (null != this.start)
	{
		if (null != inRange.start)
		{
			result = (this.start > inRange.start) ? this.start : inRange.start;
		}
		else
		{
			result = this.start;
		}
	}
	else
	{
		result = inRange.start;
	}
	
	return result;
}


// Returns the minimum of two range start values, with null being the minimal start
InclusiveRange.prototype.GetMinStart = function(inRange)
{
	var result = null;
	
	if ((null != this.start) && (null != inRange.start))
	{
		result = (this.start < inRange.start) ? this.start : inRange.start;
	}
	else
	{
		result = null;
	}
	
	return result;
}


// returns the relationship between this' start and inRange start.
// negative === this.start comes 'before' inRange.start
// zero === this.start is equivalent to inRange.start
// positive === this.start comes 'after' inRange.start
InclusiveRange.prototype.CompareStart = function(inRange)
{
	var result = 0;
	
	if (null != this.start)
	{
		if (null != inRange.start)
		{
			if (this.start < inRange.start)
			{
				// lhs value < rhs value
				result = -1;
			}
			else if (this.start == inRange.start)
			{
				// lhs value == rhs value
				result = 0;
			}
			else
			{
				// lhs value > rhs value
				result = 1;
			}
		}
		else
		{
			// lhs value > null
			result = 1;
		}
	}
	else
	{
		if (null != inRange.start)
		{
			// null < rhs value
			result = -1;
		}
		else
		{
			// null == null
			result = 0;
		}
	}
	
	return result;
}


// returns the relationship between this' end and inRange end.
// negative === this.end comes 'before' inRange.end
// zero === this.end is equivalent to inRange.end
// positive === this.end comes 'after' inRange.end
InclusiveRange.prototype.CompareEnd = function(inRange)
{
	var result = 0;
	
	if (null != this.end)
	{
		if (null != inRange.end)
		{
			if (this.end < inRange.end)
			{
				// lhs value < rhs value
				result = -1;
			}
			else if (this.end == inRange.end)
			{
				// lhs value == rhs value
				result = 0;
			}
			else
			{
				// lhs value > rhs value
				result = 1;
			}
		}
		else
		{
			// lhs value < null
			result = -1;
		}
	}
	else
	{
		if (null != inRange.end)
		{
			// null > rhs value
			result = 1;
		}
		else
		{
			// null == null
			result = 0;
		}
	}
	
	return result;
}


// Returns a range that represents the intersection of this and the
// specified range, or null if there is no intersection
InclusiveRange.prototype.GetIntersection = function(inRange)
{
	var result = null;
	
	// intersectionStart = max(this.start, inRange.start)
	// null if start is beginning of line
	var intersectionStart = this.GetMaxStart(inRange);
	
	// intersectionEnd = min(this.end, inRange.end)
	// null if end is end of line
	var intersectionEnd = this.GetMinEnd(inRange);
	
	// result is only defined if [start, end] defines a range
	// null in either is considered that endpoint's infinity
	if ((null != intersectionStart) && (null != intersectionEnd))
	{
		if (intersectionStart <= intersectionEnd)
		{
			result = new InclusiveRange(intersectionStart, intersectionEnd);
		}
		else
		{
			result = null;
		}
	}
	else
	{
		result = new InclusiveRange(intersectionStart, intersectionEnd);
	}
	
	return result;
}


// Returns an array of two ranges that represent the inverse of this range
// The first of these is the range that comes before this one
// The second of these is the range that comes after this one
// If there is no before and/or after range, that element in the result
// array will be set to null.  The result of this function is always
// an array of two elements, though those elements may be null.
InclusiveRange.prototype.GetInverse = function()
{
	var result = new Array();
	
	var beforeRange = null;
	if (null != this.start)
	{
		beforeRange = new InclusiveRange(null, this.start - 1);
	}
	result.push(beforeRange);
	
	var afterRange;
	if (null != this.end)
	{
		afterRange = new InclusiveRange(this.end + 1, null);
	}
	result.push(afterRange);
	
	return result;
}


// Returns an array of one or more ranges that represent the union
// of this and the specified range.  The result will always be
// an array and will always contain at least one range.  The ranges
// will be non-overlapping, non-abutting, and will be in sorted
// order such that ranges that are numerically lower on a number line
// appear earlier in the array than ranges that come later.
InclusiveRange.prototype.GetUnion = function(inRange)
{
	var result = new Array();
	
	// If the ranges overlap or abut, the union is one range
	// with their maximal start and end points.
	// If the ranges do not overlap, the union is the two
	// ranges in sorted order.
	var intersection = this.GetIntersection(inRange);
	if (null == intersection)
	{
		// THE RANGES DO NOT OVERLAP, but may abut
		// order them
		var firstRange = null;
		var secondRange = null;
		var minStart = this.GetMinStart(inRange);
		if (minStart == this.start)
		{
			firstRange = this;
			secondRange = inRange;
		}
		else
		{
			firstRange = inRange;
			secondRange = this;
		}
		
		// check for abutting ranges
		if ((firstRange.end + 1) == secondRange.start)
		{
			// the ranges abut, merge them
			result.push(new InclusiveRange(firstRange.start, secondRange.end));
		}
		else
		{
			// the ranges do not abut, result is the two of them in order
			result.push(firstRange);
			result.push(secondRange);
		}
	}
	else
	{
		// THE RANGES OVERLAP
		// result is [minimum start, maximum end]
		var resultStart = this.GetMinStart(inRange);
		var resultEnd = this.GetMaxEnd(inRange);
		result.push(new InclusiveRange(resultStart, resultEnd));
	}
	
	return result;
}


function ColoredLine()
{
	var fullColorlessRange = new InclusiveRange(null, null);
	fullColorlessRange.color = null;
	
	this.coloredRanges = new Array();
	this.coloredRanges.push(fullColorlessRange);
}


// Internal method that replaces adjascent ranges on the colored line
// that have the same color with a single range that represents the
// entire span.
ColoredLine.prototype.MergeAdjascentSameColorRanges = function()
{
	var newColoredRanges = new Array();
	var accumulatedRange = null;
	
	for (var i = 0; i < this.coloredRanges.length; ++i)
	{
		var curRange = this.coloredRanges[i];
		
		if (accumulatedRange)
		{
			if (curRange.color == accumulatedRange.color)
			{
				accumulatedRange.end = curRange.end;
			}
			else
			{
				newColoredRanges.push(accumulatedRange);
				accumulatedRange = curRange;
			}
		}
		else
		{
			accumulatedRange = curRange;
		}
	}
	
	if (accumulatedRange)
	{
		newColoredRanges.push(accumulatedRange);
	}
	
	this.coloredRanges = newColoredRanges;
}


// Paints "holes" in the specified range with the specified color.
// Typically "holes" are those sub-ranges for which a color is not
// already specified, however this default behavior can be overridden
// by specifying an alternate key color.  When a gap key color has been
// specified, only the portion of the range already in that color is painted.
ColoredLine.prototype.PaintGapsInInclusiveRange = function(inRange, inColor, inGapKeyColor)
{
	this.PaintInclusiveRange(inRange,
								  inColor,
								  function(inOriginalColor, inReplacementColor)
								  {
									  var result = inOriginalColor;
									  if (inOriginalColor == inGapKeyColor)
									  {
										  result = inReplacementColor;
									  }
									  return result;
								  }
								 );
}


// Paint the specified range of the line with the specified color.
// If a color function is specified, it is called with the original color
// and the painting color and is responsible for returning the color to actually use.
// If no color function is specified, the paint color is used and overwrites the
// original color completely.
ColoredLine.prototype.PaintInclusiveRange = function(inRange, inColor, inColorFunction)
{
	var newColoredRanges = new Array();
	
	// replace each range with up to three components that exaclty fill that range
	// where the first is the portion of the range prior to the intersection
	// with the painted range, the middle is the portion of the range that
	// represents the intersection, and the last is the portion of the range
	// after the intersection
	for (var i = 0; i < this.coloredRanges.length; ++i)
	{
		var curRange = this.coloredRanges[i];
		var tempRange = null;
		var intersection = curRange.GetIntersection(inRange);
		if (intersection)
		{
			// if there is a segment of curRange before the intersection,
			// keep that segment with its existing color
			if (curRange.CompareStart(intersection) < 0)
			{
				tempRange = new InclusiveRange(curRange.start, intersection.start - 1);
				tempRange.color = curRange.color;
				newColoredRanges.push(tempRange);
			}
			
			// output the intersection in the new color or the one returned
			// by the color function if one has been provided
			if (null != inColorFunction)
			{
				intersection.color = inColorFunction(curRange.color, inColor);
			}
			else
			{
				intersection.color = inColor;
			}
			newColoredRanges.push(intersection);
			
			// if there is a segment of curRange after the intersection,
			// ouput that in the original color
			if (curRange.CompareEnd(intersection) > 0)
			{
				tempRange = new InclusiveRange(intersection.end + 1, curRange.end);
				tempRange.color = curRange.color;
				newColoredRanges.push(tempRange);
			}
		}
		else
		{
			newColoredRanges.push(curRange);
		}
	}
	
	this.coloredRanges = newColoredRanges;
	this.MergeAdjascentSameColorRanges();
}


// Overpaints this line with another one using the specified painting function.
// See PaintInclusiveRange() for details on inColorFunction details.
ColoredLine.prototype.PaintWithLine = function(inPaintLine, inColorFunction)
{
	for (var i = 0; i < inPaintLine.coloredRanges.length; ++i)
	{
		var curPaintRange = inPaintLine.coloredRanges[i];
		
		this.PaintInclusiveRange(curPaintRange, curPaintRange.color, inColorFunction);
	}
}


// Returns the color of the specified point on the line
ColoredLine.prototype.GetPointColor = function(inPointValue)
{
	var resultColor = null;
	
	if (null == inPointValue)
		throw "Invalid null parameter in ColoredLine.GetPaintColor().";
	
	var pointRange = new InclusiveRange(Number(inPointValue), Number(inPointValue));
	for (var i = 0; i < this.coloredRanges.length; ++i)
	{
		var curRange = this.coloredRanges[i];
		if (null != curRange.GetIntersection(pointRange))
		{
			resultColor = curRange.color;
			break;
		}
	}
	
	return resultColor;
}


// Returns whether or not the colored line includes the specified
// inColor (or has a colorless patch if inColor is null)
ColoredLine.prototype.ContainsColor = function(inColor)
{
	var result = false;
	
	for (var i = 0; i < this.coloredRanges.length; ++i)
	{
		if (inColor == this.coloredRanges[i].color)
		{
			result = true;
			break;
		}
	}
	
	return result;
}


// Returns an ordered array of ranges.  Those that have a color have
// a 'color' attribute whose value is the color.  The resulting range
// array represents an entire number line, with each range abutting
// the next.  The ranges are all inclusive ranges, with the endpoints
// having a null value.
ColoredLine.prototype.GetColoredRanges = function()
{
	var result = new Array(this.coloredRanges.length);
	
	for (var i = 0; i < this.coloredRanges.length; ++i)
	{
		var curRange = this.coloredRanges[i];
		var coloredRangeClone = new InclusiveRange(curRange.start, curRange.end);
		coloredRangeClone.color = curRange.color;
		result[i] = coloredRangeClone;
	}
	
	return result;
}


function RunSystemRequirementsCheck(inSession, inOptPayloadList)
{
	var requirementsNotMetErrors;
	var requirementsNotMetWarnings;
	var excludedSystemErrors;
		
	// Setup the payloads to look at, defaulting to the whole session.
	var payloadsToCheck = inOptPayloadList;
	if (null == payloadsToCheck)
	{
		payloadsToCheck = inSession.sessionPayloads;
	}

	try
	{	
		var systemInfo = inSession.systemInfo;
		var systemRequireSet = null;

		var currentMemory = parseInt(systemInfo.Memory.system);		
		var currentDisplayWidth = parseInt(systemInfo.Display.width);
		var currentDisplayHeight = parseInt(systemInfo.Display.height);
		var currentMacOSMajor = 0;
		var currentMacOSMinor = 0;
		var currentMacOSRevision = 0;
		var currentMacOS64Bit = false;
		var currentMacOSServer = false;
		
		var currentPlatform = systemInfo.Macintosh ? "OSX" : "Win32";
		var sysReqTypes = new Array("Default", "OSX", "Win32");
		
		var errorCount = 0;
		var warningCount = 0;
		
		var SystemMemoryWarning = null;
		var SystemMemoryError = null;
		var minSysMemory = 0;
		var excludeSysMemory = 0;	
		var tempMemoryMin = 0;
		var tempMemoryExclude = 0;	
		
		var networkProtocolPorts = new Object;
		
		var DisplayWarning = null;
		var DisplayError = null;		
		var minDisplayWidth = 0;
		var excludeDisplayWidth = 0;
		var minDisplayHeight = 0;
		var excludeDisplayHeight = 0;	

		var CPUWarningMMX = null;		
		var CPUErrorMMX = null;	
		var CPUWarningNX = null;		
		var CPUErrorNX = null;
		var CPUWarningPAE = null;		
		var CPUErrorPAE = null;	
		var CPUWarningPAE = null;		
		var CPUErrorSSE = null;
		var CPUWarningSSE = null;		
		var CPUErrorSSE2 = null;
		var CPUWarningSSE2 = null;		
		var CPUError3DNow = null;
		var CPUWarning3DNow = null;		
		var CPUErrorAltivec = null;	
		var CPUWarningAltivec = null;				
		
		var currentWinOSMajor = 0;
		var currentWinOSMinor = 0;
		var currentWinSPMajor = 0;
		var currentWinSPMinor = 0;
		var currentWinOS64Bit = 0;
		var currentWinOSVariantName = null;
		var WinOSAccumulatedRequirements = new Object();
		var MacHasAnyOSRequirementData = false;
		var MacSupportedOSVersionMin = null;
		var MacSupportedOSPointSamples = null;
		var MacExcludedOSVersionUpperBound = null;
		var MacExcludedOSPointSamples = null;
		var validOSXP = true;
		var validOSVista = true;
		var validOSServer2003 = true;
		var validOSServer2008 = true;
		
		// TODO collect space information for requirements
		// TODO collect space information for the startup disk		
		var neededStartupSpace = 100;
		var haveStartupSpace = 1000;
		var StartupSpaceError = null;
		
		var errorStringMap = new Object();							

		if ("OSX" == currentPlatform)
		{
			currentMacOSMajor = parseInt(systemInfo.Macintosh.VersionMajor);
			currentMacOSMinor = parseInt(systemInfo.Macintosh.VersionMinor);
			currentMacOSRevision = parseInt(systemInfo.Macintosh.VersionDot);
			currentMacOSVersion = String(currentMacOSMajor) + "." + String(currentMacOSMinor) + "." + String(currentMacOSRevision);
			currentMacOS64Bit = systemInfo.Macintosh.Is64Bit ? true : false;
			currentMacOSServer = systemInfo.Macintosh.IsServer ? true : false;	
			
			inSession.properties["OS64Bit"] = systemInfo.Macintosh.Is64Bit ? 1 : 0;			
		}
		else if ("Win32" == currentPlatform)
		{
			currentWinOSMajor = parseInt(systemInfo.Windows.VersionMajor);
			currentWinOSMinor = parseInt(systemInfo.Windows.VersionMinor);
			currentWinSPMajor = parseInt(systemInfo.Windows.SPMajor);
			currentWinSPMinor = parseInt(systemInfo.Windows.SPMinor);						
			currentWinOS64Bit = ("1" == systemInfo.Windows.Is64Bit) ? true : false;
			var windowsType = parseInt(systemInfo.Windows.ProductType);
			
			if ((currentWinOSMajor == 6) && (currentWinOSMinor == 0))
			{
				if (1 == windowsType)
				{
					validOSServer2008 = false;
					validOSVista = true;
				}
				else
				{
				 	validOSVista = false;	
					validOSServer2008 = true;				 			
				}	
			}
			else
			{
					validOSServer2008 = false;
					validOSVista = false;
			}

			if (!(5 == currentWinOSMajor && 1 == currentWinOSMinor))
				validOSXP = false;
			
			if ((5 == currentWinOSMajor) &&  (2 == currentWinOSMinor))
			{
				if (1 == windowsType)
				{
					validOSServer2003 = false;
					validOSXP = true;
				}
				else
				{
				 	validOSXP = false;	
					validOSServer2003 = true;				 			
				}	
			}
			else
			{
				validOSServer2003 = false;
			}
						
			if (validOSXP)
				currentWinOSVariantName = "XP";
			else if (validOSVista)
				currentWinOSVariantName = "Vista";
			else if (validOSServer2003)
				currentWinOSVariantName = "Server2003";
			else if (validOSServer2008)
				currentWinOSVariantName = "Server2008";
			else
				currentWinOSVariantName = null;
			
			inSession.properties["OS64Bit"] = ("1" == systemInfo.Windows.Is64Bit) ? 1 : 0;
		}	
		
		// Startup space checks
		if (neededStartupSpace > haveStartupSpace)
		{
			errorStringMap["neededStartupSpace"] = bytesToText(neededStartupSpace * 1048576);
			StartupSpaceError = new LocalizedString("systemPageHardDiskSpaceTarget", null, errorStringMap);
		
			errorCount++;
		}

		// For each payload...
		for (var p in payloadsToCheck)
		{
			// If it has system requirements...
			systemRequireSet = _jsonToObject(payloadsToCheck[p].GetSessionData().SystemRequirements);
			
			if (systemRequireSet && systemRequireSet[0])
			{
				inSession.LogInfo("System Requirements for payload with name : " + payloadsToCheck[p].GetSessionData().ProductName  + " and AdobeCode: " + payloadsToCheck[p].GetSessionData().AdobeCode + "  is: \n " + payloadsToCheck[p].GetSessionData().SystemRequirements + "\n" );
				for (var i = 0; i < systemRequireSet.length; i++)
				{
					// System Memory =========================================================
					if (systemRequireSet[i].Memory)
					{
						if (systemRequireSet[i].Memory.System)
						{
							var accessPoint = null;
							
							// For each of Default, OSX, and Win32
							for (var k = 0; k < sysReqTypes.length; k++) 
							{
								
								if ("OSX" == sysReqTypes[k])
									accessPoint = systemRequireSet[i].Memory.System.OSX;
								else if ("Win32" == sysReqTypes[k])
									accessPoint = systemRequireSet[i].Memory.System.Win32;
								else
									accessPoint = systemRequireSet[i].Memory.System.Default;
								
								// Only check Default and the current platform
								if (accessPoint && ((sysReqTypes[k] == "Default") || (sysReqTypes[k] == currentPlatform)))
								{
									tempMemoryMin = 0;
									tempMemoryExclude = 0;
									
									if (accessPoint.Require)
										tempMemoryMin = parseInt(accessPoint.Require);
									
									if (accessPoint.Exclude)
										tempMemoryExclude = parseInt(accessPoint.Exclude);
									
									// Only update min and exclude if they are greater than the previous runs
									if (tempMemoryMin > minSysMemory)
										minSysMemory = tempMemoryMin;
									
									if (tempMemoryExclude > excludeSysMemory)
										excludeSysMemory = tempMemoryExclude;
									
									if (currentMemory <= excludeSysMemory)
									{
										if (currentMemory < minSysMemory)
										{
											errorStringMap["minMemory"] = bytesToText(minSysMemory * 1048576);
											SystemMemoryError = new LocalizedString("systemPageMemoryRequire", null, errorStringMap);
										}
										else
										{
											errorStringMap["excludeMemory"] = bytesToText(excludeSysMemory * 1048576);
											SystemMemoryError = new LocalizedString("systemPageMemoryExclude", null, errorStringMap);
										}
										errorCount++;
									}
									else if (currentMemory < minSysMemory)
									{
										errorStringMap["minMemory"] = bytesToText(minSysMemory * 1048576);
										SystemMemoryWarning = new LocalizedString("systemPageMemoryRequire", null, errorStringMap);
										warningCount++;
									}
								}
							}
						}
					}
					
					// Networking ======================================================
					if (systemRequireSet[i].Networking)
					{
						// Accumulate network port entries into networkProtocolPorts
						var portEntry = systemRequireSet[i].Networking.Port;
						if (portEntry)
						{
							var portNumber = portEntry.number;
							if (portNumber)
							{
								var theProtocol = portEntry["protocol"];
								if (null == theProtocol)
								{
									theProtocol = "tcp";
								}
								
								var isOptional = Number(portEntry["optional"]);
								if (isOptional != 1)
								{
									if (null == networkProtocolPorts[theProtocol])
										networkProtocolPorts[theProtocol] = new Object;
								
									networkProtocolPorts[theProtocol][String(portNumber)] = "warn";							
								}
							}
							else
							{
								inSession.LogWarning("Networking system requirements port entry exists without a port number.");
							}
						}
						else
						{
							inSession.LogWarning("Networking system requirements entry exists without a port entry.");
						}
					}
					
					// Display =========================================================
					if (systemRequireSet[i].Display)
					{
						var accessPoint = null;
						
						// For each of Default, OSX, and Win32
						for (var k = 0; k < sysReqTypes.length; k++) 
						{
							
							if ("OSX" == sysReqTypes[k])
								accessPoint = systemRequireSet[i].Display.OSX;
							else if ("Win32" == sysReqTypes[k])
								accessPoint = systemRequireSet[i].Display.Win32;
							else
								accessPoint = systemRequireSet[i].Display.Default;
							
							// Only check Default and the current platform
							if (accessPoint && ((sysReqTypes[k] == "Default") || (sysReqTypes[k] == currentPlatform)))
							{						
								tempWidthMin = 0;
								tempWidthExclude = 0;
								tempHeightMin = 0;
								tempHeightExclude = 0;							
								
								if (accessPoint.Require && accessPoint.Require.Width)
									tempWidthMin = parseInt(accessPoint.Require.Width);
								
								if (accessPoint.Require && accessPoint.Require.Height)
									tempHeightMin = parseInt(accessPoint.Require.Height);
								
								if (accessPoint.Exclude && accessPoint.Exclude.Width)
									tempWidthExclude = parseInt(accessPoint.Exclude.Width);
								
								if (accessPoint.Exclude && accessPoint.Exclude.Height)
									tempHeightExclude = parseInt(accessPoint.Exclude.Height);
								
								// Only update min and exclude for width and height if they are greater than the previous run
								if (tempWidthMin > minDisplayWidth)
									minDisplayWidth = tempWidthMin;
								
								if (tempHeightMin > minDisplayHeight)
									minDisplayHeight = tempHeightMin;
								
								if (tempWidthExclude > excludeDisplayWidth)
									excludeDisplayWidth = tempWidthExclude;
								
								if (tempHeightExclude > excludeDisplayHeight)
									excludeDisplayHeight = tempHeightExclude;								
								
								if (currentDisplayWidth <= excludeDisplayWidth || currentDisplayHeight <= excludeDisplayHeight)
								{
									if (currentDisplayWidth < minDisplayWidth || currentDisplayHeight < minDisplayHeight)
									{
										errorStringMap["minMonitorResW"] = minDisplayWidth;
										errorStringMap["minMonitorResH"] = minDisplayHeight;
										DisplayError = new LocalizedString("systemPageMonitorRequire", null, errorStringMap);
									}
									else
									{
										errorStringMap["excludeMonitorResW"] = minDisplayWidth;
										errorStringMap["excludeMonitorResH"] = minDisplayHeight;
										DisplayError = new LocalizedString("systemPageMonitorExclude", null, errorStringMap);										
									}
									errorCount++;
								}
								else if (currentDisplayWidth < minDisplayWidth || currentDisplayHeight < minDisplayHeight)
								{
									errorStringMap["minMonitorResW"] = minDisplayWidth;
									errorStringMap["minMonitorResH"] = minDisplayHeight;
									DisplayWarning = new LocalizedString("systemPageMonitorRequire", null, errorStringMap);
									warningCount++;
								}
							}
						}
					}					

					// CPU =========================================================
					if (systemRequireSet[i].CPU)
					{
						var accessPoint = null;
						
						for (var k = 0; k < sysReqTypes.length; k++) 
						{

							if ("OSX" == sysReqTypes[k])
								accessPoint = systemRequireSet[i].CPU.OSX;
							else if ("Win32" == sysReqTypes[k])
								accessPoint = systemRequireSet[i].CPU.Win32;
							else
								accessPoint = systemRequireSet[i].CPU.Default;
				
							// Only check Default and the current platform
							if (accessPoint && ((sysReqTypes[k] == "Default") || (sysReqTypes[k] == currentPlatform)))
							{
								// Error for requirements not met 
								if (accessPoint.Require && systemInfo.CPU)
								{
									if (accessPoint.Require.MMX) {
										if (systemInfo.CPU.MMX) {
											if ("1" != systemInfo.CPU.MMX)
											{
												CPUErrorMMX = new LocalizedString("systemPageCPURequireMMX");
												errorCount++;
											}
										}
										else 
										{
											CPUErrorMMX = new LocalizedString("systemPageCPURequireMMX");
											errorCount++;
										}
									}
												
									if (accessPoint.Require.NX) {
										if (systemInfo.CPU.NX)
										{
											if ("1" != systemInfo.CPU.NX)
											{
												CPUErrorNX = new LocalizedString("systemPageCPURequireNX");
												errorCount++;
											}	
										}
										else 
										{
											CPUErrorNX = new LocalizedString("systemPageCPURequireNX");
											errorCount++;	
										}
									}
												
									if (accessPoint.Require.PAE) {
										if (systemInfo.CPU.PAE)
										{
											if ("1" != systemInfo.CPU.PAE)
											{
												CPUErrorPAE = new LocalizedString("systemPageCPURequirePAE");
												errorCount++;
											}	
										}
										else 
										{
											CPUErrorPAE = new LocalizedString("systemPageCPURequirePAE");
											errorCount++;	
										}										
									}												

									if (accessPoint.Require.SSE)
									{
										if (systemInfo.CPU.SSE)
										{
											if ("1" != systemInfo.CPU.SSE)
											{
												CPUErrorSSE = new LocalizedString("systemPageCPURequireSSE");
												errorCount++;
											}
										}
										else 
										{
											CPUErrorSSE = new LocalizedString("systemPageCPURequireSSE");
											errorCount++;	
										}
									}
												
									// #1639101 - We treat SSE2 equivalent to PPC
									if (accessPoint.Require.SSE2) 
									{
										if (systemInfo.CPU.SSE2)
										{	
											if ("1" != systemInfo.CPU.SSE2)
											{
												CPUErrorSSE2 = new LocalizedString("systemPageCPUPPCNotValid");
												errorCount++;
												payloadsToCheck[p].failedSystemRequirements["CPUErrorSSE2"] = true;
											}	
										}
										else 
										{
											CPUErrorSSE2 = new LocalizedString("systemPageCPUPPCNotValid");
											errorCount++;	
											payloadsToCheck[p].failedSystemRequirements["CPUErrorSSE2"] = true;
										}
									}												

									if (accessPoint.Require.ThreeDNOW)
									{
										if (systemInfo.CPU.ThreeDNOW)
										{
											if ("1" != systemInfo.CPU.ThreeDNOW)
											{
												CPUError3DNow = new LocalizedString("systemPageCPURequire3DNow");
												errorCount++;
											}	
										}
										else 
										{
											CPUError3DNow = new LocalizedString("systemPageCPURequire3DNow");
											errorCount++;	
										}
									}											

									if (accessPoint.Require.AltiVec)
									{
										if (systemInfo.CPU.Altivec)
										{
											if ("1" != systemInfo.CPU.Altivec)
											{
												CPUErrorAltivec = new LocalizedString("systemPageCPURequireAltiVec");
												errorCount++;
											}
										} 
										else 
										{
											CPUErrorAltivec = new LocalizedString("systemPageCPURequireAltiVec");
											errorCount++;	
										}	
									}	
								}

								// Error for exclusions encountered
								if (accessPoint.Exclude)
								{
									if (accessPoint.Exclude.MMX) {
										if (systemInfo.CPU.MMX) {
											CPUErrorMMX = new LocalizedString("systemPageCPUExcludeMMX");
											errorCount++;
										}
									}

									if (accessPoint.Exclude.NX) {
										if (systemInfo.CPU.NX)
										{
											CPUErrorNX = new LocalizedString("systemPageCPUExcludeNX");
											errorCount++;	
										}
									}

									if (accessPoint.Exclude.PAE) {
										if (systemInfo.CPU.PAE)
										{
											CPUErrorPAE = new LocalizedString("systemPageCPUExcludePAE");
											errorCount++;	
										}										
									}												

									if (accessPoint.Exclude.SSE)
									{
										if (systemInfo.CPU.SSE)
										{
											CPUErrorPAE = new LocalizedString("systemPageCPUExcludeSSE");
											errorCount++;	
										}
									}

									if (accessPoint.Exclude.SSE2) 
									{
										if (systemInfo.CPU.SSE2)
										{	
												CPUErrorSSE2 = new LocalizedString("systemPageCPUExcludeSSE2");
												errorCount++;	
										}
									}												

									if (accessPoint.Exclude.ThreeDNOW)
									{
										if (systemInfo.CPU.ThreeDNOW)
										{
											CPUError3DNow = new LocalizedString("systemPageCPUExclude3DNow");
											errorCount++;
										}
									}											

									if (accessPoint.Exclude.AltiVec)
									{
										if (systemInfo.CPU.Altivec)
										{
											CPUErrorAltivec = new LocalizedString("systemPageCPUExcludeAltiVec");
											errorCount++;
										} 
									}
								}
	
							}
						}
					}  

					// Operating System =========================================================
					if (systemRequireSet[i].OS)
					{
						var accessPoint = null;
						
						// Mac OS =========================================================	
						if (systemRequireSet[i].OS.Macintosh && ("OSX" == currentPlatform))
						{
							accessPoint = systemRequireSet[i].OS.Macintosh;
							MacHasAnyOSRequirementData = true;
							
							// Determine supported OS for current payload and
							// intersect with the overall supported OS values
							if (accessPoint.Require)
							{
								var payloadMacSupportedOSVersionMin = null;
								var payloadMacSupportedOSPointSamples = null;
								var payloadMacSupportedOSRequires64Bit = false;
								var payloadMacSupportedOSRequiresServer = false;
								
								// accumulate required version & point samples from payload
								for (var requireIndex = 0; requireIndex < accessPoint.Require.length; ++requireIndex)
								{
									var curRequire = accessPoint.Require[requireIndex];
									if ((null == curRequire["@lowerBound"]) || ("1" == curRequire["@lowerBound"]))
									{
										// require is a range, merge it with the existing payload range
										if (null == payloadMacSupportedOSVersionMin)
										{
											payloadMacSupportedOSVersionMin = curRequire;
										}
										else
										{
											payloadMacSupportedOSVersionMin = MaximizeRequiredRanges(payloadMacSupportedOSVersionMin, curRequire);
										}
									}
									else
									{
										// require is a point sample, add it to the set of payload point samples
										if (null == payloadMacSupportedOSPointSamples)
											payloadMacSupportedOSPointSamples = new Object();
										
										UnionRequiredPointSampleWithSet(payloadMacSupportedOSPointSamples, curRequire);
									}
								}
								
								// intersect payload required data with installer set
								if (payloadMacSupportedOSVersionMin)
								{
									if (MacSupportedOSVersionMin)
										MacSupportedOSVersionMin = MaximizeRequiredRanges(MacSupportedOSVersionMin, payloadMacSupportedOSVersionMin);
									else
										MacSupportedOSVersionMin = payloadMacSupportedOSVersionMin;
								}
								if (payloadMacSupportedOSPointSamples)
								{
									if (MacSupportedOSPointSamples)
										MacSupportedOSPointSamples = IntersectPointSampleSets(MacSupportedOSPointSamples, payloadMacSupportedOSPointSamples);
									else
										MacSupportedOSPointSamples = payloadMacSupportedOSPointSamples;
								}
							}
							
							if (accessPoint.Exclude)
							{
								var payloadMacExcludedOSVersionUpperBound = null;
								var payloadMacExcludedOSPointSamples = null;
								
								// accumulate exclude version & point samples from payload
								for (var excludeIndex = 0; excludeIndex < accessPoint.Exclude.length; ++excludeIndex)
								{
									var curExclude = accessPoint.Exclude[excludeIndex];
									
									// only check exclude if the OS attributes indicate it's appropriate to "include"
									if ((currentMacOS64Bit || ("1" != curExclude.Only64Bit))
										&& (currentMacOSServer || ("1" != curExclude.OnlyServer)))
									{
										if ((null == curExclude["@upperBound"]) || ("1" == curExclude["@upperBound"]))
										{
											// exclude is a range, merge it with the existing payload exclude range
											if (null == payloadMacExcludedOSVersionUpperBound)
											{
												payloadMacExcludedOSVersionUpperBound = curExclude.Version;
											}
											else
											{
												// "highest" exclude version takes precedence
												if (1 == compareVersions(curExclude.Version, payloadMacExcludedOSVersionUpperBound))
													payloadMacExcludedOSVersionUpperBound = curExclude.Version;
											}
										}
										else
										{
											// exclude is a point sample, add it to the set of payload exclude point samples
											if (null == payloadMacExcludedOSPointSamples)
												payloadMacExcludedOSPointSamples = new Object();
											
											payloadMacExcludedOSPointSamples[curExclude.Version] = 1;
										}
									}
								}
								
								// intersect payload exclusion data with installer set
								if (payloadMacExcludedOSVersionUpperBound)
								{
									if (MacExcludedOSVersionUpperBound)
									{
										if (1 == compareVersions(payloadMacExcludedOSVersionUpperBound, MacExcludedOSVersionUpperBound))
											MacExcludedOSVersionUpperBound = payloadMacExcludedOSVersionUpperBound;
									}
									else
									{
										MacExcludedOSVersionUpperBound = payloadMacExcludedOSVersionUpperBound;
									}
								}
								
								if (payloadMacExcludedOSPointSamples)
								{
									if (null == MacExcludedOSPointSamples)
										MacExcludedOSPointSamples = new Object();
									for (var aKey in payloadMacExcludedOSPointSamples)
									{
										MacExcludedOSPointSamples[aKey] = 1;
									}
								}
							}
						}
						
						// Win OS =========================================================
						if (systemRequireSet[i].OS.Windows && ("Win32" == currentPlatform))
						{
							// iterate through the Windows variant requirements
							for (var curVariantKey in systemRequireSet[i].OS.Windows)
							{								
								var curVariant = systemRequireSet[i].OS.Windows[curVariantKey];
								var curAccumulatedRequirements = new ColoredLine();
								
								// "paint" the payload's requirement line with all Exclusions
								if (curVariant.Exclude)
								{
									var curExclude = curVariant.Exclude;
									
									// Exclusions are only taken into account if the "Only" Exclude attributes
									// are met by the current OS
									if (currentWinOS64Bit || ("1" != curExclude.Only64Bit))
									{
										var maxSPValue = null;
										if (currentWinOS64Bit && curExclude["@servicePack64Bit"])
											maxSPValue = curExclude["@servicePack64Bit"];
										if (!maxSPValue && curExclude.MaxServicePack)
											maxSPValue = curExclude.MaxServicePack;
																																														
										if (maxSPValue)
										{
											// service pack specified, exclude that SP or up to and including that SP
											// based on @uppperBound
											if ((null != curExclude["@upperBound"])
												&& ("0" == curExclude["@upperBound"]))
											{
												// current Exclude is a point sample, paint a range of just that point
												curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(maxSPValue, maxSPValue), "exclude");
											}
											else
											{
												// current Exclude is a range, paint the range up to and including that point
												curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(null, maxSPValue), "exclude");
											}
										}
										else
										{
											// no service pack specified, exclude entire OS variant
											curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(null, null), "exclude");
										}
									}
								}

								// "paint" the payload's requirement line with all Require
								// Require trumps exclude in a single payload
								if (curVariant.Require)
								{
									var curRequire = curVariant.Require;

									var minSPValue = null;
									if (currentWinOS64Bit && curRequire["@servicePack64Bit"])
										minSPValue = curRequire["@servicePack64Bit"];
									if (!minSPValue && curRequire.MinServicePack)
										minSPValue = curRequire.MinServicePack;
									
									if (minSPValue)
									{
										// if Require specifies a minimum service pack, include that SP or that SP and greater
										// based on @lowerBound
										if  ((null != curRequire["@lowerBound"])
											 && ("0" == curRequire["@lowerBound"]))
										{
											// current Require is a point sample, paint a range of just that point
											curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(minSPValue, minSPValue), "require");
										}
										else
										{
											// current Require is a range, paint the range from that point to infinity
											curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(minSPValue, null), "require");
										}
									}
									else
									{
										// no service pack specified, 'require' entire OS variant
										curAccumulatedRequirements.PaintInclusiveRange(new InclusiveRange(null, null), "require");
									}
									
									// 64-bit property is sticky
									if ("1" == curRequire.Need64Bit)
										curAccumulatedRequirements.need64Bit = true;
								}
								
								
								// Merge this payload's requirement with the combined accumulation
								if (null == WinOSAccumulatedRequirements[curVariantKey])
								{
									WinOSAccumulatedRequirements[curVariantKey] = curAccumulatedRequirements;
								}
								else
								{
									WinOSAccumulatedRequirements[curVariantKey].PaintWithLine(curAccumulatedRequirements,
											function(inOriginalColor, inPaintColor)
											{
												var colorToUse = inOriginalColor;
												
												// when merging requirements from payloads,
												// Exclude values union and trump Require
												// Require values intersect
												if ("exclude" == inPaintColor)
												{
													colorToUse = "exclude";
												}
												else if ("require" == inPaintColor)
												{
													if ("exclude" == inOriginalColor)
													{
														colorToUse = "exclude";
													}
													else if ("require" == inOriginalColor)
													{
														colorToUse = "require";
													}
													else
													{
														colorToUse = null;
													}
												}
												else
												{
													colorToUse = inOriginalColor;
												}
												
												return colorToUse;
											}
										);
									
									if (curAccumulatedRequirements.need64Bit)
										WinOSAccumulatedRequirements[curVariantKey].need64Bit = true;
								}
							}
						} // WinOS
					} // OS
				} // System requirement set [i]
			} // System requirements
		} // Payloads
		
		
		// Check whether or not the current Windows OS is supported
		var isSupportedWinOS = false;
		var isExcludedWinOS = false;
		if (WinOSAccumulatedRequirements)
		{
			if (null != currentWinOSVariantName)
			{
				var curVariantLine = WinOSAccumulatedRequirements[currentWinOSVariantName];
				if (curVariantLine)
				{
					var osColor = curVariantLine.GetPointColor(currentWinSPMajor);
					if (("exclude" == osColor) || (!currentWinOS64Bit && curVariantLine.need64Bit))
					{
						isSupportedWinOS = false;
						isExcludedWinOS = true;
					}
					else if ("require" == osColor)
					{
						isSupportedWinOS = true;
						isExcludedWinOS = false;
					}
					else
					{
						isSupportedWinOS = false;
						isExcludedWinOS = false;
					}
				}
			}
		}
		if (!isSupportedWinOS)
		{
			if (isExcludedWinOS)
				++errorCount;
			else
				++warningCount;
		}
		
		
		// determine whether or not there is an OS version problem on MacOS
		var isSupportedMacOS = !MacHasAnyOSRequirementData;
		var isExcludedMacOS = false;
		if (MacHasAnyOSRequirementData)
		{
			if (MacSupportedOSVersionMin)
			{
				// in supported range?
				isSupportedMacOS = ((compareVersions(currentMacOSVersion, MacSupportedOSVersionMin.Version) >= 0)
									&& (currentMacOS64Bit || (1 != MacSupportedOSVersionMin.Need64Bit))
									&& (currentMacOSServer || (1 != MacSupportedOSVersionMin.NeedServer)));
			}
			if (!isSupportedMacOS && MacSupportedOSPointSamples)
			{
				// in list of supported specific OSes?
				for (var aSupportedPointKey in MacSupportedOSPointSamples)
				{
					var aSupportedVersion = MacSupportedOSPointSamples[aSupportedPointKey];
					isSupportedMacOS = ((0 == compareVersions(currentMacOSVersion, aSupportedVersion.Version))
										&& (currentMacOS64Bit || (1 != aSupportedVersion.Need64Bit))
										&& (currentMacOSServer || (1 != aSupportedVersion.NeedServer)));
					if (isSupportedMacOS)
						break;
				}
			}
			if (MacExcludedOSVersionUpperBound)
			{
				// in excluded range?
				isExcludedMacOS = (compareVersions(currentMacOSVersion, MacExcludedOSVersionUpperBound) < 0);
			}
			if (!isExcludedMacOS && MacExcludedOSPointSamples)
			{
				// in list of excluded specific OSes?
				for (var anExcludedVersion in MacExcludedOSPointSamples)
				{
					isExcludedMacOS = (0 == compareVersions(currentMacOSVersion, anExcludedVersion));
					if (isExcludedMacOS)
						break;
				}
			}
		}
		if (!isSupportedMacOS)
		{
			if (isExcludedMacOS)
				++errorCount;
			else
				++warningCount;
		}
		
		
		// add network port errors if there are any
		var unavailableProtocolPorts = new Object;
		for (var aNetworkProtocolName in networkProtocolPorts)
		{
			// convert port dict to a numerically sorted array of port numbers to check
			var portArray = new Array();
			
			var aNetworkProtocolPortDict = networkProtocolPorts[aNetworkProtocolName];
			for (var aNetworkPort in aNetworkProtocolPortDict)
			{
				portArray.push(parseInt(aNetworkPort));
			}
			portArray.sort(function(a, b) { return a - b; });
			
			for (var portIndex = 0; portIndex < portArray.length; ++portIndex)
			{
				var portCheckResult = inSession.IsPortForProtocolAvailable(portArray[portIndex], aNetworkProtocolName);
				var portIsAvailable = (portCheckResult && portCheckResult.success);
				if (!portIsAvailable)
				{
					if (null == unavailableProtocolPorts[aNetworkProtocolName])
						unavailableProtocolPorts[aNetworkProtocolName] = new Array;
					
					unavailableProtocolPorts[aNetworkProtocolName].push(portArray[portIndex]);
					++warningCount;
				}
			}
		}
		
		// Display the system requirements errors
		if (errorCount > 0 || warningCount > 0) 
		{	
			requirementsNotMetErrors = new Array();
			requirementsNotMetWarnings = new Array();
			excludedSystemErrors = new Array();
			
			// Add the actual errors and the "correct" values for requirements
			
			// if there was a Win version problem, report it
			if (!isSupportedWinOS)
			{
				// if there are any require's, tell the user about the
				// required OS varieties, otherwise tell the user about
				// the excluded OS varieties
				var hasAnyRequire = false;
				for (var curVariantKey in WinOSAccumulatedRequirements)
				{
					var curVariantLine = WinOSAccumulatedRequirements[curVariantKey];
					
					if (curVariantLine.ContainsColor("require"))
					{
						hasAnyRequire = true;
						break;
					}
				}
				
				if (hasAnyRequire)
				{
					// if the OS is excluded, it's an error; if it's simply not supported, show warning
					var listToReportTo = isExcludedWinOS ? requirementsNotMetErrors : requirementsNotMetWarnings;
					
					// list required OS versions
					for (var curVariantKey in WinOSAccumulatedRequirements)
					{
						var curVariantLine = WinOSAccumulatedRequirements[curVariantKey];
						
						var curColoredRanges = curVariantLine.GetColoredRanges();
						for (var i = 0; i < curColoredRanges.length; ++i)
						{
							var curRange = curColoredRanges[i];
							
							if ("require" == curRange.color)
							{
								var formedOSDescription = DescribeWinOS(curVariantKey,
																			 curRange,
																			 currentWinOS64Bit);
								listToReportTo.push(formedOSDescription);
							}
						}
					}
				}
				else
				{
					// list excluded OS versions
					for (var curVariantKey in WinOSAccumulatedRequirements)
					{
						var curVariantLine = WinOSAccumulatedRequirements[curVariantKey];
						
						var curColoredRanges = curVariantLine.GetColoredRanges();
						for (var i = 0; i < curColoredRanges.length; ++i)
						{
							var curRange = curColoredRanges[i];
							
							if ("exclude" == curRange.color)
							{
								var formedOSDescription = DescribeWinOS(curVariantKey,
																			 curRange,
																			 currentWinOS64Bit);
								excludedSystemErrors.push(formedOSDescription);
							}
						}
					}
				}
			}
			
			
			// if there was a MacOS version problem, report it
			if (!isSupportedMacOS)
			{
				// post ranged requirement
				if (MacSupportedOSVersionMin)
				{
					var requirementString = RequireToString(MacSupportedOSVersionMin);
					if (isExcludedMacOS)
						requirementsNotMetErrors.push(requirementString);
					else
						requirementsNotMetWarnings.push(requirementString);
				}
				
				// post point requirements
				if (MacSupportedOSPointSamples)
				{
					for (var aSupportedPointKey in MacSupportedOSPointSamples)
					{
						var aSupportedVersion = MacSupportedOSPointSamples[aSupportedPointKey];
						var requirementString = RequireToString(aSupportedVersion);
						if (isExcludedMacOS)
							requirementsNotMetErrors.push(requirementString);
						else
							requirementsNotMetWarnings.push(requirementString);
					}
				}
			}
			
			// if there were any networking port problems, report them
			for (var aProtocolName in unavailableProtocolPorts)
			{
				var unavailablePorts = unavailableProtocolPorts[aProtocolName];
				for (var aPortIndex = 0; aPortIndex < unavailablePorts.length; ++aPortIndex)
				{
					var errorStringMap = new Object;
					errorStringMap["protocol"] = aProtocolName;
					errorStringMap["port"] = unavailablePorts[aPortIndex];
					requirementsNotMetWarnings.push(new LocalizedString("systemPageUnavailablePort", null, errorStringMap));
				}
			}
			
			if (errorCount > 0) {
				// Insert errors for all failures
				if (StartupSpaceError)
					requirementsNotMetErrors.push(StartupSpaceError);	
				if (SystemMemoryError)
					requirementsNotMetErrors.push(SystemMemoryError);
				if (DisplayError)
					requirementsNotMetErrors.push(DisplayError);
				if (CPUErrorMMX)
					requirementsNotMetErrors.push(CPUErrorMMX);
				if (CPUErrorNX)
					requirementsNotMetErrors.push(CPUErrorNX);
				if (CPUErrorPAE)
					requirementsNotMetErrors.push(CPUErrorPAE);
				if (CPUErrorSSE)
					requirementsNotMetErrors.push(CPUErrorSSE);
				if (CPUErrorSSE2)
					requirementsNotMetErrors.push(CPUErrorSSE2);			
				if (CPUError3DNow)
					requirementsNotMetErrors.push(CPUError3DNow);
				if (CPUErrorAltivec)
					requirementsNotMetErrors.push(CPUErrorAltivec);
			}
			
			if (warningCount > 0) 
			{
				// Insert warnings for all failures
				if (!SystemMemoryError && SystemMemoryWarning) 
					requirementsNotMetWarnings.push(SystemMemoryWarning);
				if (!DisplayError && DisplayWarning) 
					requirementsNotMetWarnings.push(DisplayWarning);
				if (!CPUErrorMMX && CPUWarningMMX) 
					requirementsNotMetWarnings.push(CPUWarningMMX);
				if (!CPUErrorNX && CPUWarningNX) 
					requirementsNotMetWarnings.push(CPUWarningNX);				
				if (!CPUErrorPAE && CPUWarningPAE) 
					requirementsNotMetWarnings.push(CPUWarningPAE);
				if (!CPUErrorSSE && CPUWarningSSE) 
					requirementsNotMetWarnings.push(CPUWarningSSE);
				if (!CPUErrorSSE2 && CPUWarningSSE2) 
					requirementsNotMetWarnings.push(CPUWarningSSE2);
				if (!CPUError3DNow && CPUWarning3DNow) 
					requirementsNotMetWarnings.push(CPUWarning3DNow);				
				if (!CPUErrorAltivec && CPUWarningAltivec) 
					requirementsNotMetWarnings.push(CPUWarningAltivec); 											
			}		
		}	
	}
	catch(e)
	{
		inSession.LogWarning("Exception while testing system requirements.");
		inSession.LogWarning(listProperties(e, "exception"));
	}	

	return new Array(requirementsNotMetErrors, requirementsNotMetWarnings, excludedSystemErrors);	
}

// maximizes all aspects of the required range
// version is set to the maximum, and the most
// restrictive set of flags is set in the result
// returns a new version that is a maximal intersection of the two ranges
function MaximizeRequiredRanges(lhs, rhs)
{
	var newVersion = new Object();
	
	// max version field
	if (compareVersions(lhs.Version, rhs.Version) < 0)
		newVersion.Version = rhs.Version;
	else
		newVersion.Version = lhs.Version;
	
	// 'max' OS flags by having set trump not set
	newVersion.Need64Bit = lhs.Need64Bit | rhs.Need64Bit;
	newVersion.NeedServer = lhs.NeedServer | rhs.NeedServer;
	
	return newVersion;
}


// the point intersection is empty if the versions don't match,
// and the more restrictive set of flags if the versions do match
// returns the intersection of the two versions, or null if they don't 'overlap'
function IntersectRequiredPointSamples(lhs, rhs)
{
	var result = null;
	
	if (0 == compareVersions(lhs.Version, rhs.Version))
	{
		result = new Object();
		result.Version = lhs.Version;
		result.Need64Bit = ((1 == lhs.Need64Bit) || (1 == rhs.Need64Bit)) ? 1 : 0;
		result.NeedServer = ((1 == lhs.NeedServer) || (1 == rhs.NeedServer)) ? 1 : 0;
	}
	
	return result;
}


// Adds the point sample to the set of point sample, unioning
// in the event of a collision.  This manipulates ioSet in place
function UnionRequiredPointSampleWithSet(ioSet, pointSample)
{
	var updatedSample = null;
	var overlappingSample = ioSet[pointSample.Version];
	if (overlappingSample)
	{
		updatedSample = new Object();
		updatedSample.Version = pointSample.Version;
		updatedSample.Need64Bit = ((1 == lhs.Need64Bit) && (1 == rhs.Need64Bit)) ? 1 : 0;
		updatedSample.NeedServer = ((1 == lhs.NeedServer) && (1 == rhs.NeedServer)) ? 1 : 0;
	}
	else
	{
		updatedSample = pointSample;
	}
	
	ioSet[pointSample.Version] = updatedSample;
}


// intersects two point sample sets, returning the intersetion
// the intersection could be an empty set, but it will never be null
function IntersectPointSampleSets(lhsSet, rhsSet)
{
	var result = new Object;
	
	for (var aKey in lhsSet)
	{
		var lhsValue = lhsSet[aKey];
		var rhsValue = rhsSet[aKey];
		if (lhsValue && rhsValue)
		{
			var intersectionValue = IntersectRequiredPointSamples(lhsValue, rhsValue);
			if (intersectionValue)
				result[intersectionValue.Version] = intersectionValue;
		}
	}
	
	return result;
}


// returns the requirement as a human comprehendable string
function RequireToString(aRequire)
{
	var result = "MacOS X"
				 + ((1 == aRequire.NeedServer) ? " Server" : "")
				 + " " + aRequire.Version
				 + ((1 == aRequire.Need64Bit) ? " 64-Bit" : "");
	return result;
}

/**
Forms a human-readible string describing the particular Windows OS variant, range, and 64-Bittedness
inSession is the session to use for localizing the text
inWindowsVariant is the Windows variant, such as XP, Vista, or Server2003
inServicePackRange is the InclusiveRange of supported service packs
inIs64Bit is a boolean indicating whether or not the OS is 64-Bit
*/
function DescribeWinOS(inWindowsVariant, inServicePackRange, inIs64Bit)
{
	var formatString = null;
	var replacements = new Object;
	replacements["Variant"] = inWindowsVariant;
	replacements["64BitWithLeadingSpace"] = inIs64Bit ? " 64-Bit" : "";
	
	if (null == inServicePackRange.start)
	{
		if (null == inServicePackRange.end)
		{
			// (null, null)
			// e.g. Windows XP 64-Bit
			// e.g. Windows [Variant][64BitWithLeadingSpace]
			formatString = new LocalizedString("systemPageWinOSAllSP", null, replacements);
		}
		else
		{
			// (null, inServicePackRange.end]
			// e.g. Windows XP 64-Bit through Service Pack 1
			// e.g. Windows [Variant][64BitWithLeadingSpace] through Service Pack [SP]
			replacements["SP"] = inServicePackRange.end;
			formatString = new LocalizedString("systemPageWinOSThroughSP", null, replacements);
		}
	}
	else
	{
		if (null == inServicePackRange.end)
		{
			// [inServicePackRange.start, null)
			// e.g. Windows XP 64-Bit Service Pack 8 and greater
			// e.g. Windows [Variant][64BitWithLeadingSpace] Service Pack [MinSP] and greater
			replacements["SP"] = inServicePackRange.start;
			formatString = new LocalizedString("systemPageWinOSFromSP", null, replacements);
		}
		else
		{
			// [inServicePackRange.start, inServicePackRange.end]
			if (inServicePackRange.start == inServicePackRange.end)
			{
				// e.g. Windows XP Service Pack 3
				// e.g. Windows [Variant][64BitWithLeadingSpace] Service Pack [SP]
				replacements["SP"] = inServicePackRange.start;
				formatString = new LocalizedString("systemPageWinOSExactlySP", null, replacements);
			}
			else
			{
				// e.g. Windows Vista 64-Bit Service Pack 2 through Service Pack 4
				// e.g. Windows [Variant][64BitWithLeadingSpace] Service Pack [MinSP] through Service Pack [MaxSP]
				replacements["MinSP"] = inServicePackRange.start;
				replacements["MaxSP"] = inServicePackRange.end;
				formatString = new LocalizedString("systemPageWinOSFromMinSPThroughMaxSP", null, replacements);
			}
		}
	}
	
	return formatString;
}



/**
Determine what payloads need to be shown on the Finish screens based on the payload operation results
*/
function GetPayloadListByOperationResult(inSession, extraOptions)
{
	var result = {
		pass: {
			visibleList: [],
			hiddenList: [],
			messageCount: 0
		},
		partialPass: {
			visibleList: [],
			hiddenList: [],
			messageCount: 0
		},
		fail: {
			visibleList: [],
			hiddenList: [],
			messageCount: 0
		},
		driverFailure: false
	};

	var session = inSession;
	var bShowOnlyLaunchableApps = false;
	if(extraOptions && (extraOptions.bShowOnlyLaunchableApps == true))
		bShowOnlyLaunchableApps = true;

	// This really, really doesn't belong here, but the correct place only
	// exists in a future refactored Finish.js.
	var uninstallMethods = new Array();
	
	// nagendra: 2nd Oct 2007. This message should be shown in some case only.
	// case where a suite upgrade a PP and that PP remains behind. We can't ask the end-user to locate and launch its original installer.
	// the end-user will most like not have it. So commenting the line below but conditionally adding this line 
	//uninstallMethods.push(new LocalizedString("locRemoveMethodOriginal", "Launch its original installer"));
	
	if (session.properties["platform"] == "OSX")
		uninstallMethods.push(new LocalizedString("locRemoveMethodOSX", "Use the Uninstall Utility in [installerPath]", {installerPath: "/Applications/Utilities/Adobe Installers"}));
	if (session.properties["platform"] == "Win32")
		uninstallMethods.push(new LocalizedString("locRemoveMethodWin32", "Use the Add/Remove Programs Utility"))

	// Get a proper UI sort
	var sessionPayloads = PayloadUISort(session.sessionPayloads, session);

    /*
	// Performance fix: Get the installed state for all payloads in the session at one go
	// instead of each payload asking for its own in its constructor
	var allSessionPayloadsAdobeCodesString = "";
	var allSessionPayloadsAdobeCodesArray = new Array();
	for (var ac in sessionPayloads)
	{
	    allSessionPayloadsAdobeCodesArray[ac] = sessionPayloads[ac].GetAdobeCode();
	}
	allSessionPayloadsAdobeCodesString = allSessionPayloadsAdobeCodesArray.join(",");
	
	var sessionPayloadsInstallState = inSession.GetPayloadsPhysicalInstallState(allSessionPayloadsAdobeCodesString);
	*/
	
	for (var adobeCode in sessionPayloads)
	{
		var p = sessionPayloads[adobeCode];
		var uiInfo = p.GetUISelectionAction(session);

		// Pick a list based on the operation result
		var operationResult = p.GetOperationResult();
		var success = (operationResult 
			&& operationResult.message
			&& (operationResult.message.code == gConstants.kORSuccess
		 		|| operationResult.message.code == gConstants.kORSuccessWithReboot 
				|| operationResult.message.code == gConstants.kORSuccessWithMessage));
		
	    // We are assuming that the GetPayloadsPhysicalInstallState call return payloads in the order we asked for them
	    // sessionPayloads[adobeCode]._installedStatus = sessionPayloadsInstallState.payloads[adobeCode];

		// Driver failure implies total failure		
		if(!(success) && p.IsDriverForSession(session))
		    result.driverFailure = true;

        // Payload has succeeded, but it's dependents have failed				
		var partialSuccess = success && p.HasDependentFailures();
				
		var list;
				
		if(operationResult)
		{
		    if(success)
		    {
		        if(partialSuccess)
		            list = result.partialPass;
		        else
		            list = result.pass;
		    }
		    else
		    {
		        list = result.fail;
		    }
		}
		else
		{
		    list = null;
		}

		if (list)
		{
		    var alp = p.GetAppLaunchPathAndArguments();
			// Formulate the UI text
			var uitext = {
				payloadOperation: p.GetInstallerAction(),
				payloadName: p.GetUIProductName(session),
				payloadIcon: p.GetProductIcon(),
				payloadNote: (ContainerNotEmpty(p.GetPayloadsToUpgradeFrom()) && p.GetInstallerAction() == kInstallerActionInstall) ? uiInfo.payloadNote : null,
				payloadDetail: null,
				payloadResult: operationResult,
				payloadLaunchPath: alp["AppLaunchPath"],
				payloadLaunchArguments: alp["AppLaunchArguments"],
				payloadArguments: alp["AppLaunchArguments"],
				payloadType: p._payloadType,
				payloadAdobeCode: p.GetAdobeCode()
			}

			// Flag if this payload needs a restart
			if (operationResult.message
				&& operationResult.message.code == gConstants.kORSuccessWithReboot)
			{
				this.restartNeeded = true;
			}

            /*
			// Are we a de-refcount?
			var installState = p.GetPhysicalInstallState(inSession);
			if (success && p.GetInstallerAction() == kInstallerActionRemove
				&& installState && installState.physicallyInstalled != "0")
			{
				if (p.policyNode.isSessionDriver)
				{
					list.messageCount++;
					//nagendra: 2nd Oct 2007. Adding this line only in this case. 
					uninstallMethods.push(new LocalizedString("locRemoveMethodOriginal", "Launch its original installer"));
					
					uitext.payloadDetail = [
						new LocalizedString("locToFinishlyRemoveDo", "To Finishly remove this software do one of the following:"),
						uninstallMethods
					];
				}
				else if (p.policyNode.visible)
				{
					list.messageCount++;
					uitext.payloadDetail = [
						new LocalizedString("locDeRefcountVisible", "[payloadName] remains on your computer because a different installer originally installed it.", { payloadName: p.GetUIProductName(session) }),
					 	new LocalizedString("locToRemoveDo"),
						uninstallMethods
					];
				}
				else
				{
					uitext.payloadDetail = new LocalizedString("locDeRefcountHidden", "Some components remain on your computer because they are in use by other Adobe products.");
				}

			}
            */
            
			// Pick a sublist based on visibility and checked state.
			// Completely successful
			if(success && !partialSuccess)
			{
				var shouldDisplay = false;
				
				var workflowMode=gSession.getWorkflowMode();

			    if(bShowOnlyLaunchableApps == true)
						shouldDisplay = ((uiInfo.visible || p.IsDriverForSession(session)) && (uitext.payloadIcon != null) && (uitext.payloadLaunchPath != ""));
				else
						shouldDisplay = ((uiInfo.visible || p.IsDriverForSession(session)) && (uitext.payloadIcon != null));
						
		        if (shouldDisplay == true)
		        {
			        if (uiInfo.checkedState)
			        {
				        list.visibleList.push(uitext);
				        if (operationResult.message.code == gConstants.kORSuccessWithMessage)
					        list.messageCount += 1;
			        }
		        }
		        else
		        {
			        list.hiddenList.push(uitext);
		        }
			}
			// This payload successful but some dependent failed
			else if(success && partialSuccess)
			{
			    // Only show non driver top level payloads
			    
				var shouldDisplay = false;
				var payloadSatisfies = p.GetSatisfiedArray();
			    if(bShowOnlyLaunchableApps == true)
					shouldDisplay = ((uiInfo.visible || (p.IsDriverForSession(session) && !(gSession.IsSuiteMode()))) && ((payloadSatisfies.length == null) || (payloadSatisfies.length <= 0)) && (uitext.payloadIcon != null) && (uitext.payloadLaunchPath != ""));
				else
					shouldDisplay = ((uiInfo.visible || (p.IsDriverForSession(session) && !(gSession.IsSuiteMode()))) && ((payloadSatisfies.length == null) || (payloadSatisfies.length <= 0)));

			 
		        if (shouldDisplay == true)
		        {
			        if (uiInfo.checkedState)
			        {
				        list.visibleList.push(uitext);
				        if (operationResult.message.code == gConstants.kORSuccessWithMessage)
					        list.messageCount += 1;
			        }
		        }
		        else
		        {
			        list.hiddenList.push(uitext);
		        }
			}
			// This payload failed
			else
			{
			    // Only show non driver top level payloads
			    var payloadSatisfies = p.GetSatisfiedArray();
		        if ((uiInfo.visible || (p.IsDriverForSession(session) && !(gSession.IsSuiteMode()))) && ((payloadSatisfies.length == null) || (payloadSatisfies.length <= 0)))
		        {
			        if (uiInfo.checkedState)
			        {
				        list.visibleList.push(uitext);
			        }
		        }
		        else
		        {
			        list.hiddenList.push(uitext);
		        }
			}
			
		}
	}

	return result;
}

function _createMidStreamFailureElement(inSession, failedPayloadsArray)
{
	var midStreamFailureDOM = document.createElement("span");
	
	var errorTitle = document.createElement("span");
	errorTitle.innerHTML = inSession.localization.GetString("locInstallationErrorHeader", "Installation of the following components has failed:");
	errorTitle.className = "midStreamFailureHeader";
	midStreamFailureDOM.appendChild(errorTitle);
	
	var fp;
	for (fp in failedPayloadsArray)
	{
		var failedPayload = document.createElement("span");
		failedPayload.className = "midStreamFailurePayload";
		failedPayload.innerHTML = failedPayloadsArray[fp];
		midStreamFailureDOM.appendChild(failedPayload);
	}
	
	var errorText1 = document.createElement("span");
	errorText1.className = "midStreamFailureText";
	errorText1.innerHTML = inSession.localization.GetString("locMidStreamErrorText1", "To continue installing the remaining options, click Continue");
	midStreamFailureDOM.appendChild(errorText1);
	
	var errorText2 = document.createElement("span");
	errorText2.className = "midStreamFailureText";
	errorText2.innerHTML = inSession.localization.GetString("locMidStreamErrorText2", "To cancel this installation, click Quit");
	midStreamFailureDOM.appendChild(errorText2);
	
	return midStreamFailureDOM.outerHTML;
}