
/**
 * @namespace  Container for all IG objects.
 */
var IG = window.IG || {};

(function() {
   var namespaces = [
      /**
       * @name IG.business
       * @namespace Container for business-related code.
       */
      "business",

      /**
       * @name IG.format
       * @namespace Container for formatting code.
       */
      "format",

      /**
       * @name IG.streaming
       * @namespace Container for Lightstreamer code.
       */
      "streaming",

      /**
       * @name IG.ticket
       * @namespace Container for deal ticket code.
       */
      "ticket",

      /**
       * @name IG.ui
       * @namespace Container for UI code.
       */
      "ui",

      /**
       * @name IG.util
       * @namespace Container for generic utility methods.
       */
      "util",

      /**
       * @name IG.validation
       * @namespace Container for validation code.
       */
      "validation",

      /**
       * @name IG.widget
       * @namespace Container for UI widgets.
       */
      "widget",
	  
      /**
       * @name IG.rtcharts
       * @namespace Container for Realtime Charting.
       */
      "rtcharts"
   ];
   
   for (var i = 0, len = namespaces.length; i < len; i++) {
      IG[namespaces[i]] = IG[namespaces[i]] || {};
   }
})();


/**
 * @name IG
 * @namespace Container for all IG objects.
 */
var IG = window.IG || {};

(function() {
   /**
    * @function
    * @description Returns the closest available logging level in console
    */
   var consoleLevel = function(logLevel) {
      if (window.console && IG._isDebugEnabled) {
         switch(logLevel) {
            case "error":
               if (console.error) {
                  return "error";
               }
               // fall through
            case "warn":
               if (console.warn) {
                  return "warn";
               }
               // fall through
            case "debug":
               if (console.debug) {
                  return "debug";
               }
               // fall through
            case "info":
               if (console.info) {
                  return "info";
               }
               // fall through
            default:
               if (console.log) {
                  return "log";
               }
         }
      }

      return "none";
   }

   /**
    * @function
    * @description Returns a function containing generic setup code for logging. If possible the IG method directly 
    * points to the closest equivalent console method, otherwise it wraps the console call.
    */
   function setupLog(logMethod) {
      return function() {
         var level = consoleLevel(logMethod);
         var args = Array.prototype.slice.call(arguments);

         if (level != "none") {
            IG[logMethod] = console[level];

            try {
               if (IG[logMethod].apply) {
                  IG[logMethod].apply(this, args);
               } else {
                  IG[logMethod](args);
               }
            } catch (e) {
               IG[logMethod] = function() {
                  console[level].apply(console, Array.prototype.slice.call(arguments));
               }

               IG[logMethod].apply(this, args);
            }
         }
      }
   }

   /**
    * @function
    * @description Log non-recoverable exceptions. 
    */
   IG.error = setupLog("error");
   
   /**
    * @function
    * @description Log recoverable exceptions.
    */
   IG.warn = setupLog("warn");
   
   /**
    * @function
    * @description Log messages useful for solving code problems.
    */
   IG.debug = setupLog("debug");
   
   /**
    * @function
    * @description Log messages useful for solving code problems.
    */
   IG.info = setupLog("info");
})();


/**
 * @fileOverview Guarantees cross-browser compliance with Array methods up to JavaScript 1.8
 */

