// $Id: ads_dfp_on.js,v 1.30 2011/07/07 13:17:07 bottif Exp $
// WARNING: THIS FILE IS AUTO-GENERATED FROM THE FALCON VERSION
// IF YOU NEED TO CHANGE IT SEE THE BUILD INSTRUCTIONS:
//    http://epcvs.osb.ft.com/twiki/bin/view/Projects/DartForPublishers#Legacy_Ads_Libraries_FTCOMBASE_a
// It has been assembled from Javascript files in
//       C:/ftcms/ft/cms/homepages/static/javascript
// and   C:/_dev/ft-cms-RELEASE-2.7/ft-cmsWeb/src/main/webapp/media/js
FT = window.FT || {};
FT.env = FT.env || {};
// It has been set to serve ads from DFP by default.
//    ads.old.js
//    FT/Properties/Properties.js
//    FT/Properties/Properties-p.js
//    C:/ftcms/ft/cms/homepages/static/javascript/Lib.classic.js
//    C:/ftcms/ft/cms/homepages/static/javascript/pre-init.classic.js
//    FT/RenderHeadBundle/Advertising/HTMLAds.js
//    FT/RenderHeadBundle/Advertising/DartForPublishers.dfp-on.js
//=========================================================================
// Originally from PHOENIX: ads.old.js
//=========================================================================

//=========================================================================
// Originally from Falcon: FT/Properties/Properties.js
//=========================================================================
/* 
 * This is a singlton instance of the Properties class.  Use it like this e.g.:
 * var lsHost = FT.Properties.APP;
 */
var FT = FT || {};
FT.Properties = FT.Properties || {};

FT.Properties.extend = function(opts) {
    for (var o in opts) {
        this[o] = opts[o];
    }
};

FT.Properties.extend({
    APP: "global",
    ENV: "local",
    BCReadAPIToken: "JPqBh6fm9xN8DI8Fx-r4q3UKCW2My5bxA6_zDYKIUZzODRDB3wZIFg..", //dev
    CM_REQUEST_HTML_PATH: "/m/html/cm_request.htm", // Cognitive Match
    CORPORATE_BARRIER_BASE: "http://www.ft.com/m/registration.ft.com/corporate/ns/cb/"
});

FT.Properties.extend({
    TIMEOUT:{
	COMPONENT_PUBLISH_TIMEOUT:           90000,
	COMPONENT_SAVE_TIMEOUT:              90000,
	PAGE_PUBLISH_TIMEOUT:                300000,
	PAGE_PUBLISH_UNLOCKED_TIMEOUT:       90000,
	PAGE_LONG_AJAX_TIMEOUT:              600000,
	AJAX_DEFAULT_TIMEOUT:                30000,
	LIGHTBOX_PROMOTE_TIMEOUT:            90000,
	SEARCH_RESULTS_STATUS_POLL_INTERVAL: 30000,  // Number of milliseconds between polling for status updates
    TOOLBOX_SEARCH:                      90000
    }
});
//=========================================================================
// Originally from Falcon: FT/Properties/Properties-p.js
//=========================================================================
/*
 * Overrides for properties for this environment.
 */

FT.Properties.extend({
    ENV: "p",
    BCReadAPIToken: "8LjOvl7btdZiTFL3E5a3gPq9uLBIDGp7qTnXnHM0mQ5G_Tjp2GIPfQ..",
    SCRIPT_BASE: "http://s2.media.ft.com/m/js/",
    IMG_BASE:    "http://im.media.ft.com/m/img/",
    CORPORATE_BARRIER_BASE: "http://www.ft.com/m/registration.ft.com/corporate/ns/cb/"
});

//=========================================================================
// Originally from Falcon: C:/ftcms/ft/cms/homepages/static/javascript/Lib.classic.js
//=========================================================================
/*global window,QUnit */

/**
 *   FT.lib is mini-library that provides some basic helper functions.
 *   It is used when jQuery isn't available -
 *      e.g. in the document head or body, or before document.ready
 *   or when jQuery doesn't provide the functionality.
 *   Can be extended - e.g. by Advertising code
 */
 
var FT = FT || {};

/**
 * @namespace
 */
FT.lib = {

    /**
     * Enhanced version of native typeof, also returns "arrayLike"
     * @param  {*}    unk The thing to be inspected
     * @return {String}
     */
    type: function( unk ) {
        function arrayLike( o ){ return ( typeof o.length === 'number' && !( o.propertyIsEnumerable('length') ) ); }
        function type( o ){ return ( Object.prototype.toString.apply( o ) ).match( /\[object (\w+)\]/ )[1].toLowerCase(); }
        var t = type( unk );
        return unk === undefined ? "undefined" : unk === null ? "null" : t === "object" && arrayLike( unk ) ? "arraylike" : t === "arguments" ? "arraylike" : t;
    },

    /**
     * Iterates over an array performing a function within its context
     * @param {Array} context The array or arraylike object
     * @param {f}     f       The function to call
     */
    iterator : function( context, f ) {
        for( var ct = 0; ct < context.length && f.call( context, context[ ct ], ct ) !== false; ct++ ) {}
    },

    /**
     * Trims whitespace from the start and end of a string
     * @param  {String} str The string to be trimmed
     * @return {String} The trimmed string
     */
    trim: function (str) {
        return String(str).replace(/^\s+|\s+$/g, "");
    },

    /**
     * Iterates over an object, calling a function within its context
     * @param {Array} obj The object/hash
     * @param {f}     f   The function to call
     */
    index : function( obj, f ) {
        for ( var key in obj ) { if ( f.call( obj, key, obj[ key ] ) === false ) { break; } }
    },

    /**
     * Makes sure a number is within two limits, if not returns the limit number
     * @param {number} val The number to act on
     * @param {number} min Lower limit
     * @param {number} max Upper limit
     * @return {number}
     */
    constrain: function( val, min, max ) {
        return +val < min || +val > max ? Math.max( min, Math.min( val, max ) ) : val;
    },

    /**
     * Adds a CSS rule to the first stylesheet on the page
     * @param {String} selector The css selector
     * @param {String} style    The style declaration
     */
    addCssRule : function( selector, style ) {
        var  ss = document.styleSheets[ 0 ];
        if ( ss.addRule ) {
            ss.addRule( selector, style );
        } else {
            ss.insertRule( selector + "{" + style + "}", 0 );
        }
    },

    /**
     * Turns a string into a hash array
     * @param {String} str       The string to transform
     * @param {String} delimiter The character that delimits each name/value pair
     * @param {String} pairing   The character that separates the name from the value
     * @return {object}
     */
    hash: function( str, delimiter, pairing ) {
        // Create an object hash from a delimited string
        // Beware all properties on the resulting object will have  string values.
        var hash = {};
        FT.lib.iterator( str.split( delimiter ), function( value ) {
            var pair = value.split( pairing );
            if ( pair.length > 1 ) { hash[ FT.lib.trim( pair[ 0 ] ) ] = Array.prototype.slice.call( pair, 1 ).join( pairing ); }
        });

        //if there is a key with no value then that key will be dropped
        return hash;
    },

    /**
     * Create delimited string from object hash
     * @param {object} obj       The object to transform
     * @param {String} delimiter The character that delimits each name/value pair
     * @param {String} pairing   The character that separates the name from the value
     * @return {String}
     */
    delimit: function( obj, delimiter, pairing ) {
        var first = "";
        var str = "";
        for ( var i in obj ) {
            if ( obj.hasOwnProperty(i) ) {
                str += first + i + pairing + obj[i];
                first =  delimiter;
            }
        }
        return str;
    },

    /**
     * Extends an object using one or more object
     * @return {object}
     */
    extend: function() {
        // RECURSIVE EXTEND.  Be warned.  Functions and arrays are not recursivley extended;
        // A new object is always returned.
        // objects are consumed from left to right;
        // Does not accept arrays or functions as base inputs
        var f = arguments.callee, o = {};
        function second_loop( k, i ) {
            var cast = FT.lib.type( i );
            if ( cast === "object" ) {
                o[ k ] = f( i );// copy ( create new ) objects
            } else if ( cast === "array" || cast === "arraylike" ) {
                o[ k ] = Array.prototype.slice.call( i , 0 );// copy ( create new ) arrays
            } else {
                o[ k ] = i;// pass functions by ref and values by value
            }
        }

        function main_loop( item ) {
            FT.lib.index( item, second_loop );
        }

        FT.lib.iterator( arguments, main_loop );

        return o;
    },

    /**
     * Turns a document.cookie string into an object hash
     * @param  {String} fakeCookies The cookie string (optional)
     *                  if not passed, then document.cookie is used
     * @return {object}
     */
    hashCookies: function(fakeCookies) {
        FT.cookies = FT.lib.hash(fakeCookies || document.cookie, ";", "=");
        return FT.cookies;
    },

    /**
     * Turns a querystring into a object hash.
     * @param {String} [fakeQueryString] The querystring to use (optional)
     *                                   Defaults to window.location.search
     * @return {object}
     */
    hashQueryString: function(fakeQueryString) {
        FT.queryString = FT.lib.hash(fakeQueryString || window.location.search, /[?&]/, "=");
        return FT.queryString;
    },

    /**
     * Tests current page to see if it was sent over http
     * @param  {document} doc The document to test, defaults to current document
     * @return {boolean}
     */
    isSecure: function( doc ) {
        var d = doc || document;
        return (d.location.protocol=='https:');
    },

    /**
     * Writes a script tag into the document.
     * This type of script tag is synchronous, page rendering will be blocked
     * until the script has loaded (or failed to load)
     * @param {String} url The src of the script file
     */
    writeScript: function( url ) {
        // Stop document.write() from happening after page load (unless QUnit is present)
        if( document.readyState !== "complete" || typeof QUnit === "object" ) {
            document.write('<scr' + 'ipt src="' + url + '"></scr' + 'ipt>');
        }
    },

    /**
     * Include a script in the page, without causing rendering to be blocked
     * @param {String} scriptSrc
     */
    writeScriptAsynchronous: function (scriptSrc) {
        var se = document.createElement('script');
        se.async = "async";
        se.type = "text/javascript";
        se.src = scriptSrc;
        document.getElementsByTagName('head')[0].appendChild(se);
    },

    /**
     * Gets DOM elements with a certain class name
     * @param {element} oElm         The DOM node to search within (e.g. document.body)
     * @param {String}  strTagName   The element type, pass "*" for all elements
     * @param {String}  strClassName The name of the class to search for
     * @return {Array}
     */
    getElementsByClassName: function(oElm, strTagName, strClassName){
        var arrElements = (strTagName == "*" && oElm.all)? oElm.all :
        oElm.getElementsByTagName(strTagName);
        var arrReturnElements = [];
        strClassName = strClassName.replace(/\-/g, "\\-");
        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
        var oElement;
        for(var i=0; i<arrElements.length; i++){
            oElement = arrElements[i];
            if(oRegExp.test(oElement.className)){
                arrReturnElements.push(oElement);
            }
        }
        return (arrReturnElements);
    },

    /**
     * Adds a class name to a element (if it is not already there)
     * @param {element} oElm         The element to add the class to
     * @param {String}  strClassName The class to add
     */
    addClassName: function(oElm, strClassName) {
        if (oElm) {
            this.removeClassName(oElm,strClassName);
            oElm.className += " " + strClassName;
        }
    },

    /**
     * Removes a class from a DOM element
     * @param {element} oElm         The element to remove it from
     * @param {String}  strClassName The name of the class
     */
    removeClassName: function(oElm, strClassName) {
        if (oElm) {
            oElm.className = oElm.className.replace(new RegExp(" ?" + strClassName),"");
        }
    },

    clearSelection: function() {
        if(document.selection && document.selection.empty) {
            document.selection.empty();
        } else if(window.getSelection) {
            var sel = window.getSelection();
            sel.removeAllRanges();
        }
    }

};

FT.lib.hashCookies();
FT.lib.hashQueryString();//=========================================================================
// Originally from Falcon: C:/ftcms/ft/cms/homepages/static/javascript/pre-init.classic.js
//=========================================================================
/*global window,runMobileCompatibilityScript */

/** @namespace **/
var FT = FT || {};

/**
 * Page Pre-init
 * @class Pre intialisation functions - if you really can't wait for document.ready e.g.
 *      nav item spacing
 *      news/quotes search radio buttons
 *      login name display
 *      Note, you can't rely on jQuery, it isn't imported until the init stage'
 */
