/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 1555 $
  $Date: 2010-01-05 09:07:40 -0700 (Tue, 05 Jan 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/CodeBaby.js $
*/
/*
Object: CodeBaby
Holds all CodeBaby objects, classes and functions.
*/
CodeBaby = {};  //Create CodeBaby object to act as a namespace
/*
Title: Getting Started
CodeBaby conversations are created in CodeBaby Suite, which 
generate data to be used by CodeBaby Delivery Manager (CDM).  CDM is a
JavaScript library that enables CodeBaby conversations.

To add a CodeBaby conversation to a web page:

    - Plan a conversation in CodeBaby Suite
    - Create the conversation segments using CodeBaby Create Studio
    - Deliver the conversation via the Deploy button in CodeBaby Suite
    - Add the CDM script tag to the web page.  The exact contents of the script tag are shown in the Deliver dashboard of CodeBaby Suite.  See <Script tag>.

Topic: Script tag
In the simplest implementation, once all of the CodeBaby segments have been
published, the CodeBaby conversation can be added to a web page using a single <
script> tag.  For example

><script type='text/javascript'
>         src='http://cdn.codebaby.com/${cb.cdm.bootJSPath}?CodeBaby=account:1001;conversation:1234;page:shopFAQ>
>  </script>

In this example, we include the JavaScript file ${cb.cdm.bootJSPath}, which is
the 2.1.1.2802 version
of the boot CDM JavaScript file.  This is a lightweight file that does has
the minimal code to initialize CodeBaby conversation JavaScript and prepare
to load other files.

We also pass configuration settings to the script via the query portion of the
src attribute (the part after the ?) via the CodeBaby parameter.
The value of the CodeBaby parameter is a semi-colon separated list of
name:value pairs for configuration settings.

The minimal set of setting to start a conversation are:

    account - The id of the account, 1001 in the example above.  This value
    is always required.
    conversation - The id of the conversation, set to 1234 in the example.  This
    means we want to use conversation 1234 created in CodeBaby Suite.
    page - The name of the page as assigned in CodeBaby Suite.

Passing these values tells CDM which conversation is to be enacted on this page.

Many parameters can be passed via any one of three different methods: the page url, the src attribute, or programmatically.
See "Methods for setting configuration values" in <CodeBaby.config> for details.

Topic: Positioning

By default, CDM looks for an HTML element called CodeBabyParent and places
the digital character player inside of that element.  However, the CodeBabyParent
element is not required; if not found, the player will be placed in the <body> tag.

Some configuration items assist in positioning:
    player.parent - The name of the div that will contain the player
    player.wrapper - The name of the div that is created to hold the player
       within <player.parent>.  This wrapper is used for positioning and styling.
       If the default value already exists in the page, then this should be changed.
    player.position - CSS-style position attribute for the wrapper
    player.left - CSS-style position attribute for the wrapper
    player.top - CSS-style top attribute for the wrapper

Also, the wrapper may obtained via <CodeBaby.flashPlayer.getWrapperElement>.
Then it may be positioned or otherwise styled via its style attribute.
For example

>
>  // Position 100 px down, and place a solid border around the player.
>
>  var wrapper = CodeBaby.flashPlayer.getWrapperElement();
>  if (wrapper != null){
>      wrapper.style.position = "absolute";
>      wrapper.style.top = "100px"; // don't forget the px!
>      wrapper.style.borderStyle = "solid";
>  }
>

The wrapper is not created until the player has been initialized, which may
not happen until a segment is about to play.  To deal with this, add
a listener so that you know when the wrapper has been created

>
>    // places a solid border around the player
>
>    CodeBaby.flashPlayer.addChangeListener("wrapperElement",function(event){
>        if (event.newValue != null){
>            var el = event.newValue;
>            el.style.borderStyle = "solid";
>        }
>    });
>

*/

/*
Topic: Debugging

Various debugging messages have been placed in CDM, and you can view these
by turning on debugging through configuration, see <debug>.  Debug messages
are logged at different levels, from TRACE (most detail) to FATAL (least detail).

For example, to get messages logged in the INFO level through to FATAL:

>
><script type='text/javascript'
>         src='http://cdn.codebaby.com/${cb.cdm.bootJSPath}?CodeBaby=debug:info;account:1001;conversation:1234;page:shopFAQ>
>  </script>
>

By default, these are logged to the browser's console, which is accessed
in different ways in different browsers.

You can add your own listener to the logger so you can view the messages
in different ways:

><script>
>   CodeBaby.logger.addListener(CodeBaby.logger.Levels.ALL, function(category, level, message) {
>       document.getElementById("logBox").innerHTML += "Log: " + message;
>   });
></script>

You can also log your own messages via <CodeBaby.logger.log> or other
convenience methods in <CodeBaby.logger>.

See <CodeBaby.logger> for details.

*/

/*
Topic: Account and conversation configuration JavaScript

CodeBaby Suite enables you to add custom JavaScript code directly from the Suite application.  This code is loaded 
after the CDM boot files, via the account and conversation configuration files
(the first file loaded by the script tag).

The primary use of these files is to:
- Modifying the DOM dynamically.  In many situations, links or buttons on the page
    must be modified in order to successfully implement a CodeBaby conversation.  
    In an ideal situation, including the single <script> tag should be the only
    HTML change required.
- Modify configuration settings.  Note that this only affects objects in
    JavaScript files loaded after these configuration files.  Common configuration
    settings would be conversation and page if not encoded in the script url.
- Setting up load of JavaScript code for additional JavaScript library files,
for example an AJAX library to support dialog boxes.
- Defining CodeBaby.ui.dialogBoxManager.
- Define CodeBaby.start.  See <Starting up>.

The account configuration file is found based on the <account> setting.  It
can set the <conversation> setting via <CodeBaby.config.set> and affect
which conversation config is loaded.

After these files load, the rest of CDM is loaded, unless the configuration files
modified the load behavior.

*/

/*
Topic: Starting up

After the initial CDM script loads, it checks if CodeBaby is set to automatically
start executing conversation logic on on the
page via the setting <autoStart>, which by default, is "yes".
If so the CodeBaby.start() function is called.

You can overwrite the CodeBaby.start function to do whatever is required
when the scripts have loaded.

E.g.,
CodeBaby.start = function(){
   alert("CodeBaby CDM script have loaded");
}

However, before you can define this function, the initial ${cb.cdm.bootJSName}
tag must have loaded first, so you can only define this in the account or
conversation config JavaScript that you enter into CodeBaby Suite.
*/

/*
Topic: Using Unobfuscated, Modulized JavaScript

The CodeBaby Delivery Manager JavaScript is highly optimized,
and so several source files are combined and minified (obfuscated)
to make ${cb.cdm.bootJSName}.
JavaScript that is loaded later is by default also from concatenated
and  minified files.  To assist with understanding this JavaScript
and debugging, all of it can be loaded in non-minified versions,
and much of it in separate JavaScript files with only one high-level object or class per file.

To use the non-minified version of ${cb.cdm.bootJSPath}, use ${cb.cdm.bootNoMinJSPath}.

To use non-minified version of the rest of the JavaScript,
use the configuration parameter <script.minJS>=no either via the page url,
or the src attribute in the script tag.

For example

><script type='text/javascript'
>         src='http://cdn.codebaby.com/${cb.cdm.bootNoMinJSPath}?CodeBaby=script.minJS:no;account:1001;conversation:1234;page:shopFAQ>
>  </script>


If you want to look directly at each JavaScript source file, it is available
on the CodeBaby Delivery Network server under ${cb.cdm.jsPath}.
Generally, the source for each class or object is in a file based on its name.
Take away the CodeBaby prefix (except for the CodeBaby object) and use the
rest for the file name.  For example, the CodeBaby.util object can be found in
src/util.js.
*/

/*
Topic: Dialog Boxes
Dialog boxes are an important part of conversations, but are not included in CDM.
Because dialog boxes are primarily visual component that may require distinct
styles, and may require specialized libraries to support them (such as jQuery).
For this reason, CDM makes calls to a dialog box interface that must be
implemented outside of CDM, named CodeBaby.ui.dialogBoxManager.

See <CodeBaby.ui.dialogBoxManager> for details.
*/

/*
Topic: Having a conversation

When a page loads the CodeBaby conversation automatically
executes for that page (unless configured otherwise; see <autoStart>).
For example, if the conversation map has a connection
from the page with the description 'When the page loads, play the Greeting segment', then
the Greeting segment plays when the page loads and the conversation follow from there.
For example, the segment may show a dialog box, and the user can select
a choice, which causes a connection to be followed, etc.

Throttling logic, such as only showing the Greeting segment on the first visit,
may be implemented by having a connection from the page node to a conditional node.
The conditional node logic returns a value that plays the Greeting segment only
on the first visit.  This is the prefered way to implement throttling, rather
than configuring <autoStart> to "no" in the configuration scripts.

*/

/*
Topic: Triggering conversation actions

In some cases you may want to trigger an action on a node based on a user action
or some logic in the page.
Any node may be triggered by adding a "Respond" node to the map, and calling
<CodeBaby.conversation.trigger> ("trigger node name").

For example, if you want to play the "Greeting" segment when a button is clicked:

    -  Add a Trigger node to the conversation map, and give it a name like "Play Greeting"
    -  In the web page, add the code:
><button onclick="CodeBaby.conversation.trigger('Play Greeting')">Hello</button>
*/
/*
Topic: Playing a Segment

Segment playing is automatically managed through the <CodeBaby.conversation> API.
However, it is simple to play a segment programmatically after you create
the <CodeBaby.SegmentVideo> object.

For example

>
> var vid = new CodeBaby.SegmentVideo("doesn't matter","what you put here");
> vid.setUrl("http://cdm.codebaby.com/accounts/1001/conversations/1234/videos/5.flv"
> CodeBaby.player.play(vid);
>

Execute this code only after the page has finished loading and CodeBaby.start has
executed. For example,
link it to a <button> onClick event in CodeBaby.start,
and the segment will play when the button is clicked.

You can also execute this when the player is ready by passing assigning a function to CodeBaby.start:

>CodeBaby.start = function(){
>    var vid = new CodeBaby.SegmentVideo("doesn't matter","what you put here");
>    vid.setUrl("http://cdm.codebaby.com/accounts/1001/conversations/1234/videos/5.flv"
>    CodeBaby.player.play(vid);
>};
*/

/*
Topic: Anatomy of the Boot JavaScript

The "boot" JavaScript is the minimal script loaded to enable CodeBaby conversations on a page.
It loads the follow objects and classes.

    CodeBaby - the CodeBaby object, which acts as a container for all CodeBaby objects, vars and functions.
    CodeBaby.ui - A container for ui objects, in this case just for a dialogBox manager.
    See <CodeBaby.ui.dialogBoxManager>
    CodeBaby.util - A library of useful functions.
    See <CodeBaby.util>.
    CodeBaby.config - An object to find and manage configuration settings.
    See <CodeBaby.config>.
    CodeBaby.consoleLog - Writes log messages to the browser's console.
    See <CodeBaby.consoleLog>.
    CodeBaby.popupLog - Writes log messages to a pop-up window.
    See <CodeBaby.popupLog>.
    CodeBaby.logger - A logging object used for debugging.
    See <CodeBaby.logger>.
    CodeBaby.cookies - For accessing and setting cookies, to support <CodeBaby.storage>.
    CodeBaby.storage - For storing CodeBaby-related data.
    See <CodeBaby.storage>.
    CodeBaby.loader - For loading additional scripts efficiently.
    See <CodeBaby.loader>.
    CodeBaby.metrics - For capturing metrics.
    See <CodeBaby.metrics>.
    CodeBaby.init - Initializes custom account and conversation JavaScript from CodeBaby Suite. This also loads the Core JavaScript (see below).
    See <CodeBaby.init>.
*/

/*
Topic: Anatomy of the Core JavaScript
The core is composed of additional objects / classes required to execute conversations.
They  are

    swfobject - A standard library for adding <object> tags to html
    to add Flash objects.
    CodeBaby.ChangeEvent - A simple class that defines change events for change listeners.
    See <CodeBaby.ChangeEvent>.
    CodeBaby.ChangeListeners - A class that holds listeners to change.  Used
    by other objects to support change listeners.
    See <CodeBaby.ChangeListeners>.
    CodeBaby.BeforeAfterListeners - A class that implements before and after
    change listeners.  Used by other objects to support before and after listeners.
    CodeBaby.TimedListeners - A class that holds listeners that respond to changes
    in a time index.
    See <CodeBaby.TimedListeners>.
    CodeBaby.SegmentVideo - Holds information about a video for a segment.
    (A segment may have many vidoes for different sizes).
    See <CodeBaby.SegmentVideo>.
    CodeBaby.flashPlayer - Wrapper around the Flash player.
    See <CodeBaby.flashPlayer>.
    CodeBaby.conversation - Holds conversation data and executes conversation
    logic.
    See <CodeBaby.conversation>.
*/

/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 1183 $
  $Date: 2009-11-24 09:19:37 -0700 (Tue, 24 Nov 2009) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/ui.js $
*/
/*
Object: CodeBaby.ui
Holds all CodeBaby UI objects, classes and functions.
*/
CodeBaby.ui = {};  //Create CodeBaby object to act as a namespace
/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2756 $
  $Date: 2010-07-19 14:05:05 -0600 (Mon, 19 Jul 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/util.js $
*/
/*
Object: CodeBaby.util
Collection of useful functions for CodeBaby conversations
*/
CodeBaby.util = (function(){
    //private
    var CONVERSATION_EXPIRES_DEFAULT = 300; // seconds
    var CONVERSATION_STORAGE_GROUP_NAME = "CodeBabyConversation";
    var CONVERSATION_STORAGE_VARIABLE_NAME = "id";
    /* Function getCurrentScript
       Get the <script> tag that loaded this file

       Returns:
            the last <script> element loaded on the page so far.
    */
    var getCurrentScript = function(){
        var scripts = document.getElementsByTagName('script');
        return scripts[scripts.length - 1];
    }
    var scriptElement = getCurrentScript();
    var scriptSrc = scriptElement.src;

    // Gets the script host e.g., http://cdn.codebaby.com
    var scriptHost = (function(){
            if (scriptHost){ // scriptHost should only be set once
                return scriptHost;
            }
            var decodedSrc = decodeURI(scriptSrc);
            //regex = non '\' chars + :// + non '\' chars
            var regex = new RegExp("([^\\/]*:\\/\\/[^\\/]*)");
            var result = regex.exec(decodedSrc);
            if (result == null){
                scriptHost = null;
            } else {
                scriptHost = result[1];
            }
            return scriptHost;
    })();
/*
Function: getUrls
Gets urls for accountData, config, data, code, style and video; uses config settings if
account and conversation parameter are the same as the
configured account and conversation.  Otherwise
uses standard values.

Parameters:
    account - the account for which you want the urls
    conversation - the conversation for which you want the urls.

Returns: an object with five urls {accountData:, config:, data:,code:,video:}.
*/
    var getUrls = function(account, conversation){
        var config = CodeBaby.config;
        var retVal = {};
        var conf = config.get("conversation",null);
        var acct = config.get("account",null);
        if (conf == conversation && acct == account){
           retVal.config = config.get("script.conversationConfigUrl",null);
           retVal.accountData = config.get("account.dataUrl",null);
           retVal.data = config.get("conversation.dataUrl",null);
           retVal.code = config.get("conversation.codeUrl",null);
           retVal.style = config.get("conversation.styleUrl",null);
           retVal.video = config.get("conversation.videoPath",null);
        } else {
            retVal.config = getStandardUrl("config",account,conversation);
            retVal.accountData = getStandardUrl("accountData",account,conversation);
            retVal.data = getStandardUrl("data",account,conversation);
            retVal.code = getStandardUrl("code",account,conversation);
            retVal.style = getStandardUrl("style",account,conversation);
            retVal.video = getStandardUrl("video",account,conversation);
        }
        return retVal;
    };
    
    /*
    Function setUpConversationStorage
    Sets up a conversation storage group with expiration from config setting
    <conversation.expires>
    */
    var setUpConversationStorage = function(){
        var expires
        = CodeBaby.config.get("conversation.expires",CONVERSATION_EXPIRES_DEFAULT);
        CodeBaby.storage.setUpGroup(CONVERSATION_STORAGE_GROUP_NAME,expires);
    };

    /*
    Function getConversationSettings
    Gets the conversation value from config and from a conversation cookie.  The
    cookie value is removed when this is called.
    
    Returns: an object {config:, current:} which has the conversation setting
    from configuration and from a short-lived cookie that keeps track of
    the current conversation (defaults to config setting)
    */
    var getConversationSettings = function(){
        var retVal = {};
        // get the currentConversation from storage or set to config value
        retVal.config = CodeBaby.config.get("conversation",null);
        setUpConversationStorage();
        retVal.current
        = CodeBaby.storage.get(CONVERSATION_STORAGE_GROUP_NAME
        ,CONVERSATION_STORAGE_VARIABLE_NAME,retVal.config);
        return retVal;
    };
    
    /*
    Function resetStoredConversation
    Resets the conversation stored in the conversation cookie.
    */
    var resetStoredConversation =  function(){
        setUpConversationStorage();
        CodeBaby.storage.remove(CONVERSATION_STORAGE_GROUP_NAME
        ,CONVERSATION_STORAGE_VARIABLE_NAME);
    };
    
    /*
    Function setStoredConversation
    Sets the stored conversation
    
    Parameters:
        conversation - the value of the stored conversation
    */
    var setStoredConversation = function(conversation){
        setUpConversationStorage();
        CodeBaby.storage.set(CONVERSATION_STORAGE_GROUP_NAME
        ,CONVERSATION_STORAGE_VARIABLE_NAME,conversation);
    };

// functions to get standard urls and video path
/*
Function: getStandardUrl
Gets the standard url for config, data, code or video

Parameters:
    type - "config", "data", "accountData", "code" or "video"
    account - the account
    conversation - the conversation, optional for "accountData"
*/
    var getStandardUrl = function(type,account,conversation){
        var end = "";
        var url = "";
        if (type === "accountData"){
            url = CodeBaby.config.get("script.host")
                  + "/accounts/" + account
                  + "/accountData.js";
            return url;
        }
        switch(type){
            case "config":
                end = "config.js";
                break;
            case "data":
                end = "data.js";
                break;
            case "code":
                end = "code.js";
                break;
            case "style":
                end = "style.css";
                break;
            case "video":
                end = "videos";
                break;
            default:
                break;
        }
        url = CodeBaby.config.get("script.host")
              + "/accounts/" + account
              + "/conversations/" + conversation
              + "/" + end;
        return url;
    };

    //public
    return {
/*
   Function: getScriptHost
   Gets the host name of this script's host server.

   Returns:
        String the host name of the server that has this script; from the
   src attribute on the script element.

   See Also:
        <getScriptElement>
*/
            getScriptHost: function(){
            return scriptHost;
        },
/*
    Function: getScriptElement
    Gets the script element that loaded this script.

    Returns:
        HTMLElement The element that holds this script.
*/
        getScriptElement: function(){
            return scriptElement;
        },
/*
    Function: generateGUID
    Generates a GUID
    Returns:
        The generated GUID
*/
        generateGUID: function(){
		    function getRandomLetter() {
			    return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
		    }
		    var guid = "";
		    for (var i = 0; i < 8; i++) {
			   guid += getRandomLetter();
		   }
		   return guid;
	    },


	    /*
	    Function: cloneObject
	    Clones an object in JavaScript.  For example, if you would like to assign
	    b to a, but then you want changes in b to NOT be reflected in a.
	    NOTE: this may not clone private variables.

	    Parameters:
	        object - The object to be cloned
	    Returns:
	        The cloned object.
	    */
	    cloneObject: function(obj) {
	        if(obj == null || typeof(obj) != 'object') {
	            return obj;
	        }
            var tmp = new obj.constructor(); // = {};
	        for(var key in obj) {
                tmp[key] = this.cloneObject(obj[key]);
            }
            return tmp;
	    },

	    /*
	    Function: each
	    Executes a function for each item in an array

	    Parameters:
	        arr - The array to iterate through
	        f - The function to execute. Passed in the item in the array
	    */
	    each: function(arr, f) {
            if (null != arr && "function" == typeof(f)) {
                for (var i = 0; i < arr.length; i++) {
                    f(arr[i]);
                }
            }
        },

        /*
        Function: blank
        Checks if an object is null or an empty or whitespace only string

        Parameters:
            obj - The object to check
        */
        blank: function(obj) {
            if (null != obj) {
                if ("string" == typeof(obj)) {
                    return /^\s*$/.test(obj)
                }
            }
            return true;
        },

        /**
         * Function: setStyle
         * set the style of an element; these style properties should match the
         * javascript properties defined in the javascript style property spec
         * i.e backgroundColor, not background-color
         * 
         * Set a style to null if you want it cleared.  Note that some browsers
         * do not accept a null value for some style settings (e.g., IE for background-color)
         * and will not change the value.
         * 
         *  Parameters:
         *      elem - the element for which you want to set the style
         *      obj - an object containing style attributes (as members) e.g., {color:"black",position:"absolute"}
         */
	    setStyle: function(elem, obj) {
	        if (null != elem && null != obj) {
	            if (elem.style == null) {
	                throw new Error("Trying to style something that isn't " +
	                		"an element");
	            }
	            var style = null;
	            if ("undefined" == typeof(obj.length)) {
	                // we have an object
	                var styles = [];
                    for (prop in obj) {
                        if (typeof obj[prop] != null) {
                            elem.style[prop] = obj[prop];
                        } else {
                            alert("trying to delete prop");
                            try {
                                delete elem.style[prop];
                            } catch (e) {
                                elem.style[prop] = null;
                            }
                        }
                    }
	            } else {
	                throw new Error("style object must be an object, not an array");
	            }
	        }
	    },
	    
	    /*
	     * Positioning helpers
	     */
	    /*
	     * Function: getElementPosition
	     * gets various positioning values helpful for absolute positioning;
	     * NOTE: positioning may be different if the display css is set to "none"
	     * because most browsers will treat the element as if it had no
	     * width and height (i.e. it's not on the page at all)
	     * 
	     * Parameters:
	     * element - the element to find the positioning values for
	     * 
	     * Returns:
	     * a hash including the following:
	     *    1) left - the position of the left side of the element relative 
	     *              to the left side of the document
	     *    2) right - the position of the right side of the element relative
	     *               to the left side of the document
	     *    3) top - the position of the top side of the element relative to
	     *             the top of the document
	     *    4) bottom - the position of the bottom side of the element 
	     *                relative to the top of the document
	     *    5) width - the width of the element 
	     *    6) height - the height of the element
         *    6) zIndex - the zIndex of the element; returns the zIndex up the
         *    element's ancestry
	     */
	    getElementPosition: function(element) {
            if (element == null) {
                throw new Error("Can't get positioning for null or " +
                		"undefined element");
            }
            if (element.parentNode == null) {
                throw new Error("Can't get positioning for element that " +
                		"isn't attached to the DOM: " + element);
            }
            //left
            var xPos = element.offsetLeft;
            var tempEl = element.offsetParent;
            while (tempEl != null) {
                xPos += tempEl.offsetLeft;
                tempEl = tempEl.offsetParent;
            }
            //top
            var yPos = element.offsetTop;
            var tempEl = element.offsetParent;
            while (tempEl != null) {
                yPos += tempEl.offsetTop;
                tempEl = tempEl.offsetParent;
            }
            //width and height
            var width = element.offsetWidth;
            var height = element.offsetHeight;
            if (element.style.display == "none") {
                element.style.display = "block";
                width = element.offsetWidth;
                height = element.offsetHeight;
                element.style.display = "none";
            }
            var zIndex = element.style.zIndex;
            var tempEl = element.parentNode;
            while (tempEl != null && (zIndex == null || zIndex == "")) {
                if (tempEl.style != null) {
                    zIndex = tempEl.style.zIndex;
                } else {
                    zIndex = null;
                }
                tempEl = tempEl.parentNode;
            }
            return {
                left: xPos,
                right: xPos + width,
                top: yPos,
                bottom: yPos + height,
                width: width,
                height: height,
                zIndex: ((zIndex != null && zIndex != "") ? parseInt(zIndex) : 0)
            };
	    },
	    /*
	     * Function: getOffsetFromPosition
	     * Gets the offset (relative to a specified element) for a specified position (which is relative to the document)
	     * 
	     * Parameters:
	     * position - a hash including the following:
	     *    1) left - the position of the left side of the element relative 
	     *              to the left side of the document
	     *    2) right - the position of the right side of the element relative
	     *               to the left side of the document
	     *    3) top - the position of the top side of the element relative to
	     *             the top of the document
	     *    4) bottom - the position of the bottom side of the element 
	     *                relative to the top of the document
	     * anchorType - either 'window' or 'element'
         * anchorElement - the element where the return offset will be relative to
         * attachmentPoint - the 9-grid point on the element
         * anchorAttachmentPoint - the 9-grid point on the anchor
         * Returns:
         *    the offset as a hash with "x" and "y"
	     */
	    getOffsetFromPosition: function(position, anchorType, anchorElement, attachmentPoint, anchorAttachmentPoint) {
	        var anchorElementPosition;
	        if (anchorType == 'element') {
	            anchorElementPosition = CodeBaby.util.getElementPosition(anchorElement);
	        } else if (anchorType == 'window') {
                var windowPos = CodeBaby.util.getWindowPosition();
                anchorElementPosition = {
                    left: windowPos.scrollLeft,
                    right: windowPos.scrollLeft + windowPos.width,
                    top: windowPos.scrollTop,
                    bottom: windowPos.scrollTop + windowPos.height 
                }
	        } else if (anchorType == 'document') {
	            anchorElementPosition = CodeBaby.util.getElementPosition(document.body);
	        } else {
	            throw new Error("Invalid anchorType: '" + anchorType + "'");
	        }
	        
	        var positionX;
	        var positionY;
	        var anchorElementPositionX;
	        var anchorElementPositionY;
            
            switch (attachmentPoint) {
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_BOTTOM:
                    positionX = position.left;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_BOTTOM:
                    positionX = position.right;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_BOTTOM:
                    positionX = (position.right + position.left) / 2;
                    break;                
            }
            
            switch (attachmentPoint) {
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_TOP:
                    positionY = position.top;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_BOTTOM:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_BOTTOM:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_BOTTOM:
                    positionY = position.bottom;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_MIDDLE:
                    positionY = (position.bottom + position.top) / 2;
                    break;                
            }
            
            switch (anchorAttachmentPoint) {
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_BOTTOM:
                    anchorElementPositionX = anchorElementPosition.left;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_BOTTOM:
                    anchorElementPositionX = anchorElementPosition.right;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_BOTTOM:
                    anchorElementPositionX = (anchorElementPosition.right + anchorElementPosition.left) / 2;
                    break;                
            }
            
            switch (anchorAttachmentPoint) {
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_TOP:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_TOP:
                    anchorElementPositionY = anchorElementPosition.top;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_BOTTOM:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE_BOTTOM:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_BOTTOM:
                    anchorElementPositionY = anchorElementPosition.bottom;
                    break;
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.LEFT_MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.MIDDLE:
                case CodeBaby.dom.Element.ATTACHMENT_POINTS.RIGHT_MIDDLE:
                    anchorElementPositionY = (anchorElementPosition.bottom + anchorElementPosition.top) / 2;
                    break;                
            }    
            
	        return {
	            x: positionX - anchorElementPositionX,
	            y: positionY - anchorElementPositionY
	        }
	    },
	    
	    /*
	     * Function: getWindowPosition
	     * similar to getElementPosition except returns similar data for the
	     * window and document to help with fixed positioning
	     * 
         * Returns:
         * a hash including the following:
         *    1) scrollLeft -the left scroll position of the window
         *    2) scrollTop - the top scroll position of the window
         *    3) documentWidth - the full width of the document
         *    4) documentHeight - the full height of the document
         *    5) width - the viewable width of the window
         *    6) height - the viewable height of the window
	     */
	    getWindowPosition: function() {
	        //TODO: get the window positioning info
            return {
                scrollLeft: Math.max(document.documentElement.scrollLeft, 
                        document.body.scrollLeft),
                scrollTop: Math.max(document.documentElement.scrollTop, 
                        document.body.scrollTop),
                documentWidth: Math.max(
                        Math.max(document.body.scrollWidth, 
                                document.documentElement.scrollWidth),
                        Math.max(document.body.offsetWidth, 
                                document.documentElement.offsetWidth),
                        Math.max(document.body.clientWidth, 
                                document.documentElement.clientWidth)
                    ),
                documentHeight: Math.max(
                        Math.max(document.body.scrollHeight, 
                                document.documentElement.scrollHeight),
                        Math.max(document.body.offsetHeight, 
                                document.documentElement.offsetHeight),
                        Math.max(document.body.clientHeight, 
                                document.documentElement.clientHeight)
                    ),
                width: ((document.compatMode === "CSS1Compat" && 
                        document.documentElement[ "clientWidth" ]) ||
                        document.body[ "clientWidth" ]),
                height: ((document.compatMode == "CSS1Compat" && 
                        document.documentElement[ "clientHeight" ]) ||
                        document.body[ "clientHeight" ])
            };
	    },
	    /*
	     * Function: isIE()
	     * is the current browser our least favorite
	     * 
	     * Returns:
	     * true, if we are in IE of any version
	     */
       isIE: function() {
            return /msie/i.test(navigator.userAgent) && 
            !/opera/i.test(navigator.userAgent);
       },
       /*
        * Function: getBody
        * get the body element
        */
       getBody: function() {
           return document.getElementsByTagName("body")[0];
       },
       /*
        * Function: addEventHandler
        * cross browser way to add an event handler to an object
        * 
        * Parameters:
        * object - the object to add the event to (a DOM Element); ex. window
        * event - the name of the event; ex. resize
        * func - the function to add as the listener
        */
       addEventHandler: function(object, event, func) {
           if (object.addEventListener) {
               object.addEventListener(event, func, false);
           } else {
               object.attachEvent('on' + event, func);
           }
       },
       /*
        * Function: removeEventHandler
        * cross browser way to remove an event handler from an object
        * 
        * Parameters:
        * object - the object to add the event to (ex. window)
        * event - the name of the event (ex. resize)
        * func - the function to add as the listener
        */
       removeEventHandler: function(object, event, func) {
           if (object.removeEventListener) {
               object.removeEventListener(event, func, false);
           } else {
               object.detachEvent('on' + event, func);
           }
       },


/*
     Function: ready(fn)
     Calls a passed function when the document is ready.  If the document
     is already ready, calls immediately.  If this function is called multiple
     times, all functions will be scheduled to execute when ready.
     Based on jQuery's ready function.

     Parameters:
       fn - Function The function to be called when the document is ready.

*/
        ready: function(){
/* Note: this function returns a function, which is what becomes the ready function.  This is so we can take advantage of function closure to scope
private vars.
*/
//private
            var readyBound = false;
            var isReady = false;
            var readyList = [];

            var bindReady = function(){
                if ( readyBound ) return;
                readyBound = true;

                // Mozilla, Opera and webkit nightlies currently support this event
                if ( document.addEventListener ) {
                    // Use the handy event callback
                    document.addEventListener( "DOMContentLoaded", function(){
                    document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
                        callReady();
                    }, false );

                    // If IE event model is used
                } else if ( document.attachEvent ) {
                    // ensure firing before onload,
                    // maybe late but safe also for iframes
                    document.attachEvent("onreadystatechange", function(){
                        if ( document.readyState === "complete" ) {
                            document.detachEvent( "onreadystatechange", arguments.callee );
                            callReady();
                        }
                    });

                   // If IE and not an iframe
                   // continually check to see if the document is ready
                    if ( document.documentElement.doScroll && window == window.top ) (function(){
                        if ( isReady ) return;

                        try {
                            // If IE is used, use the trick by Diego Perini
                            // http://javascript.nwbox.com/IEContentLoaded/
                            document.documentElement.doScroll("left");
                        } catch( error ) {
                            setTimeout( arguments.callee, 0 );
                            return;
                        }

                        // and execute any waiting functions
                        callReady();
                    })();
                }

                // A fallback to window.onload, that will always work
                if (window.onload){
                    var __onload = window.onload;
                    window.onload = function(){__onload();callReady();};
                }  else {
                    window.onload = callReady;
                }

            };
            var rdy = function(fn){
                // Attach the listeners
                bindReady();

                // If the DOM is already ready
                if ( isReady )
                    // Execute the function immediately
                    fn.call();

                // Otherwise, remember the function for later
                else
                    // Add the function to the wait list
                     readyList.push( fn );
            };

            var callReady = function(){
                if (isReady){
                    return;
                }
                isReady = true;
                for (var i = 0; i < readyList.length; i++){
                    readyList[i]();
                }
            };
            return rdy;
        }(),
        getConversationSettings: getConversationSettings,
        setStoredConversation: setStoredConversation,
        resetStoredConversation: resetStoredConversation,
        getStandardUrl: getStandardUrl,
        getUrls:getUrls
    };
})();
CodeBaby.util.ready(function(){}); // set up the ready callback

/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2756 $
  $Date: 2010-07-19 14:05:05 -0600 (Mon, 19 Jul 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/config.js $
*/
// Requires: CodeBaby.util  
// Requires: CodeBaby.storage


/*
 Object: CodeBaby.config
 Contains string values for settings that affect CodeBaby conversations.
 
 Required settings:
 The values that must be set in order to load a conversation successfully are
 
     account - See <account>.
     conversation - See <conversation>.
     page - See <page>.
     
 These are typically set in the CodeBaby <script> tag in the CodeBaby parameter,
 but may be set in other ways as described below in "Methods for setting configuration values".
 For example
 
> <script type='text/javascript'
>     src='http://cdm.codebaby.com/cdm-2.0.js?CodeBaby=account:1001;conversation:1234;page:Page 1'>
> </script>

Default settings:
    All other config settings have default values that should work in most cases.

Values may be set via any of the following methods:

 - Page url query string parameters, using name value pairs.
   Name and value are separated by a ":" and each pair by a ";".
   For example, 
   > http://mysite.com/index.html?CodeBaby=debug:trace;conversation:1001
   Or more generally
   > http://mysite.com/index.html?...&CodeBaby=name1:value1;name2:value2;
 - Page url as above, but prefixing the setting name with $, to make the setting persist
   throughout the session.  For example
   > http://mysite.com/index.html?CodeBaby=$debug:warn
 - Custom setting (set via <set>)
   > CodeBaby.config.set("debug", "warn");
 - The script tag src attribute's query string parameter (in the same manner as the page url).
   The use of the "$" is not allowed here, and results in the setting being ignored.
   > <script type='text/javascript' src='http://cdn.codebaby.com/${cb.cdm.bootJSPath}?CodeBaby=account:1001;conversation:1234;page:shopFAQ'></script>
 - A default value; this is hard-coded in this object.

<get> goes through this list in the order above, and whichever item in the list has a
value for the setting is returned by <get>.

The <set> function sets the custom level; it may be set to a function, which,
by convention, should return a string.  If set to a function,
a call to <get> ("settingname","default value") executes the function and returns the string.

Any value may be placed in the page or script urls' query portion in the CodeBaby= parameter;
they are automatically added as configuration settings, and may be accessed via <get>.

Predefined settings (with defaults):

Note that curly braces, e.g. {setting}, means this part of the value comes from another configuration setting.

Setting: account
   (no default) The id of the current account.
   Used by <CodeBaby.conversation>.

Setting: conversation
  (no default) The id of the current conversation.

Setting: page
   (no default) The name of the current page.
   Used by <CodeBaby.conversation>.
   
Setting: autoStart
    (yes) Whether to start the conversation logic when the page loads.  If no,
    then the user-defined function <CodeBaby.start> is not called.  Also, if
    <script.initLoad> is no, then autoStart has no effect.
    Used by <CodeBaby.init>.

Setting: debug
    (error) Debug logging level.  From order of most detailed to least level of
    messages is trace, debug, info, warn, error and fatal.
    Used by <CodeBaby.logger>.
    
Setting: showUtilityConsole
    (no) Whether to show the utility console.  This is a tool that allows you to test
    out parts of a conversation during development or debugging.  Set to "yes" if
    you'd like this to appear.

Setting: enableKeyControls
    (no) Whether to enable the keyboard for playback control.  This is mostly used internally
    for things like video capture, but could be useful for debugging purposes as well.  When 
    set to "yes", using the key '[' will play a segment, and ']' will stop or seek to 0 if already stopped

Setting: logger.listener
    (CodeBaby.consoleLog)  Default listener to log messages.
    Used by <CodeBaby.logger>.

Setting: script.apiVersion
    (2.1.1.2802) The version of the CDM API to use.  This should be
        set with care, to ensure the version selected is compatible with the intial
        version loaded.
        Used by <CodeBaby.init>.

Setting: script.initLoad
    (yes)  Load core files by <CodeBaby.init>, which executes in the "boot" script
    (that is, on page load).
    Used by <CodeBaby.init>.
    
Setting: script.loadNow
    (yes)  Load conversation (and the scripts it depends upon) when the page loads.
     Used by <CodeBaby.init>.

Setting: script.minJS
    (yes) use the minified version of CDM's JavaScript for additional
        JavaScript.  Otherwise, use the
        unminified version (which is also separated by Class and object).
        Used by <CodeBaby.init>.

Setting: script.host
    (the protocol (http:/ or https:// plus the host portion of the src attribute in the <script> tag).
    http:// or https:// +  the name of the host from where to load additional scripts.
    Used by <CodeBaby.init>.

Setting: script.jsPath
    (<script.host>/shared/<script.apiVersion>)  The location of CDM JavaScript
    files, other than the single CDM script tags used to add CodeBaby conversation
    logic to a web page.  Used by <CodeBaby.init>.

Setting: script.accountConfigFile
    (config.js)  The name of the configuration file for the account.
    Used by <CodeBaby.init>.

Setting: script.accountConfigUrl
    ({<script.host>}/accounts/{<account>}/{<script.accountConfigFile>})
    The url to the account configuration file.  If blank, no load is attempted.
    Used by <CodeBaby.init>
    
Setting: script.conversationConfigFile
    (config.js)  The name of the configuration file for the conversation.  By
    default this has the same name as the account config file, but is accessed
    via a different path.
    Used by <CodeBaby.init>.

Setting: script.conversationConfigUrl
    ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/{<script.conversationConfigFile>})
    The url to the conversation configuration file. If blank, no load is attempted.
    Used by <CodeBaby.init>
    
Setting: script.jqueryFile
    (jquery-1.4.2.min.js) The file name of the jquery JavaScript.

Setting: script.jqueryUrl
    ({<script.jsPath>}/{<script.jqueryFile>})  The url of the jquery JavaScript
    file.  Set to "no" or null to prevent the url from being loaded.

Setting: storage.domain
    (location.hostname) Domain that has access to the storage variables.
    Used by <CodeBaby.storage>.
    
Setting: storage.sessionGroup
    (CodeBabySession) Storage group name for session variables.
    Used by <CodeBaby.storage>.
    
Setting: storage.visitGroup
    (CodeBabyVisit) Storage group name for visit variables.
    Used by <CodeBaby.storage>.
    
Setting: storage.visitSeconds
    (1800) The length of time that a the visit storage group persists.
    Used by <CodeBaby.storage>.
    
Setting: storage.neverGroup
    (CodeBabyNever) Storage group name for storage variables that "never" expire.
    Used by <CodeBaby.storage>.
    
Setting: storage.neverSeconds
    (10 * 365 * 24 * 86400) = 10 years; The length of time
    that the "never expires" storage group persists.
    Used by <CodeBaby.storage>.
    
Setting: player.parent
    (CodeBabyParent) The id the of the element that will host the player.
    Used by <CodeBaby.flashPlayer>.
    
Setting: player.wrapper
    (CodeBabyWrapper) The id of the html element inserted to add the player to the
    web page.  This would only need to be changed if the default is already
    an id on the page.
    Used by <CodeBaby.flashPlayer>.

Setting: player.swfObject
    (CodeBabySwfObject) The id of the object element inserted to add the player to the
    web page.  This would only need to be changed if the default is already
    an id on the page.
    Used by <CodeBaby.flashPlayer>.

Setting: player.position
    (relative) The css-style position of the player set for the
    {<player.wrapper>} element.  This defaults to "relative" so that
    child elements (such as a console) can center itself using absolute
    positioning.  (Absolute positions is relative to the first non-staticly
    positioned parent).
    Used by <CodeBaby.flashPlayer>

Setting: player.left
    (null) The css-style left attribute to set the position of the player
    within the {<player.parent>} element.  Works with <player.top> and <player.position>
    to set the position.
    Used by <CodeBaby.flashPlayer>.

Setting: player.top
    (null) The css-style left attribute to set the position of the player
    within the {<player.parent>} element.  Works with <player.left> and <player.position>
    to set the position.
    Used by <CodeBaby.flashPlayer>.
    
Setting: player.readyTimeout
    (5000) The amount of time to wait for the player to initialize before reporting an error.
    Used by <CodeBaby.flashPlayer>.
    
Setting: player.file
    (VideoPlayer.swf) The name of the file that contains the flash player.
    Used by <CodeBaby.flashPlayer>.
    
Setting: player.url
    ({<script.jsPath>}/{<player.file>})
    The url of the flash player.
    Used by <CodeBaby.flashPlayer>.
    
Setting: metrics.enabled
    (yes) Enables the tracking of metrics. 
    Used by <CodeBaby.metrics>.

Setting: metrics.log
    (yes) Send metrics to the log.
    Used by <CodeBaby.logger>.

Setting: metrics.trackingUrl
    (s3.amazonaws.com/metrics_v2.codebaby.com/CB-metric_v2) The URL to log metrics to.
    Shouldn't include the protocol (e.g. http://).
    Used by <CodeBaby.metrics>.
    
Setting: metrics.ignoreType
    (null) A string indicating the reason to ignore metrics.  If non-null, this parameter will be added to all tracked
    metrics so that the reports can filter and ignore these.  For example, all links from our showcase section on 
    www.codebaby.com set ignoreType to "showcase".
    Used by <CodeBaby.metrics>.

Setting: metrics.player.percentages
      (0,25,50,75,100).
      the video progress percentages to track metrics at.
      Used by <CodeBaby.metrics>.

Setting: account.dataUrl
    ({<script.host>}/accounts/{<account>}/accountData.js)
    The location of the data file that contains account info.
    Used by <CodeBaby.conversation> and <CodeBaby.init>.

Setting: conversation.expires
    (300)
    The number of seconds before the conversation expires; used
    when moving to a new page and need to set which conversation that
    page will continue with.
    Used by <CodeBaby.init> and indirectly by <CodeBaby.conversation>

Setting: conversation.dataUrl
    ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/data.js)
    The location of the data file that describes a conversation.
    Used by <CodeBaby.conversation>.

Setting: conversation.codeUrl
        ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/code.js)
        The location of the JavaScript code file that is included in a conversation.
        Used by <CodeBaby.conversation>.

Setting: conversation.styleUrl
        ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/style.css)
        The location of the CodeBaby style CSS file that is included in a conversation.
        Used by <CodeBaby.conversation>.

Setting: conversation.videoPath
    ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/videos)
    The path to videos for a conversation.
    Used by <CodeBaby.conversation>.
    
Setting: conversation.resourcePath
    ({<script.host>}/accounts/{<account>}/conversations/{<conversation>}/resources)
    The path to resources (JavaScript, images, etc) for a conversation.

Setting: ui.consoleFile
    (standardConsole.js) The file containing the console code.

Setting: ui.consoleUrl
    ({<script.jsPath>}/{<ui.consoleFile>} The location of the console for
    the player.  Set to "no" (or null) to prevent it from being used.

Setting: ui.theme
    (Redmond)  A themed jQuery stylesheet to load.  Set to 'no' to
    not load any theme.

Setting: ui.jqueryUiFile
    (jquery-ui-1.8.1.custom.min.js) The name of the jquery ui file.
    
Setting: ui.jqueryUiUrl
    ({<script.jsPath>/{<ui.jqueryUiFile>}) The url of the jquery ui JavaScript.
    Set to "no" or null to prevent loading this url.

Setting: ui.themeStyleSheetFile
    (jquery-ui-1.8.1.custom.css) The name of the jquery ui css file

Setting: ui.themeStyleSheet
    ({<script.jsPath>}/css/{<ui.theme>}/{<ui.themeStyleSheetFile>})  The style sheet
    to be loaded.  If ui.theme is "no", then by default this is "no" and no stlye
    sheet is loaded.
    
Setting: ui.dialogBoxStyleSheetFile
    (standardDialogBox.css) The name of the dialog box css file

Setting: ui.dialogBoxStyleSheet
    ({<script.jsPath>}/css/{<ui.dialogBoxStyleSheetFile>})  The style sheet
    to be loaded.  If this is "no", no style sheet is loaded.

Setting: ui.dialogBoxManagerFile
    (standardDialogBoxManager.js) The name of the dialog box to be loaded.  If no,
    no dialog box is loaded (unless <ui.dialogBoxManagerUrl> is set)
    
Setting: ui.dialogBoxManagerUrl
        ({<script.jsPath>}/ui/{<ui.dialogBoxManagerFile>}) The location of the dialog
        box to load.


    */
CodeBaby.config = (function(){
    //private
    var util = CodeBaby.util;
    
    var CONFIG_PARAM = "CodeBaby";
    var NAME_VALUE_DELIMITER = ":";
    var NAME_VALUE_PAIR_DELIMITER = ";";
    var VALUE_DEFAULT = "yes";
    var ESCAPE_CHAR = "\\";
    var SESSION_CHAR = '$';
    var sessionGroup = null;
    var storage = null;
    
    var custom = {};
    var defaults = {
    
        "account": null,
        "conversation": null,
        "page": null,
        "autoStart": "yes",
        "debug": "error",
        "showUtilityConsole": "no",
        "enableKeyControls": "no",

        "config.sessionGroup":"CodeBabyConfig",
        
        "logger.listener":"CodeBaby.consoleLog",

        "script.apiVersion": "2.1.1.2802",
        "script.host": util.getScriptHost(),
        "script.jsPath": function(){
            return get("script.host") + "/shared/"
                + get("script.apiVersion");
        },
        "script.accountConfigFile":"config.js",
        "script.accountConfigUrl":function(){
            return get("script.host") + "/accounts/"
                + get("account") + "/" + get("script.accountConfigFile");
        },
        "script.conversationConfigFile":"config.js",
        "script.conversationConfigUrl":function(){
            return get("script.host") + "/accounts/"
                + get("account") + "/conversations/" + get("conversation")
                + "/" + get("script.conversationConfigFile");
        },
        "script.minJS": "yes",
        "script.initLoad":"yes",
        "script.loadNow": "yes",
        "script.jqueryFile":"jquery-1.4.2.min.js",
        "script.jqueryUrl":function(){
            return get("script.jsPath") + "/" + get("script.jqueryFile");
        },
        "storage.domain": location.hostname,
        "storage.sessionGroup": "CodeBabySession",
        "storage.visitGroup": "CodeBabyVisit",
        "storage.visitSeconds": "1800",
        "storage.neverGroup": "CodeBabyNever",
        "storage.neverSeconds": (10 * 365 * 86400).toString(), // 10 years

        "player.parent": "CodeBabyParent",
        "player.wrapper":"CodeBabyWrapper",
        "player.swfObject":"CodeBabySwfObject",
        "player.position":"relative",
        "player.left":null,
        "player.top":null,
        "player.readyTimeout":"5000",
        
        "player.file":"VideoPlayer.swf",
        "player.url":function(){
            var url = get("script.host") + "/shared/"
                + get("script.apiVersion") + "/"
                + get("player.file");
            return url;
        },
        "metrics.enabled" : "yes",
        "metrics.log" : "no",
        "metrics.trackingUrl": "s3.amazonaws.com/metrics_v2.codebaby.net/CB-metric_v2",
        "metrics.ignoreType": null,
        "metrics.player.percentages": "0,25,50,75,100",
        
        "account.dataUrl":function(){
            var url = get("script.host")
                + "/accounts/" + get("account")
                + "/accountData.js";
            return url;
        },
        "conversation.expires":300,
        "conversation.dataUrl":function(){
            var url = get("script.host")
                + "/accounts/" + get("account")
                + "/conversations/" + get("conversation")
                + "/data.js";
            return url;
        },
        "conversation.codeUrl":function(){
            var url = get("script.host")
                + "/accounts/" + get("account")
                + "/conversations/" + get("conversation")
                + "/code.js";
            return url;
        },
        "conversation.styleUrl":function(){
            var url = get("script.host")
                + "/accounts/" + get("account")
                + "/conversations/" + get("conversation")
                + "/style.css";
            return url;
        },
        "conversation.videoPath":function(){
            var path = get("script.host")
                + "/accounts/" + get("account")
                + "/conversations/" + get("conversation")
                + "/videos";
            return path;
        },
        "conversation.resourcePath":function(){
            var path = get("script.host")
                + "/accounts/" + get("account")
                + "/conversations/" + get("conversation")
                + "/resources";
            return path;
        },
        "ui.theme": "redmond",
        "ui.dialogBoxManagerFile":"standardDialogBoxManager.js",
        "ui.dialogBoxManagerUrl":function(){
            var dbFile = get("ui.dialogBoxManagerFile");
            if (dbFile == null || dbFile == "no"){
                return "no";
            } else {
                return get("script.jsPath") + "/ui/" + dbFile;
            }
        },
        "ui.dialogBoxManagerInCore":function(){ //checks if the dialog box is part of core
            var dbUrl = get("ui.dialogBoxManagerUrl");
            var stdUrl = get("script.jsPath") + "/ui/" + get("ui.dialogBoxManagerFile");
            if (stdUrl == dbUrl){
                return "yes";
            } else {
                return "no";
            }
        },
        "ui.consoleFile":"standardConsole.js",
        "ui.consoleUrl":function(){
            return get("script.jsPath") + "/" + get("ui.consoleFile");
        },
        "ui.consoleInCore":function(){
            var consoleUrl = get("ui.consoleUrl");
            var stdUrl = get("script.jsPath") + "/standardConsole.js";
            if (stdUrl == consoleUrl){
                return "yes";
            } else {
                return "no";
            }
        },
        "ui.jqueryUiFile":"jquery-ui-1.8.1.custom.min.js",
        "ui.jqueryUiUrl":function(){
           return get("script.jsPath") + "/" + get("ui.jqueryUiFile");
        },
        "ui.themeStyleSheetFile":"jquery-ui-1.8.1.custom.css",
        "ui.themeStyleSheet":function(){
            var theme = get("ui.theme");
            if (theme == null || theme == "no"){
                return "no";
            }
            //({script.jsPath}/css/{ui.theme}/jquery-ui-1.8.1.custom.css)
            return get("script.jsPath") + "/css/" + theme + "/"
            + get("ui.themeStyleSheetFile");
        },
        "ui.dialogBoxStyleSheetFile":"standardDialogBox.css",
        "ui.dialogBoxStyleSheet":function(){
            //({script.jsPath}/css/standardDialogBox.css)
            return get("script.jsPath") + "/css/" + get("ui.dialogBoxStyleSheetFile");            
        }
    };
    
    /*
    Function splitUrl
    Decodes a url and splits it
    into the preQuery portion (before ?), the query string
    (between ? and #) and the # (anchor)
    
    Parameters:
        url - the url to split

    Returns:
        An object with {preQuery:, query:, hash); Members may be null
        if there is no query string or hash.  The leading ?  and # are
        excluded from query and hash.
    */
    
    var splitUrl = function(url){
        var retVal = {};
        var decoded = decodeURI(url);
        var hashSplit = decoded.split("#",2);
        var preHash = hashSplit[0];
        if (hashSplit.length < 2){ // no hash
            retVal.hash = null;
        } else {
            retVal.hash = hashSplit[1];
        }
        var querySplit = preHash.split("?",2);
        retVal.preQuery = querySplit[0];
        if (querySplit.length < 2){ // no ?
            retVal.query = null;
        } else {
            retVal.query = querySplit[1];
        }
        return retVal;
    };
    
    /*
    Function joinUrl
    Opposite of split url

    Parameters:
        urlComponents - an object with {preQuery:, query:, hash}

    Returns:
        preQuery + "?" + query + "#" + hash, leaving out "?" or "#" if
        query or hash are null respectively.  If all are null, returns null.
        Encodes the url
    */


    var joinUrl = function(urlComponents){
        var url = "";
        if (urlComponents.preQuery !== null){
            url = urlComponents.preQuery;
        }
        if (urlComponents.query !== null){
            url += "?" + urlComponents.query;
        }
        if (urlComponents.hash != null){
            url += "#" + urlComponents.hash;
        }
        if (url === ""){
           return null;
        } else {
            return encodeURI(url);
        }
    };

    /*
    Function parseQueryParams
    Parses out the query parameters from a query string
    
    Parameters:
        query - The query string, excluding the intial ?
        
    Returns: an array of params, each a {name:, value:} object

    */
    var parseQueryParams = function(query){
        var retVal = [];
        // get the param string by splitting at first ?
        var params = decodeURIComponent(query).split("&");
        for (var i = 0; i < params.length; i++){
            retVal[i] = {};
            var pv = params[i].split("=");
            retVal[i].name = pv[0];
            if (pv.length == 2){
                retVal[i].value = pv[1];
            } else {
                retVal[i].value = null;
            }
        }
        return retVal;
    };
    
    /*
    Function makeQueryString
    Makes the query string for a url.  Opposite of parseQueryParams.
    
    Parameters:
        params - an array of {name:,value:} objects
       
    Returns:
        A query string, not encoded and without the leading ?.  If a name is null,
        it is not included.  If a value is null, no =value is include (just name).
        If there are no params in the final string, null is returned.
    */
    var makeQueryString = function(params){
        var q = "";
        for (var i = 0; i < params.length; i++){
            if (params[i].name !== null){
                if (q !== ""){
                    q += "&" + params[i].name;
                } else {
                    q += params[i].name;
                }
                if (params[i].value !== null){
                    q += "=" + params[i].value;
                }
            }
        }
        if (q === ""){
            return null;
        } else {
            return q;
        }
    };
    
    /*
    Function createConfigParam
    Creates the config query parameter suitable for adding into a url
    
    Parameters:
        params - An object with config properties corresponding to each name,
        and the value associated with that name.
        
    Returns:
        A query parameter name:value;pairs:etc;  If params is
        empty, returns null.
    */
    var createConfigParam = function(params){
        var q = "";
        for (var name in params){
            q += name + NAME_VALUE_DELIMITER
            + params[name] + NAME_VALUE_PAIR_DELIMITER;
        }
        if (q !== ""){
            return q;
        } else {
            return null;
        }
    };
    
    /*
    Function getConfigFromParam
    Get the config settings from a query parameter value (part after CONFIG_PARAM = )

    Parameters:
        configString - list of name + NAME_VALUE_DELIMITER + value
        each delimited by NAME_VALUE_PAIR_DELIMIER;
        
    Returns:
        An object with config properties corresponding to each name,
        and the value associated with that name.
    */
    var getConfigFromParam = function(configString){
        var values = {};
        var pairs = configString.split(NAME_VALUE_PAIR_DELIMITER);
        for (var j = 0; j < pairs.length; j++){
            var nv = pairs[j].split(NAME_VALUE_DELIMITER);
            if (nv[0] !== ""){ // exclude if name is blank
                if (nv.length === 1){
                    values[nv[0]] = VALUE_DEFAULT;
                } else {
                    var name = nv[0];
                    nv.shift();  // get rid of the name
                    // put back in name_value_delimiter if more than one
                    // element left in split string
                    values[name] = nv.join(NAME_VALUE_DELIMITER);
                }
            }
        }
        return values;
    };

    /*
    Function findConfigParam
    Finds the config param in an array of {name:,value:} objects
    
    Parameters:
        params - an array of {name:,value:} objects
    Returns: the index of the first name that matches CONFIG_PARAM;
    -1 if not found
    */
    var findConfigParam = function(params){
        var configFound = false;
        var i = 0;
        while (i < params.length && !configFound){
            if (params[i].name === CONFIG_PARAM){
                configFound = true;
            }
            if (!configFound){
                i++;
            }
        }
        if (configFound){
            return i;
        } else {
            return -1;
        }

    };
    /*
    Function: updateConfigInUrl
    Takes a url and updates (or adds) CONFIG_PARAM with passed values
    
    Parameters:
        url - the url to update
        settings - an object where each member is the name of a config setting
        and its value is the value of that setting.

        
    Returns:
       An updated url with CONFIG_PARAM= updated
    */
    var updateConfigUrl = function(url,settings){
        var configParam = createConfigParam(settings);
        if (configParam === null){ // no config params
            return url;
        }
        var urlComps = splitUrl(url);
        var params = [];
        var configFound = false;
        var newSettings = {};
        if (urlComps.query !== null){
            params = parseQueryParams(urlComps.query);
            var i = findConfigParam(params);
            if (i !== -1){
                configFound = true;
                // initialize newSettings
                newSettings = getConfigFromParam(params[i].value);
            }
        }
        if (configFound){
            // update newSettings
            for (name in settings){
                newSettings[name] = settings[name];
            }
            configParam = createConfigParam(newSettings);
            params[i].value = configParam;
        } else {
            params.push({name:CONFIG_PARAM,value:configParam});
        }
        // update the query string
        urlComps.query = makeQueryString(params);
        // reassemble the url
        return joinUrl(urlComps);
    };
    /*
    Function getConfigFromUrl
    Parse the CONFIG_PARAM parameter's value into name/value pairs.
    Parameters:
        url - has the query string
    Returns: 
        an new object with properties corresponding to each name, and the value associated with that name
    */
    var getConfigFromUrl = function(url){
        var urlComps = splitUrl(url);
        var params = [];
        if (urlComps.query !== null){
            params = parseQueryParams(urlComps.query);
            var i = findConfigParam(params);
            if (i !== -1){
                return getConfigFromParam(params[i].value);
            }
        }
        return {};
    };
    
   // get initial params from the script tag and page url.
    var scriptParams = getConfigFromUrl(CodeBaby.util.getScriptElement().src);
    var pageParams = getConfigFromUrl(location.href);
    var sessionParams = {}; // not available yet (need CodeBaby.storage).
    /*
    Function moveIntoScriptParams
    Moves parameters starting with SESSION_CHAR from the passed array to
    sessionParams, without the SESSION_CHAR prefix.  These params are removed
    from the passed array
    
    Parameters:
        params - the object containing the param names as members, and their values
        as the param values (params[name] = value).
    */
    var moveIntoSessionParams = function(params){
        for (var n in params){
            if (n.charAt(0) == SESSION_CHAR){
                var sessName = n.substring(1);
                sessionParams[sessName] = params[n];
                delete params[n];
            }
        }
    };
    
    moveIntoSessionParams(pageParams);

    //calculate the param value, either a value or if a function, executes
    // the function and returns that.
    var calc = function(val){
        if (val instanceof Function){
            return val();
        }
        return val;
    };    
    
    /*
    Function get
    Get the setting for the passed name.
    
    Parameters:
        name - the name of the setting
        defaultValue - the value to return if there is no setting

    Returns:
        The setting for the passed name, in order of precedence as described in <CodeBaby.config>.
    */
    var get = function(name,defaultValue){
            if (pageParams[name]){
                return calc(pageParams[name]);
            }
            if (sessionParams[name]){
                return calc(sessionParams[name]);
            }
            if ((storage != null) && storage.get(sessionGroup,name)){
                return storage.get(sessionGroup,name);
            }
            if (custom[name]){
                return calc(custom[name]);
            }
            if (scriptParams[name]){
                return calc(scriptParams[name]);
            }
            if (defaults[name]){
                return calc(defaults[name]);
            }
            if (defaultValue){
                return defaultValue;
            }
            return null;
    };
    
    /*
    Function: setInUrl
    Sets up the url so that the configuration value is specified in the url.
    
    Parameters:
        name - the name of the setting
        value - its value
        url - a url
        
    Returns: updated url
    */
    var setInUrl = function(name,value,url){

    };
    //public
    return {
    /*
        Function: get
        Get the setting for the passed name.
        
        Parameters:
            name - the name of the setting
            defaultValue - the value to return if there is no setting
        
        Returns:
            The setting for the passed name, in order of precedence as described in <CodeBaby.config>.
    */
        get: function(name,defaultValue){
            return get(name,defaultValue);
        },

        /*
        Function: set
        Sets a setting value
        
        Parameters:
        name - name of the setting.  Optionally this may be an object,
        and all properties in that object are placed into the settings.
        In this case, just pass one parameter to this function. Note that since most
        settings have a "." in the name, in your object, they must be assigned using
        the following syntax:
>       obj["config.name1"] = "value1";
>       obj["config.name2"] = "value2";

        Alternatively, you can define the object in the set call
        
>       CodeBaby.config.set({"config.name1":"value1","config.name2":"value2"})
        
        value - the value to set this setting to.  This may be a function; when the setting is retrieved via get, the function will be executed and its return value returned as the value.  Do not pass this if the name parameter is an object containing properties and values.
        */
        set: function(name,value){
            if (typeof(value) == "undefined"){ // if only passing an object, set all sub values
                for (var i in name){
                    custom[i] = name[i];
                }
            } else {
                custom[name] = value;
            }
        },
        updateConfigUrl:updateConfigUrl,
        /*
        Function updateSessionParams  (not for general use)
        Stores session parameters from the url in a session cookie, and remove
        page params that are not session param from the session cookie.
        This relies on <CodeBaby.storage> and so cannot be called until
        after that object has been loaded.
        */
        updateSessionParams: function(){
            sessionGroup = CodeBaby.config.get("config.sessionGroup","CodeBabyConfig");
            storage = CodeBaby.storage;
            storage.setUpGroup(sessionGroup);
            // a page param removes previous session param
            for (var v in pageParams){
                storage.remove(sessionGroup,v);
            }
            for (var v in sessionParams){
                storage.set(sessionGroup,v,sessionParams[v]);
            }
        }
    };
})();/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2515 $
  $Date: 2010-06-17 13:52:47 -0600 (Thu, 17 Jun 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/consoleLog.js $
*/    
// Requires: CodeBaby.logger

/*
Object: CodeBaby.consoleLog
Sends log messages to the error console.  This location and availability of this
log vary by browser.  For example, in Firefox, it logs to Firebug's console.
In IE8, it logs to the Developer Tools' console.  This is intended to be used with <CodeBaby.logger>
and added via <CodeBaby.logger.addListener>

Configuration settings:

    logger.listener - Set it to this object (CodeBaby.consoleLog) to use this show
    log messages.
    See <logger.listener>.

*/
CodeBaby.consoleLog = (function(){
    var logger = CodeBaby.logger;
    var levelText = ["ALL  ","TRACE","DEBUG","INFO ","WARN ","ERROR","FATAL","NONE "];
    var metricText = "METRIC";
    var begin = new Date();

/*
Function: log
The function that shows regular log messages.

Parameters:
    level - The level of the log message
    msg - The message
*/
    var log = function (level, msg){
        if (typeof console != "undefined"){
            console.log(levelText[level] + ":" + (new Date() - begin)/1000
            + ":" + msg);
        }
    };
    
/*
Function: metric
The function that shows metrics logs

Parameters:
    params - An object containing the metrics parameters
*/
    var metric = function(params){
        if (typeof console != "undefined"){
            var msg = CodeBaby.logger.stringify(params);
            console.log(metricText + ":" + (new Date() - begin)/1000
            + ":" + msg);
        }
    };

    return {
        log:log,
        metric:metric
    };
})();

/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2516 $
  $Date: 2010-06-17 14:25:06 -0600 (Thu, 17 Jun 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/popupLog.js $
*/
/*
Object: CodeBaby.popupLog
Sends log messages to a popup window. This is intended to be used with <CodeBaby.logger>.
*/
CodeBaby.popupLog = (function(){
    var logger = CodeBaby.logger;
    var levelText = ["ALL  ","TRACE","DEBUG","INFO ","WARN ","ERROR","FATAL","NONE "];
    var levelColor = ["black","silver","gray","teal","olive","maroon","purple"];
    var metricText = "METRIC";
    var metricColor = "blue";
    var begin = new Date();
    var popup = null;
    // choose a name so that sites with the same host name reuse the same window.
    // Cannot have one for all sites since you will get a security error
    // if you try to access a window opened by another host.
    // remove : and . since they are not accepted in the window name by IE when opening
    var popupName = (location.protocol + location.hostname).replace(/[\:\.]/g,"");
    
    var lastLogEl = null;
    var popupHtml =
"<html>\
    <head>\n\
        <title>CodeBaby Debug Window</title>\n\
    </head>\n\
    <body>\n\
        <h1>CodeBaby Debug Window</h1>\n\
        <h3>Most recent messages listed first</h3>\n\
        <div id='msgs'></div>\n\
    </body>\n\
</html>";
/*
Function addMessage
Adds a message to the popup window with timestamp

Parameters:
    color - The color of the message
    prefix - Start of message, before elapsed time
    msg - The message to add
*/
    var addMessage = function(color,prefix,msg){
        var elapsed = (new Date() - begin)/1000;
        if (popup == null){
            popup = window.open("",popupName);
            msgsEl = popup.document.getElementById('msgs');
            if (msgsEl === null){
                popup.document.writeln(popupHtml);
            }
            msgsEl = popup.document.getElementById('msgs');
            insertMessageElement(msgsEl,"h4","black","New page opened:" + location);
        }
        if (!popup.closed){
            insertMessageElement(msgsEl,"div",color,prefix + ':' + elapsed + ':' + msg);
        }
    };
/*
Function insertMessageElement
Inserts and element in the page that wraps a message
*/
    var insertMessageElement = function(msgsEl,tag,color,msg){
        var insEl = popup.document.createElement(tag);
        insEl.style.color = color;
        insEl.innerHTML = msg;
        msgsEl.insertBefore(insEl,msgsEl.firstChild);
    };
/*
Function: log
Function called when <CodeBaby.logger.log> is called

Parameters:
    level - the level of the message
    msg - the message
*/
    var log = function(level, msg){
        // set color based on level
        var color = "black";
        if (level < levelColor.length){
            color = levelColor[level];
        }
        addMessage(color,levelText[level],msg);
    };
/*
Function: metric
The function that shows metrics logs

Parameters:
    params - An object containing the metrics parameters
*/
    var metric = function(params){
        // make the uri smaller
        if (params.uri){
            params.uri = "<span style='font-size:0.7em'>"
            + params.uri + "</span>";
        }
        var msg = CodeBaby.logger.stringify(params);
        addMessage(metricColor,metricText,msg);
    };
    //public
    return {
        log:log,
        metric:metric
    };
})();

/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2605 $
  $Date: 2010-06-24 16:24:40 -0600 (Thu, 24 Jun 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/logger.js $
*/   
// Requires: CodeBaby.config

/*
Object: CodeBaby.logger
Logs messages at a level (TRACE through FATAL).
To hear the messages, add a listener,
and set the level - it will hear all messages for that level and higher.
Once your listener gets the message, it can do what it wants with it such as
show an alert, write to a pop-up or set params for an ajax request for remote
logging.

Settings:
    debug - Debug level to listen to, trace through fatal.
    See <debug>.
    
    logger.listener - The name of the default listener to log messages.
    This must be a object suitable for <addListener>, and available
    before this object is loaded.
    See <logger.listener>.
    
    metrics.log - Set to "yes" to send metrics to the log.
    See <metrics.log>
*/
CodeBaby.logger = (function(){
    //private
    var config = CodeBaby.config;

    var enabled = false;
    var metricsLogEnabled = false;

    var defaultListener = null;
    var defaultLevel = null;
    
    var listeners = [];
    var metricsListeners = [];


/*
Function: stringify
A simple function to convert an object to a string.

Parameters:
    object - The object to stringify.  Each member must be an object, a string
    or a number; otherwise no guarantees this will work.  
*/
    var stringify = function(object){
        var result = "";
        switch (typeof object){
            case "object":
                result = "{";
                var firstMem = true;
                for (var mem in object){
                    if (firstMem){
                        firstMem = false;
                    } else {
                        result += ",";
                    }
                    result += "< " + mem + " : " + stringify(object[mem]) + " >";
                }
                result += "}";
                break;
            case "string":
                result = object;
                break;
            case "number":
                result += object;
                break;
            default:
                break;
        }
        return result;
    };
    
/*
Function: metric
Calls the listeners' metric function

Parameter:
    values - A list of values used by metrics
*/
    var metric = function(values){
        if (!metricsLogEnabled){
            return;
        }
        for (var i = 0; i < metricsListeners.length; i++){
            metricsListeners[i].metric(values);
        }
    };
/*
Function: setMetricsLogEnabled
Enables the logging of metrics.

Parameters:
    enabled - set to true to enable
*/
    var setMetricsLogEnabled = function(enabled){
        metricsLogEnabled = enabled;
    };

/*
Function: getMetricsLogEnabled
Gets whether metrics logging has been enabled.

Returns: true if enabled.
*/
    var getMetricsLogEnabled = function(enabled){
        return metricsLogEnabled;
    };
    
    return {
        /*
        Enum: Level
        Logging levels.
        
            ALL - all messages
            TRACE - fine detail debug messages
            DEBUG - debug messages
            INFO - informational messages
            WARN - warning messages
            ERROR - error messages
            FATAL - fatal messages
            NONE - no messages
        */
        Level: {
            ALL  :0,
            TRACE:1,
            DEBUG:2,
            INFO :3,
            WARN :4,
            ERROR:5,
            FATAL:6,
            NONE :7
        },
        
        //enable or disable logging
        /*
        Function: setEnabled
        Enable logging.  Note that a listener must be added for logging
        to have any effect.

        Parameters: 
            e - set to true to enable.
        */
        setEnabled: function(e){
            enabled = e;
        },
        /*
        Function: getEnabled
        Gets the enabled value.
        
        Returns: 
            true if logging enabled.
        */
        getEnabled: function(){
            return enabled;
        },
        /*
        Function: addListener
        Add a function to listen to log messages.  It should have two methods
        with the signatures log(level,message) and metric(values).
        The log function is passed (level, message)
        If the level of the message is at or higher the level of the listerner, then
        the listener is called.
        If the listener has already been added, the level
        is just modified.
        In particular, this means that the same listener cannot be added again
        (if you try, the level just gets updated).

        Parameters:
            level - Number <Level>; The threshold level that a message must
                meet or exceed to trigger the listener.  For example, if you pass in CodeBaby.logger.Levels.ERROR,
                you will receive callbacks for ERROR and FATAL log messages.
            listener - objet with functions log(level,message) and metric(values);
                the object whose functions are called when a message with
                the matching criteria is reached.
        */
        addListener: function(level, listener){
            // first remove the listener so that it is not
            // added twice 
            this.removeListener(listener);
            //  initialize a list of listeners for each level
            if (listeners.length == 0){
                for (var i = 0; i < this.Level.NONE; i++){
                    listeners[i] = [];
                }
            }
            // add the listener to the listeners for the category and levels
            // at or higher than the one passed in
            if (typeof listener !== "undefined"){
                if (typeof listener.log === "function"){
                    for (var i = level; i < this.Level.NONE; i++){
                        listeners[i].push(listener);
                    }
                }
                if (typeof listener.metric === "function"){
                    metricsListeners.push(listener);
                }
            }
        },
        /*
        Function: removeListener
        Removes a listener 
        
        Parameters:
            listener - the listener to be removed.
            
        Returns: 
            true if the listener found (and so removed)
        */
        removeListener: function(listener){
            var found = false;
            for (var i = 0; i < listeners.length; i++){
                //iterate from highest element to lowest, so decrementing j properly points
                // to the prev element, even if the element at the current position is removed.
                for (var j = listeners[i].length - 1; j >=0; j--){
                    if (listeners[i][j] === listener){
                        found = true;
                        listeners[i].splice(j,1); // remove the jth element
                    }
                }
            }
            for (var k = metricsListeners.length - 1; k >= 0; k--){
                if (metricsListeners[k] === listener){
                    found = true;
                    metricsListeners.splice(k,1);
                }
            }
            return found;
        },
        /*
        Function: log
        Logs a message to a level.  Calss all listeners at a level
        lower than or equal to the one passed
        to this call.
        
        Parameters:
            lvl - the <Level> of the message.  Listeners are only called if logging
            is enabled at this level or lower (see <setLevel>) and logging
            is enabled overall (see <setEnabled>).
            msg - the message to log
            
        */
        log: function(lvl,msg){
            if (!enabled){
                return;
            }
            // call listeners at this level; note that listeners at a lower
            // level are added to all higher levels, so they will be called as well
            if (listeners[lvl]){
                for (var i = 0; i < listeners[lvl].length; i++){
                    listeners[lvl][i].log(lvl,msg);
                }
            }
        },
        /*
        Function: trace
        Logs a message to the trace level - see <log>.

        Parameters:  
            msg - the message to log
        */
        trace: function(msg){
            this.log(this.Level.TRACE, msg);
        },
        /*
        Function: debug
        Logs a message to the debug level - see <log>.

        Parameters:  
            msg - the message to log
        */
        debug: function(msg){
            this.log(this.Level.DEBUG, msg);
        },
        /*
        Function: info
        Logs a message to the info level - see <log>.

        Parameters:  
            msg - the message to log
        */
        info: function(msg){
            this.log(this.Level.INFO, msg);
        },
        /*
        Function: warn
        Logs a message to the warn level - see <log>.

        Parameters:  
            msg - the message to log
        */
        warn: function(msg){
            this.log(this.Level.WARN, msg);
        },
        /*
        Function: error
        Logs a message to the error level - see <log>.

        Parameters:  
            msg - the message to log
        */
        error: function(msg){
            this.log(this.Level.ERROR, msg);
        },
        /*
        Function: fatal
        Logs a message to the fatal  level - see <log>.

        Parameters:  
            msg - the message to log
        */
        fatal: function(msg){
            this.log(this.Level.FATAL, msg);
        },
        /*
        Function: reset
        Removes all listeners
        */
        resetListeners: function(){
            listeners = [];
            metricsListeners = [];
        },
        /*
        Function setDefaults (not for general use)
        Sets up a default listener and will update based on changed to config.
        This was created so that a default listener is created before the
        storage object is loaded (which provides access to config settings
        stored in cookies).  Then this is called againg
        to update the default settings based on any changes that have come
        through the config in cookies.
        */
        setDefaults: function(){
            //this.fatal("logger.setDefaults called");

            // get the name of the listener object from config
            var listener = config.get("logger.listener",null);
            //this.fatal("logger.setDefaults listener is:" + listener);
            if (listener === null){
                if (defaultListener !== null){
                    this.info("logger.setDefaults: removing default listener since config logger.listener is null");
                    this.removeListener(defaultListener);
                }
                return;
            }
            var metricsLog = config.get("metrics.log","no");
            this.setMetricsLogEnabled(metricsLog === "yes");
            // get the debug level from config
            var debug = config.get("debug","warn");
            this.setEnabled((debug !== "no"));

            if (debug == "no" && metricsLog == "no"){
                if (defaultListener !== null){
                    this.info("logger.setDefaults: removing default"
                    + " listener since config debug is no and metrics.log is no");
                    this.removeListener(defaultListener);
                }
                return;
            }
            var level = this.Level.WARN;
            // e.g., if debug is "trace" then set level to logger.Level.TRACE
            if (debug != "yes"){
                if (this.Level[debug.toUpperCase()]){
                    level = this.Level[debug.toUpperCase()];
                }
            }

            // Get the logger listner based on the function's name
            // e.g CodeBaby.consoleLog.logListener can be accessed via
            // window["CodeBaby"]["consoleLog"]
            // Assume that each member in the logger listener's name
            // chain does not have a "." in it
            // so we can safely split at "." to go down the chain

            var chain = listener.split(".");
            var currObj = window;
            var objError = false;
            var i = 0;
            while(!objError && i < chain.length){
                if (typeof currObj != "undefined" && currObj != null){
                    currObj = currObj[chain[i]];
                } else {
                    objError = true;
                }
                i++;
            }
            if (objError){
                this.error("logger.setDefaults: could not find listener " + listener);
                return;
            }
            // if a default listener has been set up, remove it and
            // add the new default
            
            this.info("logger.setDefaults: checking log listener; default level is:" + defaultLevel + " new level is:" + level +  " default listener is:" + defaultListener);
            if ((listener != defaultListener) || (level != defaultLevel)){
                this.removeListener(defaultListener);
                this.addListener(level,currObj);
                defaultListener = listener;
                defaultLevel = level;
                this.info("logger.setDefaults: added log listener; debug setting is:" + debug + " listener is:" + listener);
            }
        },
        stringify:stringify,
        metric:metric,
        setMetricsLogEnabled:setMetricsLogEnabled,
        getMetricsLogEnabled:getMetricsLogEnabled
    };
})();
CodeBaby.logger.setDefaults();/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 1233 $
  $Date: 2009-11-25 16:31:35 -0700 (Wed, 25 Nov 2009) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/cookies.js $
*/
/*
Object CodeBaby.cookies
A wrapper for cookies; not intended for public API access
*/
CodeBaby.cookies = function(){
//private
    var cookies = [];
    /*
    Function parseCookies
    Parses all of the document's cookies into an hash; an array indexed
    by the cookie's name.
    */
    var parseCookies = function(){
        cookies = [];
        var pairs = document.cookie.split("; ");
        for (var i = 0; i < pairs.length; i++){
            var nv = pairs[i].split("=");
            if (nv.length ==2){
                cookies[nv[0]] = decodeURIComponent(nv[1]); 
            } else {
                cookies[nv[0]] = null;
            }
        }
    };
    parseCookies();
//public
    return {
        /*
        Function getAll
        Gets all cookies
        
        Returns:
        An object where each property is a cookie name, and its value
        is the cookie value.
        */
        getAll: function(){
            var cks = {};
            for (var n in cookies){
                cks[n] = cookies[n];
            }
            return cks;
        },
        /*
        Function get
        Gets the value of a cookie.
        
        Parameters:
            name - the cookie name

        Returns: 
            the value of the cookie
        */
        get: function(name){
            return cookies[name];
        },
        /*
        Function store
        Stores a cookie.  If value is empty, then the cookie is not stored,
        as it is unclear how the browser handles this.
        
        Parameters:
            name - String. Name of the cookie
            value - String. Value of the cookie
            expires - (optional) Number - when the cookie expires in number of seconds
            since Jan 1, 1970.  If omitted or 0, then the cookie is a session cookie.
            domain - (optional)from which domain the cookie is accessible.  Browsers
            restrict what this may be
            secure - (optional) if true, only transmit the cookie if the connection is secure (https)
        */
        store: function(name, value, expires, domain, path, secure){
            if ((typeof(value)== "undefined") || value == null || value == ""){
                expires = ((new Date()).getTime() / 1000 - 86400);// cookie expires immediately
                value = "expired"; // need a value for the browser to care
            }
            cookies[name] = value;
            var cookie = name + "=" + encodeURIComponent(cookies[name]);
            if ((typeof expires) != "undefined" && (expires != null) &&  expires > 0){
                cookie += "; expires=" + (new Date(expires * 1000)).toGMTString();
            }
            if (domain) cookie +="; domain=" + domain;
            if (path) cookie += "; path=" + path;
            if (secure) cookie += "; secure";
            document.cookie = cookie;
            parseCookies();
        },
        /*
        Function remove
        Removes a cookie
        
        Parameters:
            name - cookie's name
        */
        remove: function(name){
            this.store(name,"");
        }
    };    
}();/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2468 $
  $Date: 2010-06-08 14:18:19 -0600 (Tue, 08 Jun 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/storage.js $
*/
// Requires: CodeBaby.cookies
// Requires: CodeBaby.config
// Requires: CodeBaby.logger

/*
Object: CodeBaby.storage
Used to store information (strings only) during a session or over a longer period of time.
Variables are stored in "storage groups"; all variables within a single group expire at the same time (session or longer).
Since storage is limited to strings, numbers should be converted to strings when
setting and back when getting.  More complex objects should be serialized as well.

By default, three storage groups are created.

Storage Groups:

    CodeBabySession - expires at the end of a session
    CodeBabyVisit - expires as defined by <storage.visitSeconds>; typically 30 minutes
    CodeBabyNever - expires as defined by <storage.neverSeconds>; typically 10 years

The storage group names are used as cookie names, and so potentially could
conflict with exising names on the site.  So, the names of these storage groups
can be set via <CodeBaby.config> before this object is loaded.

The names of these groups can be retrieved via <getDefaultGroupName>.

Settings:

    storage.domain - The domain that these storage groups are accessible from.
    For security, these are limited to the domain that the web site belongs to.
    For example, by using the domain codebaby.com, the storage groups are accessible
    from www.codebaby.com, info.codebaby.com and any site of the for *.codebaby.com.
    Setting the domain to a top-level domain such as "com" is not permitted.
    See <storage.domain>.

    storage.sessionGroup - The name of the default storage group that expires
    at the end of a browser session; that is, when the browser is closed.
    See <storage.sessionGroup>.

    storage.visitGroup - The name of the default storage group that expires
    a short time after the last visit to a site page (typically minutes).
    See <storage.visitGroup>.

    storage.visitSeconds - The number of seconds after the last page on
    CodeBaby-enabled site that the default visit storage group expires.
    See <storage.visitSeconds>.

    storage.neverGroup - The name of the default storage group that
    virtually never expires.
    See <storage.neverGroup>

    storage.neverSeconds - The number of seconds after a page visit that
    the default "never expires" storage group expires (should be a long time).
    See <storage.neverSeconds>.

*/
CodeBaby.storage = (function(){
//private
    var config = CodeBaby.config;
    var cookies = CodeBaby.cookies;
    // set default domain
    var domain = config.get("storage.domain",location.hostname);
    // set up default groups
    var sessionGroupName = config.get("storage.sessionGroup","CodeBabySession");
    var visitGroupName = config.get("storage.visitGroup","CodeBabyVisit");
    var neverGroupName = config.get("storage.neverGroup","CodeBabyNever");

/*
Class StorageGroup
Contains all variables in a storage group, with the ability to retrieve and store values.
Parameters:
    groupName - the storage group name
    readOnly - Optional.  Make this a read-only cookie, in which case all following parameters
    are ignored.  Default is false.
    expires - Optional.  When this group expires, in seconds from Jan 1, 1970
    dmn - Optional. The domain this can be accessed on.
    If not set, access to this group is restricted to the current host.
    This has the same restrictions as cookies.
    path - Optional. The path this group is accessible from.  Default is / (and all paths beneath it).
    secure - Optional, true/false.  Only transmit if https connection.
*/
    var StorageGroup = function(groupName,readOnly,expires,dmn,path,secure){
        this.values = [];
        this.groupName = groupName;
        if (!readOnly){ //handles case where readOnly not passed or false
            this.readOnly = false;
        } else {
            this.readOnly = true;
        }
        if (!readOnly){
            if (((typeof expires) == "number") && (expires > 0)){
                this.expires = expires;
            } else {
                this.expires = 0;
            }
            if (dmn) {
                this.domain = dmn;
            } else {
                this.domain = domain;
            }
            if (path){
                this.path = path;
            } else {
                this.path = "/";
            }
            if ((typeof secure) != "undefined"){
                this.secure = secure;
            } else {
                this.secure = false;
            }
        } else {
            this.expires = null;
            this.domain = null;
            this.path = null;
            this.secure = null;
        }
        this.parseValues(cookies.get(groupName));
        if (!this.readOnly){
            this.store();
        }
    };

/*
Function StorageGroup.parseValues
    Takes a string of name - value pairs for example name1:val1&name2:val2 and parses them so that they can be accessed via get.

Parameters:
    combined - a string of combined name/value pairs
*/
    StorageGroup.prototype.parseValues = function(combined){
        this.values = [];
        if (!combined){
            return;
        }
        var pairs = combined.split("&");
        for (var i = 0; i < pairs.length; i++){
            // only look for first ":" for the name:value pair
            var name = null;
            var value = null;
            var split = pairs[i].indexOf(":");
            if (split >= 1){
                name = pairs[i].substring(0,split); // does not include char at split
                value = pairs[i].substring(split + 1); // rest of string after split
                this.values[name] = value;
            }
        }
    };

/*
Function StorageGroup.combineValues
Combines individual name/value pairs in a storage group and combines them into a string.
Returns:
    A string of name /value pairs e.g., name1:val1&name2:val2
See <parseValues>.
*/
    StorageGroup.prototype.combineValues = function(){
        var combined = "";
        var first = true;
        for (var name in this.values){
            combined += (first?"":"&") + name + ":" + this.values[name];
            first = false;
        }
        return combined;
    };

/*
Function StorageGroup.store
Store the names/values in a cookie.
*/

    StorageGroup.prototype.store = function(){
        if (this.readOnly){
            return;
        }
        cookies.store(this.groupName, this.combineValues()
        , this.expires, this.domain, this.path, this.secure);
    };
/*
Function StorageGroup.unstore
Remove the storage group cookie
*/
    StorageGroup.prototype.unstore = function(){
        if (this.readOnly){
            return;
        }
        cookies.remove(this.groupName);
    };
    var sGroups = [];

    /*
    Function initializeDefaultGroups
    Initializes the default groups
    */
    var initializeDefaultGroups = function(){
    // set expiration
        var now = (new Date()).getTime() / 1000;
        var visitSeconds = Number(config.get("storage.visitSeconds","1800"));
        var visitExpires = now + visitSeconds;
        var neverSeconds = Number(config.get("storage.neverSeconds"
        ,(10 * 365 * 86400).toString())); // 10 years
        var neverExpires = now + neverSeconds;

        sGroups[sessionGroupName] = new StorageGroup(sessionGroupName,false);
        sGroups[visitGroupName] = new StorageGroup(visitGroupName,false,visitExpires);
        sGroups[neverGroupName] = new StorageGroup(neverGroupName,false,neverExpires);
    };
    initializeDefaultGroups();

//public
    return {
        /*
        Function: getDefaultGroupName
        Gets the name of the default session, visit or never expires group.
        These are set via config <storage.sessionGroup>, <storage.visitGroup>
        and <storage.neverGroup> and cannot be changed.

        Parameters:
            expiresType - The expires of default group: "session", "visit"
        or "never".

        Returns:
            The name of the default group with the passed expiresType.  null
        if expiresType is not valid.
        */
        getDefaultGroupName: function(expiresType){
            var name = null;
            switch (expiresType){
                case "session":
                    return sessionGroupName;
                    break;
                case "visit":
                    return visitGroupName;
                    break;
                case "never":
                    return neverGroupName;
                    break;
                default:
                    break;
            }
            return name;
        },
        /*
        Function: setDomain
        Sets the domain that would have access to the storage group.  This domain
        has the same restrictions as cookies; that is it can't be a top level domain,
        and it must be a "parent" domain of the current web page host (or the host
        name).
        This defaults to the <CodeBaby.config> storage.domain setting.

        Parameters:
            dmn - the domain to be used by storage groups set up via
        subsequent calls to <setUpGroup>.
        */
        setDomain: function(dmn){
            domain = dmn;
        },
        /*
        Function: getDomain
        Gets the current domain setting that will be used by <setUpGroup>.

        Returns:
            domain setting
        */
        getDomain: function(){
            return domain;
        },
        /*
        Function: setUpGroup
        Sets up a storage group for writing / updating.  If the storage group with
        this name already exists in storage,
        it is initialized to hold the variables from the group.

        Parameters:
            sGroupName - the name of the storage group.  Must be unique on the site (and is used as a cookie name, so must not have the same name as any cookie on the site.)
            expires - (optional) when the storage group expires.  If a number, this is seconds
            from now.  Can also be passed an an object with any or all of the following:
            {years:y, months: m, weeks:w, days:d, hours:h, minutes:n, seconds:s}
            secure - (optional) only make available when using an https connection.
        */
        setUpGroup: function(sGroupName, expires, secure){
            var exp = null; // seconds from Jan 1, 1970
            var expIn = 0;
            var now = (new Date()).getTime() / 1000;
            if (typeof expires != "undefined"){
                if (typeof expires == "number"){
                    if (expires == 0){
                        exp = 0;
                    } else {
                        expIn = expires;
                    }
                } else {
                    if (expires.years)   expIn += expires.years * 365 * 86400;
                    if (expires.months)  expIn += expires.months * 30 * 86400;
                    if (expires.weeks)   expIn += expires.weeks * 7 * 86400;
                    if (expires.days)    expIn += expires.days * 86400;
                    if (expires.hours)   expIn += expires.hours * 3600;
                    if (expires.minutes) expIn += expires.minutes * 60;
                    if (expires.seconds) expIn += expires.seconds;
                }
            }
            if (expIn > 0){
                exp = now + expIn;
            }
            if ((typeof secure) == "undefined"){
                secure = false;
            }
            sGroups[sGroupName]
            = new StorageGroup(sGroupName, false, exp, domain, "/", secure);
            sGroups[sGroupName].store();
        },
        /*
        Function: isGroupSetUp

        Parameters:
            sGroupName - the name of the storage group

        Returns:
            true if <setUpGroup> was called for this group (in this page view),
        and has not been removed via <removeGroup>,
        otherwise false.
        */
        isGroupSetUp: function(sGroupName){
            if (sGroups[sGroupName]){
                return true;
            }
        },
        /*
        Function: getGroups
        Gets information about groups that have been <setUpGroup>.

        Returns:
            an array indexed by groupName, with each object in the array
        having expires (in seconds since Jan 1, 1970), domain (which is set
        via the <CodeBaby.config> storage.domain) and secure (true / false).
        */
        getGroups: function(){
            var groups = [];
            for (var g in sGroups){
                var grp = sGroups[g];
                groups[g] = {
                    expires: grp.expires,
                    domain: grp.domain,
                    secure: grp.secure
                };
            }
            return groups;
        },
        /*
        Function: removeGroup
        Removes a group and all of its associated values.  This will remove
        a group even if you have set it up as read only.

        Parameters:
            sGroupName - name of the group
        */
        removeGroup: function(sGroupName){
            if (sGroups[sGroupName]){
                sGroups[sGroupName].unstore();
                delete sGroups[sGroupName];
            } else {
                var sg = new StorageGroup(sGroupName,false,-86400); // expires yesterday
                sg.unstore();
            }
        },
        /*
        Function: set
        Sets a variable in a storage group

        Parameters:
            sGroupName - the name of the group
            name - the name of the variable
            value - the value to set the variable to

        Returns:
            true if group was set up, otherwise false
        */
        set: function(sGroupName,name,value){
            if (sGroups[sGroupName]){
                sGroups[sGroupName].values[name] = value;
                sGroups[sGroupName].store();
                return true;
            } else {
                return false;
            }
        },
        /*
        Function: get
        Gets a variable value from a storage group

        Parameters:
            sGroupName - name of the storage group
            name - the name of the variable
            defaultValue - default value to return is variable does not exist

        Returns:
            value for the variable, or the default value
        */
        get: function(sGroupName,name,defaultValue){
            if (sGroups[sGroupName]) {
                var sg = sGroups[sGroupName];
            } else {
                var sg = new StorageGroup(sGroupName,true);
            }
            if (sg.values[name]){
                return sg.values[name];
            } else {
                return defaultValue;
            }
        },
        /*
        Function: remove
        removes a variable from a storage group

        Parameters:
            sGroupName - the name of the storage group
            name - variable name

        */
        remove: function(sGroupName,name){
            if (sGroups[sGroupName] && sGroups[sGroupName].values[name]){
                delete sGroups[sGroupName].values[name];
                sGroups[sGroupName].store();
            }
        }
    };
})();
// update session level params in a storage group (relies on CodeBaby.storage existing)
CodeBaby.config.updateSessionParams();
// update defaults for logger (may use config stored in session)
CodeBaby.logger.setDefaults();/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2548 $
  $Date: 2010-06-21 17:17:10 -0600 (Mon, 21 Jun 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/loader.js $
*/  
// Requires: CodeBaby.util
// Requires: CodeBaby.logger

/*
Object: CodeBaby.loader
Loads scripts and style sheets, allowing for dependencies between files (e.g. don't load script2 until script1 has finished)
*/
// Uses "module" pattern to create an object that can hide "private" members
CodeBaby.loader = function(){
//private
    var util = CodeBaby.util;
    var logger = CodeBaby.logger
    
    var scriptList = [];  //list of all of the scripts that need to be loaded
    var finalCallback = null;
    var leftToLoad = 0;
    
    /*
    Function requestScriptIfReady
    Request a specific script if al prereqs have been loaded.  The script is not requested again if it has already been requested.
    
    Parameters:
        script - a Script object
    */
    var requestScriptIfReady = function(script){
        if (script.requested){
            return;
        }
        if (havePrerequisitesLoaded(script)){
            script.requested = true;
            requestScript(script.src,script.loadCallback);
        }
    };
    
    /*
    Function havePrerequisitesLoaded
    Check if the prerequisites for a script have loaded.
    
    Parameters:
        script - a Script object for which to check if the prereqs have loaded.
    */
    var havePrerequisitesLoaded = function(script){
        var allLoaded = true;
        for (var prereq in script.prerequisites){
            allLoaded &= scriptList[prereq].loaded;
            if (!allLoaded){
                return false;
            }     
        }
        return allLoaded;
    };

    /*
    Function requestScript
    Request a specific url, and call a callback once it is loaded

    Parameters:
         url - the url of the script
         loadCallBack - (optional) a function to call after the script has loaded
    */
    var requestScript = function(url,loadCallback){
        insert(url,loadCallback);
    };
    /*
    Class Script
    Represents a script, and keeps track of scripts to load before and after
    this one loads
    
    Parameters:
        src - url of the script file
    */
    var Script = function(src){
        this.src = src; // url of the script
        this.type = "text/javascript";
        this.charset = null; // character set
        this.prerequisites = {}; // scripts to load in advance of this one
        this.dependencies = {}; // scripts to be uploaded after this one (set up automatically)
        this.requested = false; // whether the script has been requested already
        this.loaded = false;  // whether the script has been loaded already
        this.callback = null;
        this.loadCallback = function(url){ // callback function to update script
        //object and request any dependent scripts
            var owner = arguments.callee.owner; // the Script object
            owner.loaded = true;
            if (owner.callback != null){
                owner.callback(url);
            }
            // let dependencies know that a prereq has loaded
            // and request if ready
            for (var depend in owner.dependencies){
                if (scriptList[depend]){ // check if in list; might not be if it had been removed
                    requestScriptIfReady(scriptList[depend]);
                } 
            }
        };
        this.loadCallback.owner = this; //so the callback function has access to Script 
    };
    
    /*
    Function insert
    inserts a script tag into the document head
    
    Parameters:
        url - the script's url
        loadCallback - the function to call after the script is loaded
    */
    var insert = function(url,loadCallback){ // based on jQuery
        leftToLoad++; // keep track of how many scripts are left to load
        CodeBaby.util.ready(function(){
			var head = document.getElementsByTagName("head")[0];
			var script = document.createElement("script");
			script.src = url;
			if (this.charset != null) script.charset = this.charset;
            
			// Handle Script loading
				var done = false;

				// Attach handlers for all browsers
				// continue loading even if there is an error even
				// though this can lead to the dependents not working
				script.onload = script.onerror = script.onreadystatechange = function(){
					if ( !done && (!this.readyState ||
							this.readyState == "loaded" || this.readyState == "complete") ) {
						
						logger.debug("CodeBaby.loader.insert: loaded " + url);
						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;
						head.removeChild( script );
						
						done = true;
                        if (typeof(loadCallback) == 'function') loadCallback(url);

						if ((--leftToLoad == 0) && (finalCallback != null)){
                            var savedFinalCallback = finalCallback;
                            finalCallback = null;
                            savedFinalCallback();
                        }

						
					}
				};
			    head.appendChild(script);
                logger.debug("CodeBaby.loader.insert: inserting script " + url);
			});
        };

    //public

    return {
    /* Function: add
       Add scripts to be loaded (but do not load yet).
       
       Parameters:
           url -  the url of the script.  The same url may be added many time,
           but it will only be requested from the server once.
           prerequesites - (optional) string or array of strings.
           Urls of scripts to load first, they are implicitly added, so they do
           not need to be added first.  If the script in the url parameter has
           already been added, then these prerequisites are additive, that is,
           additional prerequisites are required.
           callback - (optional) the function to call after the script is loaded.
           This is NOT additive; if passed as a parameter, replaces any previous
           function.
     */
        add: function(url, prerequisites, callback){
            // if there is a second parameter, and it is a function, then it
            // is the callback for after the load
            //convert prerequisite param to an array if it isn't already
            // just one array of strings
            var fn = null;
            var callbackPassed = false;
            var prereqs = [];
            switch (typeof prerequisites){
                case "undefined": // no func param either
                    break;
                case "function": // use second param as the function
                    fn = prerequisites;
                    callbackPassed = true;
                    break;
                case "string":
                    prereqs[0] = prerequisites;
                    break;
                case "object":
                    if (prerequisites.constructor == Array){
                        prereqs = prerequisites;
                    }
                    break;
                default:
                    break;
            };
            if ((fn == null) && (typeof prerequisites != "undefined")
            && (typeof callback != "undefined")){
                if (typeof callback != "function"){
                    logger.warn("loader.add: callback parameter is not a function; ignoring");
                } else {
                    fn = callback;
                    callbackPassed = true;
                }
            }

            // add the url to the scriptList if it is not there ( as a Script object)
            if (!scriptList[url]){
                scriptList[url] = new Script(url);
            }
            if (callbackPassed){
                scriptList[url].callback = fn;
            }
            // add scripts (urls) to load in advance
            for (var i = 0; i < prereqs.length; i++){
                scriptList[url].prerequisites[prereqs[i]] = {};
                // for the url to be loaded ahead, record for which script it was loaded
                // create a scriptList entry if it does not exist
                if (!scriptList[prereqs[i]]){
                     scriptList[prereqs[i]] = new Script(prereqs[i]);
                }
                // record the url for which it is a prereq
                scriptList[prereqs[i]].dependencies[url] = {};
            }
        },
        /*
        Function: getCallback
        Gets the callback function that has been set up to execute after the script
        has loaded.
        
        Parameters:
            url - the url of the script.
        */
        getCallback: function(url){
            if (!scriptList[url]){
                return null;
            }
            return scriptList[url].callback;
        },
        /*
        Function: getScripts
        Gets a list (array) of all scripts that have been added
        
        Returns: 
            an array contaning urls of all of the scripts
        that have been added.
        */
        getScripts: function(){
            var scripts = [];
            for (var url in scriptList){
                scripts.push(url);
            }
            return scripts;
        },
        /*
        Function: getPrerequisites
        Gets the prerequisites for a script
        
        Parameters:
        url - the url of the script
        
        Returns:
        An array of prerequisites as url strings; length of 0 if none.
        */
        getPrerequisites: function(url){
           var p = [];
           if (scriptList[url]){
               for (var prereq in scriptList[url].prerequisites){
                   p.push(prereq);    
               }
           }
           return p;
        },
        /*
        Function: getDependents
        Get the urls of scripts that are dependent on the passed url
        
        Parameters:
        url - the url of the script
        
        Returns:
        An array of dependencies as url strings.  Length of 0 if none.
        */
        getDependents: function(url){
           var d = [];
           if (scriptList[url]){
               for (var depend in scriptList[url].dependencies){
                   d.push(depend);    
               }
           }    
           return d;
        },
        /*
        Function: remove
        Removes a script from the dependency graph.  Use with caution as
        dependent scripts are not removed.
        
        Parameters:
            url - the url of the script to be removed
        */
        remove: function(url){
            if (scriptList[url]){
                // remove as a prereq for dependencies
                for (var depend in scriptList[url].dependencies){
                    if (scriptList[depend]){
                        if (scriptList[depend].prerequisites[url]){
                            delete scriptList[depend].prerequisites[url];
                        }
                    }
                }
                delete scriptList[url];
            }
        },
        /*
        Function: load
        If no parameters passed,
        loads the scripts, loading prequisites before the script, and
        executing functions that have beed set up to be called after
        a scripts loads via <add>.
        If a url is passed, loads only that script, no prereqs loaded.
        
        Parameters:
            url - (optional).  If a url is passed, loads only that url
            If already loaded, then this will not be loaded again.
            If not passed, loads all scripts in the graph (that have
            no circular dependencies)
            callback - (optional) Function to call when passed url has loaded.
            Can be used as only parameter to have a callback when all scripts have loaded.
        */
        load: function(url,callback){
            if (typeof url == "string"){
                requestScript(url,callback);
            } else {
                if (typeof url == "function"){ // no url, just a callback
                    finalCallback = url;
                } else if (url == null && typeof callback == "function") {
                    finalCallback = callback;
                } else {
                    finalCallback = null;
                }
                for (var currUrl in scriptList){
                    requestScriptIfReady(scriptList[currUrl]);
                }
            }
        },
        /*
        Function: loadStyleSheet
        Adds a style sheet by adding a link element to the document head.

        Parameters:
            uri - String uri of the stylesheet (css file)

        Returns: 
            nothing.
        */
        loadStyleSheet: function (uri){
            var head = document.getElementsByTagName("head")[0];
            var link = document.createElement("link");
            link.rel="stylesheet";
            link.href = uri;
            link.type = "text/css";
            head.appendChild(link);
        },
        /*
        Function: force
        Forces a script to be loaded even if it has already been loaded.  Call
        after add but before load.

        Parameters:
            url - The url of the script for which you wish to force a load.

        */
        force: function(url){
            scriptList[url].loaded = false;
            scriptList[url].requested = false;
        }
    };

}();/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 915 $
  $Date: 2009-10-29 15:28:06 -0600 (Thu, 29 Oct 2009) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/logger.js $
*/
// Requires: CodeBaby.config
// Requires: CodeBaby.util
// Requires: CodeBaby.storage
// Requires: CodeBaby.loader 
// Requires: CodeBaby.conversation   
// Requires: CodeBaby.flashPlayer
// Requires: CodeBaby.logger


// TODO: change the reference to CodeBaby.conversation.getActiveSegment() when the conversation object is complete...

/*
Object: CodeBaby.metrics
Used to track metrics. A metric has three paramaters.

    Type - The name of the object metric is about
    Identifier - The name of the action or event that metrics ia tracking
    Value - Addition data that is needed to describe the metric 

    For example, to track that a segment has played to the 75% point, do the following
    
> CodeBaby.metrics.track("segment", "percentComplete", "75"); 

Only metrics Types and Identifiers that have a been registerd can be tracked.
Use <CodeBaby.metrics.addType> to add additional types and identifers.    

The metrics tracking can be configured using the configuration API.

Configuration settings:
    metrics.trackingUrl - The url metrics are sent to.  See <metrics.trackingUrl>
    metrics.ignoreType - Whether this metric should be ignored.  See <metrics.ignoreType>
    metrics.enabled - Enables or disables the tracking of metrics.  See <metrics.enabled>
    metrics.player.percentages - The video progress percentages to track metrics at.
    See <metrics.player.percentages>
    account - the account being tracked.  See <account>.
    conversation - the conversation being tracked.  See <conversation>.
    page - the page being tracked.  See <page>.

*/
CodeBaby.metrics = (function(){
//private
    var config = CodeBaby.config;
    var storage = CodeBaby.storage;

    var PARAM_METRIC_ID = "eid";
    var PARAM_USER_ID = "usrid";
    var PARAM_SESSION_ID = "ssid"; 
    var PARAM_CONVERSATION = "cnvsn";
    var PARAM_ACCOUNT = "cstr";
    var PARAM_SEGMENT = "sgmt";
    var PARAM_PAGE_NAME = "pgnm";  
    var PARAM_IS_USER = "isusr";
    var PARAM_TYPE = "typ";
    var PARAM_IDENTIFIER = "id";
    var PARAM_VALUE = "val";
    var PARAM_CLIENT_TIME = "cltm";
    var PARAM_IGNORE_TYPE = "ignr";
    var PARAM_LAST_METRIC = "levt";
    var PARAM_LAST_USER_METRIC = "lusrevt";
    var METRIC_UID_DELIMITER = "__";

    var STORAGE_NAME_USER_GUID = "userGUID";
    var STORAGE_NAME_SESSION_GUID = "sessionGUID";
    
    var trackingUrl;
    var ignoreType;

    var enabled; 
    var lastMetric = null;
    var lastUserMetric = null;
    var userGUID = null;
    var sessionGUID = null; 
    var ignoreType = null;
    
    var playerPercentages;
    
    var logPrefix = "metrics"; // for logger
    
    var types = { // this is the complete data for all built-in metric types
        dialog: {
            shortName: "dialog",
            identifiers: {
                load: {
                    shortName: "load",
                    isUser: false,
                    includeSegment: true
                },
                option: {
                    shortName: "option",
                    isUser: true,
                    includeSegment: true
                },
                button: {
                    shortName: "button",
                    isUser: true,
                    includeSegment: true
                },
                link: {
                    shortName: "link",
                    isUser: true,
                    includeSegment: true
                },
                close: {
                    shortName: "close",
                    isUser: true,
                    includeSegment: true
                },
                resize: {
                    shortName: "resize",
                    isUser: true,
                    includeSegment: true
                },
                move: {
                    shortName: "move",
                    isUser: true,
                    includeSegment: true
                }
            }
        },
        segment: {
            shortName: "segment",
            identifiers: {
                load: {
                    shortName: "load",
                    isUser: false,
                    includeSegment: true
                },
                percentComplete: {
                    shortName: "%",
                    isUser: false,
                    includeSegment: true
                },
                unload: {
                    shortName: "unload",
                    isUser: false,
                    includeSegment: true
                }
            }
        },
        console: {
            shortName: "console",
            identifiers: {
                play: {
                    shortName: "play",
                    isUser: true,
                    includeSegment: true
                },
                pause: {
                    shortName: "pause",
                    isUser: true,
                    includeSegment: true
                },
                close: {
                    shortName: "close",
                    isUser: true,
                    includeSegment: true
                },
                click: {
                    shortName: "click",
                    isUser: true,
                    includeSegment: true
                }
            }
        }, 
        form: {
            shortName: "form",
            identifiers: {
                submit: {
                    shortName: "submit",
                    isUser: true,
                    includeSegment: true
                },
                error: {
                    shortName: "error",
                    isUser: true,
                    includeSegment: true
                }
            }
        }, 
        page: {
            shortName: "page",
            identifiers: {
                load: {
                    shortName: "load",
                    isUser: true,
                    includeSegment: false
                },
                abTesting: {
                    shortName: "abtst",
                    isUser: false,
                    includeSegment: false
                },
                browserName: {
                    shortName: "browserName",
                    isUser: false,
                    includeSegment: false
                },
                browserVersion: {
                    shortName: "browserVersion",
                    isUser: false,
                    includeSegment: false
                },
                flashVersion: {
                    shortName: "flashVersion",
                    isUser: false,
                    includeSegment: false
                },
                browserPlatform: {
                    shortName: "browserPlatform",
                    isUser: false,
                    includeSegment: false
                },
                browserUA: {
                    shortName: "browserUA",
                    isUser: false,
                    includeSegment: false
                },
                screenWidth: {
                    shortName: "scrnWth",
                    isUser: false,
                    includeSegment: false
                },
                screenHeight: {
                    shortName: "scrnHgt",
                    isUser: false,
                    includeSegment: false
                },
                colorDepth: {
                    shortName: "clrDpth",
                    isUser: false,
                    includeSegment: false
                },
                timezone: {
                    shortName: "timezone",
                    isUser: false,
                    includeSegment: false
                },
                cookies: {
                    shortName: "cookies",
                    isUser: false,
                    includeSegment: false
                },
                link: {
                    shortName: "lnkclck",
                    isUser: false,
                    includeSegment: false
                },
                unload: {
                    shortName: "unload",
                    isUser: true,
                    includeSegment: false
                }
            }
        }, 
        effects: {
            shortName: "effects",
            identifiers: {
                circle: {
                    shortName: "circle",
                    isUser: false,
                    includeSegment: true
                },
                highlight: {
                    shortName: "highlight",
                    isUser: false,
                    includeSegment: true
                },
                blink: {
                    shortName: "blink",
                    isUser: false,
                    includeSegment: true
                }
            }
        }, 
        conversation: {
            shortName: "conversation",
            identifiers: {
                optin: {
                    shortName: "optin",
                    isUser: true,
                    includeSegment: false
                }
            }
        }
    };
    
    /*
    Function getActiveSegmentName
    Gets the active segment from the conversation unit if available
    
    Returns:
        The name of the active segment as a string. Returns null if the segment isn't available yet.
    */
    var getSegmentName = function() {
        var result = null;
        if (typeof(CodeBaby.conversation) != "undefined") {
            var segment = CodeBaby.conversation.getSegment(); // this may still return null.
            if (segment != null){
                result = segment.name;
            }
        }   
        return result;
    }
    
    /*
    Function getConversationName
    Gets the conversation name from either the conversation unit if available, or the config unit
    
    Returns:
        The name of the conversation as a string.
    */
    var getConversationName = function() {
        var result = null;
        if (typeof(CodeBaby.conversation) != "undefined") {
            result = CodeBaby.conversation.getName();
        } else {
            result = CodeBaby.config.get("conversation");
        } 
        return result;
    }
    
    /*
    Function getAccountName
    Gets the account name from either the conversation unit if available, or the config unit
    
    Returns:
        The name of the account as a string.
    */
    var getAccountName = function() {
        var result = null;
        if (typeof(CodeBaby.conversation) != "undefined") {
            result = CodeBaby.conversation.getAccount();
        } else {
            result = CodeBaby.config.get("account");
        }   
        return result;
    }
    
    /*
    Function getPageName
    Gets the page name from either the conversation unit if available, or the config unit
    
    Returns:
        The name of the page as a string.
    */
    var getPageName = function() {
        var result = null;
        if (typeof(CodeBaby.conversation) != "undefined") {
            result = CodeBaby.conversation.getPage().name;
        } else {
            result = CodeBaby.config.get("page");
        }     
        return result;
    }
    
    /*
    Function getURI
    Creates the URI that will be used to make the metrics request 
    
    Parameters:
        type
        identifier
        value
    */
    var getURI = function(type, identifier, value) {
        // get type data
        var typeShortName = types[type].shortName;
        var identifierShortName = types[type].identifiers[identifier].shortName;
        var includeSegment = types[type].identifiers[identifier].includeSegment;
        var isUser = types[type].identifiers[identifier].isUser;      
        
        // get the current segment, if we're supposed to include it, and if available.
        var segment = null;
        if (includeSegment) {
            segment = getSegmentName(); // this may still return null.
        }         
               
        var currentTime = new Date().getTime();
        var muid = generateMetricUID(segment, typeShortName, identifierShortName, currentTime);               
          
        //order matters  
        var uri = window.location.protocol + "//" + trackingUrl + "?"+
	    PARAM_METRIC_ID + "=" + encodeURIComponent(muid) + // add the metric id
        "&" + PARAM_USER_ID + "=" + encodeURIComponent(userGUID) + //add the user guid 
        "&" + PARAM_SESSION_ID + "=" + encodeURIComponent(sessionGUID) + // add the session guid
        "&" + PARAM_CONVERSATION + "=" + encodeURIComponent(getConversationName()) + // add the conversation
        "&" + PARAM_ACCOUNT + "=" + encodeURIComponent(getAccountName()) + //and the account
        (segment ? "&" + PARAM_SEGMENT + "=" + encodeURIComponent(segment): "") + // add the segment if needed
        "&" + PARAM_PAGE_NAME + "=" + encodeURIComponent(getPageName()) + // add the pageName if needed     
        "&" + PARAM_IS_USER + "=" + encodeURIComponent(isUser) + //and is user 
        "&" + PARAM_TYPE + "=" + encodeURIComponent(typeShortName) + //and the type
        "&" + PARAM_IDENTIFIER + "=" + encodeURIComponent(identifierShortName) + //and the identifier    
        (value != undefined && value != null ? "&" + PARAM_VALUE + "=" + encodeURIComponent(value): "") + //and the value if supplied 
        "&" + PARAM_CLIENT_TIME + "=" + encodeURIComponent(currentTime.toString()) + //add the client time   
        (ignoreType != null ? "&" + PARAM_IGNORE_TYPE + "=" + encodeURIComponent(ignoreType): "") + //add the ingnoreType 
        (lastMetric != null ? "&" + PARAM_LAST_METRIC + "=" + encodeURIComponent(lastMetric): "") + //add the last event if available
        (lastUserMetric != null ? "&" + PARAM_LAST_USER_METRIC + "=" + encodeURIComponent(lastUserMetric): "");    
        
        //set last metric
        lastMetric = muid;
        if (isUser) {
            lastUserMetric = muid;
        }

        return uri;
    };

    /*
    Function generateMetricUID
    Generates a UID for the metric 
    
    Parameters:
        segment
        type
        identifier
        currentTime
    */  
    var generateMetricUID = function(segment, type, identifier, currentTime) {
        return currentTime.toString() +
               METRIC_UID_DELIMITER + segment + 
               METRIC_UID_DELIMITER + type + 
               METRIC_UID_DELIMITER + identifier;
    };

    /*
    Function validateMetric
    vaildaites the given type exist and that the given identifier belongs to the type.
    Parameters: 
        type - the type that metric context is about.      
        identifier - the action that is being track.  
    */
    var validateMetric = function(type, identifier) {
        if (typeof(types[type]) == "undefined") return false;
        if (typeof(types[type].identifiers[identifier]) == "undefined") return false;
        return true;
    };
    
    /*
    Function track  
    Logs metric of the given type and identifier.
    Will return false is metrics are disabled.
    If the given type and identifier are invalid it will fail and return false.
    Addtional information is tracked along with the given metric.
    
    Parameters:
        type - the type context of that metric   
        identifier - the idenifier context of the metric.  
        value - any context relevent data.  
        callback - function(url). Called when the metric is succesfully tracked.  url is the metrics URL.

    Returns:
        true if successful
    */
    var track = function(type, identifier, value, callback) {    
        if(!enabled) return false; 
        // do type validation
        if(!validateMetric(type, identifier)) {
            return false;
        }
        
        var uri = getURI(type, identifier, value);   
        
        // request script tag with URI
        CodeBaby.loader.load(uri, callback);

        // log it
        var segment = null;
        if (types[type].identifiers[identifier].includeSegment){
            segment = getSegmentName();
        }

        CodeBaby.logger.metric({
             type:type
            ,identifier:identifier
            ,value:value
            ,segment:segment
            ,page:getPageName()
            ,conversation:getConversationName()
            ,account:getAccountName()
            ,uri:uri})
        
        return true;            
    };

    /*
    Function trackUserMetrics
    Tracks metrics about the users environment the first time the user loads a conversation.  Should ONLY be
    called by Codebaby.init!
    */
    var trackUserMetrics = function() {
        CodeBaby.metrics.track("page", "browserName", navigator.appName);
        CodeBaby.metrics.track("page", "browserVersion", navigator.appVersion);
        CodeBaby.metrics.track("page", "browserPlatform", navigator.platform);
        CodeBaby.metrics.track("page", "browserUA", navigator.userAgent);
        CodeBaby.metrics.track("page", "timezone", new Date().getTimezoneOffset() / 60);
        CodeBaby.metrics.track("page", "screenWidth", window.screen.availWidth);
        CodeBaby.metrics.track("page", "screenHeight", window.screen.availHeight);
        CodeBaby.metrics.track("page", "colorDepth", window.screen.colorDepth);
        CodeBaby.metrics.track("page", "cookies", navigator.cookieEnabled)         
    };  

    return {        
        /*
        Function: setEnabled
        Enable or disable metrics logging.  
        
        Parameters: 
            e - set to true to enable.
        */
        setEnabled: function(e){
            enabled = e;
        },
        
        /*
        Function: getEnabled
        Gets whether metric logging is enabled.
        
        Returns: 
            true if enabled
        */
        getEnabled: function(){
            return enabled;
        },  
        
        /*
        Function: getPlayerPercentages
        get an array of the percentages that segment progress will be tracked at
        
        Returns:
            array of perecentages
        */ 
        getPlayerPercentages: function(){
            return playerPercentages;    
        },      
        /*
        Function: track  
        Logs metric of the given type and identifier.
        Will return false if metrics are disabled.
        If the given type and identifier are invalid it will fail and return false.
        Addtional information is tracked along with the given metric.
        
        Parameters:
            type - the type context of that metric   
            identifier - the idenifier context of the metric.  
            value - any context relevent data.  
            callback - callback when the metric is succesfully tracked
   
        Returns: 
            true if successful
        */
        track: function(type, identifier, value, callback) {
          return track(type, identifier, value, callback);            
        },
        /*
        Function: addType
        Adds custom types to the metrics, used for customer-specific metric types, etc.  
        If the type specified by "name" already exists, the specified identifiers will
        be added to the type, unless any of the identifiers already exist (in which case
        this function will fail).

        Parameters: 
            name - The name of the type as a string.
            shortName - The shortname of the type, used for URL encoding.
            identifiers - An object with all of the identifiers for the new type. 
        
        Example of an identifier object:
        > { load: {shortName: "load", isUser: true, includeSegment: false }}

        Returns: 
            true if successful
        */
        addType: function(name, shortName, identifiers) {
            // first, let's validate that all of the correct data is provided.
            if (typeof(name) == "undefined") return false;
            if (typeof(shortName) == "undefined") {
                // default to the name
                shortName = name;
            }
            if (typeof(identifiers) != "object") return false;
            for (var i in identifiers) {
                if (typeof(identifiers[i]) != "object") return false;
                if (typeof(identifiers[i].shortName) == "undefined") {
                    // default to the name of the identifier
                    identifiers[i].shortName = i;
                }
                if (typeof(identifiers[i].isUser) == "undefined") {
                    // default to true for isUser
                    identifiers[i].isUser = true;
                }
                if (typeof(identifiers[i].includeSegment) == "undefined") {
                    // default to true for includeSegment
                    identifiers[i].includeSegment = true;
                }
            }
            
            // check to see if the type is already defined, 
            if (typeof(types[name]) == "undefined") {
                // this is a new type, just create it            
                types[name] = {
                    shortName: shortName,
                    identifiers: identifiers
                }
            } else {
                // otherwise, we'll need to append to the current type
                for (var i in identifiers) {
                    // make sure the identifier doesn't already exist
                    if (typeof(types[name].identifiers[i]) != "undefined") return false;
                    types[name].identifiers[i] = identifiers[i];
                }
            }
            
            return true;
        },
        
        /*
        Function: getTypes
        Gets the types object, for viewing only (read-only).  The types object describes 
        the data for all of the different metrics types.
        
        Returns: 
            the types object that describes all metrics types.
        
        Example of a returned object:
        > dialog: { shortName: "dialog", identifiers: { load: { shortName: "load", isUser: false, includeSegment: true } } } )
        */
        getTypes: function() {
            return CodeBaby.util.cloneObject(types);
        },
        /*
        Function testValidateMetric
        test helper for the private validateMetric function
        */
        testValidateMetric: function(type, identifier) {
            return validateMetric(type, identifier); 
        },
        /*
        Function testURI
        test helper for the private getURI function
        */
        testURI: function(type, identifier, value) {   
            return getURI(type, identifier, value);   
        },
                   
        /*
        Function initialize
        Initialize the metrics. 
        It sets up varables need for the paramaters string 
        These include the user and session GUIDs      
        Note: this should only be called from init.js!
        */
        initialize: function(){
            //get enabled from config       
            if(CodeBaby.config.get("metrics.enabled","yes") == "no"){
                enabled = false; 
            } else {
                enabled = true;
            }       

            //get ingoreType from config
            ignoreType = CodeBaby.config.get("metrics.ignoreType", "null");
            // set the tracking URL
            trackingUrl = CodeBaby.config.get("metrics.trackingUrl");

            //create percentages array        
            var percentString = CodeBaby.config.get("metrics.player.percentages");
            playerPercentages = percentString.split(",");

            //get or create the session guid
            var vistGroupName = storage.getDefaultGroupName("visit");
            sessionGUID = storage.get(vistGroupName,STORAGE_NAME_SESSION_GUID,null);
            if(sessionGUID == null) {
                sessionGUID = CodeBaby.util.generateGUID();
                CodeBaby.storage.set(vistGroupName,STORAGE_NAME_SESSION_GUID,sessionGUID);
            }

            //get or create the user guid 
            var neverGroupName = storage.getDefaultGroupName("never");
            userGUID = storage.get(neverGroupName,STORAGE_NAME_USER_GUID,null);
            if(userGUID == null){
                userGUID = CodeBaby.util.generateGUID();
                CodeBaby.storage.set(neverGroupName,STORAGE_NAME_USER_GUID,userGUID);
                //set that the user metrics need to be logged
                trackUserMetrics();
            }              
            //track page metric
            //need to use CodeBaby.metrics to force use of public methood for unit tests
            CodeBaby.metrics.track("page", "load", document.referrer);      
        }
        
    }
})();
/*
  CodeBaby Delivery Manager
  Copyright (c) 2009 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the
  license agreement with CodeBaby.

  $Rev: 2789 $
  $Date: 2010-08-09 16:30:36 -0600 (Mon, 09 Aug 2010) $
  $URL: https://codebaby.svn.cvsdude.com/codebabysuite/trunk/cdm/javascript/src/init.js $
*/  
// Requires: CodeBaby.config
// Requires: CodeBaby.loader
// Requires: CodeBaby.logger
// Requires: CodeBaby.util
// Requires: CodeBaby.metrics
// Requires: CodeBaby.conversation
// Requires: CodeBaby.start (user-defined function)

/*
Function: CodeBaby.init
Initializes CDM to suppport CodeBaby conversations on a web page.  This function
is loaded and called in the CodeBaby script tag.  It configures which other CDM
JavaScript files to load, and loads a configuration JavaScript
file for the account and the conversation, where changes to the files to load can be made, and
configurations settings changed for objects not yet loaded.

Once all CDM files have been loaded, the conversation is loaded and
the (user-defined) function
CodeBaby.start is called if it exists.

Load order:  The default load order is:

    "boot" ${cb.cdm.bootJSName} - this is the file that <CodeBaby.init> is in and so
        is loaded before this object executes.  Note that ${cb.cdm.bootNoMinJSPath}
        is a non-minified version, useful for tracing through code for debugging.
    <script.accountConfigUrl> - The account configuration JavaScript.  This can alter
        configuration settings for the follow on files, including the conversation
        config location.  Via <CodeBaby.loader>,
        the load sequence may be modified.
    <script.conversationConfigUrl> - The conversation configuration JavaScript.  Similar
        alterations as in the account config may be made.
    ${cb.cdm.coreJSPath} - The core file containing player, conversation and other essential
        cdm objects.  Note that setting <script.minJS> to "no" loads each
        (non-minified) component of core.js separately.

Configuration settings:

    autoStart - Whether to start the conversation logic as soon as the page loads.
    If "no", the conversation logic must be executed via <CodeBaby.conversation.startPage>
    or via <CodeBaby.conversation.trigger>.  See <autoStart>.
    
    script.initLoad - Whether init should load the config and core files.
    If this is "no", the JavaScript in the page must load all other required
    files in the proper order (possibly using <CodeBaby.loader>).  This
    can also be set to no in the account config or conversation config
    to prevent further loading.
    See <script.initLoad>.
    
    script.accountConfigUrl - The url of the configuration file for the account.
    If blank, then no attempt will be made to load this url.
    See <script.accountConfigUrl>.
    
    script.conversationConfigUrl - The url of the configuration file for the conversations.
    If blank, then no attempt will be made to load this url.
    See <script.conversationConfigUrl>
    
    script.loadNow - whether to load core files (and any others added through
    <CodeBaby.loader> immediately after the config files load. If "no",
    the load dependencies are set up, and a
    call to <CodeBaby.loader.load> must be made before any segments are shown,
    or any conversation logic is executed.  If <script.initLoad> is no, this
    setting has no effect.
    See <script.loadNow>
    
    script.jsPath - The path to source files.  See <script.jsPath>.
    
    script.minJS - Whether to use minified JavaScript.  See <script.minJS>.
    
    script.jqueryUrl - The location of jquery JavaScript.
    See <script.jqueryUrl>.
    
    account.dataUrl - The location of the account data file
    See <account.dataUrl>

    conversation.expires - The number of seconds before the current conversation
    value expires after navigating to a new page.
    See <conversation.expires>
    
    ui.themeStyleSheet - Which style sheet to load, typically to support dialog
    boxes and consoles.  If "no" or null, not loaded.
    See <ui.themeStyleSheet>.
    
    ui.dialogBoxManagerUrl - The dialog box code to load.  If "no" or null, then not
    loaded.
    See <ui.dialogBoxManagerUrl>
    
    ui.jqueryUiUrl - The jquery ui url to load.  If "no" or null, then not loaded.
    See <ui.jqueryUiUrl>
    
    

*/
CodeBaby.init = (function(){

var config = CodeBaby.config;
var loader = CodeBaby.loader;
var logger = CodeBaby.logger;
var util = CodeBaby.util


// Function definitions
/*
Function addMinifiedScripts
Adds the minified version of the core script to the loader.
*/
var addMinifiedScripts = function(){
    loader.add(core);
};

/*
Function addFullScripts
Adds the full version of the scripts to the loader.
*/
var addFullScripts = function(){
    var changeEvent = jsPath + "/ChangeEvent.js";
    var changeListeners = jsPath + "/ChangeListeners.js";
    var beforeAfterListeners = jsPath + "/BeforeAfterListeners.js";
    var timedListeners = jsPath + "/TimedListeners.js";
    var segmentVideo = jsPath + "/SegmentVideo.js";
    var swfObject = jsPath + "/swfobject.js";
    var flashPlayer = jsPath + "/flashPlayer.js";
    var conversation = jsPath + "/conversation.js";
    var dom = jsPath + "/dom.js";
    var domElement = jsPath + "/dom/Element.js";

    loader.add(changeEvent);
    loader.add(changeListeners);
    loader.add(beforeAfterListeners,[changeListeners,changeEvent]);
    loader.add(timedListeners);
    //TODO: this shouldn't be here, but for now it has to be
    if (loadUtilityConsole) {
        var utilityConsoleURL = jsPath + "/utilityConsole.js";
        loader.add(utilityConsoleURL, [beforeAfterListeners]);
    }

    loader.add(segmentVideo,[beforeAfterListeners,timedListeners]);
    loader.add(swfObject);
    loader.add(flashPlayer,[swfObject,beforeAfterListeners]);
    loader.add(conversation,flashPlayer);
    loader.add(dom);
    loader.add(domElement,dom);
};

/*
Function addConsole
Adds the console url to the load. 
*/
var addConsole = function(){
    var url = config.get("ui.consoleUrl","no");
    if (url == "no"){
        return;
    }
    if (minJS){
    // check if this is the "standard" console; if so, it is already in the core
    // so don't load if loading core
        var inCore = config.get("ui.consoleInCore","yes") == "yes";
        if (!inCore){
            loader.add(url,core);
        }
    } else { // if not using minified, load after flashPlayer
        loader.add(url,jsPath + "/flashPlayer.js");
    }
};
/*
Function addDialogBox
Adds the dialog box defined in config <ui.dialogBoxManagerUrl>.  If this is "no:,
then a dialog box is not loaded.  Loads jquery as a prereq if loading
a dialog box.
*/
var addDialogBox = function(){
    var url = config.get("ui.dialogBoxManagerUrl","no");
    if (url == "no"){
        return;
    }
    // Need to know which url requires jQuery ui.
    // If in the core, and using minified code, the core requires it.
    // Otherwise, just the dialog box url requires it.
    var jqRequiredBy = null;
    if (minJS){ // load after core
    // check if this is the "standard" dialog box; if so, it is already in the core
    // so don't load if loading core
        var inCore = config.get("ui.dialogBoxManagerInCore","yes") == "yes";
        if (!inCore){
            loader.add(url,core);
            jqRequiredBy = url;
        } else {
            jqRequiredBy = core;
        }
    } else { // if not using minified, load after conversation
        loader.add(url,jsPath + "/conversation.js");
        jqRequiredBy = url;
    }
    // add jquery as a prereq
    addJquery(jqRequiredBy);
};
/*
Function addJquery
Adds jquery files - ui javascript included before the dependent

Parameter
    requiredBy - the url of the script that requires jquery
*/
var addJquery = function(requiredBy){
    // add jquery as a prereq
    var jq = config.get("script.jqueryUrl");
    if (jq != null && jq != "no"){
       loader.add(requiredBy,jq,function(){
           logger.info(requiredBy + " which requires jQuery has loaded");
       });
       loader.add(jq,function(){
       // callback after jquery loaded to prevent conflicts
       // with other libs that use $
           logger.debug("about to call jQuery.noConflict");
           CodeBaby.jQuery = jQuery.noConflict();
       });
    }
    // add jquery ui as a prereq
    var jqui = config.get("ui.jqueryUiUrl");
    if (jqui != null && jqui != "no"){
        loader.add(requiredBy,jqui);
        if (jq != null && jq != "no"){
            loader.add(jqui,jq);
        }
    }
}
/*
Function loadScript
Loads a script url using <CodeBaby.loader.load>.  Only loads if
<script.initLoad> is "yes".

Parameters:
    url - the url to load
    callback - the function to call after the script loads
*/
function loadScript(url,callback){
    var initLoad = (config.get("script.initLoad") == "yes");
    logger.info("init.loadScript(" + url + ",...); initLoad is " + initLoad );
    if (initLoad){
        if (url != null && url != ""){
            loader.load(url,callback);
        } else {
            if (typeof(callback) == 'function') callback();
        }
    } else {
        start();
    }
}

/*
Function loadAccountConfig
Loads the account configuration script file (if <script.initLoad> is yes).
*/
function loadAccountConfig(){
    var account = config.get("account", null);
    if (account == null) {
        logger.error("init.loadAccountConfig(); account is null.");
    } else {
        loadScript(config.get("script.accountConfigUrl",null),loadAccountData);
    }
}

/*
Function getInitConversation
Gets the initial converation from the conversation storage var or
config setting "conversation"; the storage var takes precedence

Returns: the conversation as described above; null if not set
*/
function getInitConversation(){
    var convSet = util.getConversationSettings();
    var conv = convSet.current;
    if (convSet.current === null){
        conv = convSet.config;
    }
    return conv;
};


/*
Function loadAccountData
Loads account data to get the default conversation for a page.  Only loads
if conversation is not already set up in via config or via the conversation
storage var, and the "page" must have been set up in config.
Regardless of if the account data is loaded or not, this function calls
loadConversationConfig (after the account data is loaded if required).
*/
function loadAccountData(){
// determine conversation from config / cookie settings (cookie takes precedence)
    var conversation = getInitConversation();
    
    logger.info("init.loadAccountData; conversation is: " + conversation);
    var account = config.get("account",null);
    logger.info("init.loadAccountData; account is: " + account);
    var page = config.get("page",null);
    logger.info("init.loadAccountData; page is: " + page);
    if (conversation === null && account !== null && page !== null){
        loadScript(util.getStandardUrl("accountData",account),function(){
            // find the default conversation
            if (typeof CodeBaby.accountData === "undefined"){
                logger.info("init.loadAccountdata; CodeBaby.accountData is undefined");
            } else {
                // find the page with the matching name
                var pages = CodeBaby.accountData.pages;
                var conv = null;
                for (var pageId in pages){
                    if (pages[pageId].name == page){
                        conv = pages[pageId].defaultConversation;
                        break;
                    }
                }
                logger.info("init.loadAccountData; defaultConversation from accountData is: " + conv);
                if (conv !== null){
                    config.set("conversation",conv);
                }
            }
            loadConversationConfig();
        });
    } else {
         loadConversationConfig();
    }
}
/*
Function loadConverationConfig
Loads the conversation configuration script file.
*/

function loadConversationConfig(){
    var conversation = getInitConversation();
    logger.info("init.loadConversationConfig; conversation is: " + conversation);
    if (conversation == null) {
        // log metrics here.  We don't log these in CodeBaby.metrics because the account scripts haven't been executed yet.
        CodeBaby.metrics.initialize();
    } else {
        var convSettings = util.getConversationSettings();
        if (convSettings.config == convSettings.current){
            // get config javascript path from config object if current conversation
            // matches the configurated conversation
            loadScript(config.get("script.conversationConfigUrl",null),loadCoreFiles);
        } else {
            // otherwise get it from a standard location
            loadScript(util.getStandardUrl("config",config.get("account")
            ,convSettings.current),loadCoreFiles);
        }
    }
}

/*
Function loadCoreFiles
Loads core files if <script.loadNow> is yes.  Calls <start> when done.
*/
function loadCoreFiles(){

    // log metrics here.  We don't log these in CodeBaby.metrics because the account scripts haven't been executed yet.
    CodeBaby.metrics.initialize();
    
    var loadNow = config.get("script.loadNow");
    logger.info("init.loadCoreFiles; script.loadNow is " + loadNow);
    
    // load rest of core 
    if (loadNow == "yes"){
        loader.load(start);
    }
}

/*
Function start
Checks of <autoStart> is yes, and if so, loads the conversation and starts
executing its logic.
*/
function start(){
    var autoStart = config.get("autoStart");
    logger.info("init.start: autoStart is " + autoStart);
    if (autoStart == "yes"  && CodeBaby.conversation){
        CodeBaby.conversation.load("",startPage);
    }
}
/*
Function startPage
Starts conversation logic, calling CodeBaby.start first if it exists.
*/
function startPage(){
    if ((typeof CodeBaby.start) == "function"){
        CodeBaby.start();
    }
    logger.info("init.startPage: calling conversation.startPage()");
    CodeBaby.conversation.startPage();
}

// Find various paths so we know where to get scripts from
var jsPath = config.get("script.jsPath");
var core = jsPath + "/core.js";

// check if init should load scripts
// if not, something else should have loaded them for conversations to work
// e.g. load via script tags for cdm files and config files in the html.
var initLoad = (config.get("script.initLoad") == "yes");

// check if using minified version or full version
var minJS = (config.get("script.minJS") == "yes");

// check if the utility console is supposed to be loaded
var loadUtilityConsole = (config.get("showUtilityConsole") == "yes");

// Set up the core files before account config called, so that
// the load order can adjust with the account config.
if (initLoad){
    if (minJS){
        addMinifiedScripts();
    } else {
        addFullScripts();
    }
    addConsole();
    addDialogBox();
}

// load the theme style sheet
var themeStyleSheet = config.get("ui.themeStyleSheet");
if (themeStyleSheet != null && themeStyleSheet != "no"){
    loader.loadStyleSheet(themeStyleSheet);
}

// load the dialog box style sheet
var dbStyleSheet = config.get("ui.dialogBoxStyleSheet");
if (dbStyleSheet != null && dbStyleSheet != "no") {
    loader.loadStyleSheet(dbStyleSheet);
}

// load account config; this starts a chain of functions that typically
// loads the conversation config, calls CodeBaby.start and starts the page logic.
loadAccountConfig();

})();