if (!Array.prototype.indexOf) {
  /**
   * Returns the first index at which a given element can be found in the array, or -1 if it is not present.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/indexOf" target="_blank">Mozilla Core JavaScript Reference: indexOf</a>
   * @param searchElement Element to locate in the array.
   * @param [fromIndex] The index at which to begin the search. Defaults to 0, i.e. the whole array will be searched. If the index is greater than or equal to the length of the array, -1 is returned, i.e. the array will not be searched. If negative, it is taken as the offset from the end of the array. Note that even when the index is negative, the array is still searched from front to back. If the calculated index is less than 0, the whole array will be searched.
   */
  Array.prototype.indexOf = function(elt /*, from*/)
  {
    var len = this.length >>> 0;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}

if (!Array.prototype.lastIndexOf) {
  /**
   * Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/lastIndexOf" target="_blank">Mozilla Core JavaScript Reference: lastIndexOf</a>
   * @param searchElement Element to locate in the array.
   * @param [fromIndex] The index at which to start searching backwards. Defaults to the array's length, i.e. the whole array will be searched. If the index is greater than or equal to the length of the array, the whole array will be searched. If negative, it is taken as the offset from the end of the array. Note that even when the index is negative, the array is still searched from back to front. If the calculated index is less than 0, -1 is returned, i.e. the array will not be searched.
   */
  Array.prototype.lastIndexOf = function(elt /*, from*/)
  {
    var len = this.length;

    var from = Number(arguments[1]);
    if (isNaN(from))
    {
      from = len - 1;
    }
    else
    {
      from = (from < 0)
           ? Math.ceil(from)
           : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len - 1;
    }

    for (; from > -1; from--)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}

if (!Array.prototype.some) {
  /**
   * Tests whether some element in the array passes the test implemented by the provided function.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/some" target="_blank">Mozilla Core JavaScript Reference: some</a>
   * @param callback Function to test for each element.
   * @param [thisObject] Object to use as this when executing callback.
   */
  Array.prototype.some = function(fun /*, thisp*/)
  {
    var i = 0,
        len = this.length >>> 0;

    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (; i < len; i++)
    {
      if (i in this &&
          fun.call(thisp, this[i], i, this))
        return true;
    }

    return false;
  };
}

if (!Array.prototype.map) {
  /**
   * Creates a new array with the results of calling a provided function on every element in this array.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map" target="_blank">Mozilla Core JavaScript Reference: map</a>
   * @param callback Function that produces an element of the new Array from an element of the current one.
   * @param [thisObject] Object to use as this when executing callback.
   */
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

if (!Array.prototype.every) {
  /**
   * Tests whether all elements in the array pass the test implemented by the provided function.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/every" target="_blank">Mozilla Core JavaScript Reference: every</a>
   * @param callback Function to test for each element.
   * @param [thisObject] Object to use as this when executing callback.
   */
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}

if (!Array.prototype.filter) {
  /**
   * Creates a new array with all elements that pass the test implemented by the provided function.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter" target="_blank">Mozilla Core JavaScript Reference: filter</a>
   * @param callback Function to test each element of the array.
   * @param [thisObject] Object to use as this when executing callback.
   */
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}

if (!Array.prototype.forEach) {
  /**
   * Executes a provided function once per array element.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach" target="_blank">Mozilla Core JavaScript Reference: forEach</a>
   * @param callback Function to execute for each element.
   * @param [thisObject] Object to use as this when executing callback.
   */
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}

if (!Array.prototype.reduce) {
  /**
   * Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduce" target="_blank">Mozilla Core JavaScript Reference: reduce</a>
   * @param Function to execute on each value in the array.
   * @param [initialValue] Object to use as the first argument to the first call of the callback.
   */
  Array.prototype.reduce = function(fun /*, initial*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    // no value to return if no initial value and an empty array
    if (len == 0 && arguments.length == 1)
      throw new TypeError();

    var i = 0;
    if (arguments.length >= 2)
    {
      var rv = arguments[1];
    }
    else
    {
      do
      {
        if (i in this)
        {
          rv = this[i++];
          break;
        }

        // if array contains no values, no initial value to return
        if (++i >= len)
          throw new TypeError();
      }
      while (true);
    }

    for (; i < len; i++)
    {
      if (i in this)
        rv = fun.call(null, rv, this[i], i, this);
    }

    return rv;
  };
}

if (!Array.prototype.reduceRight) {
  /**
   * Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value.
   * @see <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduceRight" target="_blank">Mozilla Core JavaScript Reference: reduceRight</a>
   * @param callback Function to execute on each value in the array.
   * @param [initialValue] Object to use as the first argument to the first call of the callback.
   */
  Array.prototype.reduceRight = function(fun /*, initial*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    // no value to return if no initial value, empty array
    if (len == 0 && arguments.length == 1)
      throw new TypeError();

    var i = len - 1;
    if (arguments.length >= 2)
    {
      var rv = arguments[1];
    }
    else
    {
      do
      {
        if (i in this)
        {
          rv = this[i--];
          break;
        }

        // if array contains no values, no initial value to return
        if (--i < 0)
          throw new TypeError();
      }
      while (true);
    }

    for (; i >= 0; i--)
    {
      if (i in this)
        rv = fun.call(null, rv, this[i], i, this);
    }

    return rv;
  };
}
/**
 * @fileOverview Guarantees cross-browser compliance with String methods up to JavaScript 1.8
 */

// trim() method

if (!String.prototype.trim) {

   String.prototype.trim = function() {

      return this.replace(/^\s+|\s+$/g, '');
   }
}

/*

These two methods are part of JavaScript 1.8, but not particularly useful:
https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods_2

// trimLeft() method

if (!String.prototype.trimLeft) {

   String.prototype.trimLeft = function() {

      return this.replace(/^\s+/, '');
   }
}


// trimRight() method

if (!String.prototype.trimRight) {

   String.prototype.trimRight = function() {

      return this.replace(/\s+$/, '');
   }
}

*/


/**
 * @class Lang
 * @exports Lang as IG.util.Lang
 */
(function( IG, Undefined ) {
   
   /**
    * Utility library for the JavaScript language
    * @constructor
    * @name IG.util.Lang
    */
   function Lang() {
      
   }
   
   /**
    * Extends the prototype of cls with that of base, creates cls.superclass
    * as a pointer to the prototype methods of base.
    * 
    * @param {Function} cls
    * @param {Function} base
    */
   Lang.prototype.extend = function( cls, base ) {
      function F(){ };
      F.prototype = base.prototype;
      
      cls.prototype = new F();
      cls.prototype.constructor = cls;
      cls.superclass = base.prototype;
   };
   
   /**
    * Accepting a variable number of arguments where each argument is a space
    * delimited fully qualified namespace string that needs to be created.
    * 
    * @example
    * IG.util.Lang.namespace( 'IG.app', 'IG.something.anything' )
    * // result:
    * //   IG.app = {};
    * //   IG.something = { anything: {} }
    * 
    * @param {String<>}
    */
   Lang.prototype.namespace = function() {
      
      var i = 0, iMax = arguments.length, j, jMax, nsItems, nsResult;
      for(; i < iMax; i++ ) {
         nsResult = {};
         nsItems = arguments[i].split( '.' );
         
         for( j = 0, jMax = nsItems.length; j < jMax; j++ ) {
            
            if( ! j ) {
               nsResult = window[ nsItems[j] ] ? window[ nsItems[j] ] : window[ nsItems[j] ] = {};
            }
            else {
               nsResult = nsResult[ nsItems[j] ] || ( nsResult[ nsItems[j] ] = {} );
            }
         }
      }
   };
   
   /**
    * Applies the properties and methods of obj to cls. Note that this will
    * overwrite any identically named properties on cls with the value from
    * obj unless preventOverwrite is set to true.
    * This method returns the newly updated cls object, so this method can be
    * called as an object creator:
    * 
    * @example
    * // creating a shallow clone
    * var shallowClone = IG.util.Lang.applyTo( {}, someObject );
    * 
    * @example
    * // ensuring configuration options - call with preventOverwrite=true
    * // adds values for a and b so long as they are not already present
    * function myClass( config ) {
    *   this.initialConfig = IG.util.Lang.applyTo( config, { a: 123, b: 345 }, true );
    * }
    * 
    * @param {Object} cls
    * @param {Object} obj
    * @param {Boolean} obj
    * @return {Object} cls 
    */
   Lang.prototype.applyTo = function( cls, obj, preventOverwrite ) {
      
      for( var el in obj ) {
         if( '|prototype|superclass|'.indexOf('|' + el + '|') < 0 && ( ! preventOverwrite || cls[el] === undefined ) ) {
            cls[el] = obj[el];
         }
      }
      
      return cls;
   };      

   IG.util.Lang = new Lang();

})(IG);




/**
 * Utility object for creating named custom events
 * @param eventName {String} The name of the custom event
 * @param applyParams {boolean} When true, parameters passed to 'fire' are applied to the callback so they will not be
 *                              in an array (which is what YAHOO.util.CustomEvent usually does) 
 * @namespace IG.util.NamedCustomEvent
 * @constructor
 */
IG.util.NamedCustomEvent = function(eventName, applyParams) {
   this._eventName = eventName;
   this._events = {};
   this._applyParams = applyParams;
};

IG.util.NamedCustomEvent.prototype = {
   subscribe: function(name, callbackFn, assocObj, scopeBool) {
      if (!this._events[name]) {
         this._events[name] = new YAHOO.util.CustomEvent(name + this._eventName);
      }
      
      this._events[name].subscribe(callbackFn, assocObj, scopeBool);
   },
   
   fire: function(name, arg1, arg2, arg3, arg4, arg5) {
      if (name && this._events[name] && this._events[name]) {
         var customEvent = this._events[name];
         
         // When this flag is set to true we temporarily alter all callbacks so their parameters are applied to the
         // callback so we don't have to access then in an array - this improves the interface for things like event
         // listeners 
         if (this._applyParams) {
            var subscribers = customEvent.subscribers.slice();
            var len = subscribers.length;
            
            for (var i=0; i < len; i++) {
               var subscriber = subscribers[i];
               
               subscriber.origFn = subscriber.fn;
               
               subscriber.fn = (function(origFn) {
                  return function(eventId, params) {
                     origFn.apply(this, params);
                  };
               })(subscriber.origFn);
            }
         }
         
         customEvent.fire(arg1, arg2, arg3, arg4, arg5);
         
         // Reset any custom handlers back to their original that were applied above
         if (this._applyParams) {
            for (var i=0; i < len; i++) {
               var subscriber = subscribers[i];
               
               if (subscriber.origFn) {
                  subscriber.fn = subscriber.origFn;
                  delete subscriber.origFn;
               }
            }
         }
      }
   },
   
   unsubscribe: function(name, callbackFn, assocObj) {
      name && this._events[name] && this._events[name].unsubscribe(callbackFn, assocObj);
   },
   
   unsubscribeAll: function() {
      for (var event in this._events) {
         if (YAHOO.lang.hasOwnProperty(this._events, event)) {
            this._events[event].unsubscribe();
            delete this._events[event];
         }
      }
   },
   
   subscribers: function(name) {
      return this._events[name] && this._events[name].subscribers;
   },
   
   subscribersForObject: function(obj) {
      var allSubscribers = [];
      
      for (var event in this._events) {
         if (YAHOO.lang.hasOwnProperty(this._events, event)) {
            var subscribers = this._events[event].subscribers;
            
            for (var i = 0, len = subscribers.length; i < len; i++) {
               if (subscribers[i].obj === obj) {
                  allSubscribers.push({
                     name: event,
                     subscriber: subscribers[i]
                  });
               }
            }
         }
      }
      
      return allSubscribers;
   },
   
   hasSubscribers: function() {
      for (var event in this._events) {
         if (YAHOO.lang.hasOwnProperty(this._events, event)) {
            if (this._events[event].subscribers.length > 0) {
               return true;
            }
         }
      }
      
      return false;
   },
   
   lastError: function(name) {
      return this._events[name] && this._events[name].lastError;
   }
};

/**
 * @namespace Utility object for attaching even listeners to HTML elements and windows. 
 * @module
 */
 IG.util.Event = (function() {
   
   var objectMappingId = 1;
   var idToObjectMap = {};
   var eventMap = {};
   var callbackMap = {};
   var bubble = false;
   var inEventLoop = false;
	var currentEvent = null;
   
	
	/**
	 * Retrieves the DOMElement the event was fired from - cross browser and if the browser has
	 * supplied a textNode the closest DOMElement will be returned instead.
	 * @param {Event} e
	 * @return {Element}
	 */
	function getTarget( e ) {
		var t = e.target || e.srcElement;
		try {
			if ( t.nodeType == 3 ) {
				return t.parentNode;
			}
		} catch(e) { }
		return t;
   }

   /**
    * Determine if a given object is a window or not.
    * @private
    */
   function isWindow(obj) {
      return (!obj.nodeType && typeof obj != "string");
   };
   
   /**
    * Converts a given object into a unique identifier. Used as window, document and html objects cannot be used as
    * keys in hash maps.
    * @param obj The object to add to the map
    * @return The uniqueId for the supplied object
    * @private
    */
   function convertObjectToUniqueId(obj) {
      var i;
      
      // Loop over the map to see if this object has already been mapped to an id and return if it has
      for (i in idToObjectMap) {
         if (idToObjectMap[i] === obj) {
            return i;
         }
      }
      
      // Create a new id and store in the map
      i = objectMappingId++;
      idToObjectMap[i] = obj;
      
      return i;
   };
   
   /**
    * Removes the given object from the idToObjectMap hash map so the object can be garbage collected.
    * @param obj The object to remove from the map
    * @private
    */
   function removeObjectFromIdMap(obj) {
      var id = convertObjectToUniqueId(obj);
      
      idToObjectMap[id] = null;
      delete idToObjectMap[id];
   };
	
	/**
	 * Stop propagation of an event. An event object is not required as this can only be called when an event has
	 * occurred and it is in the process of being handled
	 */
	function stopPropagation() {
		if ( ! inEventLoop ) {
			IG.error("IG.util.Event.stopPropagation must not be called from a setTimeout as it will have no action");
			return;
		}
		
		bubble = false;
	};
	
	/**
	 * Prevents the browser from handling the default action of the event. If the event is not supplied,
	 * it is assumed that the event currently being handled is the event to prevent the default of.
	 * @param {Event} [event]
	 */
	function preventDefault(event) {
		YAHOO.util.Event.preventDefault( event || currentEvent );
	};
   
	/**
	 * Totally stops the event - both bubbling and default action are cancelled. If the event is not
	 * supplied, it is assumed that the event currently being handled is the event to prevent the default of.
	 * @param {Event} [event]
	 */
	function stopEvent(event) {
		stopPropagation();
		preventDefault( event );
	};
   
   /**
    * Creates a callback method and attach it to the given element
    * @param el The element to attach the (real) event listener to
    * @param eventType The type of event to listen for
    * @private
    */
   function addListenerAndCallback(el, eventType) {
      var id = convertObjectToUniqueId(el);
      
      var customEvent = eventMap[id][eventType];
      
      var callback;
      
      if (isWindow(el)) {
         // Create callback for a listener on a 'window'
         callback = function(event) {
            customEvent.fire("window", event);
            
            var error = customEvent.lastError("window");
            if (error) {
               IG.error(error);
               IG.util.Event.preventDefault(error);
            }
         };
      } else {
         callback = function(event) {
				
				// a reference to the current event for stopping/preventing above
				currentEvent = event;
				
				event.IG = {
					target: getTarget(event),
					stop: stopEvent
				}
				
            // Started handling this event so set some flags to true
            // inEventLoop tells us whether we are still handling an event and is used in the stopPropagation method
            // to ensure it is not called from a setTimeout.
            bubble = true;
            inEventLoop = true;
            
            var target = event.IG.target;
            
            outer_loop:
            while (bubble && target) {
               if (target.id) {
                  customEvent.fire(target.id, event, target);
                  
                  var error = customEvent.lastError(target.id);
                  if (error) {
                     IG.error(error);
                     IG.util.Event.preventDefault(error);
                     return;
                  }
                  
                  target = target.parentNode;
               } else {
                  while (target && !target.id) {
                     target = target.parentNode;
                     
                     if (target && target.tagName && target.tagName.toLowerCase() == "html") {
                        break outer_loop;
                     }
                  }
                  
                  if (!target) {
                     break;
                  }
               }
            }
				
				// remove IG addition to event
				event.IG.stop = null;
				event.IG.target = null;
				event.IG = null;
            
            // Finished handling this event so set the flag to false
            inEventLoop = false;
				
				// clear the reference to the current event
				currentEvent = null;
         };
      }
      
      // Add the (real) event listener
      // We pass through YAHOO.util.Event for unload events that are to be attached to a different window as otherwise
      // they will be attached (incorrectly) to the current window by the Yahoo code
      YAHOO.util.Event.addListener(el, eventType, callback, (eventType == "unload") && (el != window) ? YAHOO.util.Event : null);
      
      // Store the callback for later retrieval - necessary to remove the listener later without
      // removing external listeners (i.e. ones not added through IG.util.Event)
      callbackMap[id] = callbackMap[id] || {}
      callbackMap[id][eventType] = callback;
   };
   
   /**
    * Removes the (real) event listener for the given element
    * @param el The element to remove the (real) event listener from
    * @param eventType The type of event to remove
    * @private
    */
   function removeListenerAndCallback(el, eventType) {
      var id = convertObjectToUniqueId(el);
      
      if (callbackMap[id][eventType]) {
         // Remove the (real) listener and the callback
         YAHOO.util.Event.removeListener(el, eventType, callbackMap[id][eventType]);
         delete callbackMap[id][eventType];
      }
   };
   
   /**
    * Checks the eventMap to see if anything is currently subscribed and if not removes the event listener
    * @param el The element to remove the (real) event listener from
    * @param eventType The type of event to remove
    * @private
    */
   function cleanupEventMap(el, eventType) {
      var id = convertObjectToUniqueId(el);
      
      // Look at the NamedCustomEvent and see if it has an subscribers
      if (!eventMap[id][eventType].hasSubscribers()) {
         // Remove the NamedCustomEvent
         delete eventMap[id][eventType];
         
         // Remove the (real) listener and callback method
         removeListenerAndCallback(el, eventType);
         
         // Check to see if eventMap[id] has any event types (i.e. is it now empty)
         var hasProperties = false;
         for (property in eventMap[id]) {
            if (YAHOO.lang.hasOwnProperty(eventMap[id], property)) {
               hasProperties = true;
               break;
            }
         }
         
         // No event types are mapped so delete the entry
         if (!hasProperties) {
            delete eventMap[id];
            removeObjectFromIdMap(el);
         }
      }
   };
   var _nextId = 1000;
	
   return {
		
      stopPropagation: stopPropagation,
      preventDefault: preventDefault,
      stopEvent: stopEvent,
      getTarget: getTarget,

      /**
       * Add an event listener to the supplied object.
       * @param obj A window, an HTML element or an element id (string)
       * @param eventType The type of event to listen for
       * @param callback A callback to call when the event occurs
       * @param scope An object to set as the scope of the callback
       */
      addListener: function(obj, eventType, callback, scope) {
         var listenerElement = null;
         var elementId = null;
         
         // Ensure required parameters have been supplied
         if (!obj || !eventType || !callback) {
            IG.error("IG.util.Event.addListener: element, eventType and callback must all be supplied", arguments);
            return;
         }
         
         // Figure out the object to listen on
         if (isWindow(obj)) {
            // obj is a 'window' so use as is
            listenerElement = obj;
            elementId = "window";
         } else {
				
            // obj is an HTML element or a string representing the id of a DOM element, resolve and set the 'document'
            // of the element to listen on
            var element = YAHOO.util.Dom.get(obj);
				
				// this is an element (ie: not a window), so need to have an ID on it
				// otherwise the callback will never be fired
				if( ! element.id ) {
					element.id = 'ig_util_event_' + ( _nextId++ );
				}
				
            listenerElement = element.ownerDocument;
            elementId = element.id;
         }
         
         // Retrieve the unique id for the listenerElement
         var id = convertObjectToUniqueId(listenerElement);
         
         eventMap[id] = eventMap[id] || {};
         
         // Try and retrieve an existing NamedCustomEvent for this listener element and event type
         var customEvent = eventMap[id][eventType];
         
         // No existing NamedCustomEvent exists so create one and setup the (real) listener
         if (!customEvent) {
            // The call to NamedCustomEvent passes through a value of true so callbacks will have the parameters to
            // fire applied to them as opposed to being an array.
            customEvent = eventMap[id][eventType] = new IG.util.NamedCustomEvent(eventType, true);
            addListenerAndCallback(listenerElement, eventType);
         }
         
         // Subscribe the callback to the NamedCustomEvent
         customEvent.subscribe(elementId, callback, scope, true);
      },
      
      /**
       * Remove an event listener from the supplied object.
       * @param obj A window, an HTML element or an element id (string)
       * @param eventType The type of event to remove the listener from. If null will remove all listeners from the object.
       * @param callback The callback method to remove. If null will remove all callbacks on a given object and event type.
       * @param scope An object that was used as the scope of the callback
       */
      removeListener: function(obj, eventType, callback, scope) {
         var listenerElement = null;
         var elementId = null;
         
         // Ensure required parameters have been supplied
         if (!obj) {
            IG.error("IG.util.Event.removeListener: element must all be supplied");
            return;
         }
         
         // Figure out the object to listen on
         if (isWindow(obj)) {
            // obj is a 'window' so use as is
            listenerElement = obj;
            elementId = "window";
         } else {
            // obj is an HTML element or a string representing the id of a DOM element, resolve and set the 'document'
            // of the element to listen on
            var element = YAHOO.util.Dom.get(obj);
            listenerElement = element.ownerDocument;
            elementId = element.id;
         }
         
         // Retrieve the unique id for the listenerElement
         var id = convertObjectToUniqueId(listenerElement);
         
         // Loop over all event types
         for (var type in eventMap[id]) {
            // Remove if the event type supplied matches the current event type iterating over or none was supplied
            if (!eventType || eventType == type) {
               eventMap[id][type] && eventMap[id][type].unsubscribe(elementId, callback, scope);
               cleanupEventMap(listenerElement, type);
            }
         }
      },
      
      /**
       * Given an HTML document, remove all listeners attached to it
       * @param doc An HTML document object to remove all listeners from
       */
      purgeDocument: function(doc) {
         var id = convertObjectToUniqueId(doc);
         
         if (eventMap[id]) {
            for (var type in eventMap[id]) {
               // Unsubscribe all listeners to this event
               eventMap[id][type] && eventMap[id][type].unsubscribeAll && eventMap[id][type].unsubscribeAll();
               
               // Tidy up the map by removing this event type
               eventMap[id][type] = null;
               delete eventMap[id][type];
               
               // Remove the (real) listener and callback method
               removeListenerAndCallback(doc, type);
            }
            
            // Tidy up the map
            eventMap[id] = null;
            delete eventMap[id];
         }
         
         // Remove the mapping - code is not in if statement as convertObjectToUniqueId may create an entry unnecessarily
         removeObjectFromIdMap(doc);
      },
      
      /**
       * Given an HTML window, remove all listeners attached to it. This includes all those attached to the windows document.
       * @param win An HTML window object to remove all listeners from
       */
      purgeWindow: function(win) {
         IG.util.Event.purgeDocument(win.document);
         IG.util.Event.purgeDocument(win);
      },
      
      /**
       * Given an HTML element, removes all listeners attached to it and all child elements
       * @param element An HTML element or an element id (string)
       */
      purgeElement: function(element) {
         // Resolve the HTML element and figure out the listener element used
         element = YAHOO.util.Dom.get(element);
         listenerElement = element.ownerDocument;
         
         var id = convertObjectToUniqueId(listenerElement);
         
         // Iterate over all event types for this listener element
         for (var type in eventMap[id]) {
            // Unsubscribe the supplied element
            element.id && eventMap[id][type].unsubscribe(element.id);

            // Find all child elements with ids and unsubscribe those
            YAHOO.util.Dom.getElementsBy(function(el) {
               el.id && eventMap[id][type].unsubscribe(el.id);
            }, null, element);
            
            // Cleanup the map to ensure any empty NamedCustomEvents are removed along with the (real) listerners
            cleanupEventMap(listenerElement, type);
         }
      },
		
		
		
		
		
		
		
		
		
		
		
		/**
		 * @function
		 * Static function to pause execution of a function until the document has finished loading.
		 * The onReady event is fired after the mozilla/webkit browser raises DOMContentLoaded or IE
		 * has a document.readyState of either interactive or complete (it should be enough to just
		 * check for interactive, but in small applications or when they're running locally and are
		 * served very quickly, the interactive state may actually be reached before this function
		 * is reached by the JavaScript parser).
		 * 
		 * If the document has already loaded when a call is made to IG.util.Event.onReady the function
		 * will be executed immediately.
		 * 
		 * Supplying a scope for the execution of the function will set the scope of the <code>this</code
		 * keyword. Omitting the scope (or supplying <code>null</code> or <code>undefined</code> will
		 * use the default global scope.
		 * 
		 * @param {Function} fn The function to be executed once the app is loaded
		 * @param {Object} [scope=window] The scope of the function when it is being executed
		 * @param {Variant[]} [args] An array of arguments to send to the function as it is executed
		 */
		onReady: (function(){
			
			var callbacks = [];
			var fired = false;
			
			/**
			 * @private
			 * Handler for the <code>document.readyState</code> changing in Internet Explorer 
			 */
			function IEReadyStateChangeHandler() {
				if( ! fired && ( document.readyState == "interactive" || document.readyState == 'complete' ) ) {
					callAllCallbacks();
				}
			}
			
			/**
			 * @private
			 * Iterates through any cached callbacks and executes them in the specified scope
			 */
			function callAllCallbacks() {
				fired = true;
				while( callbacks.length ) {
					var callback = callbacks.shift();
					callback[0].apply( callback[1] || window, callback[2] || [] );
				}
				
				// of a handler was added, remove it now
				if( document.detachEvent ) {
					window.detachEvent( 'onload', callAllCallbacks );
				}
			}
			
			// if addEventListener is supported, we're in a moz/webkit browser
			if(document.addEventListener) {
				document.addEventListener( "DOMContentLoaded", callAllCallbacks, false );
			}
			
			// if not, it's IE, but don't bother setting the handler if already interactive
			else if( ( document.readyState == 'complete' ) ) {
				callAllCallbacks();
			
			/*
			 * different versions of IE bodge the onreadystatechange event handler, so while we
			 * should use that, it's safer to have an onload event too
			 */
			} else {
				document.onreadystatechange = IEReadyStateChangeHandler;
				window.attachEvent( 'onload', callAllCallbacks );
			}
			
			// return the function that externally becomes the IG.util.Event.onReady
			return function( fn, scope, args ) {
				if( ! fired ) {
					callbacks[callbacks.length] = Array.prototype.slice.call( arguments, 0 );
				}
				else {
					fn.apply( scope || window, args || [] );
				}
			}
			
		})()

   };
})();

/**
 * @class
 * @exports Browser as IG.util.Browser
 */
(function( IG, Event ) {
   
   /**
    * Static container for the class string that would be added to the document body or
    * any other element, this is set each time the _addBrowserAndPlatformClasses runs,
    * and will be returned if the addBrowserAndPlatformClasses method is called without
    * any suitable selection parameters.
    *  
    * @type {String}
    * @ignore
    */
   var _browserClass = '';
   
   /**
    * Encapsulates browser detection.
    * @name IG.util.Browser
    * @constructor
    */
   function Browser() {
      this.detect();
      this.addBrowserAndPlatformClasses();
   }
   
   /**
    * The current user agent string
    * @static
    * @type {String}
    */
   Browser.userAgent = '';
   
   /**
    * Detects the presence of any string in the browser userAgent
    * @static
    * @param {String} str The string to find
    * @return {Boolean} 
    */
   Browser.userAgentContains = function( str ) {
      return (Browser.userAgent.indexOf(str) > -1);
   };
   
   /**
    * Finds the version number in the userAgent string, given a regular expression
    * @static
    * @param {RegExp} re The regular expression to use
    * @return {Number} 
    */
   Browser.getVersion = function(re) {
      var matches = re.exec(Browser.userAgent);
      return ((matches && matches.length >= 2) ? +matches[1] : 0);
   };
   
   /**
    * Reruns the browser detection for a specified user agent string (will default to
    * the navigator useragent string if not supplied) and sets the internal properties
    * for the detected browser.
    * Note: this method does not apply the detected classes to the DOM in any way.
    * 
    * @param {String} [ua] A navigator user agent string
    */
   Browser.prototype.init = function( ua ) {
      this.detect(ua);
   };
   
   /**
    * Runs a detection for the browser and operating system types and sets internal
    * properties to describe the browser.
    * @param {String} [ua] A navigator user agent string
    */
   Browser.prototype.detect = function(ua) {
      var css3Browser;
      
      Browser.userAgent = ( ua || navigator.userAgent ).toLowerCase();
      
      // browser detection - unsupported browsers will return null (as they will not be listed here)
      this.chrome = Browser.userAgentContains("chrome") && Browser.getVersion(/Chrome\/([^.]*)\./i);
      this.firefox = Browser.userAgentContains("firefox") && Browser.getVersion(/Firefox\/([^.]*)\./i);
      this.ie = Browser.userAgentContains("msie") && Browser.getVersion(/MSIE ([^.]*)\./i);
      this.ie67 = ( this.ie && ( this.ie == 6 || this.ie == 7 ) );
      this.safari = Browser.userAgentContains("safari") && !Browser.userAgentContains("chrome") && (Browser.getVersion(/Version\/([^.]*)\./i) || 2);
      
      // platform detection - iphone is special case that stores the version
      this.iphone = (Browser.userAgentContains("iphone") || Browser.userAgentContains("ipod")) && (Browser.getVersion(/iPhone OS ([^_]*)./i) || 1);
      this.android = Browser.userAgentContains("android");
      this.linux = Browser.userAgentContains("linux") && !Browser.userAgentContains("android");
      this.mac = Browser.userAgentContains("mac") && !Browser.userAgentContains("iphone") && !Browser.userAgentContains("ipod");
      this.win = Browser.userAgentContains("win");
      
      // CSS3 support detection
      this.css3 = false;
      for( css3Browser in Browser.css3versions ) {
         if( this[ css3Browser ] && this[ css3Browser ] >= Browser.css3versions[ css3Browser ] ) {
            this.css3 = true;
            break;
         }
      }
   };
   
   /**
    * Adds various classes to the supplied element
    * @param {String} [el] The ID of an element to apply the classes to, if omitted is the document.body
    * @return {String} Returns the new className, or null if the document isn't loaded yet 
    */
   Browser.prototype.addBrowserAndPlatformClasses = function(el) {
      var callback, rtn = _browserClass || null;
      
      if( ! el ) {
         Event.onReady( this._addBrowserAndPlatformClasses, this, [] );
      }
      else if( el && ( el.nodeName || document.getElementById(el) ) ) {
         rtn = this._addBrowserAndPlatformClasses( el );
      }
      else {
         IG.warn( "Cannot add browser platform classes to supplied element" );
      }
      
      return rtn;
   };
   
   /**
    * The actual implementation of adding classes to the supplied element
    * @ignore
    * @param {String} [el] The ID of an element to apply the classes to, if omitted is the document.body
    * @return {String}
    */
   Browser.prototype._addBrowserAndPlatformClasses = function(el){
      var i, domEl, browserClass;
      
      if( el ) {
         domEl = ( el.nodeName ) ? el : document.getElementById(el);
      }
      else {
         domEl = document.body;
      }
      
      if( domEl ) {
         domEl = domEl.ownerDocument.body;
         
         browserClass = [domEl.className];
         
         // browser type
         for ( i = 0; i < Browser.browserTypes.length; i++ ) {
            if ( this[Browser.browserTypes[i]] && this[Browser.browserTypes[i]] > 0) {
               browserClass[ browserClass.length ] = Browser.browserTypes[i] + this[Browser.browserTypes[i]];
               break;
            }
         }
         
         // operating system
         for ( i = 0; i < Browser.operatingSystems.length; i++ ) {
            if ( this[Browser.operatingSystems[i]] ) {
               browserClass[ browserClass.length ] = Browser.operatingSystems[i];
               
               // special case for iphone is to include the version of IOS in the class name
               if( Browser.operatingSystems[i] == 'iphone' ) {
                  browserClass[ browserClass.length - 1 ] += this.iphone;
               }
               
               break;
            }
         }
         
         // IE special case - pseudo browser for IE6 and IE7
         if ( this.ie67 ) {
            browserClass[ browserClass.length ] = 'ie67';
         }
         
         // chrome updates so often there's little point in adding the version to the class name
         if( this.chrome ) {
            browserClass[ browserClass.length ] = 'browser-chrome';
         }
         
         // can we use CSS3 goodies
         if ( this.css3 ) {
            browserClass[ browserClass.length ] = 'css3';
         }
         
         browserClass = browserClass.join(' ').replace( /^\s*/, '' ).replace( /\s*$/, '' ).replace( /\s+$/, ' ' );
         domEl.className = browserClass;
         
         _browserClass = browserClass
         return browserClass;
      }
   };
   
   /**
    * List of browsers
    * @type {String[]}
    */
   Browser.browserTypes = ['chrome', 'firefox', 'ie', 'safari'];
   
   /**
    * List of operating systems
    * @type {String[]}
    */
   Browser.operatingSystems = ['android', 'iphone', 'linux', 'mac', 'win' ];
   
   /**
    * Minimum versions of browsers that (mostly) support CSS3 whether they need to have
    * manufacturer additions the CSS selector or not.
    */
   Browser.css3versions = {
      chrome: 4,
      firefox: 3,
      ie: 9,
      safari: 4
   };
   
   IG.util.Browser = new Browser();
   
})( IG, IG.util.Event );


/**
 * @namespace wrapper for SWFObject
 * 
 * SWFObject v2.1 <http://code.google.com/p/swfobject/>
 * Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
 * This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
 * 
 * Removed lots of unused methods that were causing errors and added a new method getFlashMovieObject
 * There is also a fix to 'ua'
 */
IG.ui.SWFObject = function() {
   
   var UNDEF = "undefined",
      OBJECT = "object",
      SHOCKWAVE_FLASH = "Shockwave Flash",
      SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
      FLASH_MIME_TYPE = "application/x-shockwave-flash",
      
      win = window,
      doc = document,
      nav = navigator,
      
      objIdArr = [],
      script;
   
   /* Centralized function for browser feature detection
      - Proprietary feature detection (conditional compiling) is used to detect Internet Explorer's features
      - User agent string detection is only used when no alternative is possible
      - Is executed directly for optimal performance
   */ 
   var ua = function() {
      var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
         playerVersion = [0,0,0],
         d = null;
      if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
         d = nav.plugins[SHOCKWAVE_FLASH].description;
         if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
            d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
            playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
            playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
            playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
         }
      }
      else if (typeof win.ActiveXObject != UNDEF) {
         var a = null, fp6Crash = false;
         try {
            a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
         }
         catch(e) {
            try { 
               a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".6");
               playerVersion = [6,0,21];
               a.AllowScriptAccess = "always";   // Introduced in fp6.0.47
            }
            catch(e) {
               if (playerVersion[0] == 6) {
                  fp6Crash = true;
               }
            }
            if (!fp6Crash) {
               try {
                  a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
               }
               catch(e) {}
            }
         }
         if (!fp6Crash && a) { // a will return null when ActiveX is disabled
            try {
               d = a.GetVariable("$version");   // Will crash fp6.0.21/23/29
               if (d) {
                  d = d.split(" ")[1].split(",");
                  playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
               }
            }
            catch(e) {}
         }
      }
      var u = nav.userAgent.toLowerCase(),
         p = nav.platform.toLowerCase(),
         webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false; // returns either the webkit version or false if not webkit
      
      return { w3cdom:w3cdom, pv:playerVersion, webkit:webkit, ie:IG.util.Browser.ie, win:IG.util.Browser.win, mac:IG.util.Browser.mac };
   }();

   /* Cross-browser dynamic SWF creation
   */
   function createSWF(attObj, parObj, id) {
      var r, el = getElementById(id);
      if (el) {
         if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
            attObj.id = id;
         }
         if (ua.ie && ua.win) { // IE, the object element and W3C DOM methods do not combine: fall back to outerHTML
            var att = "";
            for (var i in attObj) {
               if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries, like Object.prototype.toJSONString = function() {}
                  if (i.toLowerCase() == "data") {
                     parObj.movie = attObj[i];
                  }
                  else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
                     att += ' class="' + attObj[i] + '"';
                  }
                  else if (i.toLowerCase() != "classid") {
                     att += ' ' + i + '="' + attObj[i] + '"';
                  }
               }
            }
            var par = "";
            for (var j in parObj) {
               if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
                  par += '<param name="' + j + '" value="' + parObj[j] + '" />';
               }
            }
            el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
            objIdArr[objIdArr.length] = attObj.id; // Stored to fix object 'leaks' on unload (dynamic publishing only)
            r = getElementById(attObj.id);   
         }
         else if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements: fall back to the proprietary embed element
            var e = createElement("embed");
            e.setAttribute("type", FLASH_MIME_TYPE);
            for (var k in attObj) {
               if (attObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
                  if (k.toLowerCase() == "data") {
                     e.setAttribute("src", attObj[k]);
                  }
                  else if (k.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
                     e.setAttribute("class", attObj[k]);
                  }
                  else if (k.toLowerCase() != "classid") { // Filter out IE specific attribute
                     e.setAttribute(k, attObj[k]);
                  }
               }
            }
            for (var l in parObj) {
               if (parObj[l] != Object.prototype[l]) { // Filter out prototype additions from other potential libraries
                  if (l.toLowerCase() != "movie") { // Filter out IE specific param element
                     e.setAttribute(l, parObj[l]);
                  }
               }
            }
            el.parentNode.replaceChild(e, el);
            r = e;
         }
         else { // Well-behaving browsers
            var o = createElement(OBJECT);
            o.setAttribute("type", FLASH_MIME_TYPE);
            for (var m in attObj) {
               if (attObj[m] != Object.prototype[m]) { // Filter out prototype additions from other potential libraries
                  if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
                     o.setAttribute("class", attObj[m]);
                  }
                  else if (m.toLowerCase() != "classid") { // Filter out IE specific attribute
                     o.setAttribute(m, attObj[m]);
                  }
               }
            }
            for (var n in parObj) {
               if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // Filter out prototype additions from other potential libraries and IE specific param element
                  createObjParam(o, n, parObj[n]);
               }
            }
            el.parentNode.replaceChild(o, el);
            r = o;
         }
      }
      return r;
   }
   
   function createObjParam(el, pName, pValue) {
      var p = createElement("param");
      p.setAttribute("name", pName);   
      p.setAttribute("value", pValue);
      el.appendChild(p);
   }
   
   /* Functions to optimize JavaScript compression
   */
   function getElementById(id) {
      var el = null;
      try {
         el = doc.getElementById(id);
      }
      catch (e) {}
      return el;
   }
   
   function createElement(el) {
      return doc.createElement(el);
   }
   
   /* Flash Player and SWF content version matching
   */
   function hasPlayerVersion(rv) {
      var pv = ua.pv, v = rv.split(".");
      v[0] = parseInt(v[0], 10);
      v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
      v[2] = parseInt(v[2], 10) || 0;
      return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
   }
   
   /* Filter to avoid XSS attacks 
   */
   function urlEncodeIfNecessary(s) {
      var regex = /[\\\"<>\.;]/;
      var hasBadChars = regex.exec(s) != null;
      return hasBadChars ? encodeURIComponent(s) : s;
   }
   
   return {
      /**
       * Load a swf file into the dom, replacing a given node
       * @param {String} swfUrlStr The url of the flash movie to load
       * @param {String} replaceElemIdStr  Id of an element to replace
       * @param {String} widthStr The width of the movie
       * @param {String} heightStr The height of the movie
       * @param {String} swfVersionStr The minimum version of flash player required 
       * @param {Object} [flashvarsObj] Variables to pass through to flash movie
       * @param {Object} [parObj] Map to print out parameters as param tags
       * @param {Object} [attObj] Map to print out parameters as attributes of the object tag
       */
      embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, flashvarsObj, parObj, attObj) {
         if (!ua.w3cdom || !swfUrlStr || !replaceElemIdStr || !widthStr || !heightStr || !swfVersionStr) {
            return;
         }
         widthStr += ""; // Auto-convert to string
         heightStr += "";
         if (hasPlayerVersion(swfVersionStr)) {
            var att = {};
            if (attObj && typeof attObj === OBJECT) {
               for (var i in attObj) {
                  if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
                     att[i] = attObj[i];
                  }
               }
            }
            att.data = swfUrlStr;
            att.width = widthStr;
            att.height = heightStr;
            var par = {}; 
            if (parObj && typeof parObj === OBJECT) {
               for (var j in parObj) {
                  if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
                     par[j] = parObj[j];
                  }
               }
            }
            if (flashvarsObj && typeof flashvarsObj === OBJECT) {
               for (var k in flashvarsObj) {
                  if (flashvarsObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
                     if (typeof par.flashvars != UNDEF) {
                        par.flashvars += "&" + k + "=" + flashvarsObj[k];
                     }
                     else {
                        par.flashvars = k + "=" + flashvarsObj[k];
                     }
                  }
               }
            }
            
               createSWF(att, par, replaceElemIdStr);
         }
      },
      
      getFlashPlayerVersion: function() {
         return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
      },
      
      hasFlashPlayerVersion: hasPlayerVersion,
      
      createSWF: function(attObj, parObj, replaceElemIdStr) {
         if (ua.w3cdom) {
            return createSWF(attObj, parObj, replaceElemIdStr);
         }
         else {
            return undefined;
         }
      },
      
      /** IG added method to return the flash object on the page by the objects id */
      getFlashMovieObject: function(movieName)
      {
         if (window.document[movieName]) 
         {
            return window.document[movieName];
         }
         
         if (navigator.appName.indexOf("Microsoft Internet")==-1 && document.embeds && document.embeds[movieName])
         {
            return document.embeds[movieName]; 
         }
         
         // For opera and IE
         return document.getElementById(movieName);
      }
   };
}();



