// The property that will exist in the returned JSON object that denotes an error
var kError = "_error";


/**
The installer session object.  InstallerSession is subclass of
ContainerProxy for convenient access to the container methods
which this class makes heavy use of.
*/
function InstallerSession()
{
	/** A union of Session and CAPS InstallerPayloads, indexed by payload AdobeCode. */
	this.allPayloads = new Object;
	
	/** InstallerPayloads in this session, indexed by payload AdobeCode. */
	this.sessionPayloads = new Object;
	
	/** The collection that represents this session, initialized by CreatePayloadSession */
	this.sessionCollection = null;
	
	/** A union of this Session and all CAPS collections, indexed by CollectionID */
	this.allCollections = new Object;
	
	/** raw session data returned from container during CreatePayloadSession() */
	this.sessionData = null;
	
	/** raw caps data returned from container during CreatePayloadSession() */
	this.capsMap = null;
	
	/** Legacy...migrate to using this.sessionPayloads[adobeCode].GetSessionData() */
	this.payloadMap = this.GetSessionData().payloadMap;
	
	/** Object that stores installer properties such as EULA selected state, SN#, etc */
	this.properties = this.GetDefaultProperties();
	
	/** Augment the default property list with a few things for page localization */
	this.defaultProperties = this.GetDefaultProperties();

	/** Map of AdobeCodes to error log messages */
	this.errorMessages = new Object();
	
	/** Session creation error messages, usually fatal, with {error_string_code, default_string} pair */
	this.sessionErrorMessages = new Array();
	
	/** Supported ISO codes for the current collection of payloads.  This will be initialized
	 	in the first call to GetSupportedLanguagesArray()*/
	this.sessionSupportedLanguages = null;
	
	/** Have payload attributes been updated ? */
	this.payloadOptionsUpdated = false;
}


/**
InstallerSession is subclass of ContainerProxy.
*/
InstallerSession.prototype = new ContainerProxy();


/**
Calculate the set of supported languages and return the set as an array of ISO codes
*/
InstallerSession.prototype.GetSupportedLanguagesArray = function(inUICallbackObject)
{
	
	if (this.sessionSupportedLanguages == null)
	{
		this.sessionSupportedLanguages = new Array();
			
		var mapFamilyNameToLangs = new Object();
		for (var adobeCode in this.payloadMap)
		{
			var payload = this.payloadMap[adobeCode];
			var satisfiesObj = this.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)
		{
			this.LogDebug("Testing keypair languages: " + keyPair);
			// Get all the languages and intersect that with the existing set
			if (firstIteration)
			{
				var supportedLangMap = mapFamilyNameToLangs[keyPair];
				for (eachSupportedLang in supportedLangMap)
				{
					this.sessionSupportedLanguages.push(eachSupportedLang);
				}
				firstIteration = false;	
			}
			else
			{
				if (this.sessionSupportedLanguages.length <= 0)
				{
					break;	
				}
				// Remove any element that doesn't exist
				var updatedSupportedLangs = new Array();
				for (var supportedIndex = 0; supportedIndex < this.sessionSupportedLanguages.length; ++supportedIndex)
				{
					if (mapFamilyNameToLangs[keyPair][this.sessionSupportedLanguages[supportedIndex]] != null)
					{
						updatedSupportedLangs.push(this.sessionSupportedLanguages[supportedIndex]);
					}
					else
					{
						this.LogDebug("Removing language from working set: " + this.sessionSupportedLanguages[supportedIndex]);
					}
				}
				this.sessionSupportedLanguages = updatedSupportedLangs;
			}		
		}
		this.LogDebug("Supported languages: " + this.sessionSupportedLanguages.toString());
	
		if (this.sessionSupportedLanguages.length <= 0)
		{
			if (inUICallbackObj && inUICallbackObj.onEmptyLanguageSet)
			{
				inUICallbackObj.onEmptyLanguageSet();
			}
		
		}
	}
	else
	{
		this.LogDebug("Using cached language set");
	}
	return this.sessionSupportedLanguages;
}


