/**********************************************************************
PACKED.JS : Packed Array Wrapper Class Library

Base for QS and Gauntlet
**********************************************************************/
//<#SCRIPT>

// Ctor
function Packed()
{
	// Collections
	this.aData			= new Array();				// Stored data
	this.abValid		= new Array();				// Is data under same key valid ?
	
	// Methods
	this.Add				= Packed_Add;				// Add a mapping
	this.Combine		= Packed_Combine;			// Combine two Packed Objects
	this.Crop			= Packed_Crop;				// Remove all but one mapping
	this.Feed			= Packed_Feed;				// Create a Packed Object from a true packed array
	this.Flush			= Packed_Flush;			// Clear all mappings
	this.GetLength		= Packed_GetLength;		// Get number of mappings
	this.GetValue		= Packed_GetValue;		// Obtain a key's value
	this.HasKey			= Packed_HasKey;			// Does this object have this key ?
	this.Mutation		= Packed_Mutation;		// Expert conversion to a string
	this.Remove			= Packed_Remove;			// Remove a mapping
	this.Seed			= Packed_Seed;				// Create a Packed Object from an assoc. array 
	this.SimpleFeed	= Packed_SimpleFeed;		// Create a Packed Object from a true packed array
	this.Spawn			= Packed_Spawn;			// Create a conversion to an assoc. array
	this.toString		= Packed_toString;		// Default conversion to a string
}

// Packed::Flush()
//	Empty the collections
function Packed_Flush()
{
	this.aData = new Array();
	this.abValid = new Array();
}

// Packed::Crop()
// Reduce to a single mapping
// strKeep -- mapping to keep
// Returns FALSE if strKeep cannot be found, and aborts the cropping
// Returns TRUE if cropping successful
function Packed_Crop(strKeep)
{
	if ( !this.HasKey(strKeep) )						// Abort
		return false;
		
	var oKeptValue = this.GetValue( strKeep );	// Preserve mapping to be cropped
	
	this.Flush();
	this.Add( strKeep, oKeptValue );					// Add preserved mapping
	
	return true;
}

// Packed::Add()
// Add a mapping, overwriting on a collision
// strKey -- mapping's key
// strVal -- mapping's value
// Returns -1 if not a valid key
// Returns  0 if mapping was added with no collisions
// Returns  1 if mapping was added with a collision
function Packed_Add( strKey, strVal )
{	
	if ( !IsDefined(strKey) )							// Don't add a bad key								// 0 is still valid
			return -1;

	if ( !IsDefined(strVal) )							// Force malformed values to NULL
			strVal = "";
		
	var nRet = this.HasKey( strKey ) ? 1 : 0;		// Collision occurred ?

	this.aData[ strKey ] = escape( strVal );
	this.abValid[ strKey ] = true;
	
	return nRet;											// Return collision detector
}

// Packed::Remove()
// Remove a mapping
// strRem -- mapping to be removed
// Returns FALSE if mapping does not exist
// Returns TRUE if mapping successfully removed
function Packed_Remove( strKey )
{
	if ( !this.HasKey(strKey) )						// Mapping does not exist
		return false;
	
	this.aData[ strKey ] = "";							// Nullify value
	this.abValid[ strKey ] = false;					// Enforce invalidness
	
	return true;
}

// Packed::GetValue()
// Retrieve a key's value
// strKey -- lookup key
// Returns NULL if key cannot be found
// Returns Key's Value if found
// IMPORTANT: Value might actually be NULL, so return value will be ambiguous.
//		However, GOOD code calling this method will reduce this ambiguity.
//		( See XMLDOMNode::getAttribute() )
function Packed_GetValue( strKey )
{
	if ( !this.HasKey(strKey) )						// Mapping does not exist
		return "";
	
	var oVal = unescape( this.aData[strKey] );
	
	return oVal;
}

// Packed::Mutation()
// Creates a true packed array using the parameterized delimiters
// cInterDel -- Delimiter to be used inbetween mapp0ings
// cIntraDel -- Delimiter inbetween a mapping's key and value
// Returns NULL if there are no mappings or if the delimiters are malformed.
// Returns the true packed array, otherwise
function Packed_Mutation( cInterDel, cIntraDel )
{
	// No mappings are malformed parameters, early exit
	if ( !cInterDel || !cIntraDel  || !this.GetLength() )
		return "";
		
	var strRet = "";										// New true packed array
	
	// For each mapping ...
	for ( var strKey in this.aData )
	{
		// Only concatenate truly valid mappings
		if ( this.abValid[strKey] )
			strRet += cInterDel + strKey + cIntraDel + this.aData[ strKey ];
	}
	
	// Chuck off leading inter-mapping delimiter
	strRet = strRet.substring( 1, strRet.length );
	
	return strRet;
}

// Packed::toString()
// Default conversion Object --> String
function Packed_toString()
{
	return this.Mutation( '&', '=' );
}

// Packed::HasKey()
// Does Packed Object already contain this mapping ?
// strKey -- lookup key
function Packed_HasKey( strKey )
{
	if ( !IsDefined(strKey) )							// Malformed key
		return false;
		
	return Boolean( this.abValid[strKey] );		// Force to boolean for good measure
}

