//****************************************************************
//****************************************************************
//	COPYRIGHT 2000, Vertex Software
//****************************************************************
//****************************************************************


//----------------------------------------------------------------
//----------------------------------------------------------------
// General routines for "flattening" JavaScript objects into a string
// and then "expanding" the string back into an object. Useful for
// passing arrays and other objects across the internet.
//----------------------------------------------------------------
//----------------------------------------------------------------


var kDateTypeID = "_date";
var kDoubleQuote = "_quote";


//****************************************************************
// GetObjectType
// Returns the actual object type for any object passed to it.
// PARAMETERS:
//	obj:object			Object for which the type will be returned.
// RETURNS:
//	string				The type of object.
// EXAMPLE:
//	var objectType = GetObjectType( [ 1,2 ] );
//		objectType --> "Array"
//****************************************************************
function GetObjectType( obj, ignoreConstructors ) {
	var type = typeof obj;
	try {
		if (typeof obj == "function") return "function"; 
		if (typeof obj == "undefined") return "undefined"; 
		if (typeof obj == "date") return "date"; 
		if (obj == null) return type; 
		if (typeof obj.constructor != "unknown" ) {
			var constructor = "" + obj.constructor;
			if (constructor) {
				if (constructor.search( /function ([^(]+)/gi ) > -1) {
					type = RegExp.$1;
					}
				}
				// 03MAR2007 RFM - Added Pack.IgnoreConstructors to allow custom objects to be packed
			if (Pack.IgnoreConstructors && !type.match(/(string|date|array|number|boolean)/i)) type = "object"; 
			if (ignoreConstructors) type = "object"; 
			}
		else if (!ignoreConstructors && typeof obj.pop == "function" && typeof obj.join == "function") {
			// HACK!!!!!
			// Attempted global fix to the problem whereby an array's Constructor comes back unknown
			type = "Array";
			} 
		}
	catch (error) {
		alert( "GetObjectType: " + error.description );
		}
	return type;
	}

//----------------------------------------------------------------
// EncodeQueryStringCharacters
// Must encode all special query string characters as HTML
// before sending over the wire otherwise they get stripped!!!
//----------------------------------------------------------------
function EncodeQueryStringCharacters(text) {
	text = text.replace( /\+/g, "%2B" );
	return text;
	}


//----------------------------------------------------------------
// Flatten
//----------------------------------------------------------------
function Flatten(obj){
	var data = "";
	try {
		var objectType = GetObjectType(obj).toLowerCase(); 
		if (objectType == "date") {
			data += "'" + kDateTypeID + obj.getTime() + "'";
			}
		else {
			switch (objectType) {
				case "object":
					for(var attribute in obj){
						if (typeof attribute == undefined || GetObjectType( obj[attribute]) == "undefined") continue;
						if (data) data += ", ";
						// This fix breaks when an atribute with a number is parsed!!!!
						// Need to find out what the real pattern was and tighten it up!!!
						//
						if (new String(attribute).match( /^[0-9]+$/g )) {
							// !!!!!!! ERROR!!! We have an array masquerading as an object! Only
							// know solution is to call "" + obj.constructor before hand to cause the
							// constructor to get updated. No idea why it works.
							//
							// Occurs in SitePress when used ClientSide to pass FormProcessing objects
							objectType = "objectarray";
							}
						switch (objectType) {
							case "object":
								data += attribute + ":" 
								break;
							}
						switch (GetObjectType( obj[attribute]).toLowerCase()) {
							case "date":
								data += "'" + kDateTypeID + obj[attribute].getTime() + "'";
								break;
							case "string":
								var quote = (obj[attribute].match( /\'/g ) ? "\"" : "'");
								if (obj[attribute].match(/\'/g ) && obj[attribute].match( /\"/g)) {
									quote = "\"";
									// 26NOV2007 RFM - Replacing double quotes with single quotes will cause
									// data loss!! Obvious when dealing with HTML attributes wich have two 
									// types of quotes already
									//obj[attribute] = obj[attribute].replace( /\"/g, "'" );
									obj[attribute] = obj[attribute].replace( /\"/g, kDoubleQuote );
									}
								var stringValue = obj[attribute];
								data += quote + EncodeQueryStringCharacters(escape(stringValue)) + quote;
								break;
							case "object":
								data += Flatten( obj[attribute] );
								break;
							case "array":
								data += Flatten( obj[attribute] );
								break;
							case "undefined":
								break;
							default:
								data += obj[attribute] 
								}
						 }
					if (objectType == "objectarray") {
						data = "[ " + data + " ]";
						}
					else {
						data = "{ " + data + " }";
						}
					break;
				case "array":
					data += "[ ";
					for (var item=0; item<obj.length; item++) {
						if (item > 0) data += ","; 
						data +=  Flatten( obj[item] );
						}
					data += "]";
					break;
				case "string":
					var quote = (obj.match( /\'/g ) ? "\"" : "'");
					if (obj.match(/\'/g ) && obj.match( /\"/g)) {
						quote = "\"";
						// 26NOV2007 RFM - Replacing double quotes with single quotes will cause
						// data loss!! Obvious when dealing with HTML attributes wich have two 
						// types of quotes already
						//obj = obj.replace( /\"/g, "'" );
						obj = obj.replace( /\"/g, kDoubleQuote );
						}
					// 10JAN2006 RFM - replace newlines and tabs with symbolic values
					obj = obj.replace( /\n/g, "\\n" ).replace( /\t/g, "\\t" ).replace( /\r/g, "\\r" );
					data += quote + obj + quote;
					break;
				case "number":
					data += obj;
					break;
				default:
					window.status = ( "UNKNOWN: " + objectType + " = " + attribute );
				}
			}
		}
	catch (error) {
		alert( "Flatten: " + error.description );
		}	
	return data;
    }


//----------------------------------------------------------------
// Expand
//----------------------------------------------------------------
function Expand(obj){
    for(var attribute in obj){
		switch (typeof obj[attribute]) {
			case "string":
				if (obj[attribute].indexOf(kDateTypeID) > -1) {
					obj[attribute] = new Date( parseInt( obj[attribute].substring( kDateTypeID.length  ) ))
					}
				else {
					obj[attribute] = unescape( obj[attribute] );
					}
				break;
			case "object":
				obj[attribute] = Expand( obj[attribute] );
				break;
			}
         }
	return obj;
    }


//****************************************************************
// Pack
// Converts any Javascript entity passed to it into a textual
// representation suitable for tranmission over the internet.
// The packed entity can later be "unpacked" by calling <A HREF='/global/scripts/index.asp?functionID=Unpack&includeFile=/global/scripts/Serialization.asp'>Unpack</A>.
// PARAMETERS:
//	entity:any		Any valid Javascript entity such as an object, array, string, or number.
// RETURNS:
//	string			Testxaul representation of <I>entity</I> suitable for tranmission over the internet.
//****************************************************************
function Pack( entity) {
	switch (typeof entity) {
		case "object":
			entity = Flatten( entity );
			break;
		}
	return entity;
	}


//****************************************************************
// Unpack
// Converts a text string created by calling <A HREF='/global/scripts/index.asp?functionID=Pack&includeFile=/global/scripts/Serialization.asp'>Pack</A> into it's original
// Javascript object. 
// PARAMETERS:
//	packedResult:string			String created from call to <A HREF='/global/scripts/index.asp?functionID=Pack&includeFile=/global/scripts/Serialization.asp'>Pack</A>.
// RETURNS:
//	any							An object corresponding the the type of the original entity passed to the <A HREF='/global/scripts/index.asp?functionID=Pack&includeFile=/global/scripts/Serialization.asp'>Pack</A> routine.
// EXAMPLE:
//	var testObject = {};
//		testObject.now = new Date();
//		testObject.text = "this is text";
//		testObject.number = 56;
//	var packedTest = Pack( testObject );
//	packedTest -->	"{ now:'_date977089255006', text:'this%20is%20text', number:56 }"
//	var newObject = Unpack( packedTest );
//		newObject.number --> 56
//		newObject.text  --> "this is text";
//****************************************************************
function Unpack( packedResult ) {
// Sorry Charlie, 4.0 browsers don't support this
//	try {
		eval( "var result = " + packedResult );
		switch (typeof result) {
			case "object":
				result = Expand( result );
				break;
			}
//		}
//	catch ( error ) {
//		}
	return result;
	}


//****************************************************************
// GetObjectAttributeList
// Returns a comma-delimited list of all of the attributes for 
// <I>object</I>.
// PARAMETERS:
//		object:object				Object for which comma-delimited list of attributes is to be retrieved.
// RETURNS:
//		string						Comma-delimited list of attribute names for <I>object</I>
//****************************************************************
function GetObjectAttributeList( object ) {
	var attributes = "";	
	for (var attribute in object) {
		if (attributes) attributes += ",";
		attributes += attribute; 
		}
	return attributes;
	}


//****************************************************************
// DenormalizeObject
// Returns a new object with any nested objects denormalized or flattened
// into a single object having unique attributes created from teh nested objects.
// For example, given an object with the the attributes person.address1.city, person.address2.city,
// DenormalizeObject will return a new object with teh attributes personaddress1city and personaddress2city
// without any nested objects.
// PARAMETERS:
//		object:object				Object to be denormalized.
// RETURNS:
//		object						Denormalized version on <I>object</I>.
//****************************************************************
function DenormalizeObject( object, attributeDelimiter ) {
	var denormalizeObject = {};
	try {
		if (!attributeDelimiter) attributeDelimiter = "_"; 
		for(var attribute in object){
			if (attribute && object[attribute] != null)  {
				var objectType = GetObjectType(object[attribute]).toLowerCase(); 
				switch (objectType) {
					case "object":
						var denormalizeSubObject = DenormalizeObject( object[attribute] );
						for(var subattribute in denormalizeSubObject){
							if (subattribute && denormalizeSubObject[subattribute] != null)  {
								eval( "denormalizeObject." + attribute + attributeDelimiter + subattribute + " = denormalizeSubObject[subattribute];" )
								}
							}
						break;
					case "array":
						for (var item=0; item<object[attribute].length; item++) {
							var subObject = object[attribute][item];
							var subobjectType = GetObjectType(subObject).toLowerCase(); 
							if (subobjectType == "object") {
								var denormalizeSubObject = DenormalizeObject( subObject );
								for(var subattribute in denormalizeSubObject){
									eval( "denormalizeObject." + attribute + item + attributeDelimiter + subattribute + " = denormalizeSubObject[subattribute];" );
									}
								}
							else {
								eval( "denormalizeObject." + attribute +  item  + " = subObject;" );
								}
							}
						break;
					default:
						eval( "denormalizeObject." + attribute + " = object[attribute];" );
						break;
					}
				}
			}
		}
	catch (error) {
		WriteBR( "DenormalizeObject: " + error.description );
		}
	return denormalizeObject;
	}



//****************************************************************
// NormalizeObject
// Returns a new object with any denormalized attributes expanded into
// equivalent nested objects.
// PARAMETERS:
//		object:object				Object to be normalized.
// RETURNS:
//		object						Normalized version on <I>object</I>.
//****************************************************************
function NormalizeObject( object, attributeDelimiter ) {
	var normalizedObject = {};
	try {
		var indexPattern = new RegExp( "[^0-9]+[0-9]+" );
		var currentElementIndex = -1;
		if (!attributeDelimiter) attributeDelimiter = "_"; 
		//  attribute_attributeB# --->  object.attribute[].attributeB
		for(var attribute in object){
			var elements = attribute.split( attributeDelimiter );
			if (elements.length > 1) {
				var subObject = elements[0];
				var subObjectAttribute = elements[1];
				if (subObject.search(indexPattern) > -1) {
					var elementIndex = parseInt( subObject.replace( /[^0-9]+/, "" ) );
					subObject = subObject.replace( /[0-9]+/, "" )
					if (eval( "typeof normalizedObject." + subObject ) == "undefined" ) {
						eval( "normalizedObject." + subObject + " = new Array()" );
						}
//					if (eval( "typeof normalizedObject." + subObject + "[ elementIndex ]"  ) == "undefined" ) {
					if (currentElementIndex != elementIndex ) {
						currentElementIndex = elementIndex;
						eval( "normalizedObject." + subObject + ".push( {} )"  );
						}
					eval( "normalizedObject." + subObject + "[ normalizedObject." + subObject + ".length - 1 ]." + subObjectAttribute + " = object[attribute];" );
					}
				else {
					if (eval( "typeof normalizedObject." + subObject ) == "undefined" ) {
						eval( "normalizedObject." + subObject + " = {}" );
						}
					eval( "normalizedObject." + subObject + "." + subObjectAttribute + " = object[attribute];" );
					}
				}
			else {
				eval( "normalizedObject." + attribute + " = object[attribute];" );
				}
			}
		}
	catch( error ) {
		alert( "NormalizeObject: " + error.description );
		}
	return normalizedObject;
	}



//****************************************************************
// ExtractObjectAttributes
// Returns an new object having only the attributes specified in <I>attributeList</I>
// copied from <I>object</I>.
// PARAMETERS:
//	object:object			Object from whcih attributes specified in <I>attributeList</I> will be extracted.
//	attributeList:string	Comma-delimited list of attributes to be extracted from <I>object</I>.
// RETURNS:
//	object					New object having only the attributes specified in <I>attributeList</I> extracted from <I>object</I>.
//****************************************************************
function ExtractObjectAttributes( object, attributeList ) {
	var attributes = attributeList.split( "," );
	var newObject = {};
	for (var item=0; item<attributes.length; item++) {
		var attribute = attributes[item];
		eval( "newObject." + attribute+ " = object." + attribute );
		}
	return newObject;
	}


//****************************************************************
// CopyObjectAttributes
// Copies all of the attributes from <I>sourceObject</I> to <I>destinationObject</I>. Existing 
// attributes in <I>destinationObject</I> are only overwritten if <I>sourceObject</I> has
// the same attribute.
// PARAMETERS:
//	sourceObject:object			Object from which attributes will be copied.
//	destinationObject:object	Object to which attributes will be copied.
//	[defaultValue]:any			Default value used for attributes not found in soureObject.
//  [excludeUndefined]:boolean	If true, undefined values are filtered
//	[excludeFields]:string		Comma-delimited list of fields to exclude. Default is to copy all fields;
//****************************************************************
function CopyObjectAttributes( sourceObject, destinationObject, defaultValue, excludeUndefined, excludeFields ) {
	try {
		// 19MAY2009 RFM - TSS Self Help - Added ability to exclude fields
		if (excludeFields) excludeFields = MapList(excludeFields)
		for(var attribute in sourceObject){
			if (excludeFields && excludeFields[attribute]) continue;
			var value = sourceObject[ attribute ];
			// 13MAY2009 RFM - Added for Babytalk
			if (excludeUndefined && typeof value == "undefined") continue; 
			if (typeof defaultValue != "undefined" && typeof value == "undefined") value = defaultValue;
			destinationObject[ attribute ] = value;
			}
		}
	catch (error) {
		alert( "CopyObjectAttributes: " + error.description );
		}
	}




//================================================================
// Encode64
// This code was written by Tyler Akins and has been placed in the
// public domain.  It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
//================================================================
function Encode64(input) {
	try {
		// 18JUN2008 RFM - replaced string cat with array
		var output = [];
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
		var keyStr = Encode64.keyStr;
		do {
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
				} 
			else if (isNaN(chr3)) {
				enc4 = 64;
				}
			output.push( keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4) );
			} 
		while (i < input.length);
		return output.join("");
		}
	catch (error) {
		alert( "Encode64: " + error.description  );
		}
	return input;
	}
Encode64.keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";


//===========================================================
// Decode64
// This code was written by Tyler Akins and has been placed in the
// public domain.  It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
//===========================================================
function Decode64(input) {
	try {
		// 18JUN2008 RFM - replaced string cat with array
		var output = [];
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
		var keyStr = Encode64.keyStr;
		// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		do {
			enc1 = keyStr.indexOf(input.charAt(i++));
			enc2 = keyStr.indexOf(input.charAt(i++));
			enc3 = keyStr.indexOf(input.charAt(i++));
			enc4 = keyStr.indexOf(input.charAt(i++));
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
			output.push( String.fromCharCode(chr1) );
			if (enc3 != 64) output.push( String.fromCharCode(chr2) );
			if (enc4 != 64) output.push( String.fromCharCode(chr3) );
			} 
		while (i < input.length);
		return output.join("");
		}
	catch (error) {
		alert( "Decode64" + error.description  );
		}
	return input;
	}