/**
Return an array of AdobeCodes for all payloads whose version isn't supported
by this RIBS host
@param  inContainerProxy    proxy interface object instance
@param  inPayloadMapObject  The JS object reprepresenting the payloads in the current session
@retval true                All payloads are supported by this version
@retval false               At least one payload version is not supported
*/
InstallerSession.prototype.GetUnsupportedVersionedPayloads = function(inUICallbackObj)
{
    this.LogDebug(FormatLogHeader("verifyPayloadVersioning"));
    
    var arrUnsupportedVersions = new Array();
    var versionObj = this.GetRIBSBuildInfo();

    this.LogInfo("Supported RIBS version range: [" + versionObj.Version.compatibleLowerBound + "," + versionObj.Version.compatibleUpperBound + "]");
 	for (var adobeCode in this.payloadMap)
    {
		var payload = this.payloadMap[adobeCode];
        this.LogDebug("Testing payload " + adobeCode + " built with RIBS version " + payload.version);
        if (compareVersions(payload.version, versionObj.Version.compatibleUpperBound) > 0 ||
            compareVersions(payload.version, versionObj.Version.compatibleLowerBound) < 0)
           {
				if (inUICallbackObj && inUICallbackObj.onUnsupportedVersion)
				{
					try
					{
						inUICallbackObj.onUnsupportedVersion(this, adobeCode);
					}
					catch (ex)
					{
						
					}
				}
                arrUnsupportedVersions.push(adobeCode);
           }
    }
    return arrUnsupportedVersions;
}

/**
Return an array of AdobeCodes for any incompatible payloads installed.  The array is empty if
there are no incompatible (same AdobeCode, different versions) payloads installed.
@param  inCAPSMapObject     CAPS payload map
@param  inUICallbackObject  Callback object to handle onIncompatiblePayload() event
@returns	Array of incompatible payloads.  Array is empty if not incompatible paylaods are installed
*/
InstallerSession.prototype.GetIncompatiblePayloadBuildsInstalled = function(inCAPSMapObject, inUICallbackObject)
{
    this.LogDebug(FormatLogHeader("GetIncompatiblePayloadBuildsInstalled"));
	
    var arrIncompatible = new Array();
 	for (var adobeCode in this.payloadMap)
    {
		var payload = this.payloadMap[adobeCode];
		sessionPayloadIdentifier = payload.BuildInfo.Created;
	        
		// Does this payload already exist in the CAPS data?
		var existingPayload = inCAPSMapObject.Payloads[adobeCode];
		if (existingPayload != null)
		{
			if (existingPayload.identifier != sessionPayloadIdentifier)
			{			
				try
				{
					if (inUICallbackObject && inUICallbackObject.onIncompatiblePayload)
					{
						inUICallbackObject.onIncompatiblePayload(this, adobeCode, sessionPayloadIdentifier, existingPayload.identifier);
					}
				}	
				catch (ex) 
				{
					this.LogDebug("inUICallbackObject.onIncompatiblePayload threw exception");
					this.LogDebug(ex);
				}		
				finally
				{
					arrIncompatible.push(adobeCode);
				}
			}
		}
	}   
    return arrIncompatible;
}


