
// ----- Start Debugging Tools

// Whether or not to log debug messages.
adUtilDebug = false;

/**
 * Logs a message to the browser's console.
 * Messages are logged if and only if:
 *  1) the global variable named "adUtilDebug" is set to true;
 *  2) the global variable named "console" is an object.
 * Otherwise, the message is ignore.
 *
 * @public
 *
 * @param {String} msg  The message to write to the browser's console.
 */
function adUtilLog(msg) {
  if (typeof(adUtilDebug) == 'undefined') return;
  if (adUtilDebug !== true) return;
  if (typeof(console) != 'object') return;
// if ((adUtilDebug !== true) || (typeof(console) != 'object')) return;

  var now = new Date();
  var h   = now.getHours();
  var m   = now.getMinutes();
  var s   = now.getSeconds();

  if (h < 10) h = '0' + h;
  if (m < 10) m = '0' + m;
  if (s < 10) s = '0' + s;

  msg = h + ":" + m + ":" + s + "    " + msg;

  console.log(msg);
}

// ----- End Debugging Tools


// ----- Start Brightcove Callbacks

// Keep track of whether or not Brightcove's bcsyncroadblock() callback has fired.
// Brightcove only calls bcsyncroadblock() if there are companion ads for the video.
// Thus, if there are no companion ads, we still need to insert ads.
//
// This variable MUST be global.
bcsyncroadblockCalled = false;


/**
 * Inserts video companion ads into a page, and then insert ads into the remaining
 * empty ad containers.
 *
 * The Brightcove player calls this function when it encounters companion ads in a
 * VAST 2.0 response.
 *
 * This function MUST remain global and public.
 *
 * @public
 *
 * @param {String} adXML   An XML document representing the video's companion ads.
 */
function bcsyncroadblock(adXML) {
  adUtilLog('bcsyncroadblock()');

  bcsyncroadblockCalled = true;

  var brightcoveTool = new BrightcoveTool();

  brightcoveTool.insertAdsFrom(adXML);
  adUtilLog('  Finished inserting companion ads.');

  if (typeof adUtility == 'object') {
    adUtilLog('  Inserting non-companion ads...');
    adUtility.insertAds();
  }
  else {
    adUtilLog('ERROR: The "adUtility" variable is not an object.');
  }
}   // end bcsyncroadblock()

// ----- End Brightcove Callbacks


// ----- Start Brightcove Tool