/**
 * Object Literal of constants for events used with rtcharts
 */
IG.rtcharts.Events = {
	SAVE_CHART_SETTINGS_COMPLETE_EVENT: "SAVE_CHART_SETTINGS_COMPLETE_EVENT"
}

/* EOF */
/**
 * @note SINGLETON
 * The charts object manages the creation and access of Realtime Chart (rtchart) swf's
 */
IG.rtcharts.Charts = (function () {
   var chartsById = [];

   // Keep track of added charts
   var chartCounter = 0;

   var saveChartSettingsCompleteEvent = typeof(YAHOO) !== "undefined" ? new YAHOO.util.CustomEvent(IG.rtcharts.Events.SAVE_CHART_SETTINGS_COMPLETE_EVENT, this) : null;

   /**
    * Internal method used by the chart swf to fire an event when the chart settings have been saved.
    * @param {Object} chartId The id of the chart
    * @param {Object} chartSettingId The id of the chart settings model
    */
   function _fireSaveChartSettingsCompleteEvent(chartId, chartSettingId) {
      if (saveChartSettingsCompleteEvent) {
         saveChartSettingsCompleteEvent.fire(chartId, chartSettingId);
      }
   }

   function _zeroPad(n, digits) {
      n = n.toString();
      while (n.length < digits) {
         n = '0' + n;
      }
      return n;
   }

   /**
    * Creates a new chart.
    *
    * @public
    * @param {String} chartUrl                                 Full URL to the Chart SWF
    * @param {ChartClientParameters} chartClientParameters     Parameters used when creating chart, 
    *                                                          this parameter will be passed to chart flash object.
    *                                                          See chartcleientsparameters.js for more info.    
    * @param {ChartDisplayParameters} chartDisplayParameters   Display parameters for the chart
    *                                                          See chartdisplayparameters.js for more info.    
    */
   function createChart(chartUrl,
                        chartClientParameters,
                        chartDisplayParameters) {

      var parObj = {
         wmode: "opaque",
         allowFullScreen: true,
         AllowScriptAccess: "always"
      };

      // create the flash chart object
      IG.ui.SWFObject.embedSWF(
            chartUrl,
            chartClientParameters.chartSwfObjectId,
            chartDisplayParameters.width,
            chartDisplayParameters.height,
            "10.0.0",
            chartClientParameters.toFlashVars(),
            parObj
            );

      chartsById[chartClientParameters.chartSwfObjectId] = IG.ui.SWFObject.getFlashMovieObject(chartClientParameters.chartSwfObjectId);
      chartCounter++;

      //return the unique identifier of the chart. This will be use to create the streamer connection.
      return chartClientParameters.chartSwfObjectId;
   }

   /**
    * Closes and destroys a chart
    *
    * @public
    * @param {String} id    id of the chart to destroy
    * @returns {void}
    */
   function removeChart(id) {
      //TODO: call destroy method on the chart swf and clean up
      chartCounter--;
   }

   /**
    * Saves the settings of the chart with the given id.
    * When the save operation is complete, #_fireSaveChartSettingsCompleteEvent will be called by the swf.
    *
    * @public
    * @param {String} id Id of the chart to save the settings of
    * @returns {void}
    */
   function saveChartSettings(id) {
      chartsById[id].saveChartSettings(id);
   }

   /**
    * Returns the requested chart swf object
    *
    * @public
    * @param {String} id    id of the chart to return
    * @returns {Object}
    */
   function getChart(id) {
      return chartsById[id];
   }

   /**
    * Returns the total number of charts created
    *
    * @public
    * @returns {Number}
    */
   function chartCount() {
      return chartCounter;
   }

   return /** @scope IG.rtcharts.Charts */ {
      createChart: createChart,
      removeChart: removeChart,
      getChart: getChart,
      chartCount: chartCount,
      saveChartSettings: saveChartSettings,
      _fireSaveChartSettingsCompleteEvent: _fireSaveChartSettingsCompleteEvent,
      saveChartSettingsCompleteEvent: saveChartSettingsCompleteEvent
   }
}());
/**
 * @class
 * @exports ChartClientParameters as IG.rtcharts.ChartClientParameters
 */