/**
Bind the payloads within this session to one another.  The second phase is 
to check the upgrades/conflicts given the bindings.

*/
InstallerSession.prototype.CreatePayloadSession = function(inUICallbackObj)
{
	//
	// Sanity check payload RIBS versions and build numbers
	//
	//var capsMapObject = this.GetCAPS();
	
	var sessionResolved = true;
	try
	{
		this.LogDebug(FormatLogHeader("InstallerSession.CreatePayloadSession: Checking for unsupported payloads"));
		var retAdobeCodes = this.GetUnsupportedVersionedPayloads(inUICallbackObj);
		if (retAdobeCodes.length > 0)
		{
		    throw (new Array("sessionErrorUnsupportedPayloads", "Unsupported payload versions included"));
		}
		
		//
		// Create payload universe & graphs
		//
		this.allPayloads = new Object;
		this.sessionPayloads = new Object;
		this.allCollections = new Object;

		if (null == this.sessionData)
			this.sessionData = this.GetSessionData();

		if (null == this.payloadMap)
			this.payloadMap = this.sessionData.payloadMap;

		if (null == this.capsMap)
			this.capsMap = this.GetCAPS();


		
		// add session payloads to the payload universe allPayloads
		// and to sessionPayloads
		this.LogDebug(FormatLogHeader("InstallerSession.CreatePayloadSession: Checking session payloads"));
		var arrDriverConflicts = new Array();
		for (var anAdobeCode in this.sessionData.payloadMap)
		{
			var thePayload = new InstallerPayload(anAdobeCode);
			this.allPayloads[anAdobeCode] = thePayload;
			this.sessionPayloads[anAdobeCode] = thePayload;
			var aResult = thePayload.SetSessionData(this.sessionData.payloadMap[anAdobeCode]);
			if (aResult != thePayload.kIntegrityOK)
			{				
				this.LogDebug("Invalid payload:");
				this.LogDebug(thePayload.GetAdobeCode());
				incompatiblePayloads++;
			}
			
			if (this.sessionData.driverPayloadID && 
				this.sessionData.driverPayloadID == anAdobeCode)
			{
				arrDriverConflicts = thePayload.GetRawConflicts();
			}
		}

		// Any incompatible payloads installed?
		this.LogDebug(FormatLogHeader("InstallerSession.CreatePayloadSession: Checking incompatible payloads"));
		var retIncompatibleAdobeCodes = this.GetIncompatiblePayloadBuildsInstalled(this.capsMap, inUICallbackObj);
		if (retIncompatibleAdobeCodes.length > 0)
		{
		    throw (new Array("sessionIncompatiblePayloadsInstalled", "Incompatible payloads already installed"));
		}
		
		// add caps payloads to the payload universe and check for conflict relationships along the way				
		this.LogDebug(FormatLogHeader("InstallerSession.CreatePayloadSession: Checking already installed payloads"));
		for (var anAdobeCode in this.capsMap.Payloads)
		{
			this.LogDebug(anAdobeCode + " " + this.capsMap.Payloads[anAdobeCode].productName);
			var thePayload = this.sessionPayloads[anAdobeCode];
			if (null == thePayload)
			{
				thePayload = new InstallerPayload(anAdobeCode);
				this.allPayloads[anAdobeCode] = thePayload;
			}
			var aResult = thePayload.SetCAPSData(this.capsMap.Payloads[anAdobeCode]);
			if (aResult != thePayload.kIntegrityOK)
			{
				this.LogDebug("Bad payload:");
				this.LogDebug(thePayload);
			}
			
			// Check the conflicts
			var capsConflicts = thePayload.GetRawConflicts();
			var index1 = index2 = -1;
			for (var k = 0; k < arrDriverConflicts.length && (-1 == index1); ++k)
			{
				if (arrDriverConflicts[k] == anAdobeCode)
				{
					index1 = k;	
				}
			}
			if (this.sessionData.driverPayloadID)
			{
				for (var k = 0; k < capsConflicts.length && (-1 == index2); ++k)
				{
					if (capsConflicts[k] == this.sessionData.driverPayloadID)
					{
						index2 = k;	
					}
				}				
			}
			if ((-1 != index1) || (-1 != index2))
			{
				doBootstrapUninstallCheck = true;
				// This is handle by PayloadPolicyNode and the system check page
				//throw (new Array("sessionErrorConflictingDriver", "You cannot install this product because a conflicting component is already installed."));
			}
		}

		// connect bidirectional conflict references between payloads
		for (var anAdobeCode in this.allPayloads)
		{
			var aPayload = this.allPayloads[anAdobeCode];
			var rawConflicts = aPayload.GetRawConflicts();

			for (var index = 0; index < rawConflicts.length; ++index)
			{
				var conflictingPayload = this.allPayloads[rawConflicts[index]];
				if (conflictingPayload)
				{
					aPayload.AddConflictingPayload(conflictingPayload);
					conflictingPayload.AddConflictingPayload(aPayload);
				}
			}
		}

		// if there is some other collection that has 
		// already installed a payload that upgrades us then we can't install this version
		var doBootstrapUninstallCheck = false;

		// connect upgrade relationships between payloads
		for (var anAdobeCode in this.allPayloads)
		{
			var aPayload = this.allPayloads[anAdobeCode];
			var rawUpgradeList = aPayload.GetRawUpgradeList();

			for (var index = 0; index < rawUpgradeList.length; ++index)
			{
				var payloadToUpgrade = this.allPayloads[rawUpgradeList[index]];
				if (payloadToUpgrade)
				{
					aPayload.AddPayloadToUpgradeFrom(payloadToUpgrade);
					payloadToUpgrade.AddPayloadToUpgradeTo(aPayload);
				}
				
				if (this.sessionData.driverPayloadID)
				{
					if (this.sessionData.driverPayloadID == rawUpgradeList[index])
					{
						// If something is already installed that upgrades us, then we're not
						// going to try to install this session
						// FIXME: This is handled in systemCheck.js in GUI mode.  Need to harmonize check with silent.
						if (!this.UIHosted())
						{
							doBootstrapUninstallCheck = true;
							throw (new Array("sessionErrorUpgradingPayloadInstalled", "A payload that upgrades the driver payload is already installed"));
						}
					}
				}
			}
		}

		// create requires/satisfies buckets for later resolution against
		// install choices in the session
	    this.LogInfo(FormatLogHeader("Verify Dependency Subscribers"));
		var missingDependencyCount = 0;
		for (var anAdobeCode in this.sessionPayloads)
		{
			var aPayload = this.sessionPayloads[anAdobeCode];
			var unresolvedRequirements = aPayload.GetUnresolvedRequirements();
			for (var index = 0; index < unresolvedRequirements.length; ++index)
			{
				var aRequirement = unresolvedRequirements[index];
				var satisfiesCount = 0;
				for (var testAdobeCode in this.sessionPayloads)
				{
					var testPayload = this.sessionPayloads[testAdobeCode];
					if (testPayload.SatisfiesRequirement(aRequirement))
					{
						aPayload.AddRequirementSatisfier(aRequirement, testPayload);
						++satisfiesCount;
					}
				}
				if (satisfiesCount <= 0)
				{
					++missingDependencyCount;
					this.LogError("Unable to find a payload that satisfies " + anAdobeCode + " dependency on:");
					this.LogError("\tFamily: " + aRequirement.family);
					this.LogError("\tProductName: " + aRequirement.productName);
					this.LogError("\tMinVersion: " + aRequirement.minVersion);
				}
			}
		}
		if (missingDependencyCount > 0)
		{
			throw (new Array("sessionErrorSessionDependenciesNotSatisfied", "Session has dependencies that cannot be satisfied"));	
		}
	
		// create map of known CAPS collections.  
		this.LogDebug(FormatLogHeader("InstallerSession.CreatePayloadSession: Checking already installed sessions"));
		if (this.capsMap && this.capsMap.Collections)
		{
			for (var aCollectionID in this.capsMap.Collections)
			{
				var aCollection = this.capsMap.Collections[aCollectionID];
				aCollection.collectionID = aCollectionID;

				if (null != aCollection.driverPayloadID)
				{
					aCollection.driverPayload = this.allPayloads[aCollection.driverPayloadID];
					if (null == aCollection.driverPayload)
					{
						// This may have occurred if the driver payload was not successfully installed
						// due to a previous session dependency payload failing or the user canceling
						this.LogWarning("Driver payload specified for collection: " + aCollectionID + " but payload: " + aCollection.driverPayloadID + " not registered.");	
					//	throw (new Array("sessionErrorDriverPayloadNotFound", "Driver payload specified but not found"));						
					}
				}

				this.allCollections[aCollectionID] = aCollection;
			}
		}

		// add collection for this session if it doesn't already exist
		if (!this.sessionData.key)
			throw (new Array("sessionErrorSessionKeyMissing", "Session key is missing from session data"));
		this.sessionCollection = this.allCollections[this.sessionData.key];
		if (!this.sessionCollection)
		{
			this.sessionCollection = new Object;
			this.sessionCollection.collectionID = this.sessionData.key;

			if (this.sessionData.driverPayloadID)
			{
				this.sessionCollection.driverPayloadID = this.sessionData.driverPayloadID;
				this.sessionCollection.driverPayload = this.allPayloads[this.sessionCollection.driverPayloadID];
				if (!this.sessionCollection.driverPayload)
				{
					this.LogWarning("Driver payload specified for current collection: " + this.sessionCollection.collectionID + " but payload: " + this.sessionCollection.driverPayloadID + " not registered.");						
					//throw (new Array("sessionErrorDriverPayloadNotFoundThis", "Driver payload could not be found for this collection"));
				}
			}
			this.allCollections[this.sessionCollection.collectionID] = this.sessionCollection;
		}

		// add payloads to collections & vice versa.  while we're here we're going to check for installed payloads
		// that conflict with our AdobeCode
		for (var anAdobeCode in this.allPayloads)
		{
			var aPayload = this.allPayloads[anAdobeCode];
			var payloadCAPSData = aPayload.GetCAPSData();
			var addedSessionCollection = false;

			// add CAPS collections
			if (payloadCAPSData && payloadCAPSData.Collections)
			{
				for (var index = 0; index < payloadCAPSData.Collections.length; ++index)
				{
					var aPayloadCollection = payloadCAPSData.Collections[index];
					aPayloadCollection.payload = aPayload;

					var aCollection = this.allCollections[aPayloadCollection.collectionID];
					aPayloadCollection.collection = aCollection;

					if (!aCollection.payloadRecords)
						aCollection.payloadRecords = new Object;

					aCollection.payloadRecords[anAdobeCode] = aPayloadCollection;

					aPayload.AddCollectionRecord(aPayloadCollection);
					if (aPayloadCollection.collection == this.sessionCollection)
						addedSessionCollection = true;
				}
			}

			// if the session collection wasn't added and this is a session payload,
			// add the session collection as well
			if (!addedSessionCollection && this.sessionPayloads[anAdobeCode])
			{
				var aPayloadCollection = new Object;
				aPayloadCollection.payload = aPayload;
				aPayloadCollection.collection = this.sessionCollection;

				if (!this.sessionCollection.payloadRecords)
					this.sessionCollection.payloadRecords = new Object;
				this.sessionCollection.payloadRecords[anAdobeCode] = aPayloadCollection;

				aPayload.AddCollectionRecord(aPayloadCollection);
			}
		}
		
		// Dump the payload operation order so folks making multi-disk media can 
		// know the constraints
		this.orderedSessionPayloads = PayloadDependencySort(this.sessionPayloads);
		this.LogDebug("Operation order summary:\nWhen distributing payloads across media, the following order must be preserved,except that\nadjacent payloads with the same mediaGroup can be re-ordered across media boundaries without introducing\nextraneous media swaps for the user.")
		var lastLevel = "0,0";
		var mediaGroup = 0;
		thisCB = this;
		LogPayloadSet(this, "Operation order for all session payloads: mediaGroup (requires,satisfies)", this.orderedSessionPayloads,
			function(p)
			{
				var thisLevel = p.GetRequiredArray().length + "," + p.GetSatisfiedArray().length;
				if (thisLevel != lastLevel)
					mediaGroup++;

				lastLevel = thisLevel;
				return  mediaGroup  + " (" + thisLevel + ")" ;
			});


	//	this.LogDebug("this.allCollections");
	//	this.LogDebug(this.allPayloads);
	}
	catch (ex)
	{
		this.sessionErrorMessages.push(ex);
			
		var logError = "";
		if (ex[0])
		{
			logError = ex[0];
		}
		if (ex[1])
		{
			if (logError.length > 0)
			{
				logError += " - ";	
			}
			logError += ex[1];
		}
		this.LogError("Exception: " + logError);
		sessionResolved = false;
	}
	// Tell the container what happened
	this.SetSessionInitialized((doBootstrapUninstallCheck || sessionResolved) ? 1 : 0);
	return sessionResolved;
}

