/**
 * PObject.js: JavaScript objects that persist across browser sessions and may
 *   be shared by web pages within the same directory on the same host.
 *
 * This module defines a PObject( ) constructor to create a persistent object.
 * PObject objects have two public methods. save( ) saves, or "persists," the
 * current properties of the object, and forget( ) deletes the persistent
 * properties of the object. To define a persistent property in a PObject,
 * simply set the property on the object as if it were a regular JavaScript
 * object and then call the save( ) method to save the current state of
 * the object. You may not use "save" or "forget" as a property name, nor
 * any property whose name begins with $. PObject is intended for use with
 * property values of type string. You may also save properties of type
 * boolean and number, but these will be converted to strings when retrieved.
 *
 * When a PObject is created, the persistent data is read and stored in the
 * newly created object as regular JavaScript properties, and you can use the
 * PObject just as you would use a regular JavaScript object. Note, however,
 * that persistent properties may not be ready when the PObject( ) constructor
 * returns, and you should wait for asynchronous notification using an onload
 * handler function that you pass to the constructor.
 *
 * Constructor:
 *    PObject(name, defaults, onload):
 *
 * Arguments:
 *
 *    name       A name that identifies this persistent object. A single pages
 *               can have more than one PObject, and PObjects are accessible
 *               to all pages within the same directory, so this name should
 *               be unique within the directory. If this argument is null or
 *               is not specified, the filename (but not directory) of the
 *               containing web page is used.
 *
 *    defaults   An optional JavaScript object. When no saved value for the
 *               persistent object can be found (which happens when a PObject
 *               is created for the first time), the properties of this object
 *               are copied into the newly created PObject.
 *
 *    onload     The function to call (asynchronously) when persistent values
 *               have been loaded into the PObject and are ready for use.
 *               This function is invoked with two arguments: a reference
 *               to the PObject and the PObject name. This function is
 *               called *after* the PObject( ) constructor returns. PObject
 *               properties should not be used before this.
 *
 * Method PObject.save(lifetimeInDays):
 *   Persist the properties of a PObject. This method saves the properties of
 *   the PObject, ensuring that they persist for at least the specified
 *   number of days.
 *
 * Method PObject.forget( ):
 *   Delete the properties of the PObject. Then save this "empty" PObject to
 *   persistent storage and, if possible, cause the persistent store to expire.
 *
 * Implementation Notes:
 *
 * JLS - NOTE:  I have removed the Flash SharedObject implementation.
 * Please ignore comments regarding Flash.  I am leaving the comments
 * just in case TTI needs to consider the pros/cons of adding the Flash
 * implementation in.  Please see David Flanagan's book for more
 * information on implementing the Flash persistance. 
 *
 * This module defines a single PObject API but provides three distinct
 * implementations of that API. In Internet Explorer, the IE-specific
 * "UserData" persistence mechanism is used. On any other browser that has an
 * Adobe Flash plug-in, the Flash SharedObject persistence mechanism is
 * used. Browsers that are not IE and do not have Flash available fall back on
 * a cookie-based implementation. Note that the Flash implementation does not
 * support expiration dates for saved data, so data stored with that
 * implementation persists until deleted.
 *
 * Sharing of PObjects:
 *
 * Data stored with a PObject on one page is also available to other pages
 * within the same directory of the same web server. When the cookie
 * implementation is used, pages in subdirectories can read (but not write)
 * the properties of PObjects created in parent directories. When the Flash
 * implementation is used, any page on the web server can access the shared
 * data if it cheats and uses a modified version of this module.
 *
 * Distinct web browser applications store their cookies separately and
 * persistent data stored using cookies in one browser is not accessible using
 * a different browser. If two browsers both use the same installation of
 * the Flash plug-in, however, these browsers may share persistent data stored
 * with the Flash implementation.
 *
 * Security Notes:
 *
 * Data saved through a PObject is stored unencrypted on the user's hard disk.
 * Applications running on the computer can access the data, so PObject is
 * not suitable for storing sensitive information such as credit card numbers,
 * passwords, or financial account numbers.
 *
 * 03/29/07 JLS P7106  Copied code from JavaScript: The Definitive Guide, 
 *              5th Edition by David Flanagan.  Removed Flash 7+ 
 *              persistance.  This Object will either use IE 
 *              persistance, or Cookies.
 *              Added test function to verify that IE Persistance is
 *              supported by IE.  If it is not supported, then the object
 *              forces IE to use Cookies.  This was necessary for EDC
 *              boxes which were missing the needed paths to use IE 
 *              Persistance.
 * 12/10/07 JLS P7138  Added optional parameter to PObject constructor
 *              to force IE to use cookies.
 */