(function() {

   /**
    * This class encapsulates all parameters that will be sent to the charts flash objects.
    * If you need to add some parameter, you should add them to both the prototype and the toFlashVars() method.
    */
   function ChartClientParameters() {
   }

   /**
    * The environment in which the chart will be run; one of:
    *    "dev"|"test"|"uat"|"demo"|"prod"
    * @type {String}
    */
   ChartClientParameters.prototype.environment;

   /**
    * The identifier of the DOM <object/> tag which holds the SWF Flash file
    * @type {int}
    */
   ChartClientParameters.prototype.chartSwfObjectId = null;

   /**
    * The epic related to the instrumnet.ie: 'CS.D.GBPEUR.TODAY.IP', 'CS.D.GBPEUR.TODAY.IP'
    * @type {String}
    */
   ChartClientParameters.prototype.epic = null;

   /**
    * The static content domain url. All static content will be downloaded from this location. ie: swf objects, javascript
    * Ie: For test we are currently using: 'https://net.marketdatasystems.com'
    * @type {String}
    */
   ChartClientParameters.prototype.staticContentDomain = null;

   /**
    * The locale to use. ie: "en_GB", "en_US", ...
    * @type {String}
    */
   ChartClientParameters.prototype.locale = null;

   /**
    * The id of the site the user's account belongs to. ie: "IGI", "AUM", "ZAM", etc
    * @type {String}
    */
   ChartClientParameters.prototype.siteId = null;

   /**
    * The id of the settings specific for this chart as saved in layout service
    * @type {int}
    */
   ChartClientParameters.prototype.chartSettingId = null;

   /**
    * Security token to use on the SSO connection.
    * @type {String}
    */
   ChartClientParameters.prototype.securityToken = null;

   /**
    * The accountID of the logged user.
    * @type {String}
    */
   ChartClientParameters.prototype.accountId = null;

   /**
    * Optional. Defines if we instantiate the chart in debug mode.
    * @default false
    * @type {Boolean}
    */
   ChartClientParameters.prototype.isDebug = false;

   /**
    * Optional. The version of the data to use when requesting data points.
    * @type {int}
    */
   ChartClientParameters.prototype.version = null;

   /**
    * Optional.The factor by which to scale a price
    * @default 10000
    * @type {int}
    */
   ChartClientParameters.prototype.scalingFactor = 10000;

   /**
    * Optional. The number of decimal places that should be used for scaled prices
    * @default 5
    * @type {int}
    */
   ChartClientParameters.prototype.decimalPlacesFactor = 5;

   /**
    * Optional. The prefix to attach to any ExternalInterface calls.
    * Do not include the trailing dot.
    * @default ""
    * @type {String}
    * @example "window.opener"
    */
   ChartClientParameters.prototype.externalInterfacePrefix = "";

   /**
    * Returns an array containing all client parameters defined above
    * @public
    * @returns {Object}
    */
   ChartClientParameters.prototype.toFlashVars = function () {
      return {
         chartSwfObjectId: this.chartSwfObjectId,
         epic: this.epic,
         isDebug: this.isDebug,
         staticContentDomain: this.staticContentDomain,
         environment : this.environment,
         locale: this.locale,
         version: this.version,
         chartSettingId: this.chartSettingId,
         scalingFactor: this.scalingFactor,
         decimalPlacesFactor: this.decimalPlacesFactor,
         accountId: this.accountId,
         securityToken: this.securityToken,
         siteId: this.siteId,
         externalInterfacePrefix: this.externalInterfacePrefix
      };

   };

   IG.rtcharts.ChartClientParameters = ChartClientParameters;
}());