/**
Utility method to determine if a payload install/repair/remove operation is successful
@param	inStatusCode	Payload operation message.code value
*/
InstallerSession.prototype.IsOperationCodeSuccess = function(inStatusCode)
{
	var isSuccess = false;	
	if (inStatusCode &&
		(inStatusCode == gConstants.kORSuccess ||
		inStatusCode == gConstants.kORSuccessWithReboot ||
		inStatusCode == gConstants.kORSuccessWithMessage))
	{
		isSuccess = true;
	}
	return isSuccess;
}

/**
Utility method to look into the crystal ball and see whether or
not the specified payload will be installed on the user's machine
if the existing set of install actions were invoked
@param inPayload the payload to test
@retval true were installation to occur, the payload would be resident afterward
@retval false were installation to occur, the payload would not be resident afterward
*/
InstallerSession.prototype.WillPayloadBeResidentPostInstall = function(inPayload)
{
	var willBeResident = false;
	
	// if the payload is either installed now or marked for installation
	if ((inPayload.GetInstallationRefCount() > 0)
		|| (kInstallerActionInstall == inPayload.GetInstallerAction()))
	{
		// then it will be resident unless there's an upgrade
		// that is/will replace it
		var willBeUpgraded = false;
		
		var upgradingPayloadsMap = inPayload.GetPayloadsToUpgradeTo();
		for (var anUpgradingAdobeCode in upgradingPayloadsMap)
		{
			var anUpgradingPayload = upgradingPayloadsMap[anUpgradingAdobeCode];
			willBeUpgraded = this.WillPayloadBeResidentPostInstall(anUpgradingPayload);
			if (willBeUpgraded)
				break;
		}
		willBeResident = !willBeUpgraded;
	}
	
	return willBeResident;
}


