(function( window ) {
    window.AdTraka = {
isReady: false,
queue: [],
cid_base: 'adtraka-' + Math.floor(100+Math.random()*1000) + '-',
cid_seq: 1,
telnum_seq: 1,
ping_interval: -1,
ping_intervals: [
         { val: 5,    wait: 1*60 },  // 5s for first minute (0-1m)
         { val: 10,   wait: 4*60 },  // 10s for next 4 minutes  (1m-5m)
         { val: 30,   wait: 25*60 }, // 30s for next 30 minutes (5m-30m)
         { val: 5*60, wait: 0 }      // 5m for the remaining time (30m+)
],
pageSettings: {}, // Last page settings
domain: 'api.adtraka.co.uk',

readyFuncs: [],
         
onReady: function(func) {
             if (AdTraka.isReady) {
                 try { func(); } catch(e) {};
             }
             else {
                 AdTraka.readyFuncs.push(func);
             }
         },
page: function(args) {
             args = args || AdTraka.pageSettings;
             AdTraka.pageSettings = args;

             if (args.api_domain) {
                 AdTraka.domain = args.api_domain;
             }
             
             AdTraka.queue.push( { cmd: 'page', settings: args } );

             if ( AdTraka.isReady ) {
                 AdTraka.processQueue();
             }
         },
event: function(name,args) {
             args = args || {}

             if (name) {
                 AdTraka.queue.push( { cmd: 'event', name: name, settings: args } );

                 if ( AdTraka.isReady ) {
                     AdTraka.processQueue();
                 }
             }
         },
ready: function() {
                if ( !AdTraka.isReady ) {
                    // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                    if ( !document.body ) {
                        return setTimeout( AdTraka.ready, 13 );
                    }

                    // Remember that the DOM is ready
                    AdTraka.isReady = true;

                    // Process pending ready functions
                    var func;
                    while (func = AdTraka.readyFuncs.shift()) {
                        try { func(); } catch(e) {};
                    }

                    // Process pending items in our queue
                    AdTraka.processQueue();
                }
         },
grabTelCommands: function (args) {
             var commands = [];
             var seen = {};

//             var tels = document.getElementsByTagName('adtraka:telephone');
             var tels = AdTraka.getElementsByTagNameClassName('span','adtrakaTelephone');

             for(var i=0; i<tels.length;i++) {
                 var e = tels[i];
                 var c = { cmd: 'telephone' };

                 if (args.tracker) c.tracker = args.tracker;
                 if (args.destination) c.destination = args.destination;

                 try {
                     var tmp = e.getAttribute('tracker');
                     if (tmp) { c.tracker = tmp; }
                     
                     tmp = e.getAttribute('destination');
                     if (tmp) { c.destination = tmp; }

                     tmp = e.getAttribute('ppc_only');
                     if (tmp) { c.ppc_only = tmp; }

                     tmp = e.getAttribute('number');
                     if (tmp) { c.number = tmp; }

                     tmp = e.getAttribute('idd');
                     if (tmp) { c.idd = tmp; }

                     tmp = e.getAttribute('number_prefix');
                     if (tmp) { c.number_prefix = tmp; }
                 }
                 catch(e) {
                     var attrs = e.attributes;
                     for (var j=0; j<attrs.length;j++) {
                         var a = attrs[j];

                         if (a.name == 'tracker') {
                             c.tracker = a.value;
                         }
                         else if (a.name == 'destination') {
                             c.destination = a.value;
                         }
                         else if (a.name == 'ppc_only') {
                             c.ppc_only = a.value;
                         }
                         else if (a.name == 'number') {
                             c.number = a.value;
                         }
                         else if (a.name == 'idd') {
                             c.idd = a.value;
                         }
                         else if (a.name == 'number_prefix') {
                             c.number_prefix = a.value;
                         }
                     }
                 }

                 if (c.destination) {
                     var key = c.tracker + '::' + c.destination;
                     var tmp = [];
                     
                     for (var j in c) {
                         tmp.push( j+":"+c[j] );
                     }
                     key = tmp.sort().join("::");
                     
                     if (seen[key] == null) {
                         seen[key] = AdTraka.telnum_seq++;
                         c.nid = seen[key];
                         commands.push( c );
                     }

                     e.setAttribute('nid',seen[key]);
                 }
             }

             return commands;
         },
processQueue: function() {
             if (AdTraka.isReady && AdTraka.queue.length > 0) {
                 var cid = AdTraka.cid_base + AdTraka.cid_seq++;
                 var commands = [];
                 var cmd = AdTraka.queue.pop();

                 commands.push(cmd);

                 if (cmd.cmd == 'page') {
                     // Grab Document Info
//                     alert( window.location.href );
//                     alert( document.location.href );
                     cmd.location = document.location.href;
                     cmd.title = document.title;
                     if (document.referrer) cmd.referrer = document.referrer;

                     // Grab Browser Information
                     cmd.browser = cmd.browser || {};
                     cmd.browser.appName = navigator.appName;
                     cmd.browser.appVersion = navigator.appVersion;

                     // Grab Screen Dimensions
                     cmd.browser.screen = cmd.browser.screen || {};
                     cmd.browser.screen.width = screen.width;
                     cmd.browser.screen.height = screen.height;

                     // Grab Window Dimensions
                     var winWidth = 0, winHeight = 0;
                     if( typeof( window.innerWidth ) == 'number' ) {
                         //Non-IE
                         winWidth = window.innerWidth;
                         winHeight = window.innerHeight;
                     } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
                         //IE 6+ in 'standards compliant mode'
                         winWidth = document.documentElement.clientWidth;
                         winHeight = document.documentElement.clientHeight;
                     } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
                         //IE 4 compatible
                         winWidth = document.body.clientWidth;
                         winHeight = document.body.clientHeight;
                     }

                     if (winWidth && winHeight) {
                         cmd.browser.window = cmd.browser.window || {};
                         cmd.browser.window.width = winWidth;
                         cmd.browser.window.height = winHeight;
                     }
                    
                     // Take a copy of the page settings so we can
                     // modify them.
                     var g_args = {};
                     for (var i in cmd.settings) { g_args[i] = cmd.settings[i]; }
                     delete g_args['tracker']; // remove page trackerID (use one in cookie).

                     var tc = AdTraka.grabTelCommands(g_args);
                     if (tc.length) {
                         commands = tc;
                         tc.unshift(cmd);
                     }
                 }
                 
                 var msg = { 'cid': cid, ver: '3.0' };
                 msg.cookies = { '_adtraka': AdTraka.cookies()._adtraka };
                 msg.commands = commands;

                 var json = AdTraka.encode(msg);

                 var cmd = document.createElement('script');
                 cmd.type = 'text/javascript';
                 cmd.async = true;
                 cmd.id = cid;
                 // TODO - change ? 'http://' to 'https://'
                 cmd.src = ('https:' == document.location.protocol ? 'http://' : 'http://') + AdTraka.domain + '/v3/cmd?data=' + encodeURIComponent(json);
                 document.getElementsByTagName('head')[0].appendChild(cmd);
             }
         },