FT.PreInit = function() {

    /**
     * Distribute navigation items evenly across the nav bar
     * @param {element} nav
     */
        this.distributeNavItems = function(nav) {
        var childNodes = nav.childNodes;
        var navItems = [];

        for (var i = 0; i < childNodes.length; i++) {
            var node = childNodes[i];
            // account for the "dummy" tools menu if there's no top level highlight
            if (node.tagName == "LI" && node.className.indexOf("dummy") < 0) {
                navItems.push(node);
            }

        }

        var numItems = navItems.length;

        if (numItems > 0) {
            var availableWidth = navItems[0].parentNode.offsetWidth;
            var unpaddedWidth = 14; // allow for left padding on first item

            // remove all distribution padding and margins
            for (var i = 0; i < navItems.length; i++) {
                var n = navItems[i];
                n.style.paddingLeft = 0;
                n.style.paddingRight = 0;
                n.style.marginLeft = 0;
                n.style.marginRight = 0;

                unpaddedWidth += n.offsetWidth;
            }

            var remainingWidth = availableWidth - unpaddedWidth;
            var paddingPerItem = Math.floor(remainingWidth / numItems);

            for (var i = 0; i < navItems.length; i++) {
                var n = navItems[i];
                if (i == 0) {
                    n.style.paddingLeft = "14px"; // reapply left padding on first item
                }

                n.style.paddingRight = paddingPerItem + "px";  // distribute the other items

                var anchors = n.getElementsByTagName("A");
                for (var a = 0; a < anchors.length; a++) {
                    var anc = anchors[a];
                    anc.style.visibility = "visible";

                }
            }
        }
    };

    /**
     * Renders markets home component (uses AYSC cookie location parameters)
     * @param {string} url
     */
    this.renderWsodMarketsHome = function(url) {
        var urlBits = url.split("?");
        var urlLocation = urlBits[0];
        var urlQs = ""; // = "?id=wsodMarketsHomePlaceholder";

        if (urlBits.length > 1) {
            urlQs += "&" + urlBits[1].replace("?", "&");
        }

        if (!urlQs.match(/&id=([^&])*/)) {
           urlQs = "?id=wsodMarketsHomePlaceholder" + urlQs;  
        } else {
           urlQs = urlQs.replace("&","?");
        }

        if (!urlQs.match(/edition=([^&])*/)) {
            if ( FT.cookies.AYSC ) {
                var aysc14 = FT.cookies.AYSC.match(/.*_14([^_]*)/) ? RegExp.$1 : null;
                var aysc28 = FT.cookies.AYSC.match(/.*_28([^_]*)/) ? RegExp.$1 : null;
            }
            var ayscEdition = aysc28 ? aysc28 : aysc14 ? (aysc14 == "GBR" ? "uk" : "") : "uk";
            urlQs += "&edition=" + ayscEdition;
        }
        var ph = document.getElementById("wsodMarketsHomePlaceholder");
        if ( ph ) {
            ph.innerHTML = "";
        }
        FT.lib.writeScript(urlLocation + urlQs);

    };

    /**
     * Removes the nojs class from the body tag (nojs CSS class is used for specific styles for users without JS)
     */
    this.removeNojsClassFromBody = function() {
        FT.lib.removeClassName(document.body, "nojs");
    };

    /**
     * Adds the "OS_Mac" class to body on Mac operation systems for Chrome, Firefox, Safari and Opera browsers
     */
    this.addOSClassToBody = function(nav) {
        if (/Mac/.test(nav.platform) && ( /Chrome|Firefox/.test(nav.userAgent) || /Apple/.test(nav.vendor) || window.opera )) {
            FT.lib.addClassName(document.body, "OS_Mac");
        }
    };

    /**
     * Adds the rendering engine to body
     */
    this.addRenderingEngineClassToBody = function(nav) {
        var c = "";
        if ( /MSIE/.test(nav.userAgent) ) {
            c = "trident";
        } else if ( /Gecko\//.test(nav.userAgent) ) {
            c = "gecko";
        } else if ( /WebKit\//.test(nav.userAgent) ) {
            c = "webkit";
        } else if ( /Presto\//.test(nav.userAgent) ) {
            c = "presto";
        }
        if ( c ) {
            FT.lib.addClassName(document.body, c);
        }
    };

    this.setBodyClass = function(nav) {
        this.removeNojsClassFromBody();
        this.addOSClassToBody(nav);
        this.addRenderingEngineClassToBody(nav);
    },

    /**
     * Inserts the Brightcove library script (non-blocking).
     */
    this.initialiseBrightcove = function() {
        if ( typeof runMobileCompatibilityScript === "function" ) {
            runMobileCompatibilityScript('myExperience', 'anId');
        }
        FT.lib.writeScriptAsynchronous("http://admin.brightcove.com/js/BrightcoveExperiences.js"); // add _all to get the API calls too
    };

    this.hideSearchboxAd = function() {
        var el = document.getElementById('searchbox');
        if (el) {
            el.style.display = 'none';
        }
    };

    this.attachHideSearchboxAdFunctionality = function() {
        var el = document.getElementById('simpleSearchField');
        if (el) {
            if (el.addEventListener) {
                el.addEventListener('focus', this.hideSearchboxAd, false);
            } else if (el.attachEvent) {
                el.attachEvent('onfocus', this.hideSearchboxAd);
            }
        }
    };

    /* Used to chain functions together e.g. for multiple onload functions
        Also used for Brightcove's onTemplateLoaded chaining.  See also brightcove.js, video.xsl
     */
    this._chain = function(args) {
            return function() {
                for (var i = 0; i < args.length; i++) {
                    args[i]();
                }
            }
        };

    /* Usage example:
        FT.preInit.addToChain("onTemplateLoaded",function(){...})
        FT.preInit.addToChain("onload",function(){...})
    */
    this.addToChain = function(originalFnStr,fn) {
        window[originalFnStr] = typeof (window[originalFnStr]) != 'function' ? fn : this._chain([window[originalFnStr], fn]);
    };
};

FT.preInit = new FT.PreInit();

//=========================================================================
// Originally from Falcon: FT/RenderHeadBundle/Advertising/HTMLAds.js
//=========================================================================
/*globals FT, XMLHttpRequest, alert, navigator, ad, document, window,
clientAds,
*/

/*members HTMLAds, HTMLAdData, urlStem, injectionDiv, injectionClass,
prototype, buildAdURL, match, urlStem, getHTMLAd, open, setRequestHeader,
userAgent, send, status, responseText, getElementById, createElement,
corpCookieMatch, match, cor, lv1, lv2, setAttribute, className, innerHTML,
appendChild, timeOut, setTime, getTime, toGMTString, cookie, location, hostname,
timeOutCookieName,timeOutCookieVal, timeOutCookieLife, log, createCorppopCookie,
type, src, style, display, injectionParentDiv, injectionLegacyParentDiv, toUpperCase,
urlStemClassicAMO, Properties, CORPORATE_AMO_BASE, */

FT.HTMLAds = function () {

	this.HTMLAdData = {
      //directory where all HTML ads are stored
      'urlStem' : 'http://media.ft.com/adimages/banner/',
      'injectionParentDiv' : 'banlb',
      'injectionLegacyParentDiv' : 'ad-placeholder-banlb',
      'injectionDiv' : 'corppop_overlay',
      'injectionClass' : 'corppop_single_occurence',
      'timeOutCookieName' : 'FT_AM',
      'timeOutCookieVal' : 'Check',
      'timeOutCookieLife' : '21600000', // 6 hours in milliseconds (default)
      'urlStemClassicAMO' : 'http://media.ft.com/m/registration.ft.com/corporate/ns/amo/'
	};

};

FT.HTMLAds.prototype.buildAdURL = function (AYSC, regex) {

   var userType = 'anon/';
   var fileName = '';
   var urlStemNewAMO;
   
   //The 98 AYSC cookie value either does not exist/old/IA/A
   var a = AYSC.match(/(_98)([^_]+)/);var val98 = '';if (a) { val98 = a[2];}

   //we get the subscription level from the AYSC field _22
   var m=AYSC.match(/(_22)([^_]+)/);var val22='';if(m){val22=m[2];}else{ userType = 'anon/';}

   //we get the corporate ID from the AYSC field _27
   var n = AYSC.match(/(_27)([^_]+)/);
   var val27 = '';

   if (n) {
      val27 = n[2];
   }

   if((val22.match(regex.cor))||(val22.match(regex.lv1))||(val22.match(regex.lv2))){
    userType = 'subscribed/';
   }else if((val22.match(regex.reg))){
    userType = 'registered/';
   }

   if(val98 === '' || (val98.match(/PVT/))){
       //make sure case is consistent
       val27 = val27.toUpperCase();
       fileName = this.HTMLAdData.urlStem + val27 + '.js';
   } 
   
   else {
	   if ((typeof(FT.Properties.CORPORATE_AMO_BASE) === "undefined") || (FT.Properties.CORPORATE_AMO_BASE === '')){
		  urlStemNewAMO = this.HTMLAdData.urlStemClassicAMO;
	   }
	   else {
	      urlStemNewAMO = FT.Properties.CORPORATE_AMO_BASE;
	   }
       fileName=urlStemNewAMO+userType+val27;
   }

   return fileName;

};

//get the html ad and inject it
FT.HTMLAds.prototype.getHTMLAd = function (adType, inj, fileName) {

   var js = '<script src="' + fileName + '" type="text/javascript">';
   js = js + '/* Do not remove comment */';
   js = js + '</script>';

   //force parent div to display: block, even if no banlb ad is served
   inj.style.display = "block";

   var CorppopDiv = document.getElementById(this.HTMLAdData.injectionDiv);
   var CorppopDivName = this.HTMLAdData.injectionDiv;

   if (CorppopDiv === null) {
	   CorppopDiv = document.createElement("div");
   }

   CorppopDiv.setAttribute("id", CorppopDivName);
   CorppopDiv.className = this.HTMLAdData.injectionClass;

   var Script1 = document.createElement('script');
   Script1.type = 'text/javascript';
   Script1.src = fileName;

   //in case CorppopDiv is null we put in try block, though this should  not happen
   try {

      inj.appendChild(CorppopDiv);

      //seems to be happier if we get this again after appending it to the div
      var corppopDivAgain = document.getElementById(CorppopDivName);
      corppopDivAgain.appendChild(Script1);
   } catch (er2) {
      clientAds.log(er2);
   }

};

//find out from the AYSC cookie whether the user is a corporate subscriber
FT.HTMLAds.prototype.corpCookieMatch = function (AYSC, regex) {

   if (typeof(AYSC) === "undefined") {
      return 0;
   }

   //The 97 AYSC cookie value either does not exist/c
   var c = AYSC.match(/(_97)([^_]+)/);
   var val97 = '';

   if (c) {
      val97 = c[2];
   }

   //The 98 AYSC cookie value either does not exist/old/IA/A
   var a = AYSC.match(/(_98)([^_]+)/);
   var val98 = '';

   if (a) {
      val98 = a[2];
   }

   var m = AYSC.match(/(_22)([^_]+)/);
   var val22 = '';

   if (m) {
      val22 = m[2];
   }

   var n = AYSC.match(/(_27)([^_]+)/);
   var val27 = '';

   if (n) {
      val27 = n[2];
   }
   else {
      //no value for _27 field? Dont attempt to serve an ad
	  return 0;
   }

   if(val98 === '' || (val98.match(/PVT/))){
       //guard against code injection
       if ((val22.match(/[<>]/)) || (val27.match(/[<>]/)) || (val27.match(/RES/)) || (val27.match(/PVT/))){
          return 0;
       }
       else if ((!val27.match(/[0-9A-Za-z]/)) || (val22.match(regex.cor)) || (val22.match(regex.lv1)) || (val22.match(regex.lv2))) {
          //corppop should not be served
          return 0;
       }
   }else if(val98.match(/A/) && !val98.match(/IA/)){
       //guard against code injection
       if ((val22.match(/[<>]/)) || (val27.match(/[<>]/)) || (val27.match(/RES/)) || (val27.match(/PVT/))){
          return 0;
       }
       else if ((!val27.match(/[0-9A-Za-z]/))) {
          //corppop should not be served
          return 0;
       }
       else if (val97.match(/c/)) {
          return 0;
       }
   }else{
       return 0;
   }

   return 1;
};

//create Corppopcookie
FT.HTMLAds.prototype.createCorppopCookie = function (timeoutTime) {

	//If timeout not set in ad, set to 6 hours as default
	if (typeof(timeoutTime) === "undefined") {
		timeoutTime = this.HTMLAdData.timeOutCookieLife;
	}

	var name = this.HTMLAdData.timeOutCookieName;
	var val = this.HTMLAdData.timeOutCookieVal;
	var date = new Date();

	date.setTime(date.getTime() + (timeoutTime));
	var expires = "; expires=" + date.toGMTString();
	document.cookie = name + "=" + val + expires + "; domain=.ft.com; path=/";

};

//only show the ad if the Corppop cookie has timed out and has been deleted
FT.HTMLAds.prototype.timeOut = function (corppopTimeoutCookie,corppopOldTimeoutCookie) {

	//while we transition to the new cookie name we dont want people being served
	//pop ads unless both values are undefined.
	if ((typeof(corppopTimeoutCookie) === "undefined") && (typeof(corppopOldTimeoutCookie) === "undefined")){
		return 1;
	}
	return 0;
};
//=========================================================================
// Originally from Falcon: FT/RenderHeadBundle/Advertising/DartForPublishers.dfp-on.js
//=========================================================================
// NOTE: TEMPORARILY THE FTCOMBASE AND PHOENIX ADS LIBRARIES DEPEND ON THIS FILE
// IF YOU MAKE BUG FIXES HERE, YOU NEED TO REBUILD THE FTCOMBASE AND PHOENIX AD
// LIBRARIES. THIS SHOULD END WHEN FALCON HAVE REDESIGNED THE THIRD PARTY WRAPPERS
// AND ARTICLE PAGES.
//    http://epcvs.osb.ft.com/twiki/bin/view/Projects/DartForPublishers#Legacy_Ads_Libraries_FTCOMBASE_a

/*jslint white: true, browser: true, undef: true, plusplus: false, onevar: false,
    nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true,
    immed: true, maxerr: 1000, indent: 3
*/

/*allowlint All 'debugger' statements should be removed */

/*globals Advert: true, AdvertDE, FT, Math, RegExp, clearInterval, clearTimeout,
    doTrackRefresh, document, parseInt, setInterval, setTimeout, window,
    escape, unescape, clientAds: true, pageUUID, getUUIDFromString
*/