/**
Utility method to look into the crystal ball and see what conflicts
would exist on the user's machine if the current set of install
actions were to be performed. The result is an array of conflict pairs where
the first element is the session InstallerPayload having the conflict and the
second element is the InstallerPayload with which it conflicts.
@return Array of conflict pairs that would exist if the install actions were performed
*/
InstallerSession.prototype.AccumulatePostInstallConflicts = function()
{
	var allFoundConflictPairs = new Array;
	
	for (var sessionPayloadCode in this.sessionPayloads)
	{
		var aSessionPayload = this.sessionPayloads[sessionPayloadCode];
		if (this.WillPayloadBeResidentPostInstall(aSessionPayload))
		{
			var conflictingPayloadMap = aSessionPayload.GetConflictingPayloads();
			for (var aConflictingAdobeCode in conflictingPayloadMap)
			{
				var aConflictingPayload = conflictingPayloadMap[aConflictingAdobeCode];
				if (this.WillPayloadBeResidentPostInstall(aConflictingPayload))
				{
					allFoundConflictPairs.push(new Array(aSessionPayload, aConflictingPayload));
				}
			}
		}
	}
	
	return allFoundConflictPairs;
}


/**
Returns an array of requirements for payloads in this session
that would not be met if the current install choices were 'installed.'
@return Array of unmet requirement instances
*/
InstallerSession.prototype.AccumulateUnmetRequirements = function()
{
	// allUnmetDependencies will be an array of requirements
	// instances that weren't met
	var allUnsatisfiedRequirements = new Array;
	
	// collect the requirements
	var allInstallingRequirements = new Array;
	for (var sessionPayloadCode in this.sessionPayloads)
	{
		var aSessionPayload = this.sessionPayloads[sessionPayloadCode];
		var futureInstallState = this.GetPayloadPostInstallStateForSession(aSessionPayload);
		if (kCapsInstallStateInstalled == futureInstallState)
		{
			var payloadRequirements = aSessionPayload.GetUnresolvedRequirements();
			allInstallingRequirements = allInstallingRequirements.concat(payloadRequirements);
		}
	}
	
	// for each one, see if it's satisfied
	for (var requirementIndex = 0; requirementIndex < allInstallingRequirements.length; ++requirementIndex)
	{
		var curRequirement = allInstallingRequirements[requirementIndex];
		var satisfied = false;
		if (null != curRequirement.satisfyingPayloads)
		{
			for (var aPayloadCode in curRequirement.satisfyingPayloads)
			{
				var aPayload = curRequirement.satisfyingPayloads[aPayloadCode];
				var futureInstallState = this.GetPayloadPostInstallStateForSession(aPayload);
				satisfied = (kCapsInstallStateInstalled == futureInstallState);
				
				if (satisfied)
					break;
			}
		}
		
		if (false == satisfied)
		{
			allUnsatisfiedRequirements.push(curRequirement);
		}
	}
	
	return allUnsatisfiedRequirements;
}