handleResponse: function(data) {
             if (data.cid) {
                 var s = document.getElementById(data.cid);
                 s.parentNode.removeChild(s);
             }

             if (data.error) {
//                 alert(data.error); // TODO - remove
             }

             if (data.cmds) {
                 for (i in data.cmds) {
                     var cmd = data.cmds[i];

                     if (cmd.action == 'cookie') {
//                         alert( cmd.name + ', ' + cmd.value + ', ' + cmd.expires );
                         AdTraka.createCookie(cmd.name, cmd.value, cmd.expires);
                     }
                     else if (cmd.action == 'telephone') {
//                         var tels = document.getElementsByTagName('adtraka:telephone');
                         var tels = AdTraka.getElementsByTagNameClassName('span','adtrakaTelephone');

                         for(var i=tels.length-1; i>=0;i--) {
                             var e = tels[i];
                             var destination = e.getAttribute('destination');

                             if (!destination || destination == cmd.destination) {
                                 var tracker = e.getAttribute('tracker');

                                 if (!tracker || tracker == cmd.tracker) {
                                     var nid = e.getAttribute('nid');

                                     if (nid && nid == cmd.nid) {
                                         var format = e.getAttribute('format') || AdTraka.pageSettings.format || '';
                                         var number = AdTraka.formatNumber(cmd.number,format);

                                         var n = document.createTextNode(number);
                                         var p = e.parentNode;
                                         p.insertBefore(n,e);
                                         p.removeChild(e);
                                     }
                                 }
                             }
                         }
                     }
                     else if (cmd.action == 'fetch-image') {
                         var img = document.createElement('img');
                         img.style.borderStyle = "none";
                         img.style.width = '1px';
                         img.style.height = '1px';
                         img.src = cmd.src;
                         document.getElementsByTagName('body')[0].appendChild(img);
                     }
                 }
             }

             if (AdTraka.queue.length > 0) {
                 AdTraka.processQueue(); // process next request
             }
             else {
                 AdTraka.queue.push( { cmd: 'ping' } );
                 if (AdTraka.ping_interval < 0) {
                     AdTraka.setInterval();
                 }
                 
                 if (AdTraka.ping_interval > 0) {
                     setTimeout( AdTraka.processQueue, AdTraka.ping_interval * 1000); // wait a while
                 }
             }
         },
getElementsByClassName: function(classname, node) {
             if(!node) node = document.getElementsByTagName("body")[0];
             var a = [];
             var re = new RegExp('\\b' + classname + '\\b');
             var els = node.getElementsByTagName("*");
             for(var i=0,j=els.length; i<j; i++)
                 if(re.test(els[i].className)) a.push(els[i]);
             return a;
         },