/**
* Construct a new BrightcoveTool object. This class parses and inserts Brightcove companion ads.
* It is mostly used by the bcsyncroadblock() Brightcove callback to insert companion ads.
* The BrightcoveTool object must be global so that bcsyncroadblock() can access it.
*
* @class The class for parsing and inserting Brightcove companion ads.
*
* @constructor
*
* @return A new Brightcove Tool object.
*
* @example
*   brightcoveTool = new BrightcoveTool();
*/
var BrightcoveTool = function() {

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private methods

  /**
   * Converts a string of XML into a JavaScript-accessible object.
   *
   * @public
   *
   * @param {String} pXML   A string of XML.
   *
   * @return A JavaScript object representing the XML document.
   */
  var getXMLDoc = function(pXML) {
    var adXML;

    if (window.ActiveXObject) { //parses the XML for IE browsers
      adXML       = new ActiveXObject("Microsoft.XMLDOM");
      adXML.async = false; 
      adXML.loadXML(pXML);
    }
    else //parses the XML for Mozilla browsers
      if (window.XMLHttpRequest) {
        adXML = (new DOMParser()).parseFromString(pXML, "text/xml");
    }

    return adXML;
  };

  /**
   * Ensures that the URL in the given string uses
   *   ads.networldmedia.net
   * rather than
   *   rotator.adjuggler.com
   *
   * @param {String}  adCall  The URL to the ad call.
   *
   * @return {String} The ad call, using ads.networldmedia.net .
   */
  var ensureAdCallUsesNetworldMedia = function(adCall) {
    if (typeof adCall != 'string') return addCall;

    var rotatorPattern    = /^http:\/\/rotator\.adjuggler\.com/;
    var networldMediaHost = 'http://ads.networldmedia.net';

    return adCall.replace(rotatorPattern, networldMediaHost);
  };

  /**
   * Chooses which type of companion ad should be shown.
   *
   * @param {object}  A <Companion> node from a VAST response.
   *
   * @return False if there's a problem with a parameter or an ad couldn't be chosen.
   *         Otherwise, an object containing which ad to show. Eg:
   *           { type: 'static', ad: '<StaticResource creativeType="...' }
   */
  var chooseAdToShow = function(companionAd) {
    if (typeof companionAd != 'object') return false;

    var width     = parseInt(companionAd.getAttribute('width'), 10);
    var height    = parseInt(companionAd.getAttribute('height'), 10);
    var staticAds = companionAd.getElementsByTagName('StaticResource');
    var iframeAds = companionAd.getElementsByTagName('IFrameResource');
    var htmlAds   = companionAd.getElementsByTagName('HTMLResource');
    var adToShow  = {};

    if (htmlAds && htmlAds.length && htmlAds.length >= 1) {
//    adUtilLog('chooseAdToShow() inserting HTML ad');
      adToShow.type = 'html';
      adToShow.ad   = htmlAds;
    }
    else if (staticAds && staticAds.length && staticAds.length >= 1) {
//    adUtilLog('chooseAdToShow() inserting static ad');
      adToShow.type = 'static';
      adToShow.ad   = staticAds;
    }
    else if (iframeAds && iframeAds.length && iframeAds.length >= 1) {
//    adUtilLog('chooseAdToShow() inserting iframe ad');
      adToShow.type = 'iframe';
      adToShow.ad   = iframeAds;
    }
    else {
      return false;
    }

    return adToShow;
  };

  /**
   * Inserts a static ad into the DOM.
   *
   * @public
   *
   * @param {Array}   staticAds       The array of static ads. Only the first will be inserted.
   * @param {Object}  companionAd     The companion ad.
   * @param {Object}  companionAdDiv  The companion ad's DIV DOM element.
   * @param {Object}  options         Additional options for inserting the ad.
   *
   * @return True or false, depending on whether or not inserting the ad was successful.
   */
  var insertStaticAd = function(staticAds, companionAd, companionAdDiv, options) {
    var staticSrc = getAdNodValue(staticAds, 'StaticResource');

    if (staticSrc == '') return false;

    var staticType = staticAds[0].getAttribute('creativeType');

    switch (staticType) {
      case 'image/jpeg':
      case 'image/gif':
      case 'image/png':

        var imageAd = staticAds[0].firstChild.nodeValue;

        // Get clickthrough informations for the image ad
        var imageAdClickURL = ensureAdCallUsesNetworldMedia(
          companionAd.getElementsByTagName('CompanionClickThrough')[0].firstChild.nodeValue
        );

        // Add the image ad to the page
        companionAdDiv.html("<a target='_blank' href='" + imageAdClickURL + "'><img src='" + imageAd + "' border='0' /></a>");
        adUtilLog('insertStaticAd() companionAdDiv.html() = ' + companionAdDiv.html());
        break;

      case 'application/x-shockwave-flash':
        var swfURL    = staticAds[0].firstChild.nodeValue;
        var swfDivId  = companionAdDiv.selector.replace(/^#/,'');

        if (companionAdDiv.html() == '') {
          companionAdDiv.html('<div id="swf_' + swfDivId + '"></div>');
          swfobject.embedSWF(swfURL, 'swf_' + swfDivId, options.width, options.height, '10.0.0');
        }
        else
          { adUtilLog('target div filled: divID: ' + swfDivId + ', content: ' + companionAdDiv.html()); }
        break;

      default:
        adUtilLog('[ERROR:] unSupportedCreativeType(' + staticType +')');
    }
  
    return true;
  };

  /**
   * INSERT DESCRIPTION HERE.
   *
   * @public
   *
   * @param {Object}  adObject  Companion advertisement, could be an IFrameResource/StaticResource/HTMLResource.
   * @param {Object}  type      INSERT DESCRIPTION HERE.
   *
   * @return {String} The node's value.
   */
  var getAdNodValue = function(adObject, type) {
    var nodeValue = '';

    if (adObject[0].nodeName != type) {
      if (adObject[0].previousElementSibling.nodeName == type)
        { nodeValue = adObject[0].previousElementSibling.firstChild.nodeValue; }
      else if (adObject[0].nextElementSibling.nodeName == type)
        { nodeValue = adObject[0].nextElementSibling.firstChild.nodeValue; }
    } 
    else
      { nodeValue = adObject[0].firstChild.nodeValue; }

    return nodeValue;
  }

  /**
   * Inserts an iframe ad into the DOM.
   *
   * @public
   *
   * @param {Array}   iframeAds       The array of iframe ads. Only the first will be inserted.
   * @param {Object}  companionAdDiv  The companion ad's DIV DOM element.
   * @param {Object}  options         Additional options for inserting the ad.
   *
   * @return True or false, depending on whether or not inserting the ad was successful.
   */
  var insertIframeAd = function(iframeAds, companionAdDiv, options) {
    adUtilLog('insertIframeAd()');

    var iframeSrc = getAdNodValue(iframeAds, 'IFrameResource');
    if (iframeSrc == '') return false;

    var iframeElementId = 'iframe_' + options.width + 'x' + options.height;
    var iframeElement   = document.getElementById(iframeElementId);
        
    if (iframeElement == null) {
      companionAdDiv.empty().writeCapture().html(
        '<iframe id="' + iframeElementId + '" marginwidth="0" marginheight="0" frameborder="0" scrolling="no">' + iframeSrc + '</iframe>'
      );

      iframeElement = document.getElementById(iframeElementId);    
    }
        
    iframeElement.src = ensureAdCallUsesNetworldMedia(iframeSrc);
    adUtilLog('insertIframeAd() iframeElement.src = ' + iframeElement.src);
        
    iframeElement.width   = options.width;
    iframeElement.height  = options.height;
    iframeSrc             = '';
        
    return true;
  };

  /**
   * Inserts an HTML ad into the DOM.
   *
   * @public
   *
   * @param {Array}   htmlAds         The array of HTML ads. Only the first will be inserted.
   * @param {Object}  companionAdDiv  The companion ad's DIV DOM element.
   *
   * @return True or false, depending on whether or not inserting the ad was successful.
   */
  var insertHTMLAd = function(htmlAds, companionAdDiv) {
    if (typeof companionAdDiv != 'object') return false;

    var htmlSrc = getAdNodValue(htmlAds, 'HTMLResource');
    if (htmlSrc == '') return false;

   companionAdDiv = jQuery(companionAdDiv);
   adUtilLog('insertHTMLAd() htmlSrc = ' + htmlSrc);

   companionAdDiv.writeCapture().html(htmlSrc);

   adUtilLog(htmlSrc);

   return true;
  };


  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //  

  this.insertAdsFrom = function(adXML) {
    adUtilLog('BrightcoveTool.insertAdsFrom()');
    var companionAd;
    var companionAds = getXMLDoc(adXML);

    if (typeof(companionAds) == 'undefined') {
      adUtilLog('ERROR: The JavaScript-formatted VAST response is invalid.');
      return;
    }

    var companionAdArray = companionAds.getElementsByTagName('Companion');

    if ((typeof(companionAdArray) == 'undefined') || !companionAdArray.length) {
      adUtilLog('  No companion ads were found. Returning.');
      return;
    }

    var width;
    var height;
    var insertOptions;
    var companionAdDiv;

    for (var i = 0; i < companionAdArray.length; i++) {
      companionAdDiv  = undefined;
      companionAd     = companionAdArray[i];
      width           = parseInt(companionAd.getAttribute('width'), 10);
      height          = parseInt(companionAd.getAttribute('height'), 10);
      insertOptions   = { width: width, height: height };

      var dimToDivMap = adUtility.getAJAdsThatAre(width, height);

      for (var j in dimToDivMap) {
        var potentialID   = dimToDivMap[j];
        var potentialDiv  = jQuery('#' + potentialID);
        
        // Skip this DIV if it wasn't found.
        if (potentialDiv.length != 1) continue;

        companionAdDiv = potentialDiv;
        break;
      }

      if (typeof(companionAd) == 'undefined') {
        adUtilLog("ERROR: companionAd is undefined. Can't insert ad.");
        continue;
      }

      if (typeof(companionAdDiv) == 'undefined') {
        adUtilLog("ERROR: companionAdDiv is undefined. Can't insert ad.");
        continue;
      }

      var adToShow = chooseAdToShow(companionAd);

      if (adToShow == false) {
        adUtilLog("ERROR: No ad could be chosen to be shown.");
        continue;
      }
//    adUtilLog('insertAdsFrom() adToShow = { type: ' + adToShow.type + ', ad: ' + adToShow.ad + ' }');
      
      if (adUtilDebug) {
        // TODO: commented out when finish debug, load specific type of ads:
        // adToShow.type = 'html';
      }
      
      if (adToShow.type == 'html')
        { insertHTMLAd(adToShow.ad, companionAdDiv); }
      else if (adToShow.type == 'static')
        { insertStaticAd(adToShow.ad, companionAd, companionAdDiv, insertOptions); }
      else if (adToShow.type == 'iframe')
         { insertIframeAd(adToShow.ad, companionAdDiv, insertOptions); }
      else {
        adUtilLog("ERROR: The chosen ad to insert has an unknown type.");
        continue;
      }
    } // End for
  }; // End insertAdsFrom()
}; // End BrightcoveTool


/**
 * Ensure that empty ad containers are filled with ads after Brightcove finishes
 * firing (or not firing) its callbacks.
 */
BrightcoveTool.fillInAdsAfterBrightcove = function(experienceID) {
  adUtilLog('BrightcoveTool.fillInAdsAfterBrightcove(' + experienceID + ')');

  var player = brightcove.getExperience(experienceID);
  if (typeof player != 'object') {
    adUtilLog('  ERROR: player is not an object');
    return;
  }

  var adModule = player.getModule(APIModules.ADVERTISING);
  if (typeof adModule != 'object') {
    adUtilLog('  ERROR: adModule is not an object');
    return;
  }

  adModule.addEventListener(BCAdvertisingEvent.AD_START, BrightcoveTool.fillInMissingAds);
};


/**
 * Fill the empty ad containers with ads.
 */
BrightcoveTool.fillInMissingAds = function() {
  adUtilLog('BrightcoveTool.fillInMissingAds()');

  if (typeof adUtility !== 'object') {
    adUtilLog('  ERROR: The "adUtility" variable is not an object.');
    return;
  }

  if ((typeof(bcsyncroadblockCalled) == 'boolean') && (bcsyncroadblockCalled == true)) {
    adUtilLog('  bcsyncroadblock has fired. Doing nothing.');
    return;
  }

  adUtilLog('  Inserting non-companion ads...');
  adUtility.insertAds();
};


// ----- End Brightcove Tools


// ----- Start RDM Ad Utility

/**
 * Generate an ORD to use for every ad.
 *
 * @public
 */
window.DART_pv_rnd  = Math.round((Math.random() + "") * 10000000000000000) + 1;
ordNum              = window.DART_pv_rnd;

/**
 * @constant
 * @public
 */
var DFP_ADSERVER_URL = "ad.doubleclick.net";
var AJ_SERVER_URL = 'http://ads.networldmedia.net/servlet/ajrotator/';
var AJ_TAG_VERSION = '1.0';
var AJ_ZONE_ID = 'networld';
var AJ_CHANNEL_FILTER = ';';


/**
 * Construct a new Advertisement object.
 * @class The Advertisement object allows for creation of DART For Publisher (DFP) supported advertisements.
 * This class should not generally be accessed on it's own and is only intended for use via usage of the {@link RDMAdUtility} class.
 * @constructor
 * @return A new Advertisement object.
 */
var Advertisement = function() {
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private methods

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private member variables

  /**
   * The ID of the Advertisement.
   * @type Number
   * @private
   */
  var id;

  /**
   * The Site of the Advertisement.
   * @type String
   * @private
   */
  var site;

  /**
   * The Zone of the Advertisement.
   * @type String
   * @private
   */
  var zone;

  /**
   * The size of the Advertisement.
   * @type SizeArray
   * @private
   */
  var size;

  /**
   * The tile number for the Advertisement.
   * @type Number
   * @private
   */
  var tile;

  /**
   * The KeyValuePair array for the Advertisement
   * @type KeyValuePairArray
   * @private
   */
  var keyValuePairs;

  /**
   * Boolean value that corresponds to whether or not this advertisement will support interstitials.
   * @type Boolean
   * @private
   */
  var allowInterstitials;

  /**
   * The DIV that will contain the advertisement.
   * @type object
   * @private
   */
  var divId;

  /**
   * Which ad service this ad belongs to.
   * Valid values are:
   *   DART
   *   AdJuggler
   * This setting is optional. If not provided, it will default to "DART".
   * @type String
   * @private
   */
  var adService;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private member constants

  /**
   * @constant
   * @private
   */
  var MAX_KEY_LENGTH= 5;

  /**
   * @constant
   * @private
   */
  var IFRAME_ID = "ad-iframe-id";

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public methods
  /**
   * Returns the advertisement's site.
   * @returns The advertisement's site.
   * @type String
   * @public
   */
  this.getSite = function() {
    return site;
  };

 /**
  * Sets a new site for this advertisement.
  * @param {String} new_site The new site to set.
  * @returns new_site if the set was completed, false otherwise. Setting will only fail if new_site is not a {String}.
  * @type Mixed
  * @public
  */
  this.setSite = function(new_site) {
    if (typeof new_site == "string") {
      site = new_site;
      return site;
    }
    else {
      return false;
    }
  };

  /**
   * Gets the zone for this advertisement.
   * @returns {String} The advertisement's zone.
   */
  this.getZone = function() {
    return zone;
  };

  /**
   * Sets a new zone for this advertisement.
   * @param {String} new_zone The new zone to set.
   * @returns {Mixed} new_zone if the set was completed, false otherwise.  Setting will only fail if new_zone is not a {String}.
   * @public
   */
  this.setZone = function(new_zone) {
    if (typeof new_zone != "string") return false;
    
    zone = new_zone.replace(/\/*$/, '');

    return zone;
  };

  /**
   * Gets the current tile number for this advertisement.
   * @returns {Number} The advertisement's tile number.
   * @public
   */
  this.getTile = function() {
    return tile;
  };

  /**
   * Sets the advertisement's tile number.
   * @param {Number} new_tile The new tile number to use for the advertisement.
   * @returns {Mixed} new_tile if the set operation was successful, false otherwise.  Setting will only fail if new_tile is not a {Number}.
   * @public
   */
  this.setTile = function(new_tile) {
    if (typeof new_tile == "number") {
      tile = new_tile;
      return tile;
    }
    else {
      return false;
    }
  };

  /**
   * Gets the current size of the advertisement.
   * @returns {SizeArray} The size of the advertisement.
   * @public
   */
  this.getSize = function() {
    return size;
  };

  /**
   * Sets a new size for the advertisement.
   * @param {SizeArray} new_size  The new size for the advertisement.
   * @returns new_size If the setting operation was successful, false otherwise.  Setting will fail if the input is not a valid
   * {SizeArray}.
   * @example
   * // An ad that only supports 300x250
   * var sizeArray1 = { width: [300], height: [250] };
   * // An ad that supports 300x250 and 301x250
   * var sizeArray2 = { width: [300,301], height: [250,250] };
   * @public
   */
  this.setSize = function(new_size) {
    if (typeof new_size == "object") {
      if (typeof new_size.width == "object" && typeof new_size.height == "object" &&
        typeof new_size.width.length == "number" && typeof new_size.height.length == "number") {
        if (new_size.width.length == new_size.height.length) {
          var allNums = true;
          for (var i = 0; (i < new_size.width.length && allNums); i++) {
            if (typeof new_size.width[i] != "number" || typeof new_size.height[i] != "number") {
              allNums = false;
            }
          }
          if (allNums) {
            size.width  = new_size.width;
            size.height = new_size.height;
            return size;
          }
          else {
            return false;
          }
        }
        else {
          return false;
        }
      }
      else {
        return false;
      }
    }
    else {
      return false;
    }
  };

  /**
   * Returns the advertisement's key-value pairs.
   * @returns {KeyValuePairArray} The advertisement's key-value pairs.
   */
  this.getKeyValuePairs = function() {
    return keyValuePairs;
  };

  /**
   * Sets a new key value pair array. Key-values greater than MAX_KEY_LENGTH will be ignored.
   * @param {KeyValuePairArray} new_keyvalues
   * @returns new_keyvalues if setting was successful, false otherwise.  Setting will fail only if new_keyvalues is not a valid
   * KeyValuePairArray.
   * @example
   * var newKeyValues = [["key1","value"],["key2","value"], ["key3",1]];
   * @public
   */
  this.setKeyvaluePairs = function(new_keyvalues) {
    if (typeof new_keyvalues == "object") {
      if (new_keyvalues.length >= 1) {
        for (var i = 0; i < new_keyvalues.length; i++) {
          if (new_keyvalues[i].length == 2) {
            if ((typeof new_keyvalues[i][0] == "string" || typeof new_keyvalues[i][0] == "number")
            && (typeof new_keyvalues[i][1] == "string") || typeof new_keyvalues[i][1]) {
              if (new_keyvalues[i][0].length <= MAX_KEY_LENGTH) {
                keyValuePairs.push(new_keyvalues[i]);
              }
            }
            else {
              return false;
            }
          }
          else {
            return false;
          }
        }
      }
      else {
        return false;
      }
    }
    else {
      return false;
    }
  };

  /**
   * Gets whether this advertisement will support interstitials in its call to DFP.
   * @returns {Boolean} A boolean value, true indicates interstitials are supported false indicates they are not.
   * @public
   */
  this.getAllowInterstitial = function() {
    return allowInterstitials;
  };

  /**
   * Sets a value for whether this advertisement will support interstitials in its call to DFP.
   * @param {Boolean} new_val The new value for support of interstitials.
   * @returns {Mixed} new_val if setting is successful, false null otherwise.
   */
  this.setAllowInterstitial = function(new_val) {
    if (typeof new_val == "boolean") {
      allowInterstitials = new_val;
      return allowInterstitials;
    }
    else {
      return null;
    }
  };

  /**
   * Gets the ID of the advertisement.
   * @returns {Number} The ID of the advertisement.
   * @public
   */
  this.getId = function() {
    return id;
  };

  /**
   * Sets a new ID for the advertisement.
   * @param {Number} new_id The new ID for the advertisement.
   * @returns {Number} new_id if the set was successful, false otherwise.  Set will fail if new_id is not a {Number}.
   * @public
   */
  this.setId = function(new_id) {
    if (typeof new_id == "number") {
      id = new_id;
      return new_id;
    }
    else {
      return false;
    }
  };

  /**
   * Gets the ID of the DIV where this advertisement will be placed.
   * @returns {String} The ID of the DIV where the string will be placed.
   * @public
   */
  this.getContainingDivId = function() {
    return divId;
  };

  /**
   * Sets a new containing DIV for the advertisment.  On any subsequent calls to {@link Advertisement#write} or
   * {@link Advertisement#refresh}, ads will be written to the new DIV.
   * @param {String} new_id The new ID of the DIV element to use.
   * @returns new_id if the set was successful, false otherwise.  Setting will fail only if new_id is not a {String}.
   * @public
   */
  this.setContainingDivId = function(new_id) {
    if (typeof new_id == "string") {
      divId = new_id;
      return divId;
    }
    else {
      return false;
    }
  };

  /**
   * Returns a reference to the containing DIV where the advertisement will be written.
   * @returns {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-22445964"
   * HTMLDivElement</a>} The DIV element where the advertisement will be written.
   * @public
   */
  this.getContainingDiv = function() {
    return document.getElementById(divId);
  };

  /**
   * Returns a string representing the ad service that the ad belongs to.
   * @returns {String} The adService that the ad belongs to.
   * @public
   */
  this.getAdService = function() {
    return adService;
  };

  /**
   * Returns a string representing the advertisement and its ID.
   * @returns The advertisement's ID.
   * @type String
   * @public
   */
  this.toString = function() {
    return "Advertisement - " + id;
  };

  /**
   * Compares the current advertisement to another ad and returns a boolean value indicating whether the two ads are equal.
   * Two advertisements are equal if and only if their site, zone and sizes are identical.
   * @param {Advertisement} otherAd The otherAd to compare to.
   * @returns {Boolean} True if the two ads are identical, false otherwise.
   * @public
   */
  this.equals = function(otherAd) {
    // First ensure we're dealing with an ad
    if (typeof otherAd.isAdvertisement == "undefined") {
      return null;
    }

    // Compare everything except the Id and allow
    if (site != otherAd.getSite()) {
      return false;
    }
    if (zone != otherAd.getZone()) {
      return false;
    }
    /*
    if (tile != otherAd.getTile()) {
      return false;
    }
    if (allowInterstitials != otherAd.getAllowInterstitial()) {
      return false;
    }
    */

    // Check sizes
    var same = true;
    var otherElement = otherAd.getSize();
    if (size.width.length == otherElement.width.length) {
      for (var i = 0; (i < otherElement.width.length && same == true); i++) {
        same = (size.width[i] == otherElement.width[i]) && (size.height[i] == otherElement.height[i]);
      }
    }
    else {
      same = false;
    }

    if (!same) { return false; }

    // Check keywords
    /*
    otherElement = otherAd.getKeyValuePairs();
    if (keyValuePairs.length == otherElement.length) {
        for (var i = 0; (i < keyValuePairs.length && same == true); i++) {
          same = (keyValuePairs[i][0] == otherElement[i][0]) && (keyValuePairs[i][1] == otherElement[i][1]);
        }
    } else {
      same = false;
    }
    if (!same) {return false; }
    */

    return true;
  };

  /**
   * Writes the current advertisement's data to the console (Firebug) if it exists.
   * @public
   */
  this.writeToConsole = function() {
    if (typeof window.console == "object") {
      adUtilLog("-------------------- Advertisement --------------------");
      adUtilLog("Id: " + id);
      adUtilLog("Site: " + site);
      adUtilLog("Zone: " + zone);
      adUtilLog("Size: " + this.getSizeAsString());
      adUtilLog("Tile: " + tile);
      adUtilLog("Keywords: " + this.getKeyValuePairsAsString());
      adUtilLog("Allow Interstitials?: " + allowInterstitials);
      adUtilLog("-------------------------------------------------------");
    }
  };

  /**
   * Gets the size of this advertisement as a string.
   * @returns {String} A string representation of the size of this string in the form: "w1xh1,w2xh2,w3xh3,...,wnxhn"
   * @public
   */
  this.getSizeAsString = function() {
    var str = "";
    var size = this.getSize();
    for (var i = 0; i < size.width.length; i++) {
      str += size.width[i] + "x" + size.height[i] + ",";
    }
    return str.substring(0, str.length-1);
  };

  /**
   * Gets the key-value pairs of this advertisement as a string.
   * @returns {String} A string representation of the key-value pairs of this string in the form: "key1=value;key2=value;key3=value"
   * @public
   */
  this.getKeyValuePairsAsString = function() {
    var str = "";
    var keyValuePairs = this.getKeyValuePairs();
    for (var i = 0; i < keyValuePairs.length; i++) {
      str += keyValuePairs[i][0] + "=" + encodeURIComponent(keyValuePairs[i][1])+";";
    }
    return str.substring(0,str.length-1);
  };


  /**
   * Builds two versions of the DFP ad call.
   *
   * The short version includes the size, zone, and other parameters.
   * The full version is a copy of the short version, prefixed with the protocol, ad server URL, and "/adj/".
   *
   * @public
   */
  this.buildDFPCall = function() {
    var protocol      = window.location.protocol == "https:" ? "https:" : "http:";
    var interstitial  = tile > 1 ? "" : allowInterstitials ? "dcopt=ist;" : "";
    var ord           = ordNum; //Math.random() * 1000000000000000000;
    var shortAdCall   = site + zone + ";" + "dcove=d;comp=;" + interstitial + "tile=" + tile + ";sz=" + this.getSizeAsString() + ";" + this.getKeyValuePairsAsString() + ";adutil=v2;ord=" + ord + "?";
    var fullAdCall    = protocol + '//' + DFP_ADSERVER_URL + '/adj/' + shortAdCall;

    return {
      fullCall:   fullAdCall,
      shortCall:  shortAdCall
    };
  };


  /**
   * Writes an advertisement to the DIV identified as the containing DIV in the constructor or by {@link Advertisement#setContainingDivId}.
   * This function will create an invisible iFrame for every individual advertisement and write the contents of the advertisement to the ad.
   * Once the iFrame's content has loaded (i.e. the advertisement exists), an event is triggered which will copy the DOM structure of the
   * iFrame to the appropriate DIV referenced by this Advertisement.
   * @public
   */
  this.write = function() {
    var adCall    = this.buildDFPCall();
    var protocol  = window.location.protocol == "https:" ? "https:" : "http:";

    document.write('<scr' + 'ipt type="text/javas' + 'cript" src="'+ adCall.fullCall +'"></scr' + 'ipt>');

    if ((!document.images && navigator.userAgent.indexOf('Mozilla/2.') >= 0) || navigator.userAgent.indexOf('WebTV') >= 0) {
      document.write('<a href="' + protocol + '//' + DFP_ADSERVER_URL + '/jump/' + adCall.shortCall + '" target="_blank">');
      document.write('<img src="' + protocol + '//' + DFP_ADSERVER_URL + '/ad/' +  adCall.shortCall + '" border="0"></a>');
    }
  };

  /**
   * Refreshes an advertisement currently on the page, if no advertisement exists it will be written for the first time.
   * @see Advertisement#write
   * @public
   */
  this.refresh = function() {
    var obj = this.getContainingDiv();

    if ( obj.hasChildNodes() ) {
      while ( obj.childNodes.length >= 1 ) {
        obj.removeChild( obj.firstChild );
      }
    }

    this.write();
  };

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public member variables

  this.isAdvertisement = true;

  // Constructor
  var a = arguments[0];

  if (typeof a == "undefined") {
    return null;
  }

  // Set the id (REQUIRED)
  if (typeof a.id == "number") {
    id = a.id;
  } else {
    return null;
  }

  // Set the containing DIV element's Id
  if (typeof a.divId == "string") {
    divId = a.divId;
  } else {
    return null;
  }

  // Set the site (REQUIRED)
  if (typeof a.site == "string") {
    site = a.site;
  } else {
    return null;
  }

  // Tile (NOT REQUIRED)
  if (typeof a.tile == "number") {
    tile = a.tile;
  }

  // Set the zone (NOT REQUIRED)
  typeof(a.zone) == 'string' ? this.setZone(a.zone) : this.setZone('');

  // Set the size (REQUIRED)
  var size = {};
  if (typeof a.size == "object") {
    if (typeof a.size.width == "object" && typeof a.size.height == "object" &&
      typeof a.size.width.length == "number" && typeof a.size.height.length == "number") {
      if (a.size.width.length == a.size.height.length) {
        size.width  = a.size.width;
        size.height = a.size.height;
      }
      else {
        return null;
      }
    }
    else {
      return null;
    }
  }
  else {
    return null;
  }

  // Set the keyValuePairs (NOT REQUIRED)
  keyValuePairs = new Array();
  if (typeof a.keyValuePairs == "object") {
    if (a.keyValuePairs.length >= 1) {
      for (var i = 0; i < a.keyValuePairs.length; i++) {
        if (a.keyValuePairs[i].length == 2) {
          if ((typeof a.keyValuePairs[i][0] == "string" || typeof a.keyValuePairs[i][0] == "number")
          && (typeof a.keyValuePairs[i][1] == "string") || typeof a.keyValuePairs[i][1]) {
            if (a.keyValuePairs[i][0].length <= MAX_KEY_LENGTH) {
              keyValuePairs.push(a.keyValuePairs[i]);
            }
          }
        }
      }
    }
  }

  allowInterstitials = typeof a.allowInterstitials == "boolean" ? a.allowInterstitials : false;

  // Set the adService.
  if (a.adService == undefined)
    {adService = 'DART';}
  else if (a.adService == 'AdJuggler')
    {adService = 'AdJuggler';}
  else
    {return null;}
};

/**
 * Construct a new AdvertisementList object.
 * @class The AdvertisementList object is intended to contain a list of Advertisements (see {@link Advertisement}).  It contains
 * certain utility functions which make it easier for usage of advertisements.
 * @constructor
 * @return A new AdvertisementList object.
 */
var AdvertisementList = function() {
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private methods

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private member variables
  var ads,
    uniqueAds;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public methods

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public member variables

  /**
   * Ads a new {@link Advertisement} to the list.
   * @param {Advertisement} ad  The Advertisement to add to the list.
   * @public
   */
  this.add = function(ad) {
    if (typeof ad.isAdvertisement == "undefined") {
      return false;
    }

    ads.push(ad);

    var tempAd = ad;

    var found = false;
    for (var j = 0; (j < uniqueAds.length && found == false); j++) {
      if (tempAd.equals(uniqueAds[j])) {
        uniqueAds[j].count++;
        found = true;
      }
    }
    if (!found) {
      tempAd.count = 1;
      uniqueAds.push(tempAd);
    }
  }

  /**
   * Removes all elements from the object's list of advertisements.
   * @public
   */
  this.removeAll = function() {
    ads = new Array();
  }

  /**
   * Returns the current number of advertisements in the list.
   * @returns {Number} the number of advertisements in the list.
   * @public
   */
  this.count = function() {
    return ads.length;
  }

  /**
   * Returns an array of unique advertisements in the current list and a count of each of these advertisements.
   * @returns {Array} Unique advertisements in the current list and a count of each of these advertisements.
   * @see Advertisement#equals
   * @public
   */
  this.getUniqueAds = function() {
    return uniqueAds;
  }

  /**
   * Checks to see whether the current list contains an instance of an advertisement.
   * @param {Advertisement} ad  The advertisement to look for within the list.
   * @returns {Mixed} Null if an improper advertisement was sent, true if the advertisement exists in the list or false otherwise.
   * @public
   */
  this.contains = function(ad) {
    if (typeof ad.isAdvertisement == "undefined") {
      return null;
    }

    var found = false;
    for (var i = 0; (i < uniqueAds.length && found == false); i++) {
      if (ad.equals(uniqueAds[i])) {
        found = true;
      }
    }
    return found;
  }

  /**
   * Returns the number of a specific advertisement within a list.
   * @param {Advertisement} ad  The ad to be looked for within the list.
   * @returns {Mixed} Null if an improper advertisement was sent, the number of ads of this type in the list otherwise.
   * @see Advertisement#equals
   * @public
   */
  this.numberOf = function(ad) {
    if (typeof ad.isAdvertisement == "undefined") {
      return null;
    }

    for (var i = 0; i < uniqueAds.length; i++) {
      if (uniqueAds[i].equals(ad)) {
        return uniqueAds[i].count;
      }
    }
    return 0;
  }

  /**
   * Returns some high-level information about the current list.  Namely, the length of the non-unique and unique
   * variants of the list.
   * @returns {String} A string showing the length of the non-unique and unique variants of the list.
   * @public
   */
  this.toString = function() {
    return "AdvertisementList (" + ads.length + " ad(s), " + uniqueAds.length + " unique ad(s))";
  }

  /**
   * Calls each advertisement's {@link Advertisement#writeToConsole} method.
   * @see Advertisement#writeToConsole
   * @public
   */
  this.writeToConsole = function() {
    if (typeof window.console == "undefined") return;

    for (var i = 0; i < ads.length; i++)
      { ads[i].writeToConsole(); }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Constructor

  ads = new Array();
  uniqueAds = new Array();
};


/**
 * Construct a new RDMAdUtility object.
 * @class This is the Rogers Digital Media Ad Utility ({@link RDMAdUtility}) class.  This class is meant to be the main interface
 * for any Rogers Digital Media developer wishing to insert standard advertisements (as defined by the Rogers Digital Media
 * Ad Standards documentation)
 * @constructor
 * @return A new Rogers Digital Media Advertising Utility object.
 * @example
 * var adUtility = new RDMAdUtility({
 *   site : "siteName",
 *   zone : "zoneName",
 *   sponsorshipId : "sponsorshipId",
 *   allowInterstitials : true,
 *   keyValuePairs : [["key1","value"],["key2","value"],["key3","value"]],
 *   isOffsite : false,
 *   zoneSuffix : "zoneSuffix",
 *   ignorePathLevel : 2
 * });
 */
var RDMAdUtility = function() {
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private methods

  /**
   * Returns the hostname to act upon. In some instances, what's returned differs from the actual hostname.
   * This is desirable when dealing with a non-production site. When a hostname begins with one of the
   * strings defined in DEV_ENVIRONMENTS, that development environment string will be removed.
   * This causes the script to behave as though it were running on the production site.
   * @returns {String} The hostname.
   * @private
   */
  var getHostname = function() {
    // If replacing the first portion of the hostname, replace it with this.
    var replacement_prefix  = 'www';

    // This stores which solution should be used to massage the hostname.
    var solution            = 'replace';

    // The hostname to massage.
    var hostname            = window.location.hostname;

    // Cut up the hostname.
    var parts               = hostname.split('.');

    // Don't do anything if there's no hostname.
    // This mostly occurs when the script is run outside of a web server.
    if (hostname == '')
      {return hostname;}

    // If the hostname has >3 parts, remove the first part, rather than replace it.
    if (parts.length > 3)
      {solution = 'remove';}

    for (var i = 0; i < DEV_ENVIRONMENTS.length; i++) {
      if (parts[0] == DEV_ENVIRONMENTS[i]) {
        // Remove the dev environment portion of the hostname.
        parts.shift();

        // If we're replacing rather than removing, add the replacement prefix.
        if (solution == 'replace')
          {parts.unshift(replacement_prefix);}

        break;
      }
    }

    hostname = parts.join('.');

    return hostname;
  };

  /**
   * Builds an ad zone based on the URL of the current page (window.location).  The rules for the actual creation
   * of the zone based on the URL are fairly complex therefore users are encouraged to view the reqiurement
   * documentation surrounding this function.
   * @return The ad zone represented as a lower-case string.
   * @private
   */
  var getZoneFromURL = function() {
    if (isTOPS) return getZoneFromURLForTOPS();

    var z = new Array();
    var d = getHostname();
    var p = window.location.pathname;

    // Get the subdomain or domain depending on isOffsite
    var a = d.split(".");
    if (this.isOffsite == true) {
      z.push(a[a.length-2]);
    } else {
      var subdomain = "";
      if (a.length <= 2) {
        subdomain = "";
      } else {
        a = a.slice(0, a.length-2);
        subdomain = a.join(".");
      }
      z.push(subdomain);
    }

    // Build the paths
    var k = 0;
    var a = p.split("/");
    for (var i = 0; i < a.length-1 && k <= 2; i++) {
      if (a[i] == '') continue;

      z.push(a[k] = a[i]);
      k++;
    }

    // Exclude path level if needed
    if (typeof ignorePathLevel == "number") {
      z.splice(ignorePathLevel,1);
    }
    // Add suffix if needed
    if (zoneSuffix != "") {
      z.push(zoneSuffix);
    }

    // Add "index" if required
    for (var i = 0; i < DEFAULT_INDEX_PAGES.length; i++) {
      if (p.indexOf(DEFAULT_INDEX_PAGES[i]) != -1) {
        z.push("index");
        break;
      }
    }

    // Build the final zone string.
    var zone = '/' + z.join('/').toLowerCase();

    // Collapse consecutive slashes.
    zone = zone.replace(/\/+/g, '/');

    // TODO: Add index coding for the case "http://en.chatelaine.com/" - this should be an index page
    return zone;
  };

	/**
	* Builds an ad zone based on the URL of the current page (window.location).  The rules for the actual creation
	* of the zone based on the URL are fairly complex therefore users are encouraged to view the reqiurement
	* documentation surrounding this function.
	* @return The ad zone represented as a lower-case string.
	* @private
	*/
  var getZoneFromURLForTOPS = function() {
    var z = new Array();
    var d = window.location.host;
    var p = window.location.pathname;

    // Get the subdomain or domain depending on isOffsite
    var a = d.split(".");
    if (this.isOffsite == true) {
      z.push(a[a.length-2]);
    } else {
      var subdomain = "";
      if (a.length <= 2) {
        subdomain = "www";
      } else {
        a = a.slice(0, a.length-2);
        subdomain = a.join(".");
      }
      z.push(subdomain);
    }

    // Build the paths
    var k = 0;
    var a = p.split("/");
    for (var i = 0; i < a.length && k <= 2; i++) {
      if (a[i] == '') continue;

      if (a[i].indexOf(".") == -1)
        { z.push(a[k] = a[i]); }

      k++;
    }

    // Exclude path level if needed
    if (typeof ignorePathLevel == "number") {
      z.splice(ignorePathLevel,1);
    }
    // Add suffix if needed
    if (zoneSuffix != "") {
      z.push(zoneSuffix);
    }

    // Add "index" if required
    for (var i = 0; i < DEFAULT_INDEX_PAGES.length; i++) {
      if (p.indexOf(DEFAULT_INDEX_PAGES[i]) != -1) {
        z.push("index");
        break;
      }
    }

    // Build the final zone string.
    var zone = '/' + z.join('/').toLowerCase();

    // Collapse consecutive slashes.
    zone = zone.replace(/\/+/g, '/');

    // TODO: Add index coding for the case "http://en.chatelaine.com/" - this should be an index page
    return zone;
  };



  /**
   * Calculates the tile number for an ad.
   * @params {Object} ad  The Advertisement object whose tile number is to be calculated.
   * @params {Object} options  An object containing the "options" that were used to build the advertisement.
   * @params {Number} numberOfOthers  How many other ads of the same type there are.
   * @returns {Number} The ad's tile number.
   * @private
   */
  var calculateTileNumberFor = function(ad, options, numberOfOthers) {
    if (typeof(options) != 'object')
      {return false;}

    if (typeof(options.type) != 'number')
      {return false;}

    if (typeof(numberOfOthers) != 'number')
      {return false;}

    var tileNumber = 0;

    //allow tile overrides
    if (typeof(options.tile) == 'number' && options.tile >= CUSTOM_TILE_START && options.tile < NON_STANDARD_TILE_START)
      {tileNumber = options.tile;}
    else if (options.type == that._AD_LEADERBOARD_MASTER && numberOfOthers == 0)
      {tileNumber = 1;}
    else if (options.type == that._AD_BIGBOX_COMPANION && numberOfOthers == 0)
      {tileNumber = 2;}
    else if (options.type == that._AD_BIGBUTTON && numberOfOthers == 0)
      {tileNumber = 4;}
    else if (options.type == that._AD_MULTIAD && numberOfOthers == 0)
      {tileNumber = 3;}
    else if (options.type == that._AD_BIGBUTTON && numberOfOthers == 1)
      {tileNumber = 5;}
    else if (options.type == that._AD_SPONSORSHIP_BUTTON && numberOfOthers == 0)
      {tileNumber = 6;}
    else if (options.type == that._AD_SLIVER && numberOfOthers == 0)
      {tileNumber = 7;}
    else {
      tileNumber = currentNonStandardAds + NON_STANDARD_TILE_START;
      currentNonStandardAds++;
    }

    return tileNumber;
  };


  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private member variables
  var ads,
    ajAds,
    args,
    ignorePathLevel,
    site,
    zone,
    sponsorshipId,
    allowInterstitials,
    isOffsite,
    zoneSuffix,
    keyValuePairs,
    currentNonStandardAds,
    ajConfig,
    ajMap,
    ajMapUsageCounter = {},
    ajSpots,
    ajDivs,
    ajDARTURLs,
    ajDims,
    ajTypes,
    ajDimDivMapping,  // Maps AJ ad dimensions to AJ ad DOM IDs.
    isTOPS,
    isBPPG,
    autoPlayingVideo;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Private member constants
  var DEFAULT_SITE_ID     = "rogers.testsite";
  var MAX_KEY_LENGTH      = 5;
  var DEV_ENVIRONMENTS    = ["dev", "qa", "stage", "staging"];
  var DEFAULT_INDEX_PAGES = [
    "index.jsp", "index.html", "index.htm", "default.jsp", "default.html", "default.htm",
    "index.php", "default.php", "index.asp", "default.asp", "index.aspx", "default.aspx",
    "index.page", "default.page"
  ];
  var DEFAULT_AJ_CONFIG   = {
    "_adspots": {
      "_AD_LEADERBOARD": [],
      "_AD_BIGBOX": [],
      "_AD_SKYSCRAPPER": [],
      "_AD_MULTIAD": [],
      "_AD_BIGBUTTON": [],
      "_AD_ADVERTORIAL": [],
      "_AD_SPONSORSHIP_BUTTON": [],
      "_AD_LEADERBOARD_MASTER": [],
      "_AD_BIGBOX_COMPANION": [],
      "_AD_CONTEST_LISTING": [],
      "_AD_RADIO_GATEWAY": [],
      "_AD_DHTML": [],
      "_AD_SLIVER": [],
      "_AD_SLIVER_2": [],
      "_AD_XTRALARGE_SPONS": [],
      "_AD_LARGE_SPONS": [],
      "_AD_MEDIUM_SPONS": [],
      "_AD_SMALL_SPONS": [],
      "_AD_HALF_PAGE": [],
      "_AD_BIGBANNER": []
    }
  };
  var CUSTOM_TILE_START       = 9;   // min allowed custom tile value
  var NON_STANDARD_TILE_START = 8;  // upped this to allow for custom tile values.
  var OMNITURE_EVENT_NUMBER   = 46;
  var ZONE_NAME_MAX_LENGTH    = 64;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public methods

  /**
   * Builds an AdJuggler call. This essentially implements the 'ajtg.js' used in "built" ad calls from their UI
   *
   * This is meant to be as close to that implementation, and should be considered "low level". IE: higher
   * level decision making should happen "above" this.
   *
   * @public
   */
  this.buildAdJugglerCall = function() {
    var args = arguments[0];
    if (typeof args == "undefined") return null;

    // set defaults, take overrides if specified
    var server      = typeof args.aj_server != "string" ? AJ_SERVER_URL : args.aj_server;
    var zone        = typeof args.aj_zone   != "string" ? AJ_ZONE_ID : args.aj_zone;
    var ch          = typeof args.aj_zone   != "string" ? AJ_CHANNEL_FILTER : args.aj_zone;
    var page        = typeof args.aj_page   != "string" ? "0" : args.aj_page;
    // not actually the pv value, but boolean determining if it's expressed
    var pv          = typeof args.aj_pv     != "boolean" ? true : args.aj_pv;
    // we call out the view as a param instead of discovering as ajtg.js does
    var view        = typeof args.aj_view    != "string" ? "vj" : args.aj_view;

    // bail if required items not set 
    if (typeof(args.aj_adspot) == "undefined") {
      adUtilLog("no aj_adspot defined");
      return null;
    }
    if (typeof(args.aj_dim) == "undefined") {
      adUtilLog("no aj_dim defined");
      return null;
    }
    // for the moment, aj_pos is required, without default incrementing
    // it gets to be automatic when "Advertisment" is treated as an interface 
    if (typeof(args.aj_pos) == "undefined") {
      adUtilLog("no aj_pos defined");
      return null;
    }
    
    // bail if items set wrongly
    if (view != "vj" && view != "vh" && view != "mvj") {
      adUtilLog("unknown view: " + view);
      return null;
    }
    if (typeof(args.aj_pos) != "number") {
      adUtilLog("aj_pos not a number: " + args.aj_pos);
      return null;
    }
    //if () return null;

    // discovered parameters...
    var tz = new Date().getTimezoneOffset();
    var url = "";
    try {
        url = window.top.location.href;
    } catch (err) {
        url = window.location.href;
    }
    var referrer = document.referrer;

    // helper function to eliminate nil parameters
    var param = function(name, value) {
        if (typeof(value) != 'undefined' && value != "") {
                return name + value;
        }
        return "";
    };

    // build the URL in tag_url
    var aj_tag = server;
    aj_tag += args.aj_adspot + "/" + page + "/" + view + "?z=" + zone;
    aj_tag += param("&ch=", ch);
    aj_tag += param("&dim=", args.aj_dim);

    if (typeof(args.aj_ct) != 'undefined') {
        aj_tag += param("&ct=", args.aj_ct);
    }
 
    aj_tag += "&pos=" + args.aj_pos;

    if (typeof(args.aj_kw) != 'undefined') {
        aj_tag += param("&kw=", args.aj_kw);
    }

    if (typeof(pv) != 'undefined' && pv == true) {
        if (typeof(window.aj_pv_rnd) == 'undefined') {
            aj_pv_rnd = Math.round( (Math.random() + "") * 10000000000000000 ) + 1;
        }
        aj_tag += "&pv=" + aj_pv_rnd;
    }

    // undocumented, but ajtg.js does it so this implementation does as well
    aj_tag += param("&nc=", Math.round( (Math.random() + "") * 100000000 ) + 1);

    if (typeof(args.aj_click) != 'undefined') {
        aj_tag += param("&click=", args.aj_click);
    }

    aj_tag += param("&tz=", tz);
    aj_tag += param("&url=", encodeURIComponent(url));
    aj_tag += param("&refer=", encodeURIComponent(referrer));

    return aj_tag;
  };
 
  this.getAJAdsThatAre = function(width, height) {
    if ((typeof(width)   != 'number') && (typeof(width)  != 'string')) return false;
    if ((typeof(height)  != 'number') && (typeof(height) != 'string')) return false;

    var dimension = width + 'x' + height;

    return ajDimDivMapping[dimension];
  };

  /**
   * Inserts a Rogers Digital Media DART advertisement in a specified DIV.
   * This function is called by {@link Advertisement#insertAd}, but can be called manually if desired.
   * See the documentation for {@link Advertisement#insertAd} for more information.
   *
   * @param {String} sourceElementId   The id of the DIV element where the advertisement should be inserted when ready.
   * @param {Object} options           An object containing either a numeric type element and optional adService element, or a site, zone,
   *                                   size, keyValuePairs and allowInterstitials argument in the case of a custom advertisement.
   * @return {Advertisement}  The ad that was inserted.
   *
   * @public
   */
  this.insertDARTAd = function(sourceElementId, options) {
    if (typeof sourceElementId != "string" || typeof options != "object")
      {return false;}

    if (typeof options.type != "number")
      {return false;}

    var ad = new Advertisement({
      site:               site,
      zone:               zone,
      size:               this._AD_SIZES[options.type],
      keyValuePairs:      keyValuePairs,
      allowInterstitials: allowInterstitials,
      divId:              sourceElementId,
      id:                 ads.count() + 1,
      adService:          'DART'
    });

    var numberOfOthers  = ads.numberOf(ad);
    var tileNumber      = calculateTileNumberFor(ad, options, numberOfOthers);

    ad.setTile(tileNumber);

    ads.add(ad);
    ad.write();

    return ad;
  };

  /**
   * Inserts a Rogers Digital Media AdJuggler advertisement in a specified DIV.
   * This function is called by {@link Advertisement#insertAd}, but can be called manually if desired.
   * See the documentation for {@link Advertisement#insertAd} for more information.
   *
   * @param {String} sourceElementId   The id of the DIV element where the advertisement should be inserted when ready.
   * @param {Object} options           An object containing either a numeric type element and optional adService element, or a site, zone,
   *                                   size, keyValuePairs and allowInterstitials argument in the case of a custom advertisement.
   *
   * @public
   */
  this.insertAjAd = function(sourceElementId, options) {
    if (typeof sourceElementId != "string" || typeof options != "object")
      {return false;}

    if (typeof options.type != "number")
      {return false;}

    var usageCounter = ajMapUsageCounter[options.type];

    // Check if this is the first time that this ad map is being used.
    if (usageCounter == undefined)
      {usageCounter = 0;}

    if ((typeof ajMap[options.type] == 'undefined') || (typeof ajMap[options.type][usageCounter] == 'undefined')) {
      adUtilLog('[WARNING] insertAjAd() No ads of type ' + options.type + ' are defined.');
      return false;
    }

    var adspec = ajMap[options.type][usageCounter];
    if (typeof adspec != "object") {
      adUtilLog('[WARNING] insertAjAd() No ad spot ID exists for ad #' + usageCounter + ' of type ' + options.type);
      return false;
    }

    // Increment the counter to keep track of which ad in ajMap to use.
    ajMapUsageCounter[options.type] = usageCounter + 1;

    var adspot  = adspec[0];
    var dim     = adspec[1].join(',');
    var type    = options.type;
    var div     = "ajdiv_" + adspot;

    // Determine how many other ads of the same dimensions there are.
    // FIXME: this and other similar items need to work this at the TYPE level, not the
    // underlying dimension sizes. (IE: think about how this should work for MULTIAD types..)
    var numberOfOthers = 0;
    for (var i = 0; i < ajTypes.length; i++) {
      if (options.type == ajTypes[i])
        {numberOfOthers++;}
    }

    ajSpots.push(adspot);
    ajDims.push(dim);
    ajTypes.push(type);
    ajDivs.push(div);

 
    var protocol    = window.location.protocol == "https:" ? "https:" : "http:";
    // FIXME: almost all of this should be coming from a 'contained' DFP ad object vs inline
    // see END FIXME for end of block..
    var tileNumber  = calculateTileNumberFor(null, options, numberOfOthers);

    var size        = '';
    var dimensions  = this._AD_SIZES[options.type];

    // Build a string version of the ad's possible sizes.
    for (var i = 0; i < dimensions.width.length; i++) {
      size += dimensions.width[i] + "x" + dimensions.height[i]; // + ",";

      // Map this ad's dimensions to its div's DOM ID.
      if (typeof(ajDimDivMapping[size]) == 'undefined')
        {ajDimDivMapping[size] = [];}
      ajDimDivMapping[size].push(div);

      // Add a comma if this isn't the last element.
      if (i + 1 < dimensions.width.length)
        {size += ',';}
    }

    var keyValuePairs = this.getKeyValuePairs() || '';
    if ((options.type == this._AD_SLIVER) || (options.type == this._AD_SLIVER_2))
      {keyValuePairs += 'mtype=rich;';}

    var interstitial  = tileNumber > 1 ? "" : allowInterstitials ? "dcopt=ist;" : "";
    var shortAdCall   = site + zone + ";" + "dcove=d;comp=;" + interstitial + "spons=" + sponsorshipId + ";tile=" + tileNumber + ";sz=" + size + ";" + keyValuePairs + "adutil=v2;ord=" + ordNum + ";click=";
    var fullAdCall    = protocol + '//' + DFP_ADSERVER_URL + '/adj/' + shortAdCall;

    // END FIXME from above

    ajDARTURLs.push(fullAdCall);


    if (doInline == false) {
      var newDivContents = '<div id="' + div + '"></div>';

      if (options.insertWith == 'jQuery') {
        var body = jQuery('body');
        if (body.length != 1) return false;

        var newDiv = jQuery(newDivContents);
        body.append(newDiv);
      } else {
        document.write(newDivContents);
      }
    }
    else if (doInline == true) {
      var newDivContents = '<div id="' + div + '">';

      if (autoPlayingVideo == true) {
        newDivContents += '</div>';
      }
      else {
        // place inline ad
        var ajKeywords = this.keywordsFromURL();

        if ((typeof sponsorshipId == 'string') && (sponsorshipId.length > 0)) {
          if (ajKeywords == '')
            {ajKeywords += sponsorshipId;}
          else
            {ajKeywords += ',' + sponsorshipId;}
        }

        adUtilLog('aj_kw = ' + ajKeywords);

        var aj_tag_url = this.buildAdJugglerCall({
          aj_view:            "vj",
          aj_pos:             ajAds.count() + 1,
          aj_adspot:          adspot,
          aj_dim:             dim,
          aj_kw:              ajKeywords
        });

        //log("aj_tag_url = " + aj_tag_url);
        adUtilLog("aj_tag_url = " + aj_tag_url);
        var ajCall =  "<script language='javascript' type='text/javascript'>";

        if (passThroughToDFP) { 
          ajCall += "\nwindow.ADUTIL = { DART_TAG: '" + fullAdCall + "' };\n"; 
        }
        //    else
        //      { ajCall += "window.ADUTIL = { };\n"; }
        ajCall += '</script>';
        ajCall += '<'+'scr'+'ipt type="text/javas'+'cript" src="'+aj_tag_url+'"></'+'scr'+'ipt>';

        newDivContents += ajCall;
        newDivContents += '</div>';
      }

      document.write(newDivContents);
    }

    var ajAd = new Advertisement({
      site:               site,
      zone:               zone,
      size:               this._AD_SIZES[options.type],
      tile:               tileNumber,
      keyValuePairs:      keyValuePairs,
      allowInterstitials: allowInterstitials,
      divId:              sourceElementId,
      id:                 ajAds.count() + 1,
      adService:          'AdJuggler'
    });

    // FIXME: there should be but one "ads", in java parlance, it would be a list of "Advertisement"
    // interface conforming objects, where we'd have implementations of DFP, AJ, and whatever else
    // we should think of passthru as encapsulation of the ad unit to it's containing call.
    // more another time...
    ajAds.add(ajAd);

    return true;
  };

  /**
   * Inserts a custom Rogers Digital Media advertisement in a specified DIV.
   * This function is called by {@link Advertisement#insertAd}, but can be called manually if desired.
   * See the documentation for {@link Advertisement#insertAd} for more information.
   *
   * @param {String} sourceElementId   The id of the DIV element where the advertisement should be inserted when ready.
   * @param {Object} options           An object containing either a numeric type element and optional adService element, or a site, zone,
   *                                   size, keyValuePairs and allowInterstitials argument in the case of a custom advertisement.
   *
   * @public
   */
  this.insertCustomAd = function(sourceElementId, options) {
    adUtilLog('WARNING: A custom ad has been requested.');

    if (typeof sourceElementId != "string" || typeof options != "object")
      {return false;}

    var ad = new Advertisement({
      site:               options.site,
      zone:               options.zone,
      size:               options.size,
      keyValuePairs:      options.keyValuePairs,
      allowInterstitials: options.allowInterstitials,
      divId:              sourceElementId,
      id:                 ads.count() + 1
    });

    // Allow tile overrides.
    if ("number" == typeof options.tile && options.tile >= CUSTOM_TILE_START && options.tile < NON_STANDARD_TILE_START) {
      ad.setTile(options.tile);
    }
    else {
      ad.setTile(currentNonStandardAds + NON_STANDARD_TILE_START);
      currentNonStandardAds++;
    }

    ads.add(ad);
    ad.write();


    var adCall = ad.buildDFPCall();
    adUtilLog('Full custom ad call: ' + adCall.fullCall);
    adUtilLog('Custom ad details:');
    ad.writeToConsole();

    return ad;
  };

  /**
   * Inserts a Rogers Digital Media standard advertisement in a specified DIV.  A new Advertisement will be created
   * and added to the AdvertisementList that the RDMAdUtility owns.  Please see the {@link Advertisement#write} and {@link AdvertisementList#add}
   * methods of classes {@link Advertisement} and {@link AdvertisementList} respectively.
   *
   * @param {String} sourceElementId   The id of the DIV element where the advertisement should be inserted when ready.
   * @param {Object} options           An object containing either a numeric type element and optional adService element, or a site, zone,
   *                                   size, keyValuePairs and allowInterstitials argument in the case of a custom advertisement.
   * @see Advertisement#write
   * @see AdvertisementList#add
   *
   * @public
   *
   * @example
   * var adUtility = new RDMAdUtility({
   *   site:               "siteName",
   *   zone:               "zoneName",
   *   sponsorshipId:      "sponsorshipId",
   *   allowInterstitials: true,
   *   keyValuePairs:      [["key1","value"],["key2","value"],["key3","value"]],
   *   isOffsite:          false,
   *   zoneSuffix:         "zoneSuffix",
   *   ignorePathLevel:    2
   * });
   *
   * var leaderboard = adUtility.insertAd("leaderboard_top",
   *   { type: adUtility._AD_LEADERBOARD_MASTER });
   *
   * var bigbox = adUtility.insertAd("bigbox",
   *   { type: RDMAdUtility._AD_BIGBOX });
   *
   * var customAd = adUtility.insertAd("custom_ad", {
   *    site:               "siteName",
   *    zone:               "zoneName",
   *    size:               {width:[300,301], height:[250,250]},
   *    keyValuePairs:      [["key1","value"],["key2","value"],["key3","value"]],
   *    allowInterstitials: true,
   *  });
   */
  this.insertAd = function(sourceElementId, options) {
    // If only one argument was provided, assume that it's the "options" argument.
    // We'll need to figure out the value for the "sourceElementId" argument.
    if (arguments.length == 1) {
      options = sourceElementId;

      if (document.activeElement == null)
        {return false;}

      sourceElementId = document.activeElement.lastChild.id;
    }

    if (typeof sourceElementId != "string" || typeof options != "object")
      {return false;}

    if (options.adService == 'AdJuggler' || (typeof options.type == "number" && options.type in this.AJ_HANDLED_TYPES))
      {return this.insertAjAd(sourceElementId, options);}
    else if (options.adService == 'DART' || (typeof options.type == "number"))
      {return this.insertDARTAd(sourceElementId, options);}
    else
      {return this.insertCustomAd(sourceElementId, options);}
  };

  /**
   * Produces a string that is supposed to populate the Omniture s.products variable if passing ad impression data to
   * Omniture.  Event 4 is used to capture ad impressions by default but this value can be changed by adjusting private
   * member, OMNITURE_EVENT_NUMBER.
   * @returns {String} A string to be assigned to the Omniture s.products variable.
   * @public
   */
  this.getOmnitureProductString = function() {
    //s.products = Category;Product;Quantity;Price;eventN=X[|eventN=X][,Category;Product;Quantity;Price;eventN=X]
    //";"+advertisements[i].site+":"+advertisements[i].zone+":"+advertisements[i].size+";;;event4="+advertisements[i].count+",";
    var str       = '';
    var uniqueAds = ads.getUniqueAds();

    for (var i = 0; i < uniqueAds.length; i++) {
      var ad    = uniqueAds[i];
      var size  = ad.getSizeAsString();

      // Replace illegal characters first
      size = size.replace(/,/g, "|");
      size = size.replace(/\:/g, "/");

      // Replace standard sizes
      size = size.replace(/^728x90$/,                     "Leaderboard");
      size = size.replace(/^300x250\|301x250$/,           "Big Box (Companion)");
      size = size.replace(/^301x250\|300x250$/,           "Big Box (Companion)");
      size = size.replace(/^300x60$/,                     "Big Button");
      size = size.replace(/^300x250\|160x600\|300x600$/,  "Multi-Ad");
      size = size.replace(/^300x250\|300x600\|160x600$/,  "Multi-Ad");
      size = size.replace(/^160x600\|300x250\|300x600$/,  "Multi-Ad");
      size = size.replace(/^160x600\|300x600\|300x250$/,  "Multi-Ad");
      size = size.replace(/^300x600\|160x600\|300x250$/,  "Multi-Ad");
      size = size.replace(/^300x600\|300x250\|160x600$/,  "Multi-Ad");
      size = size.replace(/^150x50$/,                     "Sponsorship Button");
      size = size.replace(/^460x240$/,                    "Contest Listing");
      size = size.replace(/^160x600$/,                    "Skyscrapper");
      size = size.replace(/^975x50$/,                     "Sliver");

      str += ";" + ad.getSite() + ":" + ad.getZone() + ":" + size + ";;;event" + OMNITURE_EVENT_NUMBER + "=" + ad.count + ",";
    }

    // Remove the trailing comma.
    str = str.substring(0, str.length - 1);

    return str;
  };

  /**
   * Returns the current site that is being used for all standard advertisements.
   * @returns {String} A string representing the current site being used for all standard advertisements.
   * @public
   */
  this.getSite = function() {
    return site;
  };

  /**
   * Sets the site to be used for all standard advertisements.
   * @param {String} new_site The new site to be used for all standard advertisements.
   * @public
   * @returns new_site if the setting operation was successful, false otherwise.
   */
  this.setSite = function(new_site) {
    if (typeof new_site == "string") {
      site = new_site;
      return site;
    }
    else {
      return false;
    }
  };

  /**
   * Returns the current zone being used for all standard advertisements.
   * @returns {String} A string representing the current zone being used for all standard advertisements.
   * @public
   */
  this.getZone = function() {
    return zone;
  };

  /**
   * Sets the zone to be used for all standard advertisements.
   * @param {String} new_zone  The new zone to be used for all standard advertisements.
   * @returns The new zone if the setting operation was successful, false otherwise.
   * @public
   */
  this.setZone = function(new_zone) {
    // Use the specified zone, provided it's valid. Otherwise, generate the zone from the URL.
    if ((typeof new_zone == 'string') && (new_zone != '')) {
      // Collapse consecutive slashes.
      new_zone = new_zone.replace(/\/+/g, '/');
  
      // DART zones are limited to 64 characters. Truncate the zone if it exceeds that.
      if (new_zone.length > ZONE_NAME_MAX_LENGTH) {
        new_zone = new_zone.substring(0, ZONE_NAME_MAX_LENGTH + 1);
      }

      // Ensure that the new zone begins with a slash.
      if (!new_zone.match(/^\//)) new_zone = '/' + new_zone;

      zone = new_zone;
    }
    else
      { zone = getZoneFromURL(); }

    return zone;
  };

  /**
   * Returns the key-value pairs being used for all standard advertisements.
   * @returns {Array} An key-value pair array (object).
   * @public
   */
  this.getKeyValuePairs = function() {
    return keyValuePairs;
  };

  /**
   * Sets the key-value pairs to be used by all standard advertisements.
   * @params {Object} new_keyvalues An array of new key-value pairs.  Array should be in the format [["key"],["value"]].
   * Keys with a length greater than MAX_KEY_LENGTH will be ignored.
   * @returns The new key-value pair array if successful, false otherwise.
   * @public
   */
  this.setKeyValuePairs = function(new_keyvalues) {
    if (typeof new_keyvalues == "object") {
      if (new_keyvalues.length >= 1) {
        for (var i = 0; i < new_keyvalues.length; i++) {
          if (new_keyvalues[i].length == 2) {
            if ((typeof new_keyvalues[i][0] == "string" || typeof new_keyvalues[i][0] == "number")
            && (typeof new_keyvalues[i][1] == "string") || typeof new_keyvalues[i][1]) {
              if (new_keyvalues[i][0].length <= MAX_KEY_LENGTH) {
                keyValuePairs.push(new_keyvalues[i]);
              }
            }
            else {
              return false;
            }
          }
          else {
            return false;
          }
          return keyValuePairs;
        }
      }
      else {
        return false;
      }
    }
    else {
      return false;
    }
  };

  /**
   * Returns a boolean for whether interstitials are allowed for all new standard advertisements.
   * @returns {Boolean} True if interstitials are currently allowed, false otherwise.
   * @public
   */
  this.getAllowInterstitial = function() {
    return allowInterstitials;
  };

  /**
   * Sets a new boolean value for whether interstitials are allowed for all new standard advertisements.
   * @param {Boolean} new_allow A true or false value for allowing interstitials.
   * @returns {Boolean} The new value for allowing of interstitials if successful, null otherwise.
   * @public
   */
  this.setAllowInterstitial = function(new_allow) {
    if (typeof new_allow == "Boolean") {
      allowInterstitials = new_allow;
      return allowInterstitials;
    }
    else {
      return null;
    }
  };

  /**
   * Returns the current sponsorship ID being used for all new standard advertisements.
   * @returns {Number}  The current sponsorship ID being used for all new standard advertisements.
   * @public
   */
  this.getSponsorshipId = function() {
    return sponsorshipId;
  };

  /**
   * Sets a new sponsorship ID to be used for all new standard advertisements.
   * @param {String} new_id The new sponsorship ID to be used for all new standard advertisements.
   * @returns new_id if successful, false otherwise.
   * @public
   */
  this.setSponsorshipId = function(new_id) {
    if (typeof new_id == "string") {
      sponsorshipId = new_id;
      if (keyValuePairs.length == 0 || sponsorshipId == "") {
        keyValuePairs.push(["spons",sponsorshipId]);
        return sponsorshipId;
      }
      else {
        var found = false;
        for (var i = 0; (i < keyValuePairs.length && !found); i++) {
          if (keyValuePairs[i][0] == "spons") {
            keyValuePairs[i][1] = sponsorshipId;
            found = true;
          }
        }
        return (found ? sponsorshipId : found);
      }
    }
    else {
      return false;
    }
  };

  /**
   * Returns whether or not the utility is currently assuming the site is to be considered offsite.
   * @returns {Boolean} The current boolean value being used to determine whether the site is offsite or not.
   * @public
   */
  this.getOffsite = function() {
    return isOffsite;
  };

  /**
   * Sets a new value for whether the current site is to be considered is offsite.  The zone
   * will automatically be updated upon successfully setting a new value.
   * @param {Boolean} new_val
   * @returns new_val if successful, null otherwise.
   * @public
   */
  this.setOffsite = function(new_val) {
    if (typeof new_val == "boolean") {
      isOffsite = new_val;
      zone = getZoneFromURL();
      return isOffsite;
    }
    else {
      return null;
    }
  };

  /**
   * Gets the current zone suffix being used for new standard advertisements.
   * @returns {String} The current zone suffix being used for all new standard advertisements.
   * @public
   */
  this.getZoneSuffix = function() {
    return zoneSuffix;
  };

  /**
   *
   * @param {String} new_suffix The new ad zone suffix to use for new standard advertisements.  The zone
   * will automatically be updated upon successfully setting a new value.
   * @returns new_suffix if successful, false otherwise.
   * @public
   */
  this.setZoneSuffix = function(new_suffix) {
    if(typeof new_suffix == "string") {
      zoneSuffix = new_suffix;
      zone = getZoneFromURL();
      return zoneSuffix;
    } else {
      return false;
    }
  };

  /**
   * Returns whether or not ad calls will be passed through to DFP.
   *
   * @returns {Boolean} A boolean representing whether or not ad calls will be passed through to DFP.
   *
   * @public
   */
  this.getPassThroughToDFP = function() {
    return passThroughToDFP;
  }

  /**
   * Sets whether or not ad calls should be passed through to DFP.
   *
   * @param {Boolean} passThrough   Whether or not ad calls should be passed through to DFP.
   *
   * @returns {Boolean} Whether or not the changing the option was successful.
   *
   * @public
   */
  this.setPassThroughToDFP = function(passThrough) {
    if (typeof passThrough != 'boolean') return false;

    passThroughToDFP = passThrough;

    return true;
  }

  /**
   * Returns the current AdvertisementList used by the utility.
   * @returns {AdvertisementList} The AdvertisementList currently in use by the utility.
   * @public
   */
  this.getAds = function() {
    return ads;
  };

  /**
   * Returns an object with three properties:
   *  * ajSpots:  An array of ad spot IDs.
   *  * ajDims:   An array of ad dimension IDs.
   *  * ajDivs:   An array of DOM IDs that ads will be inserted into.
   *
   * @returns {object}  An object representing all of the ads on the page.
   *
   * @public
   */
  this.getAJAds = function() {
    return ajAds;
  };

  /**
   * Returns the AdJuggler configuration.
   * @returns {object} The AdJuggler configuration object.
   */
  this.getAjConfig = function() {
      return ajConfig;
  };

  /**
   * Returns the AdJuggler mapping.
   * @returns {object} The AdJuggler mapping.
   */
  this.getAjMap = function() {
      return ajMap;
  };

  /**
   * Returns whether or not the AdUtility object will behave as though there's
   * an auto-playing video on the page.
   *
   * @returns {boolean} Whether or not the AdUtility object will behave as though
   * there's an auto-playing video on the page.
   *
   * @public
   */
  this.getAutoPlayingVideo = function() {
    return autoPlayingVideo;
  };

  /**
   * Returns whether or not the AdUtility object is configured to use inline mode.
   *
   * @returns {boolean} Whether or not the AdUtility object is configured to use inline mode.
   *
   * @public
   */
  this.getInlineMode = function() {
    return doInline;
  };

  this.lookupAjTags = function() {
    var newZoneAdSet = {};
    var zoneParts = zone.split("/");
    zoneParts.shift();
    var currentZonePart = zoneParts.shift;
    var currentNode = ajConfig;

    if (typeof currentNode["_oldDartSite"] == "undefined") {
      adUtilLog('ERROR: lookupAjTags() Failed to find the "old DART site".');
      return null
    }

    if (currentNode["_oldDartSite"] != this.getSite()) {
      adUtilLog('ERROR: lookupAjTags() The "old DART site" does not match the specified site name.');
      adUtilLog('  _oldDartSite: ' + currentNode["_oldDartSite"]);
      adUtilLog('  site:         ' + this.getSite());
      return null
    }

    var done = false;
    while (!done) {
      if (typeof currentNode["_adSpots"] != "undefined") {
        var spots = currentNode["_adSpots"];

        for (var spot in spots) {
          if (spot in this) {
            newZoneAdSet[this[spot]] = spots[spot];
          }
        }
      }

      currentZonePart = zoneParts.shift();

      if (typeof currentZonePart != "string") {
        done = true;
      }

      if (currentZonePart in currentNode) {
        currentNode = currentNode[currentZonePart];
      } else {
        done = true
      }
    }

    ajMap = newZoneAdSet;
  };

  this.keywordsFromURL = function() {
    var keywordDelimiter  = ',';
    var joinedKeywords    = getHostname();
    var path              = window.location.pathname.toLowerCase();
    var paths;
    var filename;

    // Replace special characters with corresponding common characters.
    path = path.replace(/[Ã Ã¢Ã¤@]/gi,       "a");
    path = path.replace(/Ã§/gi,            "c");
    path = path.replace(/[Ã©Ã¨ÃªÃ«]/gi,       "e");
    path = path.replace(/[Ã®Ã¯]/gi,         "i");
    path = path.replace(/[Ã´Ã¶]/gi,         "o");
    path = path.replace(/[Ã¹Ã»Ã¼]/gi,        "u");
    path = path.replace(/[ ,?!%'()"]/gi,  "-");
    path = path.replace(/\$/gi,           "dollars");

    // Remove the filename from the path, and remove the filename's extension.
    // If "paths" is empty, give it an empty string. This is needed because IE8's
    // implementation of String#split() does this:
    //   'x'.split(/x/)  => []
    // instead of this:
    //   'x'.split(/x/)  => ['', '']
    paths           = path.split(/[-\/]/);
    if (paths.length == 0) paths = [''];

    filename        = paths.pop();
    filename        = filename.split(/\.[^.]+$/)[0];
    joinedKeywords  = filename;

    // Add each path to the list of joined keywords iff the path is a non-empty string.
    for (var i = 0; i < paths.length; i++) {
      if (typeof(paths[i]) == 'string' && paths[i] != '')
        {joinedKeywords += keywordDelimiter + paths[i];}
    }

    // Remove a leading comma.
    joinedKeywords = joinedKeywords.replace(/^,/, '');

    return joinedKeywords;
  };

  /**
   * Inserts a unique, hidden DHTML ad.
   * This hidden ad supposedly supports intersitials, sponsorships, catfish, etc.
   * By inserting it, we enable Ad Operations to flight these special types of ads.
   *
   * I wanted this function to be private. Unfortunately, a private function can't
   * call insertAd(), which is a privileged function.
   *
   * @returns {Boolean} Whether or not inserting the ad was successful.
   *
   * @public
   */
  this.insertHiddenDHTMLAd = function() {
    adUtilLog('insertHiddenDHTMLAd()');

    if (top !== self) {
      adUtilLog('Preventing a DHTML ad from being inserted into an iframe.');
      return;
    }

    var body = jQuery('body');
    if (body.length != 1) return false;

    var domID = 'dhtml-ad-hook';

    // Ensure that the div for the hidden DHTML ad doesn't exist.
    if (jQuery('#' + domID).length != 0) return false

    var hiddenAd = jQuery('<div id="' + domID + '" />');

    body.append(hiddenAd);

    this.insertAjAd(domID, { type: this._AD_DHTML, insertWith: 'jQuery' });
  };

  /**
   * This function performs all of the necessary steps to get AdJuggler to insert ads.
   *
   * @returns {Boolean} Whether or not inserting the ad was successful.
   *
   * @public
   */
  this.finalizeAjAds = function() {
    adUtilLog('finalizeAjAds()');

    if (autoPlayingVideo === true) {
      adUtilLog('  An auto-playing video exists. Ad finalization will not occur.');
      return;
    }

    this.insertAds();
  };

  this.insertInlineAds = function(options) {
    adUtilLog('insertInlineAds()');

    if (typeof(options) == 'undefined') {
      options = {};
    }

    var ajKeywords = this.keywordsFromURL();

    if ((typeof sponsorshipId == 'string') && (sponsorshipId.length > 0)) {
      if (ajKeywords == '')
        {ajKeywords += sponsorshipId;}
      else
        {ajKeywords += ',' + sponsorshipId;}
    }

    adUtilLog('  Keywords: ' + ajKeywords);

    adUtilLog('  Iterating over ' + ajDivs.length + ' ads:');
    for (var i = 0; i < ajSpots.length; i++) {
      adUtilLog('    ajSpots[' + i + '] = ' + ajSpots[i]);
      var adContainer = jQuery('#' + ajDivs[i])
      adUtilLog("      The ad's DIV ID is " + adContainer.attr('id'));

      // Skip this ad if there's already content in the DIV.
      if ((options.overwriteAds != true) && adContainer.html() != '') {
        adUtilLog('      div#' + adContainer.attr('id') + ' is not empty. An ad will not be inserted into it.');
        continue;
      }

      var aj_tag_url = this.buildAdJugglerCall({
        aj_view:            "vj",
        aj_pos:             i + 1,
        aj_adspot:          ajSpots[i],
        aj_dim:             ajDims[i],
        aj_kw:              ajKeywords
      });
  
      var ajCall = '';

      ajCall += '<' + 'scr' + 'ipt type="text/javas' + 'cript">';
      if (passThroughToDFP)
        { ajCall += "\n  window.ADUTIL = { DART_TAG: '" + ajDARTURLs[i] + "' };\n"; }
      else
        { ajCall += "\n  window.ADUTIL = { };\n"; }
      ajCall += '<' + '/scr' + 'ipt>';

      ajCall += '<' + 'scr' + 'ipt type="text/javas' + 'cript" src="' + aj_tag_url + '"></' + 'scr' + 'ipt>';

      adContainer.empty().writeCapture().html(ajCall, {
        done: function() { adUtilLog('      Ad insertion complete.'); }
      });
    }
  };

  this.insertAds = function(options) {
    adUtilLog('insertAds()');
    adUtilLog('  ajSpots  = [' + ajSpots.join(', ') + ']');
    adUtilLog('  ajDims   = [' + ajDims.join(', ') + ']');
    adUtilLog('  ajTypes  = [' + ajTypes.join(', ') + ']');
    adUtilLog('  ajDivs   = [' + ajDivs.join(', ') + ']');

// testing taking this out, it happens globally, so it should be "counted on"...
//    if (window.DART_pv_rnd === undefined) {
//      window.DART_pv_rnd = Math.round((Math.random() + "") * 10000000000000000) + 1;
//    }

    if (typeof(options) == 'undefined') {
      options = {};
    }

    if (doInline == true) {
      adUtilLog('  Inline mode is enabled. Calling insertInlineAds() and returning.');
      return this.insertInlineAds(options);
    }
    else if (doInline == false) {
      this.insertHiddenDHTMLAd();
    }

    var aj_adspot = ajSpots.join(",");
    var aj_dim = ajDims.join(";");
    var aj_div = ajDivs;

    var ajKeywords = this.keywordsFromURL();

    if ((typeof sponsorshipId == 'string') && (sponsorshipId.length > 0)) {
      if (ajKeywords == '')
        {ajKeywords += sponsorshipId;}
      else
        {ajKeywords += ',' + sponsorshipId;}
    }

    var aj_kw = '';
    if (ajKeywords != '') {
      for (var i = 0; i < ajDivs.length; i++) {
        aj_kw += ajKeywords;

        if (i < ajDivs.length - 1) aj_kw += ';';
      }
    }
    adUtilLog('  Keywords: ' + ajKeywords);

    var aj_tag_url = this.buildAdJugglerCall({
      aj_view:            "mvj",
      aj_pos:             1,              // this is what ajtg.js does for multi-tag calls...
      aj_adspot:          aj_adspot,
      aj_dim:             aj_dim,
      aj_div:             aj_div,
      aj_kw:              aj_kw
    });

    var ajscript = jQuery('#ajscript');
    if (ajscript.length == 0) {
      adUtilLog('ERROR: div#ajscript is missing.');
      return;
    }

    var passThroughToDFP = this.getPassThroughToDFP();

    adUtilLog("  aj_tag_url = " + aj_tag_url);
    ajscript.empty().writeCapture().html('<script language="javascript" type="text/javascript" src="' + aj_tag_url+ '"></script>', {
      done: function() {
        adUtilLog('  Iterating over ' + ajDivs.length + ' ads:');
        for (var div = 0; div < ajDivs.length; div++) {
          adUtilLog('    ajDivs[' + div + '] = ' + ajDivs[div]);
          var domDiv = jQuery('#' + ajDivs[div])
          adUtilLog("      The ad's DIV ID is " + domDiv.attr('id'));

          // Skip this ad if there's already content in the DIV.
          if ((options.overwriteAds != true) && domDiv.html() != '') {
            adUtilLog('      div#' + domDiv + ' is not empty. An ad will not be inserted into it.');
            continue;
          }

          var ajCall =  "<script language='javascript' type='text/javascript'>";
    
          if (passThroughToDFP)
            { ajCall += "\nwindow.ADUTIL = { DART_TAG: '" + ajDARTURLs[div] + "' };\n"; }
    //    else
    //      { ajCall += "window.ADUTIL = { };\n"; }

//          ajCall += "ajAd('" + ajDivs[div] + "');"
          ajCall += '</script>';

          if (typeof(window.aj_content) != 'object') {
            adUtilLog("      ERROR: window.aj_content is not an object. It is " + typeof(window.aj_content) + " and = " + window.aj_content);
            continue;
          }

          ajCall += window.aj_content[div];

          //domDiv.empty().writeCapture().html(ajCall);
          domDiv.empty().writeCapture().html(ajCall, {proxyGetElementById:true} );
          //domDiv.empty().writeCapture().html(ajCall, {writeOnGetElementById:true} );

          adUtilLog('      ajCall = ' + ajCall);
         }
      }
    });

 };


  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Public member variables
  this._AD_LEADERBOARD        = 0;
  this._AD_BIGBOX             = 1;
  this._AD_SKYSCRAPPER        = 2;
  this._AD_MULTIAD            = 3;
  this._AD_BIGBUTTON          = 4;
  this._AD_ADVERTORIAL        = 5;
  this._AD_SPONSORSHIP_BUTTON = 6;
  this._AD_LEADERBOARD_MASTER = 7;
  this._AD_BIGBOX_COMPANION   = 8;
  this._AD_CONTEST_LISTING    = 9;
  this._AD_RADIO_GATEWAY      = 10;
  this._AD_DHTML              = 11;
  this._AD_SLIVER             = 12;
  this._AD_XTRALARGE_SPONS    = 13;
  this._AD_LARGE_SPONS        = 14;
  this._AD_MEDIUM_SPONS       = 15;
  this._AD_SMALL_SPONS        = 16;
  this._AD_SLIVER_2           = 17;
  this._AD_HALF_PAGE          = 18;
  this._AD_BIGBANNER          = 19;

  this._AD_SIZES = new Array();
  this._AD_SIZES[this._AD_LEADERBOARD]        = { width: [728],           height: [90] };
  this._AD_SIZES[this._AD_BIGBOX]             = { width: [300],           height: [250] };
  this._AD_SIZES[this._AD_SKYSCRAPPER]        = { width: [160],           height: [600] };
  this._AD_SIZES[this._AD_MULTIAD]            = { width: [300, 160, 300], height: [250, 600, 600] };
  this._AD_SIZES[this._AD_BIGBUTTON]          = { width: [300],           height: [60] };
  this._AD_SIZES[this._AD_SPONSORSHIP_BUTTON] = { width: [150],           height: [50] };
  this._AD_SIZES[this._AD_BIGBOX_COMPANION]   = { width: [300, 301],      height: [250, 250] };
  this._AD_SIZES[this._AD_LEADERBOARD_MASTER] = { width: [728],           height: [90] };
  this._AD_SIZES[this._AD_CONTEST_LISTING]    = { width: [460],           height: [240] };
  this._AD_SIZES[this._AD_RADIO_GATEWAY]      = { width: [760],           height: [270] };
  this._AD_SIZES[this._AD_DHTML]              = { width: [0],             height: [0] };
  this._AD_SIZES[this._AD_SLIVER]             = { width: [975],           height: [50] };
  this._AD_SIZES[this._AD_SLIVER_2]           = { width: [975],           height: [50] };
  this._AD_SIZES[this._AD_XTRALARGE_SPONS]    = { width: [300],           height: [50] };
  this._AD_SIZES[this._AD_LARGE_SPONS]        = { width: [300],           height: [35] };
  this._AD_SIZES[this._AD_MEDIUM_SPONS]       = { width: [280],           height: [35] };
  this._AD_SIZES[this._AD_SMALL_SPONS]        = { width: [160],           height: [20] };
  this._AD_SIZES[this._AD_HALF_PAGE]          = { width: [300],           height: [600] };
  this._AD_SIZES[this._AD_BIGBANNER]          = { width: [300],           height: [100] };

  this.AJ_HANDLED_TYPES                               = new Array();
  this.AJ_HANDLED_TYPES[this._AD_LEADERBOARD]         = 1;
  this.AJ_HANDLED_TYPES[this._AD_BIGBOX]              = 1;
  this.AJ_HANDLED_TYPES[this._AD_SKYSCRAPPER]         = 1;
  this.AJ_HANDLED_TYPES[this._AD_MULTIAD]             = 1;
  this.AJ_HANDLED_TYPES[this._AD_BIGBUTTON]           = 1;
  this.AJ_HANDLED_TYPES[this._AD_SPONSORSHIP_BUTTON]  = 1;
  this.AJ_HANDLED_TYPES[this._AD_BIGBOX_COMPANION]    = 1;
  this.AJ_HANDLED_TYPES[this._AD_LEADERBOARD_MASTER]  = 1;
  this.AJ_HANDLED_TYPES[this._AD_CONTEST_LISTING]     = 1;
  this.AJ_HANDLED_TYPES[this._AD_RADIO_GATEWAY]       = 1;
  this.AJ_HANDLED_TYPES[this._AD_DHTML]               = 1;
  this.AJ_HANDLED_TYPES[this._AD_SLIVER]              = 1;
  this.AJ_HANDLED_TYPES[this._AD_SLIVER_2]            = 1;
  this.AJ_HANDLED_TYPES[this._AD_XTRALARGE_SPONS]     = 1;
  this.AJ_HANDLED_TYPES[this._AD_LARGE_SPONS]         = 1;
  this.AJ_HANDLED_TYPES[this._AD_MEDIUM_SPONS]        = 1;
  this.AJ_HANDLED_TYPES[this._AD_SMALL_SPONS]         = 1;
  this.AJ_HANDLED_TYPES[this._AD_HALF_PAGE]           = 1;
  this.AJ_HANDLED_TYPES[this._AD_BIGBANNER]           = 1;

  this._AD_DIMS = {};
  for (var i in this._AD_SIZES) {
    if (i > 3) continue;  // REMOVE THIS
    if (typeof(this._AD_SIZES[i]) != 'object') continue;
    if (this._AD_SIZES[i].width.length != this._AD_SIZES[i].height.length) continue;

    for (var j in this._AD_SIZES[i].width) {
      var width   = this._AD_SIZES[i].width[j];
      var height  = this._AD_SIZES[i].height[j];
      var dim     = width + 'x' + height;

      if (typeof(this._AD_DIMS[dim]) == 'undefined')
        {this._AD_DIMS[dim] = [];}

      this._AD_DIMS[dim].push
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Constructor

  // Make the RDMAdUtility available to private functions.
  // See this article for more info:
  // http://www.crockford.com/javascript/private.html
  var that = this;

  args = arguments[0];

  // At least one argument will be required
  if (typeof args == "undefined") return null;

  site                  = typeof args.site != "string" ? DEFAULT_SITE_ID : args.site;
  sponsorshipId         = typeof args.sponsorshipId != "string" ? "" : args.sponsorshipId;
  allowInterstitials    = typeof args.allowInterstitials != "boolean" ? false : allowInterstitials = args.allowInterstitials;
  isOffsite             = typeof args.isOffsite != "boolean" ? false : args.isOffsite;
  zoneSuffix            = typeof args.zoneSuffix != "string" ? "" : args.zoneSuffix;
  ignorePathLevel       = typeof args.ignorePathLevel != "number" ? "" : args.ignorePathLevel;
  ajConfig              = typeof args.ajConfig != "object" ? DEFAULT_AJ_CONFIG : args.ajConfig;
  doInline              = typeof args.doInline != "boolean" ? false : args.doInline;
  isTOPS                = typeof args.isTOPS == 'boolean' ? args.isTOPS : false;
  isBPPG                = typeof args.isBPPG == 'boolean' ? args.isBPPG : false;
  keyValuePairs         = new Array();
  currentNonStandardAds = 0;
  autoPlayingVideo      = typeof args.autoPlayingVideo == 'boolean' ? args.autoPlayingVideo : false;

  var queryString = window.location.search;

  // Check if inline mode was enabled or disabled in the URL's query string.
  if (queryString.match(/[?&]adutil_doinline=true(&|$)/)) {
    adUtilLog("NOTICE: Inline mode has been enabled via the URL's query string.");
    doInline = true;
  }
  else if (queryString.match(/[?&]adutil_doinline=false(&|$)/)) {
    adUtilLog("NOTICE: Inline mode has been disabled via the URL's query string.");
    doInline = false;
  }

  // Check if debug mode was enabled or disabled in the URL's query string.
  if (queryString.match(/[?&]adutil_debug=true(&|$)/)) {
    adUtilDebug = true;
    adUtilLog("NOTICE: Debug mode has been enabled via the URL's query string.");
  }
  else if (queryString.match(/[?&]adutil_doinline=false(&|$)/)) {
    adUtilDebug = false;
    adUtilLog("NOTICE: Debug mode has been disabled via the URL's query string.");
  }

  // A site shouldn't identify itself as being on both TOPS and BPPG.
  // If it does, we can't accurately determine how to behave, so ignore
  // both requests.
  if (isTOPS && isBPPG) {
    isTOPS = false;
    isBPPG = false;
  }

  if (isBPPG) NON_STANDARD_TILE_START	= 40;

  // Configure DFP pass-through.
  if (typeof args.passThroughToDFP == 'boolean')
    {this.setPassThroughToDFP(args.passThroughToDFP);}
  else
    {this.setPassThroughToDFP(true);}

  if (sponsorshipId != "") {
    keyValuePairs.push(["spons",sponsorshipId]);
  }
  
  //Additional keywords are required for show / hide interstitials.  
  //dcopt=ist doesn't always work when rich media is served from 3rd parties.
  if (allowInterstitials) {
    keyValuePairs.push(["mtype","rich"]);
  }

  if (typeof args.keyValuePairs == "object") {
    if (args.keyValuePairs.length >= 1) {
      for (var i = 0; i < args.keyValuePairs.length; i++) {
        if (args.keyValuePairs[i].length == 2) {
          if ((typeof args.keyValuePairs[i][0] == "string" || typeof args.keyValuePairs[i][0] == "number")
          && (typeof args.keyValuePairs[i][1] == "string") || typeof args.keyValuePairs[i][1]) {
            if (args.keyValuePairs[i][0].length <= MAX_KEY_LENGTH) {
              keyValuePairs.push(args.keyValuePairs[i]);
            }
          }
        }
      }
    }
  }

  /*
  if ((typeof args.zone == 'string') && !args.zone.match(/^\/*$/))
    { this.setZone(args.zone); }
  else
    { this.setZone(getZoneFromURL()); }
  */
  this.setZone(args.zone);

  ajSpots         = [];
  ajDims          = [];
  ajTypes         = [];
  ajDimDivMapping = {};
  ajDivs          = [];
  ajDARTURLs      = [];
  ads             = new AdvertisementList();
  ajAds           = new AdvertisementList()

  this.lookupAjTags();

  // If using multi-tag, arrange for finalizeAjAds() to fire when the document's ready.
  if (doInline === false) {
    jQuery(document).ready(function() { that.finalizeAjAds() });
  }
};

// ----- End RDM Ad Utility