/**
Returns the future logical install state of the specified payload
with respect to this session. This does not return whether or not
the payload will be physically installed, but instead returns
whether or not it will be 'in use' by this session.
@param inPayload the payload to determine future install state for
@retval true the payload will be 'installed' by the session post-install
@retval false the payload will not be installed for this session post-install
*/
InstallerSession.prototype.GetPayloadPostInstallStateForSession = function(inPayload)
{
	var futureInstallState = kCapsInstallStateUninstalled;
	
	var curInstallState = inPayload.GetInstallStateForCollection(this.sessionCollection);
	var installerAction = inPayload.GetInstallerAction();
	if (null == installerAction)
		installerAction = kInstallerActionNone;
	
	switch (installerAction)
	{
		case kInstallerActionNone :
		{
			if (null != curInstallState)
				futureInstallState = curInstallState;
		}
		break;
		
		case kInstallerActionRepair :
		case kInstallerActionInstall :
		{
			futureInstallState = kCapsInstallStateInstalled;
		}
		break;
		
		case kInstallerActionRemove :
		{
			futureInstallState = kCapsInstallStateUninstalled;
		}
		break;
	}
	
	return futureInstallState;
}


/**
Determine if the session is in maintenance mode.  
@retval true at least one payload from this session is installed, go to the maintenance workflow
@retval false no payloads from this session are installed, go to install workflow
*/
InstallerSession.prototype.IsMaintenanceMode = function()
{
	// 1. Short circuit if suppress uninstall is set on the driver payload
	var driver = this.GetDriverPayload();
	if (driver && driver.suppressUninstaller == 1)
	{
		this.LogInfo("Uninstall suppressed by driver payload");
		return false;
	}
	
	// 2. See if this collection has any installed payloads
	var myCollection = this.sessionData.key;
	for (var adobeCode in this.capsMap.Payloads)
	{
		var payload = this.capsMap.Payloads[adobeCode];
		for (var c = 0; c < payload.Collections.length; c++)
		{
			if (payload.Collections[c].collectionID == myCollection)
			{
				var state = payload.Collections[c].installState;
				if (kCapsInstallStateInstalling == state || kCapsInstallStateInstalled == state)
				{
					this.LogInfo("The collection has installed payloads.");
					return true;
				}
			}
		}
	}
	return false;
}