// Packed::Seed()
// Create a Packed Object from an assoc. array
// aSeed -- assoc. array to be used as a template
// bEscaped -- are the values of aSeed escaped ?
// Returns successful creation boolean
function Packed_Seed( aSeed, bEscaped )
{
	if ( !aSeed )											// Malformed seed
		return false;
		
	var oVal;												// value holder
	
	this.Flush();
	
	// For each mapping in the seed
	for ( var strKey in aSeed )
	{
		if ( aSeed[strKey] && !(aSeed[strKey] + "") )	// If value does not convert to a string
			oVal = "object";										// .. default to "object".
		else
		{
			// unescape values if they are escaped, to counteract the escaping within Packed::Add()
			if ( bEscaped )
				oVal = unescape( aSeed[strKey] );
			else
				oVal = aSeed[ strKey ];
		}
			
		if (  this.Add( strKey, oVal ) < 0  )
			return false;
			
	}
	
	return true;
}

// Packed::Spawn()
// Return an assoc. array created from valid data contained in the Packed Object
// bNoUnescape -- Should data inserted into the assoc. array be not be unescaped ?
// Returns the created assoc. array
function Packed_Spawn( bNoUnescape )
{
	var aSpawn = new Array();							// Instantiate new assoc. array
	
	for ( var strKey in this.aData )
	{
		if ( this.abValid[strKey] )					// Make sure data is still valid
		{
			aSpawn[ strKey ] = this.aData[ strKey ];
			
			if ( !bNoUnescape )
				aSpawn[ strKey ] = unescape(  aSpawn[ strKey ]  );
		}
	}
	
	return aSpawn;
}

// Packed::Feed()
// Create a Packed Object from a true packed array
// strFeed -- true packed array, used as template
// cInterDel -- Delimiter inbetween mappings
// cIntraDel -- Delimiter inbetween a mapping's key and value
// bNotEscaped -- are the values of strFeed not escaped ?
// Returns success boolean
function Packed_Feed( strFeed, cInterDel, cIntraDel, bNotEscaped )
{
	if ( !cInterDel || !strFeed )						// Abort on malformed parameters
		return false;
		
	if ( !cIntraDel )										// Simple Feed
		return this.SimpleFeed( strFeed, 
			cInterDel, bNotEscaped );
		
	if ( strFeed.indexOf( cIntraDel ) < 1 )		// Make sure at least one mapping exists.																
		return false;										// ..cIntraDel should not exist at strFeed[0]
	
	this.Flush();
	
	var vMappings = strFeed.split( cInterDel );	// Make a mapping collection
	var oVal;												// Value holder
	
	// For each mapping ...
	for ( var iMap = 0; iMap < vMappings.length; ++iMap )
	{
		var vPair = vMappings[ iMap ].split( cIntraDel );	// Separate key from value
		
		// unescape values if they are escaped, to counteract the escaping within Packed::Add()
		if ( bNotEscaped )
			oVal = vPair[1];
		else
			oVal = unescape( vPair[1] );
		
		if ( this.Add( vPair[0], oVal ) < 0 )
			return false;
			
	}	// End for loop
	
	return true;
}

// Packed::SimpleFeed()
// Create a Packed Object from a true packed array
// strFeed -- true packed array, used as template
// cInterDel -- Delimiter inbetween mappings
// bNotEscaped -- are the values of strFeed not escaped ?
// Returns success boolean
function Packed_SimpleFeed( strFeed, cInterDel, bNotEscaped )
{
	this.Flush();
	
	var vMappings = strFeed.split( cInterDel );
	
	// For each mapping ..
	for ( var iMap = 0; iMap < vMappings.length; ++iMap )
	{
		if ( bNotEscaped )
			oVal = vMappings[ iMap ];
		else
			oVal = unescape( vMappings[iMap] );
			
		if (  this.Add( iMap, oVal ) < 0  )
			return false;
	}
	
	return true;
}

// Packed::GetLength()
// Return number of valid mappings
function Packed_GetLength()
{
	var nLen = 0;											// Counter
	
	for ( var strKey in this.abValid )
	{
		if ( this.abValid[strKey] )					// Make sure data is still valid
			++nLen;
	}
	
	return nLen;
}

// Packed::Combine()
// Combine two Packed Objects with one another,
//		with collisions resolving to the passed in object's mapping
// pckRight -- Other Packed Object to combine with
function Packed_Combine( pckRight )
{
	if ( this.GetLength() == 0 &&						// Both lval and rval have no mappings.
		pckRight.GetLength() == 0 )					// ..Early exit
		return true;

	if ( this.GetLength() == 0 )						// Invoked object has no mappings,
	{															//	..absorb passed in object
		this.Seed( pckRight.Spawn() );
		return true;
	}
	
	if ( pckRight.GetLength() == 0 )					// Passed-in object has no mappings.
		return true;										// ..Early exit
		
	// objects --> strings, then concatenate
	var strLeft = this.toString();
	var strRight = pckRight.toString();
	
	strLeft += '&' + strRight;
	
	return this.Feed( strLeft, '&', '=' );			// Feed in concatenation
}

// Convert "key1=v1&key2=v2& ... &keyn=vn" to an assoc. array
// String ==> assoc. array
// Return resultant assoc. array
function TruePackedToAssoc( str )
{
	var pck = new Packed()
	pck.Feed( str, "&", "=" )
	var a = pck.Spawn();
	
	return a;
}

// -= END of Packed.js =-