/*members "-", "02", "05", "06", "07", "14", "15", "19", "20", "21",
    "27", AD_SERVERS, AdFormat, Advertising, CONST, ENV, FTQA, FT_U,
    KeyOrder, KeyOrderVideo, KeyOrderVideoExtra, KeyOrderVideoSync,
    Properties, SubsLevelReplaceLookup, VERSION, a, adName, adServerCountry,
    ad_server, addClassName, addDiagnostic, addNewAttributes,
    additionalAdTargetingParams, ads, adverts, alt, altText, appendChild,
    asset, audSciInitial, audSciMax, banlb, baseAdvert, beginNewPage,
    beginVideo, body, breakout, buildURL, buildURLForVideo,
    buildURLFromBaseAdvert, buildURLIst, call, callType, callback, charAt,
    checkAdServerCountry, checkAdState, checkSiteZone,
    checkSubmitLongestUrl, className, clearAllIntervals, clearAllTimeouts,
    clearBaseAdvert, clearTimer, clickURL, clientHeight, clientWidth, cn,
    collapse, collapsePositionIfNoAd, collapsed, complete, console,
    constructor, content, cookie, cookies, cor, createAdRequestFromVideoUrl,
    createElement, dcopt, debug, decodeAudSci, detectAdMode,
    detectDFPTargeting, detectERights, dfp_site, dfp_zone, diagnostics,
    display, div, doublet, duplicateEID, edt, eid, encodeAudSci,
    encodeBaseAdvertProperties, endVideo, env, erightsID, excludeFields,
    exclusions, expand, extend, extendBaseAdvert, extraAds, fetch,
    fieldRegex, fieldSubstr, floor, foreach, fromBase36, getAdContainer,
    getAdFormat, getAyscVars, getCookie, getDFPSite, getElementById,
    getElementsByTagName, getKeys, getLongestUrl, getNamedAdContainer,
    getNormalAdverts, getVideoAdverts, getVideoSyncAdverts,
    handleRefreshLogic, hasAdClass, hasCalledInitDFP, hasClassName, hasDiv,
    hasInterstitial, hasOwnProperty, height, hlfmpu, href, id, imageURL,
    indexOf, init, initDFP, initialHTML, injectUnclassifiedTrackCall,
    injectUrlTrackCall, innerHTML, inputUrl, insertAdIntoIFrame,
    insertAdRequest, insertBefore, insertNewAd, inserted, int, intervals,
    intro, isLegacyAPI, isSystemDefault, isUnclassified,
    join, leading_zero_key_names, length, lib, location, log, lv1, lv2,
    marginTop, match, minHeight, mktsdata, mpu, mpusky, name, newssubs,
    noImageClickContent, noTargetDiv, offsetHeight, opera, ord, parentNode,
    pos, postError, prepareAdVars, prepareBaseAdvert, prototype,
    proxy_div_prefixes, push, random, refresh, refreshTimer, reg,
    regex_key_names, register, removeChild, removeClassName,
    remove_exes, remove_res_pvt, render, renderImage,
    rendered, replace, request, requestDFP, requestInsertedAds,
    requestNewssubs, requestUrl, requestVideoSync, requestsInterstitial,
    resetLibrary, response, rsiSegs, rsi_segs, runinterval, setAttribute,
    setDefaultSiteZone, setInitialAdState, shift,
    shouldSubmitToTrack, showCookies, showDiagnostics, slice, slv, sort,
    split, src, startRefreshTimer, state, storeResponse, stripLeadingZeros,
    style, submitToTrack, substr_key_names, substring, sz, target, test,
    tile, timeIntervalTolerance, timeoutTolerance, timeouts, tlbxrib,
    toBase36, toLowerCase, toString, toUpperCase, trackUrl, type, u,
    uParamDataLoss, uParamMax, uParamSizeException, unshift, urlMax,
    urlStem, urlThreshold, urlThresholdMax, useDFP, verticallyAligned,
    video, videoAdverts, watchAdPosition, wdesky, width, write, writeScript,
    cleanKeywords, getKeywordsParam, prepareKeywordsParam, url_location, kw,
    dfp_targeting, search, vidbut1, vidbut2, vidbut3, isComplete, isEmptyAd,
    isAdStateEmpty, marketingrib, lhn, tradcent, expandPositionIfAd,
    setZeroHeight, legacyAdCollapse, legacyAdFixup, shouldAdBeZeroHeight,
    padding, library, marginBottom, marginLeft, paddingLeft, border, alwaysHide,
    getAdInnerHTML, getAdContainers, legacyAdCalls, legacyFetchIsAllowed,
    legacyEnableFetch, clientWidth, legacyWatchAdPosition, legacyClearInterval,
    legacyStopInterval, legacyConWidth, suppressAudSci, AYSC, iterator, HTMLAds,
    corppop, corpCookieMatch, timeOut, CorpPopTimeout, buildAdURL, getHTMLAd,
    injectionLegacyParentDiv, injectionParentDiv, HTMLAdData, FT_AM, minivid, prepareUParams,
    uKeyOrder, uuid, ts, getTimestamp, getMonth, getDate, getHours, getMinutes, 
    getSeconds, getFullYear, searchbox, getDFPTargeting, getReferrer, isArticle, referrer,
    exec,bht

*/

/*
   The Falcon Ads API follows from here.
*/



FT.Advertising = function () {
   this.baseAdvert = {};

   // Set up DFP specific constants
   this.CONST = {};

   // Regex to match valid DFP ad server two letter codes at this time.
   this.CONST.AD_SERVERS = /^((a[lutre])|(b[rsgaye])|(c[nohazl])|(d[ke])|(e[gse])|(f[ri])|(g[rt])|(h[ukr])|(i[stlne])|(j[p])|(k[wr])|(l[uvt])|(m[yxaekd])|(n[olz])|(p[lthk])|(r[suo])|(s[gekai])|(t[rhw])|(u[kas])|(v[e])|(z[a]))$/i;

   // Map ad position names to ad properties.
   // sz= allowable ad sizes in this position.
   // dcopt= doubleclick options. ist means interstitial ad - only one allowed per page
   // You can omit subsequently numbered positions if they match the formatting of the unnumbered ad position.
   this.CONST.AdFormat = {
      'intro':        { 'sz': '1x1' },
      'banlb':        { 'sz': '468x60,728x90', 'dcopt': 'ist' },
      'newssubs':     { 'sz': '239x90' },
      'tlbxrib':      { 'sz': '336x60' },
      'marketingrib': { 'sz': '336x60' },
      'lhn':          { 'sz': '136x64' },
      'tradcent':     { 'sz': '336x260' },
      'mktsdata':     { 'sz': '88x31,75x25' }, // also matches mktsdata2 and mktsdata3
      'hlfmpu':       { 'sz': '300x600,336x850,300x250,336x280' },
      'doublet':      { 'sz': '342x200' },
      'refresh':      { 'sz': '1x1' },
      'mpu':          { 'sz': '300x250,336x280' },
      'mpusky':       { 'sz': '300x250,336x280,160x60' },
      'wdesky':       { 'sz': '160x600' },
      'video':        { 'sz': '592x333' },
      'minivid':      { 'sz': '400x225' },
      'vidbut1':      { 'sz': '120x29' }, // each vidbut is a different size so needs
      'vidbut2':      { 'sz': '100x50' }, // its own entry in the table.
      'vidbut3':      { 'sz': '200x50' },
      'searchbox':   { 'sz': '200x28'},
      '-':            {}
   };

   this.CONST.KeyOrder =           ['sz', 'dcopt', '07', 'a', '06', '05', '27', 'eid', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '19', '20', '21', 'slv', '02', '14', 'cn', '01', 'kw', 'u', 'pos', 'bht','tile', 'ord'];
   this.CONST.KeyOrderVideo =      ['sz', 'dcopt', 'pos'];
   this.CONST.KeyOrderVideoExtra = ['dcopt', 'brand', 'section', 'playlistid', 'playerid', '07', 'a', '06', 'slv', 'eid', '05', '19', '21', '27', '20', '02', '14', 'cn', '01', 'u'];
   this.CONST.KeyOrderVideoSync =  ['sz', 'dcopt'];
   this.CONST.uKeyOrder			=  ['eid', 'uuid', 'ts'];

   // filter constants for AYSC cookies
   this.CONST.exclusions = ['key=03', 'key=04', 'key=08', 'key=09', 'key=10', 'key=11', 'key=12', 'key=13', 'key=15', 'key=16', 'key=17', 'key=18', 'key=22', 'key=23', 'key=24', 'key=25', 'key=26', 'key=28', 'key=29', 'key=30'];
   this.CONST.leading_zero_key_names = [ '19', '21' ];
   this.CONST.remove_exes = {'02': 1, '05': 1, '06': 1, '07': 1, '19': 1,  '20': 1, '21': 1};
   this.CONST.remove_res_pvt = {'14': 1, 'cn': 1, '27' : 1};
   this.CONST.regex_key_names = ['22'];

   this.CONST.SubsLevelReplaceLookup = {
      'edt': /^edit$/,
      'int': /^Ftemp$/,
      'cor': /^[PL][01][PL]*[1]*[PL][12][A-Za-z][A-Za-z]/,
      'lv1': /^[PL]1[A-Za-z][A-Za-z]/,
      'lv2': /^[PL]2[A-Za-z][A-Za-z]/,
      'reg': /^[PL]0[A-Za-z][A-Za-z]/
   };
   // format for creating a new key name and value from an old one using substring is: 'old key=start substring=no characters in substring=new key'
   this.CONST.substr_key_names = ['24=0=3=cn'];
   // proxy names for ad divs
   this.CONST.proxy_div_prefixes = ['', 'ad-placeholder-', 'ad-container-'];

   this.CONST.audSciMax = 20;
   this.CONST.audSciInitial = 20;
   this.CONST.uParamMax = 253;
   this.CONST.urlMax = 511;

   // required for checkSubmitLongestUrl function
   this.CONST.urlThresholdMax = 2000000;
   this.CONST.urlThreshold = 10000;
   this.CONST.trackUrl = "http://track.ft.com/track/dfp_error.gif";
};

if (FT.lib) {
   FT.ads = new FT.Advertising();
}
if (FT.HTMLAds) {
	FT.corppop = new FT.HTMLAds();
}

/*
   Override the FT.ads.request() call so we can detect if we are on a Falcon
   page which does not use the legacy API. The first call on a page is either
   new Advert() [legacy API] or FT.ads.request() [Falcon API]
*/

// TESTED in expand-collapse-test.html
// This can be replaced by requestDFP() once the legacy AD API is removed. (BSAC 09/2010)
FT.Advertising.prototype.request = function (pos)
{
   // If this ever gets called, we know we are not using the Legacy API
   FT.env.isLegacyAPI = false;
   clientAds.log("FT.Advertising.prototype.request(" + pos + ")");
   this.initDFP();

   this.requestDFP(pos);
};

// Return a list of ad positions which are video pre-roll ads
// TESTED in video_test.html
FT.Advertising.prototype.getVideoAdverts = function ()
{
   var Ads = [];
   this.foreach(this.adverts, function (pos)
   {
      if ((this.adverts[pos].callType === 'video')  || (this.adverts[pos].callType === 'minivid'))
      {
         Ads.push(pos);
      }
   });
   return Ads;
};

// Return a list of ad positions which are synchronized to the video ad
// TESTED in video_test.html
FT.Advertising.prototype.getVideoSyncAdverts = function ()
{
   var Ads = [];
   this.foreach(this.adverts, function (pos)
   {
      if (this.adverts[pos].callType === 'videoSync')
      {
         Ads.push(pos);
      }
   });
   return Ads;
};

// Return a list of ad positions which are normal ads (ie non-video)
// TESTED in video_test.html
FT.Advertising.prototype.getNormalAdverts = function ()
{
   var Ads = [];
   this.foreach(this.adverts, function (pos)
   {
      if (this.adverts[pos].callType === 'normal')
      {
         Ads.push(pos);
      }
   });
   return Ads;
};

// TESTED in video_test.html
FT.Advertising.prototype.register = function (pos)
{
   clientAds.log("FT.Advertising.prototype.register(" + pos + ")");
   if (!FT.ads.hasCalledInitDFP)
   {
      FT.env.isLegacyAPI = false;
      this.initDFP();
   }

   this.adverts[pos] = this.adverts[pos] || {};
   this.adverts[pos].callType = 'videoSync';
   if (!this.videoAdverts) {
      this.videoAdverts = [];
   }
   this.videoAdverts.push(pos);
};

// TESTED in video_test.html
FT.Advertising.prototype.beginVideo = function ()
{
   this.beginNewPage();
};

// DFP specific ad request method
// Request the named ad, by document.writing a script tag (blocks page rendering)
// This can be renamed request() once the Legacy Ad API is removed. (BSAC 09/2010)
// TESTED in expand-collapse-test.html
FT.Advertising.prototype.requestDFP = function (pos)
{
   var URL = '';
   
   if (pos === 'corppop') {

      var location = document.location.href;
      if(!location.match(/Authorised=false/)) {

        if (typeof FT.cookies.AYSC === 'undefined')
          FT.cookies = FT.lib.hashCookies();

        var ASYC_OK = FT.corppop.corpCookieMatch(FT.cookies.AYSC, FT.ads.CONST.SubsLevelReplaceLookup);
        var TIME_OK = FT.corppop.timeOut(FT.cookies.FT_AM, FT.cookies.CorpPopTimeout);
        var injectionPoint = (FT.env.isLegacyAPI) ? FT.corppop.HTMLAdData.injectionLegacyParentDiv : FT.corppop.HTMLAdData.injectionParentDiv;
      
        var inj = document.getElementById(injectionPoint);
      
        //we only call the corppop ad based on the AYSC field _22 and _27 values,
        //if the FT_AM ( +/- CorppopTimeout) cookie has expired,
        //and a banlb div is present on the page
        if ((ASYC_OK) && (TIME_OK) && (inj !== null))
        {
             URL = FT.corppop.buildAdURL(FT.cookies.AYSC,FT.ads.CONST.SubsLevelReplaceLookup);
            FT.corppop.getHTMLAd(pos, inj, URL);
        }
        }
   }
   
   else {
	   
	  clientAds.log("FT.Advertising.requestDFP(" + pos + ")");
	  this.setInitialAdState(pos);

      URL = this.buildURL(pos);
      if (URL) {
         var self = this;
         FT.lib.writeScript(URL);
         if (this.adverts[pos].state.alwaysHide)
         {
            this.collapse(pos);
         }
         else
         {
            clientAds.log("setting up anon_timeout(" + pos + ") " + this.timeoutTolerance);
            this.timeouts[pos] = setTimeout(function () {
               clientAds.log("called anon_timeout(" + pos + ")");
               self.collapsePositionIfNoAd(pos);
            }, this.timeoutTolerance);
         }
      }
     
   }

   this.addDiagnostic(pos, { "requestUrl": URL });
}; // requestDFP(pos)

// TESTED in dfp-advertising.html
FT.Advertising.prototype.foreach = function (obj, func)
{
   
   if (!obj || typeof obj === "function")
   {
      return;
   }
   // check if we are iterating over an array
   if (obj.length)
   {
      for (var idx = 0, length = obj.length; idx < length; idx++)
      {
         var value = typeof obj === "string" ? obj.charAt(idx) : obj[idx];
         if (func.call(this, value, idx) === false)
         {
            break;
         }
      }
   }
   else
   {
      //otherwise we are iterating over an object
      for (var prop in obj)
      {
         if (obj.hasOwnProperty(prop))
         {
            var what = FT.lib.type(obj[prop]);
            if (!what.match(/^function$/))
            {
               if (func.call(this, prop, obj[prop]) === false)
               {
                  break;
               }
            }
         }
      }
   }
};

// TESTED dfp-advertising.html
FT.Advertising.prototype.getCookie = function (cookieName)
{
   var cookie = FT.cookies[cookieName];
   if (cookie)
   {
      return cookie.replace(/%3D/g, "=");
   }
   return undefined;
}; // getCookie(cookieName)