/**
If this session has been properly bootstrapped, there should be a collection
record in CAPS.  This method checks that.
*/
InstallerSession.prototype.IsBootstrapped = function()
{
	return this.GetSessionData().key
		&& this.capsMap
		&& this.capsMap.Collections
		&& this.capsMap.Collections[this.GetSessionData().key];
}


/**
Estimate the total disk space impact of the currently marked payload operations.
This is simply an accumulation of InstallerPayload.OperationSize() for the session
payloads.  The returned structure is the same.
*/
InstallerSession.prototype.OperationSize = function(inIgnoreMachineState)
{
	var result = {
		totalBytes: 0,
		roots: {}
	}

	for (var anAdobeCode in this.sessionPayloads)
	{
		var opSize = this.sessionPayloads[anAdobeCode].OperationSize(inIgnoreMachineState);
		result.totalBytes += opSize.totalBytes;
		for (var eachRoot in opSize.roots)
		{
			if (result.roots[eachRoot])
				result.roots[eachRoot] += opSize.roots[eachRoot];
			else
				result.roots[eachRoot] = opSize.roots[eachRoot];
		}		
	}
	
	return result;
}


/**
Begins installing, removing, repairing, and upgrading payloads
as described by their install actions.
@param inCallbackMethod method to receive status messages during installation
*/
InstallerSession.prototype.StartPayloadOperations = function(inStatusCallback, inCancelCallback)
{
	// store session properties in CAPS
	if (this.sessionCollection
		&& this.sessionCollection.collectionID)
	{
		this.StoreCollectionProperties(this.sessionCollection.collectionID, this.properties);
	}
	else
	{
		this.LogError("Unable to store session properties because the collection ID cannot be found.");
	}
	
	
	// Simulate
	/*
	try
	{
		_simulatePayloadOperations(this);
		// Dump the results
		for (var anAdobeCode in this.sessionPayloads)
		{
			var aPayload = this.sessionPayloads[anAdobeCode];
			this.LogDebug("Simulation results: " + aPayload);
			this.LogDebug(aPayload.GetOperationResult().currentStatus);
		}		
	}
	catch (ex)
	{
		window.alert("error:"  +ex);
		
	}
	finally
	{
		window.alert("quit now");
	}

	*/

	// change pages

	// Start the payloads
	_doPayloadOperations(this, inStatusCallback, inCancelCallback);
}