// This is the constructor
/*
 * @param name - name of cookie
 * @param defaults - values to return if cookie does not exist
 * @param onload - Callback function executed when initialization is
 *        complete.
 * @para [forcedCookies] - Forces IE to use cookies and not the IE
 *       persistence model.  Should be boolean value.
 */
function PObject(name, defaults, onload) {
    if (!name) { // If no name was specified, use the last component of the URL
        name = window.location.pathname;
        var pos = name.lastIndexOf("/");
        if (pos != -1) name = name.substring(pos+1);
    }

    this.$name = name;  // Remember our name, defaults, and onload
    this.$defaults = defaults;
    this.$onload = onload;
	var forcedCookies = false;
	
	if(arguments.length > 3)
	{
	    try
	    {
			if(arguments[3] == true)
				forcedCookies = arguments[3];
		}
		catch(ex)
		{
		    // ignore exception.
		}
	}	
    // Just delegate to a private, implementation-defined $init( ) method.
    if(forcedCookies)
    {
		PObject.prototype.$init = pObj_initCookies;
		PObject.prototype.$save = pObj_saveToCookies;
    }

	this.$init(name, defaults, onload);
}

// Save the current state of this PObject for at least the specified # of days.
PObject.prototype.save = function(lifetimeInDays) {
    // First serialize the properties of the object into a single string
    var s = "";                               // Start with empty string
    for(var name in this) {                   // Loop through properties
        if (name.charAt(0) == "$") continue;  // Skip private $ properties
        var value = this[name];               // Get property value
        var type = typeof value;              // Get property type
        // Skip properties whose type is object or function
        if (type == "object" || type == "function") continue;
        if (s.length > 0) s += "&";           // Separate properties with &
        // Add property name and encoded value
        s += name + ':' + encodeURIComponent(value);
    }

    // Then delegate to a private implementation-defined method to actually
    // save that serialized string.
    this.$save(s, lifetimeInDays);
};

PObject.prototype.forget = function( ) {
    // First, delete the serializable properties of this object using the
    // same property-selection criteria as the save( ) method.
    for(var name in this) {
        if (name.charAt(0) == '$') continue;
        var value = this[name];
        var type = typeof value;
        if (type == "function" || type == "object") continue;
        delete this[name];  // Delete the property
    }

    // Then erase and expire any previously saved data by saving the
    // empty string and setting its lifetime to 0.
    this.$save("", 0);
};

// Parse the string s into name/value pairs and set them as properties of this.
// If the string is null or empty, copy properties from defaults instead.
// This private utility method is used by the implementations of $init( ) below.
PObject.prototype.$parse = function(s, defaults) {
    if (!s) {  // If there is no string, use default properties instead
        if (defaults) for(var name in defaults) this[name] = defaults[name];
        return;
    }

    // The name/value pairs are separated from each other by ampersands, and
    // the individual names and values are separated from each other by colons.
    // We use the split( ) method to parse everything.
    var props = s.split('&'); // Break it into an array of name/value pairs
    for(var i = 0; i < props.length; i++) { // Loop through name/value pairs
        var p = props[i];
        var a = p.split(':');     // Break each name/value pair at the colon
        this[a[0]] = decodeURIComponent(a[1]); // Decode and store property
    }
};

/*
 * The implementation-specific portion of the module is below.
 * For each implementation, we define an $init( ) method that loads
 * persistent data and a $save( ) method that saves it.
 */

// Determine if we're in IE and, if not, whether we've got a Flash
// plug-in installed and whether it has a high-enough version number
var isIE = navigator.appName == "Microsoft Internet Explorer";

