/* •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
   ••  Project: jQuery LazyLoad For Advertisement                   ••
   ••  Author:  delarueguillaume@gmail.com                          ••
   ••  WebSite : http://www.web2ajax.fr/                            ••
   ••  Date:    2010                                                ••
   ••  Version: 1.3 (01 march 2010) : Improve in-script domWrite    ••  
   ••  Version: 1.2 (30 march 2010) : High Imporvements of code     ••   
   ••  Version: 1.1 (25 march 2010) : Improve IE compatibility      ••
   ••  Version: 1.0 (24 march 2010)                                 ••
   •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••   
   
    Defer Advertisement loading and load only if the ad is in the visible part of
    the page like lazyLoad for pictures.
    -> Highly improve general page load
    -> Improve cpu load of visitor in case of displaying flash banners (only displayed if necessary)
    –> Compatibility with AdSense and many other advertisers
    -> Cross Browser suupprt (IE5.5+, Firefox, Opera, Chrome, Safari)
    
    A big thanks to Mika Tuupola for the Lazy load part
    -> http://www.appelsiini.net/projects/lazyload 
    
    Another big thanks to Thomas Aylott and his MooTools based document.write replacement
    -> http://SubtleGradient.com/
    
---------------------------------------------------------------------- */
 
(function($) { 

	$.lazyLoadAdRunning = false ;
	$.timer = {} ;
	
	/* Write logs if possible in console */
	function _debug() {
		var debug = false ;
		if ( typeof console != 'undefined' && debug ) {	
			var args = [] ;
			for ( var i = 0 ; i < arguments.length ; i++ ) args.push(arguments[i]) ;
			if ( args.length == 1 ) args = args[0] ;
			else if ( args.length == 0 ) args = false ;
			try { console.log('LazyLoadAD |', args) ; } catch(e) {} ;
		}
	}
	
	/* PhpJS : str_replace */
	function str_replace(search,replace,subject){var s=subject;var ra=r instanceof Array,sa=s instanceof Array;var f=[].concat(search);var r=[].concat(replace);var i=(s=[].concat(s)).length;var j=0;while(j=0,i--){if(s[i]){while(s[i]=(s[i]+'').split(f[j]).join(ra?r[j]||"":r[0]),++j in f){};}}
	return sa?s:s[0];}
	
	/* Browser name Redefinition */ 
	$.browser.name = $.browser.opera ? 'Opera' : $.browser.webkit ? 'Chromium' : $.browser.mozilla ? 'Firefox' : $.browser.msie ? 'IE' : navigator.appName.toLowerCase() ;
	
	/* Sandbox the document.write function after document load */
	document.write = function(t) {
		try {
			$('<div>').addClass('document-write-sandbox').html(t).appendTo($('body')) ;
			console.log(' ** SANDBOX ** Real DomWrite avoid : ', t) ;
		} catch(e) {} ;
	} ;
	document.writeln = document.write ;
	
	
    $.fn.lazyLoadAd = function(options) { 
        var settings = {
            threshold    		: 3,
            failurelimit 		: 0,
            forceLoad	 		: false,
            event        		: "scroll",
            viewport	 		: window,
            onComplete	 		: false,
            timeout		 		: 5000,
            completeTimeout		: 1000,
            pixel				: false,
            debug		 		: true
        };
                
        if(options) {
            $.extend(settings, options);
        }

		if ( settings.forceLoad == true )$.lazyLoadAdRunning = false ;
		
        /* Fire one scroll event per scroll. Not one scroll event per image. */
        var elements = this;
        
        $(settings.viewport).bind("checkLazyLoadAd", function() {
        	var counter = 0;
        	elements.each(function() {                	
        		if ( $.lazyLoadAdRunning ) {
        			if ( $.timer.delayCheck ) clearTimeout( $.timer.delayCheck ) ;
        			 $.timer.delayCheck = setTimeout(function() {
        				$(settings.viewport).trigger("checkLazyLoadAd") ; 
        			}, 800) ;
        			return false ;
        		} else if ( settings.forceLoad == true ) {
        			$(this).trigger("load");
        		} else if (!$.belowthefold(this, settings) && !$.abovethetop(this, settings)) {
        	        $(this).trigger("load");
        	    } else {
        	        if (counter++ > settings.failurelimit) {
        	            return false;
        	        }
        	    }
        	});
        	/* Remove image from array so it is not looped next time. */
        	var temp = $.grep(elements, function(element) {
        	    return !element.loaded;
        	});
        	elements = $(temp);
        }) ; 
        
        
        if ("scroll" == settings.event) {
            $(settings.viewport).bind("scroll", function(event) {
               $(settings.viewport).trigger("checkLazyLoadAd") ;                
            });
        }
        
        this.each(function() {
            var self = this;
			
			try {
				console.log('LazyLoad :: each :: ', $(self).attr('lazyID'), 'DOM ::',  self) ;
				console.log('---------') ;
			} catch(e) {};
			
			/* Save original only if it is not defined in HTML. */
			if (undefined == $(self).attr("original")) {
			    $(self).attr("original", $(self).attr("src"));     
			}
			
			/* Detect if something to activate */
            if ("scroll" != settings.event || 
                    undefined == $(self).attr("src") || 
                    ($.abovethetop(self, settings) ||
                     $.belowthefold(self, settings) )) {
                $(self).removeAttr("src");
                self.loaded = false;
            } else {
                self.loaded = true;
            }
            
            /* Set fails counter */
            self.failCount = 0 ;
            
            /* Append a 1x1 notification pixel */
            function add_pixel(type) {
            	if ( settings.pixel ) {
            		var infos = {
            			id: $(self).attr('lazyID'),
            			format: $(self).width()+'x'+$(self).height(),
            			pos: parseInt($(self).offset().top)+'x'+parseInt($(self).offset().left),
            			start: (self.startLoad||(new Date()).getTime()),
            			type: (type||'print'),
            			place: ($(self).attr('lazyPLACE')||'')
            		} ;
            		
            		infos.loadTime = (new Date()).getTime() - infos.start ;
            		
            		var pixel = $('<img>', {
            			width: 1, 
            			height: 1
            		})
            		.attr( 'src', settings.pixel+'?lazyID='+infos.id+'&type='+infos.type+'&place='+infos.place+'&pos='+infos.pos+'&format='+infos.format+'&loadTime='+infos.loadTime+'&fails='+self.failCount+'&browser='+$.browser.name+'_'+$.browser.version+'&now='+ (new Date()).getTime() )
            		.appendTo($(self).parent()) ;
            		
            		_debug('1x1 :', infos) ;
            	}
            }
            
            /* Add call notification pixel */
            add_pixel('call') ;
           
            /* When appear is triggered load original image. */
            $(self).unbind('load').bind("load", function() {
            	
            	if ( settings.forceLoad == true ) $.lazyLoadAdRunning = false ;
            	
            	if ( ! this.loaded ) {
                
                	// Lock other ads load
                	$.lazyLoadAdRunning = true ;    
                	
                	// Store start time
                	self.startLoad = (new Date()).getTime() ;            	
                	
                	// Eval script into pub_container for adSense by example
					var scripts = [],
						script,
						regexp = /<code[^>]*>([\s\S]*?)<\/code>/gi,
						
						wrapper  = $('<div>'),
						frag = document.createDocumentFragment(),
						documentWrite = function() {},
						numWrappers = 0,
						isDocumentWriteOverload = false,
						isInlineScript = null ;
					
					
					
					
					/* =Document Write Overload
					--------------------------------------------------------------*/
					
					// -- Override the document.write defaults to write into context
					document._writeOriginal = document.write;
					document.write = function(){
						var args = arguments ;
						var html=''; for(var i=0;i<args.length;i++) html+=args[i] ;
						
						isDocumentWriteOverload = true ;
						numWrappers++ ;
						
						documentAppend = function (){
							try {
								var specialDiv = $('<div>').html(html) ;
								var doc = document.write.context ;
								frag.code = frag.code || '' ;
								
								if ( specialDiv[0].childNodes.length ) {
									
									for ( var i=0 ; i < specialDiv[0].childNodes.length ; i++ ) {
										var node = specialDiv[0].childNodes[i] ;
										var isValidHTML = node && node.nodeName == 'DIV' ? true : false ;
										
										//_debug( 'isValidHTML : ', isValidHTML ) ;
										if ( isValidHTML ) {
										
											if ( frag.code != '' ) {
												_debug("Fragment waiting inject : ", numWrappers, frag.code);
												doc.innerHTML = ( doc.innerHTML || '' ) + ( frag.code || '' ) ;
												frag.code = '' ;
											}
											
											_debug("Fragment inject direct : ", numWrappers, node.outerHTML);
											doc.innerHTML = (doc.innerHTML || '') + (node.outerHTML || '') ;
											
										} else {
											_debug('Fragment add to end : ', node.outerHTML) ;
											frag.code += ( node.outerHTML || '' ) ;
										}
									}
									
								} else {
									_debug('Fragment add to end : ', html) ;
									frag.code += ( html || '' ) ;
								}
								
							} catch(e) { _debug(e); }
						}
						
						documentWrite = function(){
							try {
								var _html = $(wrapper.html(html))[0] || '' ;
								frag.appendChild($(html)[0]);
								$(document.write.context).append(frag) ;
								$(frag).remove() ;
								_debug("Fragment append : ", numWrappers, html );
								
							} catch(e) { _debug(e); } 
						}
						
						// -- Insert domWrite in context
						if ( isInlineScript == null ) {
							isInlineScript = /<script/i.test(html) && /\/script/i.test(html) ;
						}
						
						// -- Treat HTML					
						if ( isInlineScript ) {
							documentWrite() ;
						} else {
							documentAppend() ;
						}
						
						// -- Chek ready state
						_debug('===== ASK TO CHEACK READY STATE =========') ;
						$(self).trigger('checkReadyState') ;
						
					};
					
					
					// -- Define the context
					document.write.context = $(self)[0];
					
					// -- Override writeln too
					document._writelnOriginal = document.writeln;
					document.writeln = document.write ;

					
					/* =Bind Events
					--------------------------------------------------------------*/
					
					// Bind an ad load complete
					$(self).bind('lazyLoadComplete', function() {
					
						if ( ! self.loaded ) {
						
							var sourceHTML = $(self).data('startSource') ;
							var finalHtml = $(document.write.context).html() ;
							
							/* If not content modified => fail */
							if ( sourceHTML == finalHtml  ) {
								try {
									$(self).trigger('lazyLoadFailure') ;
								} catch(e) { console.log(e) ; }
							} else {					
								
								/* Set item to loaded status and restore defaults */
								$(self).trigger('restoreDefault') ;
								$(self).attr("src", $(self).attr("original")) ;
								$(self).removeAttr("original") ;
								self.loaded = true;
								
								/* Add print notification pixel */
								add_pixel('print') ;
								
								/* If onComplete callback handler setted */
								if ( typeof settings.onComplete == 'function') {	
									try { settings.onComplete(self); } catch(e) { _debug(e); } ;
								} 
								
								_debug('LazyLoad complete...');
							}
						} 
					}) ;
					
					// Bind an ad Load Fail
					$(self).bind('lazyLoadFailure', function() {
					
						if ( $.lazyLoadAdRunning ) {
							
							self.failCount++ ;
							
							_debug('LazyLoad fail : '+self.failCount+'/'+settings.failurelimit);
							
							$(script).remove() ;
							$(self).trigger('restoreDefault') ;
							$(document.write.context).html('') ;
							if ( $.timer.chkReady ) clearInterval($.timer.chkReady);
							if (undefined == $(self).attr("original") && ! self.loaded ) {
							    $(self).attr("original", $(self).attr("src"));   
							    $(self).removeAttr("src")   
							}
							
							if ( self.failCount >= settings.failurelimit ) {
								self.loaded = true ;
								_debug('LazyLoad fail limit exceded...');
								
								/* Add fail notification pixel */
								add_pixel('fail') ;
								
								if ( typeof settings.onError == 'function') {	
									try { settings.onError() } catch(e) {} ;
								}
							} else {
								if ( typeof settings.onFailure == 'function') {	
									try { settings.onFailure() } catch(e) {} ;
								}
							}
							  		
							_debug('Fail back actioned after '+settings.timeout+' ms', document.write.context);
							
							$(self).trigger("load");							
						}
					}) ;
					
					// Check the ready state
					$(self).bind('checkReadyState', function() {
					
						if ( $.timer.complete ) clearTimeout($.timer.complete) ;
						if ( $.timer.chkReady ) clearInterval($.timer.chkReady);
						
						$.timer.complete = setTimeout(function() {
							if ( $.timer.chkReady ) clearInterval($.timer.chkReady);
							
							$.timer.chkReady = setInterval(function() {
								
								_debug(' ** Check complete Ready State : ', document.readyState) ;
								
								if ( 'undefined' != typeof frag.code ) {
									if ( $.trim(frag.code) != '' ) {
										document.write.context.innerHTML = frag.code ;
										_debug("Code Fragment append : ", numWrappers, $(self).attr('lazyID'), frag.code );
									}
								}
								
								if ( document.readyState == 'complete' ) {
									$.lazyLoadAdRunning = false ;
									$(self).trigger('lazyLoadComplete') ;
									if ( $.timer.chkReady ) clearInterval($.timer.chkReady);
								}
								
							}, 20) ;
							
						}, settings.completeTimeout ) ;
					
					}); 
					
					/* Restore some defaults after load ad */
					$(self).bind('restoreDefault', function() {
					
						_debug('Restore Defaults' );
						
						$.lazyLoadAdRunning = false ;
						
						if ( typeof document._writeOriginal == 'function') {
							document.write = document._writeOriginal ;
							document.writeln = document._writelnOriginal;
							try {
								delete document._writeOriginal ;
								delete document._writelnOriginal ;
							} catch(e) {
								document._writeOriginal = document._writelnOriginal = function() {} ;
							}
						}
					}) ;

					
					// -- Reset lazyLoad timer if load is restart or init
					if ( $.timer.complete ) clearTimeout($.timer.complete) ;

					
					/* =Eval beforeCode and call script in DOM
					--------------------------------------------------------------*/
					
					
					// -- Call script and let's dance
					_debug('------------------------------  Lazy Load Ad CALL ----') ;
					_debug('Context : '+ $(document.write.context).attr('lazyID') ) ;
					
									
					// Eval script into pub_container for adSense by example
					try {
						while ((script = regexp.exec($(self).html()))) {
							scripts.push(script[1].replace('<!--', '').replace('//-->', ''));
						}
						scripts = scripts.length ? scripts.join('\n') : '' ;
						if ( scripts != '' ) eval(scripts); 
						//$(self).html('') ;
					} catch(e) {} ;
					
					
					
					// -- Append adScript after context and bind load
					var url =  $(self).attr("original") ;
					var script = document.createElement("script") ;
					script.src = url + ( url.replace(/^[^\?]+\??/, '') == '' && ! (/_lazyAd=/).test(url) ? '?' : '&' ) + '_lazyAd=' + new Date().getTime() ;
					script.type = "text/javascript";
					
					if ( url != 'html_only' ) {
						document.write.context.appendChild(script);  // add script tag to head element
					}
					
					// Backup content at init to bind fail load
					$(self).data('startSource', $(self).html()) ;
					
					// -- Set the callback function, when script is loaded
					var callback = function() {
						_debug('Load Ad Code', url ) ;
						
						// -- In case of no use of 'document.write' in the JS file (standalone JS)
						if ( $.timer.noDOMwrite ) clearTimeout($.timer.noDOMwrite); 
						$.timer.noDOMwrite = setTimeout(function() {
							if ( ! isDocumentWriteOverload ) {
								_debug('... is considred as injected') ;
								$(document.write.context).trigger('lazyLoadComplete') ;
							}
							$(script).remove() ;
						}, 300) ;
					} ;
					
					// test for onreadystatechange to trigger callback
					script.onreadystatechange = function () {
						if (script.readyState == 'loaded' || script.readyState == 'complete') {
							callback();
						}
					}                            
					// test for onload to trigger callback
					script.onload = function () {
						callback();
						return;
					}
					// safari doesn't support either onload or readystate, create a timer
					// only way to do this in safari
					try {
						if ((Prototype.Browser.WebKit && !navigator.userAgent.match(/Version\/3/)) || Prototype.Browser.Opera) { // sniff
							$.timer.url = setInterval(function() {
								if (/loaded|complete/.test(document.readyState)) {
									clearInterval($.timer.url);
									_debug('Call for Safari complete') ;
									callback(); // call the callback handler
								}
							}, 10);
						}
					}catch(e) {};
					
					// In case of error, set a timer to simulate a good load and call next ad
					if ( $.timer.failBack ) clearTimeout($.timer.failBack) ;
					$.timer.failBack = setTimeout(function() {
						$(self).trigger('lazyLoadFailure') ;
					}, settings.timeout) ;
                };
            });




            /* When wanted event is triggered load ad */
            /* by triggering appear.                              */
            if ("scroll" != settings.event) {
                $(self).bind(settings.event, function(event) {
                    if (!self.loaded && ! $.lazyLoadAdRunning) {
                        $(self).trigger("load");
                    }
                });
            }
            
            return this ;
        });
        
        /* Force initial check if images should appear. */
        $(settings.viewport).trigger('checkLazyLoadAd');
        
        return this;

    };

    /* Convenience methods in jQuery namespace.           */
    /* Use as  $.belowthefold(element, {threshold : 100, container : window}) */
    
    $.belowthefold = function(element, settings) {
        if (settings.viewport === undefined || settings.viewport === window) {
            var fold = $(window).height() + $(window).scrollTop();
        } else {
            var fold = $(settings.viewport).offset().top + $(settings.viewport).height();
        }
        return fold <= $(element).offset().top - settings.threshold;
    };
        
    $.abovethetop = function(element, settings) {
        if (settings.viewport === undefined || settings.viewport === window) {
            var fold = $(window).scrollTop();
        } else {
            var fold = $(settings.viewport).offset().top;
        }
        return fold >= $(element).offset().top + settings.threshold  + $(element).height();
    };
    
})(jQuery);