// Get ad format info for named ad position
// TESTED dfp-advertising.html
FT.Advertising.prototype.getAdFormat = function (pos)
{
   var rFormat;
   if (this.CONST.AdFormat[pos])
   {
      rFormat = this.CONST.AdFormat[pos];
   }
   else if (/\d+$/.test(pos))
   {
      // numbered positions - if mpu2 isn't in the table, strip off the trailing digits and look for mpu
      var posStem = pos.replace(/\d+$/, "");
      if (this.CONST.AdFormat[posStem])
      {
         rFormat = this.CONST.AdFormat[posStem];
      }
   }

   return rFormat;
}; // getCookie(cookieName)

// TESTED in dfp-advertising.html
FT.Advertising.prototype.setInitialAdState = function (pos, callType)
{
   callType = callType || 'normal';
   this.adverts[pos] = this.adverts[pos] || {};
   this.adverts[pos].callType = callType;
   this.adverts[pos].state = {
      'state':           'init',
      'hasDiv':          false,
      'alwaysHide':      false,
      'requestsInterstitial': false,
      'hasInterstitial': false,
      'isSystemDefault': false,
      'isEmptyAd':       false,
      'initialHTML':     ''
   };
   
   if (pos === 'refresh')
   {
      this.adverts[pos].state.alwaysHide = true;
   }
   var adHTML = this.getAdInnerHTML(pos);
   if (typeof adHTML !== 'undefined')
   {
      this.adverts[pos].state.hasDiv = true;
      this.adverts[pos].state.initialHTML = adHTML;
   }
};

// TESTED in dfp-advertising.html
// An ad is empty if it's the system default ad, the FT no ad filler and
// it doesn't have an interstitial ad riding in its position
FT.Advertising.prototype.isAdStateEmpty = function (state)
{
   var empty = false;
   if (state.isSystemDefault || state.isEmptyAd)
   {
      if (!state.hasInterstitial)
      {
         empty = true;
      }
   }
   return empty;
};

// TESTED in dfp-advertising.html
// An ad should be zero height if it has an interstitial riding in it but
// no actual ad content (only system default or FT no ad filler)
FT.Advertising.prototype.shouldAdBeZeroHeight = function (state)
{
   var beZero = false;
   if (state.isSystemDefault || state.isEmptyAd)
   {
      if (state.hasInterstitial)
      {
         beZero = true;
      }
   }
   return beZero;
};

// TESTED -- NOT
FT.Advertising.prototype.createAdRequestFromVideoUrl = function (pos, url)
{
   this.clearBaseAdvert();
   this.prepareBaseAdvert(pos);
   var URL = this.buildURLFromBaseAdvert('videoSync');
   URL = URL.replace(/\?$/, '');

   var requestURL = url.replace(/;pos=\w+/, ';pos=' + pos);
   requestURL = requestURL.replace(/^[^\s]+;sz=\d+x\d+(,\d+x\d+){0,3}(;dcopt=ist)?/, URL);
   requestURL = requestURL.replace(/;tile=(\d{1,2})/, ';tile=' + this.baseAdvert.tile);

   return requestURL;
};

// TESTED -- NOT
FT.Advertising.prototype.insertAdIntoIFrame = function (pos, requestURL)
{
   var el = this.getAdContainer(pos).div;
   if (!el)
   {
      return undefined;
   }
   var iframeId = pos + '_iframe';

   var html = ['<iframe id="', iframeId, '"',
      ' width="', el.clientWidth, '"',
      ' height="', el.clientHeight, '"></iframe>'].join('');
   el.innerHTML = html;
   document.getElementById(iframeId).src = requestURL;
};

// TESTED in video_test.html
FT.Advertising.prototype.requestVideoSync = function (pos, url)
{
   if (!this.getAdFormat(pos))
   {
      this.addDiagnostic(pos, {
         'requestVideoSync': 'ad position not valid'
      });
      return undefined;
   }

   this.setInitialAdState(pos);
   var requestURL = this.createAdRequestFromVideoUrl(pos, url);
   this.adverts[pos].callType = 'videoSync';
   this.addDiagnostic(pos, {
      inputUrl: url,
      requestUrl: requestURL
   });

   this.insertAdIntoIFrame(pos, requestURL);

   return requestURL;
};

// TESTED in video_test.html
FT.Advertising.prototype.endVideo = function () {
   // due to same origin security policies this method is
   // stubbed and will not be used to collapse ads
   return;
};