if (isIE) {  // If we're in IE
    // The PObject( ) constructor delegates to this initialization function
    PObject.prototype.$init = function(name, defaults, onload) {
        // Create a hidden element with the userData behavior to persist data
        
        var div = document.createElement("div");  // Create a <div> tag
        this.$div = div;                          // Remember it
        div.id = "PObject" + name;                // Name it
        div.style.display = "none";               // Make it invisible

        // This is the IE-specific magic that makes persistence work.
        // The "userData" behavior adds the getAttribute( ), setAttribute( ),
        // load( ), and save( ) methods to this <div> element. We use them below.
        div.style.behavior = "url('#default#userData')";

        document.body.appendChild(div);  // Add the element to the document

        // Now we retrieve any previously saved persistent data.
        div.load(name);  // Load data stored under our name
        // The data is a set of attributes. We only care about one of these
        // attributes. We've arbitrarily chosen the name "data" for it.
        var data = div.getAttribute("data");

        // Parse the data we retrieved, breaking it into object properties
        this.$parse(data, defaults);

        // If there is an onload callback, arrange to call it asynchronously
        // once the PObject( ) constructor has returned.
        if (onload) {
            var pobj = this;  // Can't use "this" in the nested function
            setTimeout(function( ) { onload(pobj, name);}, 0);
        }
        
        // even though this is IE, IE persistance may not be supported
        // if certain paths are missing.  Test for support:
		this.$test();
    }

	/**
	 * Tests to see if IE Persistance is supported.  If it is not,
	 * then cookies are used.  IE Persistance may not be supported by
	 * all IE users.  This could happen if the following paths do not 
	 * exist on the users box:
	 * For Windows XP: c:\Documents and Settings\<USERNAME>\UserData
	 * For Win2K: c:\Documents and Settings\<USERNAME>\Application Date\Microsoft\Internet Explorer\UserData
	 */
    PObject.prototype.$test = function()
    {
        try
        {
	        this.$div.setAttribute("test", "test"); // Set text as attribute of the <div>
	        this.$div.save(this.$name);        // And make that attribute persistent
        }
        catch(ex)
        {
		    // switch to using cookies.  This could happen if the
		    // following paths do not exist on the users box:
		    // For Windows XP: c:\Documents and Settings\<USERNAME>\UserData
		    // For Win2K: c:\Documents and Settings\<USERNAME>\Application Date\Microsoft\Internet Explorer\UserData
		    PObject.prototype.$init = pObj_initCookies;
		    PObject.prototype.$save = pObj_saveToCookies;
		    
		    this.$init(this.$name, this.$defaults, this.$onload);
        }
    }

    // Persist the current state of the persistent object
    PObject.prototype.$save = function(s, lifetimeInDays) 
    {
	    try
	    {    
	        if (lifetimeInDays) { // If lifetime specified, convert to expiration
	            var now = (new Date( )).getTime( );
	            var expires = now + lifetimeInDays * 24 * 60 * 60 * 1000;
	            // Set the expiration date as a string property of the <div>
	            this.$div.expires = (new Date(expires)).toUTCString( );
	        }
	
	        // Now save the data persistently
	        this.$div.setAttribute("data", s); // Set text as attribute of the <div>
	        this.$div.save(this.$name);        // And make that attribute persistent
		}
		catch(ex)
		{
		    // Unable to save data.
		}
    };
}
else { /* If we're not IE, fall back on cookies */
    PObject.prototype.$init = pObj_initCookies;
    PObject.prototype.$save = pObj_saveToCookies;
}

function pObj_saveToCookies(s, lifetimeInDays)
{
        var cookie = this.$name + '=' + s;          // Cookie name and value
        if (lifetimeInDays != null)                 // Add expiration
            cookie += "; max-age=" + (lifetimeInDays*24*60*60);
        document.cookie = cookie;                   // Save the cookie
}

function pObj_initCookies(name, defaults, onload) 
{
        var allcookies = document.cookie;             // Get all cookies
        var data = null;                              // Assume no cookie data
        var start = allcookies.indexOf(name + '=');   // Look for cookie start
        if (start != -1) {                            // Found it
            start += name.length + 1;                 // Skip cookie name
            var end = allcookies.indexOf(';', start); // Find end of cookie
            if (end == -1) end = allcookies.length;
            data = allcookies.substring(start, end);  // Extract cookie data
        }
        this.$parse(data, defaults);  // Parse the cookie value to properties
        if (onload) {                 // Invoke onload handler asynchronously
            var pobj = this;
            setTimeout(function( ) { onload(pobj, name); }, 0);
        }
}