/**
 * @class
 * @exports ChartDisplayParameters as IG.rtcharts.ChartDisplayParameters
 */
(function() {
   /**
    * This class encapsulates all display parameters that will be used during chart flash object instantiation.
    * If you need to add some other parameter you'll need to add it to the prototype. 
    **/ 
   function ChartDisplayParameters() {}

   /**
    * The chart width to be used when instantiating the chart
    * @default "100%"
    * @type {String}
    */
   ChartDisplayParameters.prototype.width = "100%";
   
   /**
    * The chart height to be used when instantiating the chart
    * @default "100%"
    * @type {String}
    */
   ChartDisplayParameters.prototype.height = "100%";

   IG.rtcharts.ChartDisplayParameters = ChartDisplayParameters;
}());

/**
 * The streamer is a swf that connects to LightStreamer and allows streaming of data to multiple
 * client swf's (usally charts). This js object facilitates communication between them.
 */

IG.rtcharts.Streamer = (function () {
   //The internal connection ID
   var streamerConnectionId = "_mrStreamer__" + (new Date()).getTime();
   //a reference to the streamer in the browser DOM
   //it is assumed that the streamer is a singleton (i.e. never more than one)
   var streamerObject;
   /* default parameters used when creating chart swfs */
   var defaultOptions = {
      width: "600",
      height: "200",
      streamerVisible: false
   };

   var queuedClients = [];
   var queuedEpics = [];
   var processQueuesTimeout;

   function _zeroPad(n, digits) {
      n = n.toString();
      while (n.length < digits) {
         n = '0' + n;
      }
      return n;
   }

   function _timestamp() {
      var now = new Date();

      return _zeroPad(now.getHours(), 2) + ":" +
            _zeroPad(now.getMinutes(), 2) + ":" +
            _zeroPad(now.getSeconds(), 2) + "." +
            _zeroPad(now.getMilliseconds(), 3);
   }

   function _debugWithTimestamp(message) {
      /*arguments[0] = _timestamp() + " JS [DEBUG] [streamer.js] " + arguments[0];
      if (console && console.log) {
         console.log.apply(this, arguments);
      }*/
   }

   function _errorWithTimestamp(message) {
      /*arguments[0] = _timestamp() + " JS [ERROR] [streamer.js] " + arguments[0];
      if (console && console.log) {
         console.log.apply(this, arguments);
      }*/
   }

   function processedQueuedRequests() {
      if (streamerObject.addClient && streamerObject.attachEpic) {
         for (var i = queuedClients.length; i > 0; i--) {
            addClient.apply(this, queuedClients.pop());
         }

         for (i = queuedEpics.length; i > 0; i--) {
            attachEpic.apply(this, queuedEpics.pop());
         }

      } else {
         processQueuesTimeout = setTimeout(processedQueuedRequests, 1000);
      }
   }

   /**
    * Registers a flash movie with with the streamer
    *
    * @public
    * @param {String} clientId                The id of the connecting client swf
    * @param {String} connectionId            The LocalConnection id to use
    * @param {String} chartServiceTimezone    The timezone value the backend expects (e.g. "USA")
    * @returns {void}
    */
   function addClient(clientId, connectionId, chartServiceTimezone) {
      if (streamerObject.addClient) {
         streamerObject.addClient(clientId, connectionId, chartServiceTimezone);
      } else {
         queuedClients.push([clientId, connectionId, chartServiceTimezone]);

         if (!processQueuesTimeout) {
            processQueuesTimeout = setTimeout(processedQueuedRequests, 1000);
         }
      }
      _debugWithTimestamp("addClient(): complete");
   }

   /**
    * Registers a flash movie with with the streamer
    *
    * @public
    * @param {String} clientId The id of the connecting client swf
    * @returns {void}
    */
   function removeClient(clientId) {
      _debugWithTimestamp("removeClient(" + clientId + ")");
      streamerObject.removeClient(clientId);
      _debugWithTimestamp("removeClient(): complete");
   }

   /**
    * Notify a client to reconnect (called from the streamer to a connected flash movie)
    *
    * @public
    * @param {String} clientId The id of the connecting client swf
    * @returns {void}
    */
   function reconnectClient(clientId) {
      _debugWithTimestamp("reconnectClient(" + clientId + ")");
      var elementId = clientId.split('__')[0];
      try {
         var chartObj = document.getElementById(elementId);
         if (!chartObj) {
            _errorWithTimestamp("chart with element id " + elementId + "does not exist");
         }
         IG.ui.SWFObject.getFlashMovieObject(elementId).reconnect();
      }
      catch (error) {
         _errorWithTimestamp("reconnectClient(): IG.ui.SWFObject.getFlashMovieObject(" + elementId + ").reconnectToStreamer(): failure: " +
               error.name + ": " + error.message);
         throw error;
      }
      _debugWithTimestamp("reconnectClient(): complete");
   }

   /**
    * Request streamed data to a connected client
    *
    * @public
    * @param {String} clientId          The id of the connecting client swf
    * @param {String} epicName          The epic to be streamed
    * @param {String} scale             The epic scale e.g. TICK, SECOND
    * @param {Number} multiplier        The scale multipier i.e. 2second scale = 2
    * @returns {void}
    */
   function attachEpic(clientId, epicName, scale, multiplier) {
      _debugWithTimestamp("attachEpic(" + clientId + ", " + epicName + ", " + scale + ", " + multiplier + ")");
      if (streamerObject.attachEpic) {
         streamerObject.attachEpic(clientId, epicName, scale, multiplier);
      } else {
         queuedEpics.push([clientId, epicName, scale, multiplier]);

         if (!processQueuesTimeout) {
            processQueuesTimeout = setTimeout(processedQueuedRequests, 1000);
         }
      }
      _debugWithTimestamp("attachEpic(): complete");
   }

   /**
    * Request to stop streaming data to a connected client
    *
    * @public
    * @param {String} clientId         The id of the connecting client swf
    * @param {String} epicName         The epic to be streamed
    * @param {String} scale            The epic scale e.g. TICK, SECOND
    * @param {Number} multiplier       The scale multipier i.e. 2second scale = 2
    * @returns {void}
    */
   function detachEpic(clientId, epicName, scale, multiplier) {
      _debugWithTimestamp("detachEpic(" + clientId + ", " + epicName + ", " + scale + ", " + multiplier + ")");
      streamerObject.detachEpic(clientId, epicName, scale, multiplier);
      _debugWithTimestamp("detachEpic(): complete");
   }

   /**
    * Show an error message for LocalConnection fails
    * TODO: remove this when testing is complete
    * @public
    * @param {String} msg The message to show
    * @returns {void}
    */
   function showLocalConnectionError(msg) {
      var el = document.getElementById('msg');
      el.innerHTML += (msg + "<br>");
   }

   /**
    * @public
    * @param {String} streamerElementId The DOM element id of the streamer movie
    * @param {String} streamerSwfUrl    The url to the streamer swf
    * @param {String} environment       The environment the streamer is running in (test, uat, etc)
    * @param {String} options           Optional streamer configuation
    *    @param {String} options.streamerVisible    If true the Streamer swf will defaul to being visible
    *    @param {String} options.width              The Streamers width (when visible)
    *    @param {String} options.height             The Streamers width (when visible)
    * @returns {void}
    */
   function init(streamerElementId, streamerSwfUrl, environment, options) {
      //merge any supplied options with the defaults
      options = options || {};
      IG.util.Lang.applyTo(defaultOptions, options);

      create(streamerElementId, streamerSwfUrl, environment, defaultOptions.width, defaultOptions.height);
   }

   /**
    * Replaces the dom element 'streamerElementId' with the streamer flash movie
    *
    * @private
    * @param {String} streamerElementId The DOM element id of the streamer movie
    * @param {String} streamerSwfUrl The url to the streamer swf
    * @param {String} environment       The environment the streamer is running in (test, uat, etc)
    * @param {String} width             The Streamer's width (when visible)
    * @param {String} height            The Streamer's height (when visible)
    * @returns {void}
    */
   function create(streamerElementId, streamerSwfUrl, environment, width, height) {
      _debugWithTimestamp("create(" + streamerElementId + ", " + streamerSwfUrl + ", " + environment + ", " + width + ", " + height + ")");
      var flashVarsObj = {
         streamerId: streamerConnectionId,
         environment: environment
      };
      var parObj = {
         AllowScriptAccess: "always"
      };

      IG.ui.SWFObject.embedSWF(
            streamerSwfUrl,
            streamerElementId,
            width,
            height,
            "10.0.0",
            flashVarsObj,
            parObj
            );

      streamerObject = IG.ui.SWFObject.getFlashMovieObject(streamerElementId);

      if (!defaultOptions.streamerVisible) {
         hide();
      }

      _debugWithTimestamp("create(): complete");
   }

   /**
    *  Makes the Streamer swf visible
    *
    * @public
    * @returns {void}
    */
   function show() {
      //setting width to 0 in IE causes an error while setting style causes problems in FF
      //also, setting display style to none/block causes the swf to re-initisalise (see https://bugzilla.mozilla.org/show_bug.cgi?id=90268)
      //hence we need to detect browser
      if (IG.util.Browser.ie) {
         getSwf().style["width"] = defaultOptions.width;
         getSwf().style["height"] = defaultOptions.height;
      } else {
         getSwf().width = defaultOptions.width;
         getSwf().height = defaultOptions.height;
      }
   }

   /**
    *  Makes the Streamer swf invisible
    *
    * @public
    * @returns {void}
    */
   function hide() {
      //browser detection - see show() for explanation
      if (IG.util.Browser.ie) {
         getSwf().style["width"] = 0;
         getSwf().style["height"] = 0;
      } else {
         getSwf().width = 0;
         getSwf().height = 0;
      }
   }

   /**
    *  Alternates the streamer between a hiddle and visible state
    *
    * @public
    * @returns {void}
    */
   function toggle() {
      if (isVisible()) {
         hide();
      } else {
         show();
      }
   }

   /**
    *  Returns true if the streamer is visible to the user. Else returns false.
    *
    * @public
    * @returns {Boolean}
    */
   function isVisible() {
      //see comments in show() method body for notes on browser detection
      var width = 0;
      if (IG.util.Browser.ie) {
         width = parseInt(getSwf().style["width"], 10);
      } else {
         width = getSwf().width;
      }
      return !isNaN(width) && width > 0;
   }

   /**
    *  Returns a reference to the Streamer swf object
    *
    * @public
    * @returns {Object}
    */
   function getSwf() {
      return streamerObject;
   }

   return /** @scope IG.rtcharts.Streamer */ {
      init: init,
      addClient: addClient,
      removeClient: removeClient,
      reconnectClient: reconnectClient,
      attachEpic: attachEpic,
      detachEpic: detachEpic,
      showLocalConnectionError: showLocalConnectionError,
      show: show,
      hide: hide,
      isVisible: isVisible,
      toggle: toggle,
      getSwf: getSwf
   };
}());

/* EOF */