FT.Advertising.prototype.handleRefreshLogic = function (obj, timeout)
{
   clientAds.log("FT.Advertising.prototype.handleRefreshLogic(" + obj.name + ", " + timeout + ")");
   // TODO: no test case for this yet.
   timeout = timeout || 30 * 60 * 1000;  // give it 30 minutes
   if ((obj.name === 'refresh') && (FT.env.asset === 'page'))
   {
      obj.refreshTimer = timeout;
   }
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.checkAdState = function (pos)
{
   clientAds.log("FT.Advertising.prototype.checkAdState(" + pos + ")");
   //TODO how are we getting here without having the .state defined???
   this.adverts[pos] = this.adverts[pos] || { 'state': {} };
   var rState = this.adverts[pos].state;
   var adHTML = this.getAdInnerHTML(pos);
   if (typeof adHTML !== 'undefined')
   {
      rState.hasDiv = true;

      rState.innerHTML = adHTML;
      if (rState.innerHTML !== rState.initialHTML)
      {
         rState.state = 'changed';
      }
      if (rState.state === 'changed')
      {
         if (/817-grey/.test(rState.innerHTML))
         {
            rState.isSystemDefault = true;
         }
         if (/ft-no-ad-/.test(rState.innerHTML))
         {
            rState.isEmptyAd = true;
         }
         var rRegex = new RegExp("<!--\\s*Begin Interstitial Ad\\s*-->");
         if (rRegex.test(rState.innerHTML))
         {
            rState.hasInterstitial = true;
         }
      }
   }
   clientAds.log("FT.Advertising.prototype.checkAdState(" + pos + ") " + [rState.state, "hasDiv: " + rState.hasDiv, "isSystemDefault: " + rState.isSystemDefault, "isEmptyAd: " + rState.isEmptyAd, "requestsInterstitial: " + rState.requestsInterstitial, "hasInterstitial: " + rState.hasInterstitial].join(", "));
};

// TESTED in expand-collapse-test.html
FT.Advertising.prototype.collapsePositionIfNoAd = function (pos)
{
   clientAds.log("FT.Advertising.prototype.collapsePositionIfNoAd(" + pos + ")");
   this.checkAdState(pos);
   var rState = this.adverts[pos].state;

   // System Default ad or the FT no ad both indicate there is no ad present
   if (this.shouldAdBeZeroHeight(rState))
   {
      this.collapse(pos, true);
   }
   else if (this.isAdStateEmpty(rState))
   {
      this.collapse(pos);
   }
   else if (rState.state === 'init')
   {
      this.collapse(pos);
      this.watchAdPosition(pos);
   }
   else
   {
      this.expand(pos);
   }
};

// TESTED in expand-collapse-test.html
FT.Advertising.prototype.expandPositionIfAd = function (pos)
{
   clientAds.log("FT.Advertising.prototype.expandPositionIfAd(" + pos + ")");
   this.checkAdState(pos);
   var rState = this.adverts[pos].state;
   // Must expand if there is an interstitial or the ad slot is not 'empty'
   if (!this.isAdStateEmpty(rState))
   {
      clientAds.log("clearing anon_interval(" + pos + ") - is interstitial or not system default");
      this.expand(pos);
      clearInterval(this.intervals[pos]);
   }
};

// TESTED in expand-collapse-test.html
FT.Advertising.prototype.watchAdPosition = function (adPos)
{
   clientAds.log("FT.Advertising.prototype.watchAdPosition(" + adPos + ")");
   // A Closure below so protect the vars we had passed in.
   var self = this;
   var pos = adPos;

   clientAds.log("setting up anon_interval(" + pos + ") " + self.timeIntervalTolerance);
   self.intervals[pos] = setInterval(function ()
   {
      clientAds.log("called anon_interval(" + pos + ")");
      self.expandPositionIfAd(pos);
   }, self.timeIntervalTolerance);
};

// TESTED in expand-collapse-test.html
FT.Advertising.prototype.clearAllTimeouts = function ()
{
   clientAds.log("FT.Advertising.prototype.clearAllTimeouts()");
   this.foreach(this.timeouts, function (pos, id) {
      clearTimeout(id);
   });
};

// TESTED in expand-collapse-test.html
FT.Advertising.prototype.clearAllIntervals = function ()
{
   clientAds.log("FT.Advertising.prototype.clearAllIntervals()");
   this.foreach(this.intervals, function (pos, id) {
      clearInterval(id);
   });
};

FT.Advertising.prototype.clearTimer = function ()
{
   clientAds.log("FT.Advertising.prototype.clearTimer()");
   // stubbed out for the moment.
};

// Mocked method to prevent homepage from breaking.
// TESTED in ad-on-a-page.html
FT.Advertising.prototype.complete = function ()
{
   clientAds.log("FT.ads.complete() " + this.isComplete);
   if (!this.isComplete)
   {
      // Note, jsLint might say that this is better written as this.adverts.refresh
      // but don't believe it. Writing it that way will cause a javascript error
      // in IE in preditor when the refresh position is not present on a particular page.
      if (this.adverts['refresh'])
      {
         this.legacyAdCollapse('refresh', false);
      }
      this.injectUnclassifiedTrackCall();
      this.injectUrlTrackCall();
   }
   this.isComplete = true;
};

// Ad response callback
// This method is only called when the adserver returns a json response
FT.Advertising.prototype.callback = function (rResponse)
{
   if (!rResponse || typeof rResponse !== "object" || !rResponse.name)
   {
      clientAds.log("FT.Advertising.callback(" + rResponse + ") - improper");
      return false;
   }
   clientAds.log("FT.Advertising.callback(" + [rResponse.name, rResponse.type, rResponse.adName].join(", ") + ")");

   // Update status of ad position.
   this.checkAdState(rResponse.name);

   // Store the response obj for diagnostics
   this.storeResponse(rResponse);
   // Process instructions
   if (rResponse.addNewAttributes)
   {
      this.extendBaseAdvert(rResponse.addNewAttributes);
   }
   if (rResponse.insertAdRequest)
   {
      this.insertNewAd(rResponse.insertAdRequest);
   }

   var radix; // to satisfy jslint
   if (parseInt(rResponse.refreshTimer, radix) > 0)
   {
       this.startRefreshTimer(rResponse.refreshTimer);
   }

   // Handle ad types
   if (rResponse.type)
   {
      switch (rResponse.type)
      {
      case "empty":
         if (!this.adverts[rResponse.name].state.requestsInterstitial)
         {
            // An ad position which requests an intersitial must be monitored for
            // a while longer to see if the interstitial comes along.
            clientAds.log("anon_timeout(" + rResponse.name + ") is being cancelled");
            clearTimeout(this.timeouts[rResponse.name]);
         }
         this.collapse(rResponse.name);
         this.addDiagnostic(rResponse.name,
         {
            "collapsed": "emptyAd"
         });
         break;
      case "imageclick":
         // I Don't think this type of ad is booked anywhere any more. this
         // can perhaps be removed. (BSAC 09/2010)
         this.renderImage(rResponse);
         break;
      default:
         // Ad content is outside of response and is in page
         break;
      }
   }
}; // callback(rResponse)

// Store response obj
FT.Advertising.prototype.storeResponse = function (rResponse)
{
   clientAds.log("FT.Advertising.storeResponse(" + [rResponse.name, rResponse.type, rResponse.adName].join(", ") + ")");
   if (FT.lib.type(rResponse) !== "object")
   {
      return false;
   }
   if (FT.lib.type(this.adverts[rResponse.name]) !== "object")
   {
      this.adverts[rResponse.name] = {};
   }
   this.adverts[rResponse.name].response = rResponse;
}; // storeResponse()

// Return all the properties of an object as an array
// TODO This is a candidate for the Lib.js
// TESTED in dfp-advertising.html
FT.Advertising.prototype.getKeys = function (rResponse)
{
   var Keys = [];
   if (FT.lib.type(rResponse) === 'object')
   {
      this.foreach(rResponse, function (prop)
      {
         Keys.push(prop);
      });
   }
   return Keys.sort();
}; // getKeys(rResponse)

// Check a space separated class name for the class named
// TESTED in dfp-advertising.html
FT.Advertising.prototype.hasClassName = function (fullClass, className)
{
   var matcher = className.constructor === RegExp ? className : new RegExp('^' + className + '$');
   var Classes = fullClass.split(' ');
   for (var idx = 0; idx < Classes.length; ++idx)
   {
      if (Classes[idx].match(matcher) !== null)
      {
         return true;
      }
   }
   return false;
};

// Adds diagnostic info to stored response
FT.Advertising.prototype.addDiagnostic = function (pos, rDiagObj)
{
   if (!pos)
   {
      pos = '_anonymous';
   }
   if (FT.lib.type(pos) !== "string" || FT.lib.type(rDiagObj) !== "object")
   {
      return false;
   }
   clientAds.log("FT.Advertising.addDiagnostic(" + pos + ", " + this.getKeys(rDiagObj).join(", ") + ")");
   if (!this.adverts[pos])
   {
      this.adverts[pos] = {
         "diagnostics": {}
      };
   }
   this.adverts[pos].diagnostics = FT.lib.extend(this.adverts[pos].diagnostics, rDiagObj);
}; // addDiagnostic(pos, rDiagObj)

// Extend base advert with nodes and values from advert json
FT.Advertising.prototype.extendBaseAdvert = function (rResponse)
{
   clientAds.log("FT.Advertising.extendBaseAdvert(" + rResponse + ")");
   this.baseAdvert = FT.lib.extend(this.baseAdvert, rResponse);
}; // extendBaseAdvert(rResponse)

// Insert a new advert in the queue
FT.Advertising.prototype.insertNewAd = function (pos)
{
   clientAds.log("FT.Advertising.insertNewAd(" + pos + ")");
   this.extraAds.unshift(pos);
   this.addDiagnostic(pos, {
      "inserted": true
   });
}; // insertNewAd(pos)

// TESTED dfp-advertising.html
FT.Advertising.prototype.setDefaultSiteZone = function ()
{
   FT.env.dfp_site = "ftcom.5887.unclassified";
   FT.env.dfp_zone = "unclassified";
}; // setDefaultSiteZone()

// TESTED dfp-advertising.html
FT.Advertising.prototype.isUnclassified = function ()
{
   var result = false;
   if ((FT.env.dfp_site === "ftcom.5887.unclassified" || FT.env.dfp_site === "test.5887.unclassified") && FT.env.dfp_zone === "unclassified")
   {
      result = true;
   }
   return result;
}; // setDefaultSiteZone()

// Check if the ad position and site/zone are good.
// Returns
//    'ok'       if all is well
//    'invalid'  if ad position is invalid and so cannot make an ad call
//    'default'  if site/zone were invalid and have been set to a usable default
// TESTED dfp-advertising.html
FT.Advertising.prototype.checkSiteZone = function (pos)
{
   var ok = 'default';
   var fix = true;
   var reason_why;

   var rFormat  = this.getAdFormat(pos);
   var site = this.getDFPSite();
   if (!rFormat)
   {
      reason_why = "invalid ad slot name";
      fix = false;
      ok = 'invalid';
   }
   // If we don't have good DFP targeting variables make a note of it and use a default
   else if (!this.detectDFPTargeting())
   {
      reason_why = "dfp_site/zone are invalid";
   }
   else if (site.length > 31)
   {
      reason_why = "DFP site name too long: " + site;
   }
   else if (FT.env.dfp_zone.length > 32)
   {
      reason_why = "DFP zone name too long: " + FT.env.dfp_zone;
   }
   else if (site.match(/^X+$/i))
   {
      reason_why = "DFP site name is default methode metadata";
   }
   else if (FT.env.dfp_zone.match(/^X+$/i))
   {
      reason_why = "DFP zone name is default methode metadata";
   }
   else if (!site.match(/^\w+\.5887\.[\-\w]+$/))
   {
      reason_why = "DFP site name is not the FT network: " + site;
   }
   else
   {
      ok = 'ok';
      fix = false;
   }
   if (ok !== 'ok')
   {
      this.addDiagnostic(pos, { "checkSiteZone": reason_why });
   }
   if (fix)
   {
      // No valid DFP targeting, use the unclassified site/zone so we can detect this in Ad reports
      this.setDefaultSiteZone();
   }

   return ok;
};

FT.Advertising.prototype.clearBaseAdvert = function ()
{
   //make sure the base ad doesnt inherit values from previous ads
   for (var idx = 0; idx < this.CONST.KeyOrder.length; idx++)
   {
      var keyname = this.CONST.KeyOrder[idx];
      if ((keyname !== 'tile') && (keyname !== 'ord'))
      {
         delete this.baseAdvert[keyname];
      }
   }
};

FT.Advertising.prototype.prepareAdVars = function (AllVars)
{
   //now we filter the AYSC values prior to adding them to the baseAdvert
   //define fields where we need to strip leading zeros - should be in config
   AllVars = this.stripLeadingZeros(this.CONST.leading_zero_key_names, AllVars);

   //now assign new corporate codes based on certain regex of old code values
   AllVars = this.fieldRegex(this.CONST.regex_key_names, AllVars);

   //now take a substring of an input value - used for creating new continent codes - put in config?
   AllVars = this.fieldSubstr(this.CONST.substr_key_names, AllVars);

   //now add any erights value from the FT_U cookie
   AllVars = this.detectERights(AllVars);

   return AllVars;
};

FT.Advertising.prototype.erightsID = function ()
{
   if (!FT.cookies.FT_U) {
      return undefined;
   } else {
      var eid = FT.cookies.FT_U.match(/_EID\=(\d+)_/i);
      if (eid[1]) {
         return eid[1].replace(/^0*/, "");
      } else {
         return undefined;
      }
   }
};

FT.Advertising.prototype.prepareUParams = function () 
{
      var	uValue = ''; 
      var	uOrder = this.CONST.uKeyOrder;
      this.foreach(uOrder, function (key) {
            var value = this.baseAdvert[key];
            if (value) {
               uValue += !value ? '' : key + '=' + value + ',';
            }
         });
      
      if (uValue !== '') {
         uValue = uValue.slice(0, -1);
         if (uValue.length > this.CONST.uParamMax) {
            this.addDiagnostic(this.baseAdvert.pos, {
               'uParamSizeException': 'Maximum length of u (' + this.CONST.uParamMax + ') exceeded. Got ' + uValue.length
            });
            this.addDiagnostic(this.baseAdvert.pos, {
               'uParamDataLoss': 'u parameter data loss [' + uValue.slice(this.CONST.uParamMax) + ']'
            });
         }
         return uValue;
      }
      return undefined;			
}; 

FT.Advertising.prototype.duplicateEID = function (eid)
{
   if (eid) {
      var u = "eid=" + eid;
      if (u.length > this.CONST.uParamMax) {
         this.addDiagnostic(this.baseAdvert.pos, {
            'uParamSizeException': 'Maximum length of u (' + this.CONST.uParamMax + ') exceeded. Got ' + u.length
         });
         this.addDiagnostic(this.baseAdvert.pos, {
            'uParamDataLoss': 'u parameter data loss [' + u.slice(this.CONST.uParamMax) + ']'
         });
      }
      return "eid=" + eid;
   }
   return undefined;
};

FT.Advertising.prototype.rsiSegs = function () {
   if (!this.suppressAudSci && FT.cookies.rsi_segs) {
      var results = [];
      this.foreach(FT.cookies.rsi_segs.split('|'), function (value) {
         results.push(this.encodeAudSci(value));
      });
      return results;
   }
   return undefined;
};

FT.Advertising.prototype.getAyscVars = function (obj) {
   var out = {};
   if (FT.cookies.AYSC !== undefined) {
      var q =  FT.cookies.AYSC.split("_");
      FT.lib.iterator(q, function (item) {
         // is this '!!item' an idiom to avoid having to say typeof item !== 'undefined' ?
         // i.e. to avoid suffering a JS error in the browser whose name cannot be spoken?
         if (!!item)
         {
            // AYSC cookie is formatted as _NNdata_NNdata_NNdata_
            // Where NN is two digit field number and data is anything except '_'
            var m = item.match(/^(\d\d)([^_]+)/);
            if (m) {
               var key = m[1];
               var val = m[2];
               out[key] = val;
            }
         }
      });
   }
   return FT.lib.extend(obj, out);
};

FT.Advertising.prototype.prepareBaseAdvert = function (pos)
{
   // get AYSC cookie values to determine ad server
   var AllVars = this.prepareAdVars(this.getAyscVars({}));
   this.baseAdvert.pos = pos;
   this.baseAdvert.ad_server = this.adServerCountry(AllVars['15'], pos);

   // now lets exclude fields based on either key names or values
   AllVars = this.excludeFields(this.CONST.exclusions, AllVars);

   this.foreach(AllVars, function (ayscName, ayscVal)
   {
      if (!ayscVal)
      {
         return true;
      }
      if (this.CONST.remove_exes[ayscName] && /^x+$/i.test(ayscVal))
      {
         // All X's in the value, for certain fields, do not put in base advert
         return true;
      }
      if (this.CONST.remove_res_pvt[ayscName] && /^pvt|res$/i.test(ayscVal))
      {
         // RES or PVT in the value, for certain fields, do not put in base advert
         return true;
      }
      this.baseAdvert[ayscName] = ayscVal.toString().toLowerCase();
   });

   this.baseAdvert.a = this.rsiSegs();

   var rFormat  = this.getAdFormat(pos);
   this.baseAdvert.sz = rFormat.sz;
   // Check whether we need to add the interstitial dcopt parameter for this ad format
   if (rFormat.dcopt) {
      if (this.baseAdvert.hasInterstitial) {
         this.addDiagnostic(pos, {
            "buildURLIst": "multiple interstitials on page, ignoring " + pos
         });
      }
      else {
         // We add the interstitial setting only if we've not seen one before on the page
         this.baseAdvert.hasInterstitial = true;
         this.baseAdvert.dcopt = rFormat.dcopt;
         this.adverts[pos].state.requestsInterstitial = true;
      }
   }
   this.baseAdvert.eid = this.erightsID();
   this.baseAdvert.uuid = FT.env.dfp_zone; //initialize
   this.baseAdvert.bht = this.behaviouralFlag();
   
   if (typeof pageUUID !== 'undefined') 
   {
      if (pageUUID !== null && pageUUID !== '') {
         this.baseAdvert.uuid = pageUUID;
      }
   } else {
      if (typeof getUUIDFromString === 'function') { //check if common_raw.js is visible.
         var docUUID = getUUIDFromString(document.location.toString());
         if (docUUID !== null && docUUID !== '') {
            this.baseAdvert.uuid = docUUID;
         }
      }
   }
   this.baseAdvert.ts = this.getTimestamp();   
   this.baseAdvert.u = this.prepareUParams();//this.duplicateEID(this.baseAdvert.eid);

   // Check if we are running in a non-live environment and change the site name
   this.baseAdvert.dfp_site = this.getDFPSite();
   FT.env.dfp_site = this.baseAdvert.dfp_site;
   this.baseAdvert.dfp_zone = FT.env.dfp_zone;

   // Handle the per-page custom targeting value from dfp_targeting.
   // By default Methode metadata puts XXXX in this field so we ignore that
   // and fix up any semicolons at the start and end of the field
   /*if (typeof FT.env.dfp_targeting !== 'undefined')
   {
      var targeting = FT.env.dfp_targeting.replace(/^;/, '').replace(/;$/, '').replace(/;;+/, ';').toLowerCase(); 
      if (targeting !== '' && ! /^x+$/.test(targeting)) {
   	   this.baseAdvert.dfp_targeting = targeting;
      }
   }*/
   this.baseAdvert.dfp_targeting = this.getDFPTargeting();
};

FT.Advertising.prototype.getDFPTargeting = function () {
   var dfpTargeting = '';
   //Handle the per-page custom targeting value from dfp_targeting.
   //By default Methode metadata puts XXXX in this field so we ignore that
   // and fix up any semicolons at the start and end of the field  
   if (typeof FT.env.dfp_targeting !== 'undefined') {
      var targeting = FT.env.dfp_targeting.replace(/^;/, '').replace(/;$/, '').replace(/;;+/, ';').toLowerCase();
      if (targeting !== '' && !/^x+$/.test(targeting)) {
         dfpTargeting = targeting;
      }
   }
   
   //if current page is an article assign referrer and append pt to dfpTargeting if necessary
   if (this.isArticle(document.location.toString())) {
      //either environment variable does not exist or pt is not defined, add it manually.
      if (!/^.*;pt=.*$/.test(FT.env.dfp_targeting)) {
         dfpTargeting += (dfpTargeting !== '') ? ';pt=art' : 'pt=art';
      }
        
      //add referrer details to dfp_targeting if defined and non-empty.
      var referrer = this.getReferrer();
      if (referrer !== undefined && referrer !== '') {
         dfpTargeting += (dfpTargeting !== '') ? ';rf=' + referrer : 'rf=' + referrer;
      }
   }
   
   //return valid dfpTargeting else undefined.
   if (dfpTargeting !== '') {
      return dfpTargeting;
   }
   return undefined;
};

FT.Advertising.prototype.getReferrer = function ()
{
      var match = null;
      var referrer = document.referrer;  
      //referrer is not article
      if (referrer !== '') {
         var hostRegex = /^.*?:\/\/.*?(\/.*)$/;
         //remove hostname from results
         match = hostRegex.exec(referrer)[1];
      }
      if (match !== null) {
         return match.substring(1);
      }
      return undefined;      
};

FT.Advertising.prototype.isArticle = function (urlParam) 
{
      var classicArticleRegex = /^.*\/cms\/s\/\d\/[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+.html.*$/;
      var falconArticleRegex = /^.*;pt=art.*$/;
      return (classicArticleRegex.test(urlParam) || falconArticleRegex.test(FT.env.dfp_targeting));
}; 
	
FT.Advertising.prototype.getTimestamp = function () 
{
      var dateToFormat = new Date();
      var month = dateToFormat.getMonth() + 1;
      var day = dateToFormat.getDate();
      var hours = dateToFormat.getHours();
      var minutes = dateToFormat.getMinutes();
      var seconds = dateToFormat.getSeconds();
      if (month < 10) {
         month = "0" + month;
      }
      if (day < 10) {
         day = "0" + day;
      }
      if (hours < 10) {
         hours = "0" + hours;
      }
      if (minutes < 10) {
         minutes = "0" + minutes;
      }
      if (seconds < 10) {
         seconds = "0" + seconds;
      }
      var dateFormatted = dateToFormat.getFullYear() + "" + month + "" + day + "" + hours + "" + minutes + "" + seconds;
      return dateFormatted;
};

FT.Advertising.prototype.prepareKeywordsParam = function (pos)
{
   var url;
   if (FT.env.url_location)
   {
      url = FT.env.url_location;
   }
   var keywords = this.getKeywordsParam(url);
   if (keywords)
   {
      this.baseAdvert.kw = keywords;
   }
};

FT.Advertising.prototype.encodeBaseAdvertProperties = function (mode,vidKV )
{
   var results = '', initial, remaining;

   var rsiSegs = this.baseAdvert.a;
   if (rsiSegs)
   {
      initial = 'a=' + rsiSegs.slice(0, this.CONST.audSciInitial).join(';a=');
      remaining = rsiSegs.slice(this.CONST.audSciInitial, this.CONST.audSciMax);
      if (remaining.length)
      {
         remaining = 'a=' + remaining.join(';a=');
      }
   }

   var Order = this.CONST.KeyOrder;
   if (mode === 'video')
   {
      Order = this.CONST.KeyOrderVideo;
   }
   else if (mode === 'videoExtra')
   {
      Order = this.CONST.KeyOrderVideoExtra;
   }
   else if (mode === 'videoSync')
   {
      Order = this.CONST.KeyOrderVideoSync;
   }
   this.foreach(Order, function (key) {

       var value;

       if (typeof(this.baseAdvert[key]) != 'undefined')
       {
          value =   this.baseAdvert[key];
       }
       else if ((typeof(vidKV) != 'undefined') && (typeof(vidKV[key]) != 'undefined')) 
       {
          value =  vidKV[key];
       }
       
       if (key === 'u' && this.baseAdvert.dfp_targeting)
       {
          // insert the dfp_targeting values just before the u parameter
          results += this.baseAdvert.dfp_targeting + ';';
       }
       if (rsiSegs && key === 'a')
       {
          results += initial + ';';
       }
       else
       {
          results += !value ? '' : key + '=' + value + ';';
          if (rsiSegs && key === 'u' && remaining.length)
          {
             results += remaining + ';';
          }
       }
   });
   return results.replace(/;$/, '');
};

FT.Advertising.prototype.cleanKeywords = function (keywords)
{
   keywords = unescape(keywords).toLowerCase();
   keywords = keywords.replace(/[';\^\+]/g, ' ');
   keywords = keywords.replace(/\s+/g, ' ');
   keywords = keywords.replace(/^\s+/, '');
   keywords = keywords.replace(/\s+$/, '');
   keywords = escape(keywords);
   // full-stop has special meaning to DFP so we must escape it
   keywords = keywords.replace(/\./g, '%2E');
   return keywords;
};

FT.Advertising.prototype.getKeywordsParam = function (url)
{
   url = url || document.location.search;
   var keywords = "";
   if (url.indexOf('?') >= 0)
   {
      url = url.replace(/^[^\?]*\?/, '');
      var Params = url.split('&');
      for (var idx = 0; keywords === "" && idx < Params.length; ++idx)
      {
         var Match = Params[idx].match(/^(q|s|query|queryText|searchField)=(.+)$/);
         if (Match)
         {
            keywords = this.cleanKeywords(Match[2]);
         }
      }
   }
   return keywords;
};

FT.Advertising.prototype.buildURLFromBaseAdvert = function (mode)
{
   mode = mode || 'normal';
   var type = (mode === 'video') ? "/pfadx/" : "/adj/";
   type = (mode === 'videoSync') ? "/adi/" : type;
   var URL = "http://" + this.baseAdvert.ad_server + type + this.baseAdvert.dfp_site + "/" + this.baseAdvert.dfp_zone + ";";
   URL += this.encodeBaseAdvertProperties(mode);

   if (mode !== 'video')
   {
      URL = URL + '?';
      if (this.baseAdvert.tile > 16)
      {
         this.addDiagnostic(this.baseAdvert.pos, {
            "buildURLFromBaseAdvert": "too many ads, exceeds maximum tile"
         });
         URL = undefined;
      }
      // Increment the tile for the next build call
      this.baseAdvert.tile++;
   }
   return URL;
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.buildURL = function (pos)
{
   var URL;
   clientAds.log("FT.Advertising.buildURL(" + pos + ")");

   if (this.checkSiteZone(pos) === 'invalid')
   {
      return URL;
   }
   // Clear out any baseAdvert fields we need to
   this.clearBaseAdvert();
   this.prepareBaseAdvert(pos);
   this.prepareKeywordsParam();
   URL = this.buildURLFromBaseAdvert();
   return URL;
}; // buildURL(pos)

// TESTED in video_test.html
FT.Advertising.prototype.buildURLForVideo = function (zone, pos, vidKV)
{
   pos = pos || 'video';
   vidKV = vidKV || {};
   var mode = 'video';
   var URL;

   FT.env.dfp_zone = zone;
   if (this.checkSiteZone(pos) === 'invalid')
   {
      return URL;
   }

   this.adverts[pos] = this.adverts[pos] || {};
   this.adverts[pos].callType = mode;

   this.clearBaseAdvert();
   this.prepareBaseAdvert(pos);
   URL = this.buildURLFromBaseAdvert(mode);

   var result = {
      urlStem: URL,
      additionalAdTargetingParams: this.encodeBaseAdvertProperties('videoExtra', vidKV)
   };

   this.addDiagnostic(pos, result);
   return result;
};

// Request any remaining ads and move staged ads within dom
FT.Advertising.prototype.requestInsertedAds = function ()
{
   clientAds.log("FT.Advertising.requestInsertedAds()");
   var advert = this.extraAds.shift();
   while (advert)
   {
      this.request(advert);
      advert = this.extraAds.shift();
   }
}; // requestInsertedAds()

// proxy function to allow falcon pages to switch between
// legacy and DFP ad server call to newssubs position.
// We could just put FT.ads.request('newssubs'); on pages now
// and remove this function. (BSAC 09/2010)
// TESTED in dfp-advertising.html
FT.Advertising.prototype.requestNewssubs = function ()
{
   this.request('newssubs');
};

// Collapse advert element
// TESTED in expand-collapse-test.html
FT.Advertising.prototype.collapse = function (pos, zeroHeight)
{
   var why = zeroHeight ? "no ad booked but interstitial present" : "no ad booked";
   why = this.adverts[pos].state.alwaysHide ? 'position is always hidden' : why;
   clientAds.log("FT.Advertising.collapse(" + pos + ", " + zeroHeight + ") - " + why);

   var doCollapse = this.legacyAdCollapse(pos, zeroHeight);
   if (doCollapse)
   {
      var adContainer = this.getAdContainer(pos);
      if (adContainer.div)
      {
         if (zeroHeight)
         {
            adContainer.div.style.display = "block";
         }
         else
         {
            adContainer.div.style.display = "none";
         }
                  
				  
		 if (typeof(FT.lib.addClassName) !== "function")
         {
            //Falcon world using jquery
            $(document.body).addClass("no-" + adContainer.name);
         }
         else 
         {
        	//classic world using classic libs
        	FT.lib.addClassName(document.body, "no-" + adContainer.name);
         }
		 
      }
   }
   else
   {
      why = "collapse prevented by legacy handler";
   }
   this.addDiagnostic(pos, {
      "collapsed": why
   });
}; // collapse(pos)

// TESTED in expand-collapse-test.html
// Set div to take up no vertical space for when there is no ad in the div
// except for a piggybacked interstitial
FT.Advertising.prototype.setZeroHeight = function (pos, id)
{
   var rDiv = document.getElementById(id);
   if (rDiv)
   {
      rDiv.style.height  = '0px';
      rDiv.style.padding = '0px';
   }
   else
   {
      clientAds.log("FT.Advertising.setZeroHeight(" + id + ") - div not found");
      this.addDiagnostic(pos, {
         "setZeroHeight": "div not found: " + id
      });
   }
}; // setZeroHeight()

// Handle any collapse fixups needed because ads library is in use on old FTCOMBASE/PHOENIX pages
// NOT TESTED
FT.Advertising.prototype.legacyAdCollapse = function (pos, zeroHeight)
{
   // This function should not live too long. Once the article pages and all
   // third parties are migrated to the new falcon stack and wrappers there
   // should be no fixups needed. If you see this function in existence many
   // months after June 2010 check if it can be removed.

   var doCollapse = true;
   if (FT.env.isLegacyAPI)
   {
      // Refresh position is always hidden
      if (this.adverts[pos].state.alwaysHide)
      {
         clientAds.log("FT.Advertising.legacyAdCollapse(" + pos + ", " + zeroHeight + ") for " + this.library + " always hide");
         var rDiv = document.getElementById('ad-container-' + pos);
         if (rDiv)
         {
            rDiv.style.display = 'none';
         }
      }
      if (pos === 'lhn')
      {
         // Cannot collapse the lhn position as it actually contains the Left Hand Nav
         doCollapse = false;
      }
      if (pos === 'banlb')
      {
         clientAds.log("FT.Advertising.legacyAdCollapse(" + pos + ", " + zeroHeight + ") for " + this.library);
         // on legacy pages remove the containing leaderboard height to
         // allow the position to collapse
         var rLeaderBoard = document.getElementById('leaderboard');
         if (rLeaderBoard)
         {
            rLeaderBoard.style.minHeight = 0;
         }

         var Divs = [
            'ad-placeholder-banlb',
            'page-header-ad'
         ];
         for (var idx = 0; idx < Divs.length; ++idx)
         {
            this.setZeroHeight(pos, Divs[idx]);
         }
      }
   }
   if (!doCollapse)
   {
      clientAds.log("FT.Advertising.legacyAdCollapse(" + pos + ", " + zeroHeight + ") for " + this.library + " ad position collapse prevented");
   }

   return doCollapse;
}; // legacyAdCollapse()

// Handle any expand fixups needed because ads library is in use on old FTCOMBASE/PHOENIX pages
// NOT TESTED
FT.Advertising.prototype.legacyAdFixup = function (pos, adContainer)
{
   // This function should not live too long. Once the article pages and all
   // third parties are migrated to the new falcon stack and wrappers there
   // should be no fixups needed. If you see this function in existence many
   // months after June 2010 check if it can be removed.

   if (this.library === 'ftcombase' && adContainer.div.id === 'ad-placeholder-hlfmpu')
   {
      // Half MPU ad position on Article pages using FTCOMBASE library we need to
      // add a border and some padding to the ad because the old library did this
      // manually. When falcon restyles the page they ought to be able to do this
      // with proper page structure and class=advertising.

      clientAds.log("FT.Advertising.legacyAdFixup(" + pos + ", " + adContainer.div.id + ") for " + this.library);
      // This horrible hack should be temporary until Falcon has re-styled the Article and Markets Data Pages
      adContainer.div.style.padding      = "14px 0 14px 0";
      adContainer.div.style.marginBottom = 15 + "px";
      adContainer.div.style.border       = "solid 1px #999";
   }
   if (this.library === 'phoenix' && adContainer.div.id === 'ad-placeholder-hlfmpu')
   {
      // Half MPU ad position on Markets Data pages using PHOENIX library we need to
      // add some padding to the ad because the old library did this
      // manually. When falcon restyles the page they ought to be able to do this
      // with proper page structure and class=advertising.

      clientAds.log("FT.Advertising.legacyAdFixup(" + pos + ", " + adContainer.div.id + ") for " + this.library);
      // This horrible hack should be temporary until Falcon has re-styled the Article and Markets Data Pages
      adContainer.div.style.padding      = "14px 0 14px 0";
      adContainer.div.style.marginBottom = 15 + "px";
      adContainer.div.style.marginLeft = 0;
      adContainer.div.style.paddingLeft = 0;
   }
   if (this.library === 'ftcombase' && adContainer.div.id === 'ad-placeholder-tradcent')
   {
      // Tradecenter ad position on Article pages using FTCOMBASE library we need to
      // add a bottom margin of 15 px

      clientAds.log("FT.Advertising.legacyAdFixup(" + pos + ", " + adContainer.div.id + ") for " + this.library);
      // This horrible hack should be temporary until Falcon has re-styled the Article pages
      adContainer.div.style.marginBottom = 15 + "px";
   }
   if (this.library === 'ftcombase' && adContainer.div.id === 'ad-placeholder-tlbxrib')
   {
      // tlbxrib ad position on Article pages using FTCOMBASE library we need to
      // add a bottom margin of 15 px

      clientAds.log("FT.Advertising.legacyAdFixup(" + pos + ", " + adContainer.div.id + ") for " + this.library);
      // This horrible hack should be temporary until Falcon has re-styled the Article pages
      adContainer.div.style.marginBottom = 15 + "px";
   }
   if (this.library === 'ftcombase' && adContainer.div.id === 'ad-placeholder-marketingrib')
   {
      // Marketing Rib ad position on Article pages using FTCOMBASE library we need to
      // fix up the fonts so they match what they used to be.
      // When falcon restyles the page this should be fixed.

      clientAds.log("FT.Advertising.legacyAdFixup(" + pos + ", " + adContainer.div.id + ") for " + this.library);
      // This horrible hack should be temporary until Falcon has re-styled the Article and Markets Data Pages
      // Remove the ad placeholder class from the outer div which is styled horribly for the ad content.
      adContainer.div.className = "";
      adContainer.div.style.marginBottom = 15 + "px";
   }
}; // legacyAdFixup(pos, adContainer)

// Expand element position (in case content was returned without calling the callback)
// TESTED in expand-collapse-test.html
FT.Advertising.prototype.expand = function (pos)
{
   clientAds.log("FT.Advertising.expand(" + pos + ")");
   var adContainer = this.getAdContainer(pos);
   if (adContainer.div)
   {
      this.legacyAdFixup(pos, adContainer);
      if (!adContainer.div.className.match(/\bhidden\b/))
      {
         adContainer.div.style.display = "block";
      }
      
      if (typeof(FT.lib.removeClassName) !== "function")
      {
         //Falcon world using jquery
         $(document.body).removeClass("no-" + adContainer.name);
      }
      else 
      {
         //classic world using classic libs
         FT.lib.removeClassName(document.body, "no-" + adContainer.name);
      }
	  
   }
}; // expand(pos)

// Returns the element with an expected class name, which depends upon whether we
// are using the DFP or legacy API (walks up the DOM tree to find it)
// TESTED in dfp-advertising.html
FT.Advertising.prototype.getNamedAdContainer = function (idDiv, pos)
{
   clientAds.log("FT.Advertising.getNamedAdContainer(" + idDiv + ") -- looking");
   var rDiv = document.getElementById(idDiv);
   clientAds.log("FT.Advertising.getNamedAdContainer(" + idDiv + ") -- got -- " + rDiv);
   if (rDiv)
   {
      clientAds.log("FT.Advertising.getNamedAdContainer(" + idDiv + ")");
      var ancestorLimit = 3;
      var ancestorCount = 0;
      var el = rDiv;
      var rOriginalDiv = rDiv;

      while (ancestorCount <= ancestorLimit && typeof el.className === "string" && this.hasAdClass(el, pos) === false)
      {
         el = el.parentNode;
         ancestorCount++;
      }
      rDiv = (el.className && this.hasAdClass(el, pos) === true) ? el: rOriginalDiv;
   }
   return rDiv;
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.getAdContainer = function (pos)
{
   var AdContainers = this.getAdContainers(pos);
   if (AdContainers.length === 0)
   {
      this.addDiagnostic(pos, { "getAdContainer": 'div not found' });
      AdContainers = [{'div': null, 'name': null}];
   }
   return AdContainers[0];
};

// Mostly to do with Legacy Ad pages which can have a placeholder and container div
// which are separate. This should be reworked to use getAdContainer when the legacy
// sites have been migrated.
// TESTED in dfp-advertising.html
FT.Advertising.prototype.getAdContainers = function (pos)
{
   var AdContainers = [];
   // Using the legacy API we have to look for ad-container-banlb and ad-placeholder-banlb classes
   // so we loop through entire proxy array. in non-legacy we only look for the named ad container
   var stop = FT.env.isLegacyAPI ? this.CONST.proxy_div_prefixes.length : 1;
   for (var idx = 0; idx < stop; ++idx)
   {
      var idDiv = this.CONST.proxy_div_prefixes[idx] + pos;
      // Ok, this is a hack. The classic site page which has an LHN ad position
      // also has a div called lhn which has nothing to do with ads. So we
      // cannot match that div and instead look for the ad-placeholder-lhn, etc.
      // http://www.ft.com/personal-finance/banking
      // This should be resolved when the classic site is re-styled for Falcon
      if (FT.env.isLegacyAPI && idDiv === 'lhn')
      {
         continue;
      }
      var adElement = this.getNamedAdContainer(idDiv, pos);
      if (adElement)
      {
         AdContainers.push({'div': adElement, 'name': idDiv});
      }
   }
   return AdContainers;
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.getAdInnerHTML = function (pos)
{
   var html = undefined;
   var AdContainers = this.getAdContainers(pos);
   if (AdContainers.length !== 0)
   {
      html = '';
      this.foreach(AdContainers, function (rAdContainer)
      {
         if (html.length)
         {
            html = html + "\n";
         }
         html = html + "<!-- " + rAdContainer.name + " -->\n" + rAdContainer.div.innerHTML;
      });
   }
   else
   {
      this.addDiagnostic(pos, { "getAdInnerHTML": 'div not found' });
   }
   return html;
};

// Given a DOM element, we look for the parent div which needs to be collapsed
// or expanded. Falcon pages simply have the classname 'advertising' on them.
// Non-falcon pages are either an ad-container or an ad-placeholder
// TESTED in dfp-advertising.html
FT.Advertising.prototype.hasAdClass = function (rElement, pos)
{
   clientAds.log("FT.Advertising.hasAdClass(" + rElement + ")");
   if (FT.env.isLegacyAPI)
   {
      if (this.hasClassName(rElement.className, new RegExp('^ad-(container|placeholder)(-' + pos + ')?$')))
      {
         return true;
      }
   }
   else if (this.hasClassName(rElement.className, 'advertising'))
   {
      return true;
   }

   return false;
}; // hasAdClass(rElement)

FT.Advertising.prototype.startRefreshTimer = function (delay)
{
   clientAds.log("FT.Advertising.startRefreshTimer(" + delay + ")");
   // call doTrackRefresh from Track.js
   this.refreshTimer = setTimeout(function () {
      clientAds.log("refreshTimer callback()");
      doTrackRefresh(delay);
   }, delay);
}; // startRefreshTimer(delay)

// Create a linked image in the DOM
FT.Advertising.prototype.renderImage = function (rResponse)
{
   clientAds.log("FT.Advertising.renderImage(" + rResponse + ")");
   if (FT.lib.type(rResponse) !== "object" || !rResponse.content || !rResponse.content.clickURL || !rResponse.content.imageURL)
   {
      this.addDiagnostic(rResponse.name, {
         "noImageClickContent": true
      });
      return false;
   }
   var rDiv = document.getElementById(rResponse.name);
   if (!rDiv)
   {
      this.addDiagnostic(rResponse.name, {
         "noTargetDiv": true
      });
      return false;
   }
   var link = document.createElement("a");
   link.href = rResponse.content.clickURL;
   link.target = "_blank";
   var img = document.createElement("img");
   if (rResponse.content.altText)
   {
      img.alt = rResponse.content.altText;
   }
   if (rResponse.content.width)
   {
      img.width = rResponse.content.width;
   }
   if (rResponse.content.height)
   {
      img.height = rResponse.content.height;
   }
   img.src = rResponse.content.imageURL;
   link.appendChild(img);

   // create a placeholder so we can render th link and img next to it (e.g. inside the div.advert OR the #mktsdata span
   var imageclickPlaceholderId = rResponse.name + "_imageclick_placeholder";
   // Fool jslint in this occasion to accept a document.write
   var doc = document;
   doc.write('<span style="display:none" id="' + imageclickPlaceholderId + '"></span>');
   var imageclickPlaceholderDiv = document.getElementById(imageclickPlaceholderId);

   if (imageclickPlaceholderDiv.parentNode.insertBefore(link, imageclickPlaceholderDiv))
   {
      this.addDiagnostic(rResponse.name,
      {
         "rendered": "fromJSON"
      });
   }

   imageclickPlaceholderDiv.parentNode.removeChild(imageclickPlaceholderDiv);

   // Vertically centre
   if (rResponse.content.height && img.height < rDiv.offsetHeight)
   {
      link.style.marginTop = ((rDiv.offsetHeight - img.height) / 2) + "px";
      link.style.display = "block";
      this.addDiagnostic(rResponse.name, {
         "verticallyAligned": true
      });
   }
   this.expand(rResponse.name);
}; // renderImage(rResponse)

// TESTED in dfp-advertising.html
FT.Advertising.prototype.toBase36 = function (value)
{
   return parseInt(value, 10).toString(36);
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.fromBase36 = function (value)
{
   return parseInt(value, 36);
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.encodeAudSci = function (value)
{
   var rsiSeg = value.match(/^([A-L]\d{5})_(\d{5})$/i);
   if (rsiSeg) {
      var segment = parseInt(rsiSeg[2], 10) - 10000;
      if (/^J07717$/i.test(rsiSeg[1])) {
         return 'z' + segment;
      } else {
         return rsiSeg[1].charAt(0).toLowerCase() + this.toBase36(segment + rsiSeg[1].substring(1));
      }
   }
   return value.toUpperCase();
};

// TESTED in dfp-advertising.html
FT.Advertising.prototype.decodeAudSci = function (value)
{
   if (value.charAt(0).toLowerCase() === 'z')
   {
      return "J07717_" + (parseInt(value.substring(1), 10) + 10000);
   }
   else
   {
      var rsiSeg = this.fromBase36(value.substring(1)).toString();
      var segment = parseInt(rsiSeg.slice(0, -5), 10) + 10000;
      var clientId = value.charAt(0).toUpperCase() + rsiSeg.substring(rsiSeg.length - 5);
      return clientId + '_' + segment;
   }
};

// Implement DFP specific helper methods

// Constructor - a new page resets ads. If there are video ads, they are removed but
// the records of non-video ads remain in FT.ads.adverts. If there are no video ads
// then everything will be reset.
// TESTED dfp-advertising.html
FT.Advertising.prototype.beginNewPage = function (env)
{
   clientAds.log("FT.Advertising.beginNewPage()");
   env = env || FT.env;

   this.baseAdvert       = {};
   this.baseAdvert.ord   = Math.floor(Math.random() * 1E16); // 16 digit random number
   this.baseAdvert.tile  = 1; // tile = 1 .. 16 only
   this.extraAds         = [];

   var VideoAds = this.getVideoAdverts();
   var VideoSyncAds = this.getVideoSyncAdverts();
   if (VideoAds.length || VideoSyncAds.length)
   {
      this.foreach(VideoAds, function (pos) {
         delete this.adverts[pos];
      });
      this.foreach(VideoSyncAds, function (pos) {
         delete this.adverts[pos];
      });
   }
   else
   {
      this.adverts = {};
   }

   this.isComplete       = false;
   this.timeouts         = {};
   this.intervals        = {};
   this.runinterval      = undefined;
   this.refreshTimer     = null; // timer for refreshing the page

   this.timeoutTolerance = FT.env.timeoutTolerance || 25;  // Milliseconds after which to collapse ad position
   this.timeIntervalTolerance = FT.env.timeIntervalTolerance || 300; //Millisecond interval between checking for ad div state
   this.suppressAudSci   = false;

   // Let the FTQA cookie value override the timeout, if present
   var cookie = this.getCookie('FTQA');
   // For testing visit: http://admintools.internal.ft.com:86/adstools/html/FTQA.html
   // debug,timeout=2000,interval=100,longest_url=100-100 (100 out of 100)
   if (cookie)
   {
      var Match = cookie.match(/timeout=(\d+)/);
      if (Match)
      {
         this.timeoutTolerance = Match[1];
      }
      Match = cookie.match(/interval=(\d+)/);
      if (Match)
      {
         this.timeIntervalTolerance = Match[1];
      }
      Match = cookie.match(/longest_url=(\d+)-(\d+)/);
      if (Match)
      {
         this.CONST.urlThreshold = Match[1];
         this.CONST.urlThresholdMax = Match[2];
      }
      Match = cookie.match(/ord=(\d+)/);
      if (Match)
      {
         this.baseAdvert.ord = Match[1];
      }
      Match = cookie.match(/noaudsci/);
      if (Match)
      {
         this.suppressAudSci = true;
      }
      clientAds.log("Configured from Cookies:");
      clientAds.log("timeoutTolerance: " + this.timeoutTolerance);
      clientAds.log("timeIntervalTolerance: " + this.timeIntervalTolerance);
      clientAds.log("urlThreshold: " + this.CONST.urlThreshold);
      clientAds.log("urlThresholdMax: " + this.CONST.urlThresholdMax);
      clientAds.log("ord: " + this.baseAdvert.ord);
      clientAds.log("noaudsci: " + this.suppressAudSci);
   }

   this.baseAdvert.hasInterstitial  = false; // flag set if an interstitial ad has been requested - only one per page allowed
   this.submitToTrack    = false;
   this.useDFP           = true;
   this.library          = "phoenix";
   env.useDFP            = true;
}; // beginNewPage()

// Reset the library completely as if there are no ad calls
// TESTED dfp-advertising.html
FT.Advertising.prototype.resetLibrary = function ()
{
   this.beginNewPage();
   this.adverts = {};
};

// Check if ad server two letter code is valid.
// TESTED in dfp-advertising.html
FT.Advertising.prototype.checkAdServerCountry = function (iso2)
{
   return this.CONST.AD_SERVERS.test(iso2);
};


// TESTED in dfp-advertising.html
FT.Advertising.prototype.adServerCountry = function (code, pos)
{
   var server = '';
   if (code)
   {
      code = code.toLowerCase();
      if (this.checkAdServerCountry(code))
      {
         code = code.toLowerCase();
         server = code + '.';
      }
      else if (code === 'gb' || code === 'gg' || code === 'im' || code === 'je')
      {
         server = 'uk.';
      }
      else
      {
         this.addDiagnostic(pos,
         {
            "adServerCountry": "Unsupported ad server: " + code
         });
      }
   }
   return 'ad.' + server + 'doubleclick.net';
};

//find out if we have an erights field and if so return as an object property
FT.Advertising.prototype.detectERights = function (obj)
{
   if (FT.cookies.FT_U !== undefined)
   {
      var erights = FT.cookies.FT_U.split("=");
      var keyname = erights[0];
      var val = erights[1];

      if ((keyname !== undefined) && (val === undefined))
      {
         obj[keyname] = val;
         obj.u = erights;
      }
   }

   return obj;

};

// set the value of the bht  based on AudSci cookie
FT.Advertising.prototype.behaviouralFlag = function()
{
	var flag = (typeof(this.rsiSegs()) === "undefined")?"false":"true";	
	
	return flag;
}

// exclude fields on either key or val criteria
FT.Advertising.prototype.excludeFields = function (exclusions, obj)
{
   // TODO: clean this up later -- val is now unused so this could be simpler.
   this.foreach(obj, function (prop)
   {
      for (var idx = 0; idx < exclusions.length; idx++)
      {
         var keyvalsplit = exclusions[idx].split("=");
         if (((keyvalsplit[0] === "key") && (prop === keyvalsplit[1])) || ((keyvalsplit[0] === "val") && (obj[prop] === keyvalsplit[1])))
         {
            delete obj[prop];
         }
      }
   });

   return obj;
};

//here we strip leading zeros from certain fields
FT.Advertising.prototype.stripLeadingZeros = function (KeysToStrip, obj)
{
   for (var idx = 0, length = KeysToStrip.length; idx < length; idx++)
   {
      if (obj[KeysToStrip[idx]])
      {
         obj[KeysToStrip[idx]] = obj[KeysToStrip[idx]].replace(/^0+/, "");
      }
   }
   return obj;
};

//here we do substitutions based on regexs.
FT.Advertising.prototype.fieldRegex = function (RegexKeyNames, obj)
{
   this.foreach(RegexKeyNames, function (keyName)
   {
      var value = obj[keyName];
      if (value !== undefined)
      {
         this.foreach(this.CONST.SubsLevelReplaceLookup, function (replaceValue, regex)
         {
            if (value.match(regex)) {
               obj.slv = replaceValue;
            }
         });
      }
   });

   return obj;
}; // fieldRegex()

//here we take a substr of an object property value and then assign it
//to that object property
FT.Advertising.prototype.fieldSubstr = function (SubStrKeyNames, obj)
{
   this.foreach(SubStrKeyNames, function (keyName)
   {
      // This is what we are splitting:
      // '24=0=3=cn'
      // i.e. AYSC chars 0 to 3 are stored in the cn field
      // AYSC field 24=europe then cn=eur
      var SubStrItems = keyName.split("=");
      var ayscField = SubStrItems[0];
      var val = obj[ayscField];
      if (val !== undefined)
      {
         var newField = SubStrItems[3];
         obj[newField] = val.substring(SubStrItems[1], SubStrItems[2]);
      }
   });
   return obj;
};

// Determine the DFP site targeting value from FT.env.dfp_site and modified
// by the release environment you are in. If non-live, we replace the first
// word of the site name with test.  For example ftcom.5887.blogs becomes
// test.5887.blogs.  The FTQA cookie can also select the environment
// with env=nolive or env=live
// TESTED in dfp-advertising.html
FT.Advertising.prototype.getDFPSite = function ()
{
   var site = FT.env.dfp_site;
   if (FT.Properties && FT.Properties.ENV)
   {
      var env = FT.Properties.ENV.toLowerCase();
      var cookie = this.getCookie('FTQA');
      if (cookie)
      {
         // FTQA cookie present, look for env=live or env=nolive
         if (cookie.match(/env=live/))
         {
            env = 'live';
            clientAds.log("FTQA cookie has set ads from live environment");
            this.addDiagnostic(this.baseAdvert.pos, { "getDFPSite": "using FTQA cookie to set ads from live environment" });
         }
         if (cookie.match(/env=nolive/))
         {
            env = 'ci';
            clientAds.log("using FTQA cookie has set ads from non-live environment");
            this.addDiagnostic(this.baseAdvert.pos, { "getDFPSite": "using FTQA cookie to set ads from non-live environment" });
         }
      }
      if (env !== 'p' && !env.match(/^live/))
      {
         site = site.replace(/^\w+\./, "test.");
      }
   }
   return site;
}; // getDFPSite()

// Assemble all the diagnostic messages into a single string for easy viewing with
//javascript:alert(this.showDiagnostics('banlb'))
FT.Advertising.prototype.showDiagnostics = function (pos)
{
   var FullDiagnosis = ["FT.ads.showDiagnostics:\n"];
   var AdPositions = this.getKeys(this.adverts);

   this.foreach(AdPositions, function (adPos)
   {
      var thisAdvert = this.adverts[adPos];
      if (typeof thisAdvert === 'object' && (!pos || adPos === pos))
      {
         var Diagnosis = [];
         if (thisAdvert.diagnostics)
         {
            var rDiagnostics = thisAdvert.diagnostics;
            var Topics = this.getKeys(rDiagnostics);

            this.foreach(Topics, function (topic)
            {
               if (typeof rDiagnostics[topic] !== 'function')
               {
                  Diagnosis.push("   " + topic + ": " + rDiagnostics[topic]);
               }
            });
         }
         var diagnosis = Diagnosis.join("\n");
         if (diagnosis.length)
         {
            if (! adPos.match(/^_/))
            {
               adPos = adPos + " Ad Call";
               if (thisAdvert.response && thisAdvert.response.adName)
               {
                  diagnosis = "   " + thisAdvert.response.adName + "\n" + diagnosis;
               }
            }
            FullDiagnosis.push(adPos + ":\n" + diagnosis + "\n");
         }
      }
   });
   return FullDiagnosis.join("\n");
}; // showDiagnostics()

// A breakpoint inserted for debugging only if the FTQQA cookie contains
// 'breakout' - don't remove this function, it's embedded into the ad call
// response so we can easily debug ad problems.
// DO NOT REMOVE. Unless you want the ads to stop working. Stub out as
// an empty function if you absolutely must, but this is handy for diagnosing ads problems.
FT.Advertising.prototype.breakout = function (rResponse)
{
   var pause = true;
   var cookie = this.getCookie('FTQA');
   if (cookie) {
      if (rResponse && rResponse.name)
      {
         pause = false;
         var break_if = 'breakout=' + rResponse.name;
         if (cookie && (cookie.match(/breakout=all/) || cookie.indexOf(break_if) >= 0))
         {
            pause = true;
         }
      }
      if (pause)
      {
         debugger;
      }
   }
};

// Detect whether the page settings contain DFP ad targeting variables
// Looks for FT.env.dfp_site and dfp_zone
// TESTED in dfp-advertising.html
FT.Advertising.prototype.detectDFPTargeting = function (env)
{
   env = env || FT.env;
   return env.dfp_site && env.dfp_zone ? true : false;
};

// Determine whether the DFP ads system should be used or whether DE
// should be used. This is now always set to DFP mode
// TESTED in dfp-advertising.html
FT.Advertising.prototype.detectAdMode = function (env)
{
   env = env || FT.env;
   env.useDFP = true;
   return env.useDFP;
}; // detectAdMode()

// Initialise the DFP ad system (only if FT.env.useDFP flag is set)
// We remove the DE methods from FT.Advertising and add in DFP methods
// TESTED dfp-advertising.html
FT.Advertising.prototype.initDFP = function (env)
{
   clientAds.log("FT.Advertising.initDFP() - top");
   env = env || FT.env;
   this.hasCalledInitDFP = true;
   if (typeof(env.useDFP) !== 'undefined')
   {
      // We have already initialised the DFP prototype methods, all we need to
      // do is reset the baseAdvert and other global page settings.
      this.beginNewPage(env);
   }
   else
   {
      // useDFP flag does not exist yet, we need to initialize the new DFP member functions.
      env.useDFP = true;

      clientAds.log("FT.ads.initDFP() - setup DFP");

      // Switch the request function now that we have detected Legacy or Falcon API
      FT.Advertising.prototype.request = FT.Advertising.prototype.requestDFP;

      // Re-initialise object with DFP settings
      this.beginNewPage(env);
   }
}; // initDFP()

// Function looks through adverts and gets the longest ad call URL that was created.
// TESTED in dfp-advertising.html
FT.Advertising.prototype.getLongestUrl = function ()
{
   var AdPositions = this.getKeys(this.adverts);

   var longestRequestUrl;
   var longestRequestUrlLength = 0;

   this.foreach(AdPositions, function (pos)
   {
      var thisAdvert = this.adverts[pos];
      if (typeof(thisAdvert) === 'object')
      {
         var rDiagnostics = thisAdvert.diagnostics;
         if (rDiagnostics && rDiagnostics.requestUrl)
         {
            var requestUrl = rDiagnostics.requestUrl.replace(/^http:\/\/[^\/]+\.net/, '');
            if (requestUrl.length > longestRequestUrlLength)
            {
               longestRequestUrlLength = requestUrl.length;
               longestRequestUrl = requestUrl;
            }
         }
      }
   });
   return longestRequestUrl;
};

// Sticky flag which checks whether we should submit errors to the tracking server.
// TESTED in dfp-advertising.html
FT.Advertising.prototype.shouldSubmitToTrack = function ()
{
   if (!this.submitToTrack)
   {
      var rnd = Math.floor(Math.random() * this.CONST.urlThresholdMax);
      if (rnd < this.CONST.urlThreshold)
      {
         this.submitToTrack = true;
      }
   }
   return this.submitToTrack;
};

// Check if we should submit longest URL to the tracking server
// TESTED in dfp-advertising.html
FT.Advertising.prototype.checkSubmitLongestUrl = function ()
{
   if (this.shouldSubmitToTrack())
   {
      return this.getLongestUrl();
   }
   else
   {
      return undefined;
   }
};

// Inject the code to submit the longest url to the track server, only if we
// should submit it based on the random number in shouldSubmitToTrack()
// TESTED in dfp-advertising.html and ad-on-a-page.html
FT.Advertising.prototype.injectUrlTrackCall = function ()
{
   var url = this.checkSubmitLongestUrl();
   if (url && document.createElement)
   {
      clientAds.log("Injecting call to track long URL:" + url);
      var rImg = document.createElement("img");
      rImg.src = this.CONST.trackUrl + "?long_url=" + url;
      rImg.id = "injectUrlTrackCall";
      rImg.setAttribute("style", "display:none");
      document.getElementsByTagName("body")[0].appendChild(rImg);
   }
   return url;
};

// Inject the code to submit the current page to the track server if it is
// unclassified.
// TESTED in dfp-advertising.html
FT.Advertising.prototype.injectUnclassifiedTrackCall = function ()
{
   var url;
   if (this.isUnclassified())
   {
      if (this.shouldSubmitToTrack() && document.createElement)
      {
         url = document.location;
         clientAds.log("Injecting call to track unclassified page URL:" + url);
         var rImg = document.createElement("img");
         rImg.src = this.CONST.trackUrl + "?unclassified=" + url;
         rImg.id = "injectUnclassifiedTrackCall";
         rImg.setAttribute("style", "display:none");
         document.getElementsByTagName("body")[0].appendChild(rImg);
      }
   }
   return url;
};

/*
   Implement a legacy ads interface to invoke Falcon ad calls when old ad
   calls happen on the page.  The legacy system calls functions in the
   following order:

   var banlb = new Advert(AD_BANLB);
   banlb.init();
   ...
   clientAds.fetch(AD_BANLB);
   clientAds.render(AD_BANLB);
   ...
   clientAds.render();  (after all ad calls)

   The falcon system calls functions in the following order:
   FT.ads.request('banlb');
   ...
   FT.ads.requestInsertedAds();  (after all .request calls)
   FT.ads.complete()             (in page foot.js)

   To keep compatible we have the first call to Advert() detect whether we
   want DE or DFP ads.  When banlb.init() is called, FT.ads.request() will
   be called.  The clientAds.fetch() and .render(AD_BANLB) calls will do
   nothing.  The final render() call is detected because undefined is
   passed in for pos.  We will actually call FT.ads.requestInsertedAds().
   The FT.ads.complete() is normally called for each .togglable item on
   the page.

   On the Falcon home page, the Advert() call will not happen.
   FT.ads.request() will be the first opportunity to detect whether DE/DFP
   should be used.  So we initially override FT.Advertising.request(),
   save a copy of the existing .request() handler as .requestDE() and if
   our handler gets called, we detect DFP and then restore all overrides
   as needed.  We ensure we call the correct .request() so the first ad
   call doesn't get lost.

   This API compatibility means we do not have to stale the entire FT
   Cache when we implement the DFP ads across the site.  New falcon pages
   can be authored using the new API and existing pages will still be
   supported until they have been migrated to the Falcon way.

*/

// These ones are valid
var AD_BANLB     = 'banlb';
var AD_NEWSSUBS  = 'newssubs';
var AD_MPU       = 'mpu';
var AD_HLFMPU    = "hlfmpu";
var AD_MPUSKY    = "mpusky";       // AD_MPU Box/Sky      300x250, 336x280, 160x600, 120x600, collapsible
var AD_OOB       = 'oob';
var AD_CORPPOP   = 'corppop';
var AD_REFRESH   = 'refresh';
var AD_TLBXRIB   = "tlbxrib";
var AD_MARKETINGRIB = "marketingrib";
var AD_INTRO     = "intro";
var AD_TRADCENT  = "tradcent";
var AD_DOUBLET   = "doublet";
var AD_WDESKY    = "wdesky";       // Sky/Wide Sky
var AD_LHN       = "lhn";          // Left Hand Nav Sky

// These ones - are all deprecated
var AD_MACROAD   = "deadA";
var AD_HMMPU     = "deadB";
var AD_MARKETING = "deadC";
var AD_NRWSKY    = "deadD";
var AD_ARTBOX    = "deadE";
var AD_FTHBOX    = "deadF";
var AD_TLBX      = "deadG";
var AD_FMBUT2    = "deadH";
var AD_MKTBX     = "deadI";
var AD_POP       = "deadJ";
var AD_BXBAR     = "deadK";
var AD_DKTALRT   = "deadL";
var AD_DSKTICK   = "deadM";
var AD_PRNT      = "deadN";
var AD_INV       = "deadO";
var AD_MBATOP    = "deadP";
var AD_MBABOT    = "deadQ";
var AD_MBALINK   = "deadR";
var AD_SBHEAD    = "deadS";
var AD_FTNT      = "deadT";
var AD_1x1       = "deadU";
var AD_CURRCON   = "deadV";
var AD_CURRBOX   = "deadW";

clientAds =
{
   'debug' : null,
   'render': function (pos)
   {
      if (pos)
      {
         this.log('clientAds.render(' + pos + ') = NOP');
      }
      else
      {
         this.log('clientAds.render(' + pos + ') = FT.ads.requestInsertedAds() ' + (FT.env.useDFP ? '[DFP]' : '[DE]'));
         FT.ads.requestInsertedAds();
         FT.ads.complete();
      }
   },
   'fetch' : function (pos)
   {
      this.log('clientAds.fetch('  + pos + ') = NOP');
   },
   // From here, just a few functions to help with debugging for now.
   'log'   : function (msg) {
      if (this.debug === null)
      {
         this.debug = false;
         if (FT.cookies.FTQA && FT.cookies.FTQA.match(/debug/))
         {
            this.debug = true;
         }
      }
      if (this.debug)
      {
         if (window.console && window.console.log)
         {
            window.console.log(msg);
         }
         else if (window.opera)
         {
            window.opera.postError(msg);
         }
      }
   },
   'showCookies' : function (reKeys)
   {
      return "Cookies:\n" + document.cookie.split(';').sort().join(";\n");
   }
};

// Legacy ads interface to invoke Falcon ad calls when old ad calls seen on the page.
function Advert(pos)
{
   clientAds.log('new Advert('  + pos + ')');
   // If this ever gets called, we know we are using the Legacy API
   FT.env.isLegacyAPI = true;

   // Return an object which can immediately have .init() called on it.
   var obj = {
      'name'  : pos,
      'init' : function ()
      {
         clientAds.log('Advert.init(' + this.name + ') = FT.ads.request(' + this.name + ') ' + (FT.env.useDFP ? '[DFP]' : '[DE]'));
         FT.ads.request(this.name);
      }
   };

   // Don't initialise ourselves more than once
   if (!FT.ads.hasCalledInitDFP)
   {
      FT.ads.initDFP();
   }
   // Return an object which can immediately have .init() called on it.
   return obj;
}
FT.Advertising.prototype.VERSION = "Live $Rev: 79412 $";
FT.Advertising.prototype.library = "phoenix";
clientAds.log("DFP Ads: " + FT.Advertising.prototype.library.toUpperCase() + " " + FT.Advertising.prototype.VERSION);

/*  Functions with no direct test cases yet

FT.Advertising.prototype.createAdRequestFromVideoUrl = function (pos, url)
FT.Advertising.prototype.insertAdIntoIFrame = function (pos, requestURL)

FT.Advertising.prototype.handleRefreshLogic = function (obj, timeout)
FT.Advertising.prototype.clearTimer = function ()
FT.Advertising.prototype.complete = function ()
FT.Advertising.prototype.callback = function (rResponse)
FT.Advertising.prototype.storeResponse = function (rResponse)
FT.Advertising.prototype.addDiagnostic = function (pos, rDiagObj)
FT.Advertising.prototype.extendBaseAdvert = function (rResponse)
FT.Advertising.prototype.insertNewAd = function (pos)
FT.Advertising.prototype.clearBaseAdvert = function ()
FT.Advertising.prototype.prepareAdVars = function (AllVars)
FT.Advertising.prototype.erightsID = function ()
FT.Advertising.prototype.duplicateEID = function (eid)
FT.Advertising.prototype.rsiSegs = function () {
FT.Advertising.prototype.prepareBaseAdvert = function (pos)
FT.Advertising.prototype.encodeBaseAdvertProperties = function (mode)
FT.Advertising.prototype.buildURLFromBaseAdvert = function (mode)
FT.Advertising.prototype.requestInsertedAds = function ()
FT.Advertising.prototype.startRefreshTimer = function (delay)
FT.Advertising.prototype.renderImage = function (rResponse)
FT.Advertising.prototype.detectERights = function (obj)
FT.Advertising.prototype.excludeFields = function (exclusions, obj)
FT.Advertising.prototype.stripLeadingZeros = function (KeysToStrip, obj)
FT.Advertising.prototype.fieldRegex = function (RegexKeyNames, obj)
FT.Advertising.prototype.fieldSubstr = function (SubStrKeyNames, obj)
FT.Advertising.prototype.showDiagnostics = function (pos)
FT.Advertising.prototype.breakout = function (rResponse)
FT.Advertising.prototype.removeDEMethods = function () {

*/