/**
Initialize the session payload selection policy nodes.
*/
InstallerSession.prototype.PayloadPolicyInit = function(inOptInstallerMode)
{
	// Short circuit if we have already calculated this for this mode.
	if (null != this._PayloadPolicyInitMode
		&& (this._PayloadPolicyInitMode == inOptInstallerMode
			|| this._PayloadPolicyInitMode == kInstallerModeInstall && null == inOptInstallerMode))
	{
		return;
	}

	// Session payloads in dependency order
	var sessionPayloads = PayloadDependencySort(this.sessionPayloads, inOptInstallerMode == kInstallerModeRemove);
	var ac = null;

	// First pass, instantiate objects, defaulting to FN
	this.LogDebug("PayloadPolicyInit: BEGIN Creating policyNodes");
	for (ac in sessionPayloads)
	{
		var p = sessionPayloads[ac];
		p.policyNode = new PayloadPolicyNode(this, p);
	}
	this.LogDebug("PayloadPolicyInit: END Creating policyNodes");

	// Second pass, calculate the initial constraint and action states
	this.LogDebug("PayloadPolicyInit: BEGIN Calculating initial graph");
	for (ac in sessionPayloads)
	{
		var p = sessionPayloads[ac];
		p.policyNode.Init(inOptInstallerMode);
	}
	this.LogDebug("PayloadPolicyInit: END Calculating initial graph");
}


/**
Refresh the session payload policy.  Call this on a change to any of:
  - install language
  - installer mode
  - serialization
*/
InstallerSession.prototype.PayloadPolicyRefresh = function(inOptInstallerMode)
{
	var sessionPayloads = PayloadDependencySort(this.sessionPayloads, inOptInstallerMode == kInstallerModeRemove);
	var ac = null;

	this.LogDebug("PayloadPolicyRefresh: BEGIN Calculating new payload policy graph");
	for (ac in sessionPayloads)
	{
		var p = sessionPayloads[ac];
		p.policyNode.Recompute(inOptInstallerMode);
	}
	this.LogDebug("PayloadPolicyRefresh: END Calculating new payload policy graph");
}