getElementsByTagNameClassName: function(tagname,classname, node) {
             if(!node) node = document.getElementsByTagName("body")[0];
             var a = [];
             var re = new RegExp('\\b' + classname + '\\b');
             var els = node.getElementsByTagName(tagname);
             for(var i=0,j=els.length; i<j; i++)
                 if(re.test(els[i].className)) a.push(els[i]);
             return a;
         },
setInterval: function() {
             if (AdTraka.ping_intervals.length > 0) {
                 var tmp = AdTraka.ping_intervals.shift();
                 if (tmp) {
                     AdTraka.ping_interval = tmp.val;
                     if (tmp.wait) {
                         setTimeout( AdTraka.setInterval, tmp.wait * 1000 );
                     }
                 }
                 else {
                     AdTraka.ping_interval = 0;
                 }
             }
             else {
                 AdTraka.ping_interval = 0;
             }
//             alert("interval = "+AdTraka.ping_interval);
         },
domLoadedChecker: function() {
           // Code to check if DOM is loaded + fallback

           // Cleanup functions for the document ready method
           if ( document.addEventListener ) {
               AdTraka.domLoaded = function() {
                   document.removeEventListener( "DOMContentLoaded", AdTraka.domLoaded, false );
                   AdTraka.ready();
               };

               // Use the handy event callback
               document.addEventListener( "DOMContentLoaded", AdTraka.domLoaded, false );

               // A fallback to window.onload, that will always work
               window.addEventListener( "load", AdTraka.ready, false );
           } else if ( document.attachEvent ) {
               AdTraka.domLoaded = function() {
                   // Make sure body exists
                   if ( document.readyState === "complete" ) {
                       document.detachEvent( "onreadystatechange", AdTraka.domLoaded );
                       AdTraka.ready();
                   }
               };

               // The DOM ready check for Internet Explorer
               AdTraka.doScrollCheck = function() {
                   if ( AdTraka.isReady ) {
                       return;
                   }

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

                   // and execute any waiting functions
                   AdTraka.ready();
               }

                // If IE event model is used

                // ensure firing before onload,
                // maybe late but safe also for iframes
               document.attachEvent("onreadystatechange", AdTraka.domLoaded);

               // A fallback to window.onload, that will always work
               window.attachEvent( "onload", AdTraka.ready );

               // If IE and not a frame
               // continually check to see if the document is ready
               var toplevel = false;

               try {
                   toplevel = window.frameElement == null;
               } catch(e) {}

               if ( document.documentElement.doScroll && toplevel ) {
                   AdTraka.doScrollCheck();
               }
           }
         },
encode: function(data) {
             var data = JSON.stringify(data);
             
             return data;
         },
stringify: function(data) {
             var strs = [];

             if (Object.prototype.toString.apply(data) === '[object Array]') {
                 for(var i in data) {
                     strs.push(AdTraka.stringify(data[i]));
                 }
                 return '[' + strs.join(",") + ']'
             }
             else {
                 for(var n in data) {
                     var value = data[n];
                     if(typeof value == "object") { //Custom handling for arrays
                         strs[n] = AdTraka.stringify(value); /* :RECURSION: */
                     } else {
                         var str = "";
                         str = '"' + n + '":';

                         //Custom handling for multiple data types
                         if(typeof value == "number") str += value; //Numbers
                         else if(value === false) str += 'false'; //The booleans
                         else if(value === true) str += 'true';
                         else str += '"' + value + '"'; //All other things
                         // :TODO: Is there any more datatype we should be in the lookout for? (Functions?)

                         strs.push(str);
                     }
                 }
             }
             
             return '{' + strs.join(",") + '}';//Return associative JSON
         },
cookies: function() {
             var strs = document.cookie.split(';');
             var c = {};
             
             for (var i=0; i<strs.length;i++) {
//             for (i in strs) {
                 var b = strs[i].split('=');
                 var n = b[0];
                 while (n.charAt(0)==' ') n = n.substring(1,n.length); // skip initial spaces
                 c[n] = decodeURIComponent(b[1]);
             }

             return c;
         },
createCookie: function(name,value,days) {
             if (days) {
                 var date = new Date();
                 date.setTime(date.getTime()+(days*24*60*60*1000));
                 var expires = "; expires="+date.toGMTString();
             }
             else var expires = "";
             document.cookie = name+"=" + encodeURIComponent(value) + expires + "; path=/";
         },

formatNumber: function(number,format) {
             var s = "";
             
             if (format) {
                 format = format.split("");
                 var chars = number.split("");

                 for (var i=0; i<format.length;i++) {
//                 for (i in format) {
                     if (format[i] == '#') {
                         if (chars.length) {
                             s = s + chars.shift();
                         }
                     }
                     else {
                         s = s + format[i];
                     }
                 }

                 s = s + chars.join("");
             }
             else {
                 s = number;
             }

             return s;
         }

    };

    // install our DOM Content Loaded Checker
    AdTraka.domLoadedChecker();
})(window);

if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {
        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {
        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());

