/**
 * This file contains the JavaScript required to detect ad blocking browser
 * extensions and display/hide the appropriate content.
 */

window.Abd_Detector_Debug_Messages = [];

function Abd_Detector (options) {
	var self = this;	//	Pointer to main object at any scope below this.	

	/**
	 * A function used to output debugging information. This can be overloaded
	 * in the options object.
	 */
	this.debugMsg = function(msg, level) {
		//	options.debugMessage has either a boolean true if we are supposed
		//	to output messages on our own, a boolean false if we are not, or a
		//	function that deals with debug messages

		if (options.debugMessage) {
			//	Is it a function to take care of the message?
			if (typeof(options.debugMessage) == 'function') {
				//	Okay, then call it and pass the message and log level.
				options.debugMessage(msg, level);
			}
			else {
				//	Then we need to handle it ourselves. Let's output to the
				//	console any and all messages if debugging is allowed.
				console.log("ABD Detector:: " + msg);
			}
		}
		else {
			//	Then we aren't supposed to have debug messages... Don't do
			//	anything.
		}


		//	Save a running tally of log messages.
		Abd_Detector_Debug_Messages.push(msg);
	};

	/**
	 * Adds HTML elements to the DOM that will likely bait ad blockers into removing or hiding.
	 * @return void
	 */
	this.loadFakeAds = function() {
		//	All fake ads are now inserted by WordPress enqueueing.
		//	See the includes/setup.php file. In the ABD_Setup class,
		//	there is a function, enqueue_helper_footer() where the
		//	fake ads are loaded.  This is done to prevent odd dynamic
		//	element insertion errors.

		//	If you want to load any other ad type material, you can try
		//	it here.

		//////////////////////////////////////
		//	IFRAME FRAME BUST PREVENTION	//
		//////////////////////////////////////
		//	The iframe is only written to the page if JavaScript is present and
		//	the browser supports some important iframe attributes that will prevent
		//	a the iframe from busting out and redirecting the parent page.  Check the
		//	file mentioned above to see how that is done specifically.
		//
		//	To make debugging simpler, let's output a log message if the iframe is missing.
		if( !self.iframeSecurityPresent() ) {
			self.debugMsg( "Browser lacks necessary iframe security attributes. Omitting iframe checks!" );
		}
	};	//	end this.loadFakeAds

	/**
	 * Checks to see if the bait ads inserted by Abd_Detector.loadFakeAds()
	 * are present. Note that this function does not wait for the DOM to fully
	 * load, the bait to be inserted, or the presumed ad blocker to process
	 * anything.  You will likely want to delay the execution of this function
	 * using setTimeout, or similar, to prevent false positives.
	 * @return boolean true if bait ads are present and unharmed, false if ad is missing or hidden.
	 */
	this.checkAdStatus = function() {
		//	Okay, let's make some easy variable to make things readable.
		var frame = jQuery('#abd-ad-iframe');
		var frameNoJq = document.getElementById("abd-ad-iframe");

		var div = jQuery('#abd-ad-div');
		var divNoJq = document.getElementById("abd-ad-div");


		var retVal = {
			blockerDetected: false,
			status: 'noadblock',
			detectionMethod: 'N/A'
		};

		//	Ad blockers can hide things in numerous ways. Check every way I
		//	can think of.
		if(ABDSettings.enableIframe != 'no') {
			//	Check for the appended frame
			//
			//	Okay, we have a problem... We might not have loaded the iframe if the browser
			//	doesn't support some security features that prevent frame busting.  If
			//	the iframe is missing for this reason, the checks we're about to run will
			//	yield a false positive.  So, make sure the iframe was inserted before
			//	running the checks!
			if( self.iframeSecurityPresent() ) {	//	The iframe should be there! Run the checks.
				retVal = self.checkAdStatusIframeHelper('#abd-ad-iframe', frame, frameNoJq, retVal);
			}
		}
		else {
			self.debugMsg("iframe detection disabled");
		}


		if( ABDSettings.enableDiv != 'no' ) {
			retVal = self.checkAdStatusDivHelper(div, divNoJq, retVal);
		}
		else {
			self.debugMsg("div detection disabled");
		}


		if(ABDSettings.enableJsFile != 'no') {
			//	Check for bait javascript file (assets/js/advertisement.js) flags
			if (window.abd_script_load_flag !== true) {
				//	Then the bait javascript file, advertisement.js, was not loaded or run
				mthd_text = "js removal detected! (Detection Method: Bait javascript file prevented from loading and execution!)";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else {	//	It's there
				self.debugMsg("No js removal detected");
			}
		}
		else {
			self.debugMsg("js detection disabled");
		}


		//	Now, go through user defined selectors checks
		if( ABDSettings.cssSelectors ) {
			var selectors = ABDSettings.cssSelectors;
		}
		else {
			var selectors = [];
		}
		jQuery.each(selectors, function(index, val) {
			//	Is this empty or not a string?
			if(!val || typeof val != 'string') {
				return true;	//	jQuery each() equivalent of continue;
			}

			//	Is this a JSON string?
			var value = val;
			try {
				value = jQuery.parseJSON( value );
			}
			catch(e) {
				value = val;
			}

			self.debugMsg("checking user defined selector: " + value);

			//	Select this item, and make sure it exists
			var item = jQuery(value);
			if( item.length ) {	//	item exists
				self.debugMsg("found " + item.length + " matches for the specified selector");
			}
			else {
				self.debugMsg("specified selector doesn't match any element on page");
				return;	//	jQuery.each equivalent of continue statement
			}

			//	Iterate through each match
			jQuery(value).each(function(ind, elem) {				
				//	Is the wrapper height shrunk to zero?
				if(jQuery(elem).height() < 50) {
					mthd_text = "user defined wrapper removal detected! (Detection Method: wrapper height resized to near 0 - $(selector).height < 50";

					retVal = {
						blockerDetected: true,
						status: 'adblock',
						detectionMethod: mthd_text
					}

					self.debugMsg(mthd_text);
				}

				//	Is the wrapper empty?
				if(jQuery.trim(jQuery(elem).html()) == '') {
					mthd_text = "user defined wrapper removal detected! (Detection Method: wrapper is empty - $.trim($(selector).html()) == ''";

					retVal = {
						blockerDetected: true,
						status: 'adblock',
						detectionMethod: mthd_text
					}

					self.debugMsg(mthd_text);
				}

				//	If there is an iframe within the wrapper, is it screwed up?
				jQuery(elem).find('iframe').each(function() {
					retVal = self.checkAdStatusIframeHelper(value, jQuery(elem), 'skip', retVal);
				});
			});
		});


		//	Okay, retVal should still be true from its declaration above if no
		//	blocking was detected.  If it was detected, then it was set to false.
		//	Which is what we want to return.
		return retVal;
	};	//	end this.checkAdStatus

		this.checkAdStatusIframeHelper = function( selector, frameJq, frameNoJq, retVal ) {
			if (frameJq.length === 0) {	//	jQuery couldn't find it in the DOM
				mthd_text = "iframe removal detected! (Detection Method: jQuery selector empty - $('" + selector + "').length === 0)";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (frameNoJq == undefined) {	//	JavaScript (no jQuery) couldn't find it
				mthd_text = "iframe removal detected! (Detection Method: no element with id found - document.getElementById(" + selector + ") == undefined";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (frameJq.height < 50) { //	Frame resized too small
				mthd_text = "iframe removal detected! (Detection Method: iframe height resized to near 0 - $('" + selector + "').height < 50";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (frameJq.is("hidden")) {	//	Frame hidden via visibility or display
				mthd_text = "iframe removal detected! (Detection Method: iframe hidden - $('" + selector + "').is('hidden')";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (frameJq.find(":hidden").length !== 0) {	//	Yet another way to look for hidden frame in case blockers are tricky
				mthd_text = "iframe removal detected! (Detection Method: iframe hidden - $('" + selector + "').find(':hidden').length === 0";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (frameJq.css('visibility') === 'hidden' || frameJq.css('display') === 'none') {	//	and again... another way to look for hidden frame
				mthd_text = "iframe removal detected! (Detection Method: iframe css changed to hidden - $('" + selector + "').css('visibility') === 'hidden' || $('" + selector + "').css('display') === 'none')";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}

			else {	//	Well... I'm out of ideas... the damn thing must be there.
				self.debugMsg("No iframe removal detected.");
			}

			return retVal;
		}	//	end this.checkAdStatusIframeHelper
		this.checkAdStatusDivHelper = function( div, divNoJq, retVal ) {
			//	Check for appended div
			if (div.length === 0) {	//	jQuery couldn't find it in the DOM
				mthd_text = "div removal detected! (Detection Method: jQuery selector empty)";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (divNoJq == undefined) {	//	JavaScript (no jQuery) couldn't find it
				mthd_text = "div removal detected! (Detection Method: no element with id found - document.getElementById == undefined";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (div.height === 0) { //	Frame resized too small
				mthd_text = "div removal detected! (Detection Method: height resized to 0";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (div.is("hidden")) {	//	Frame hidden via visibility or display
				mthd_text = "div removal detected! (Detection Method: div hidden";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (div.find(":hidden").length !== 0) {	//	Yet another way to look for hidden div in case blockers are tricky
				mthd_text = "iframe removal detected! (Detection Method: iframe hidden - $('$abd-ad-iframe').find(':hidden').length === 0";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}
			else if (div.css('visibility') === 'hidden' || div.css('display') === 'none') {	//	Yet another way to look for hidden div in case blockers are tricky
				mthd_text = "div removal detected! (Detection Method: CSS visibility or display altered!)";

				retVal = {
					blockerDetected: true,
					status: 'adblock',
					detectionMethod: mthd_text
				}

				self.debugMsg(mthd_text);
			}

			else {	//	Well... I'm out of ideas... the damn thing must be there.
				self.debugMsg("No div removal detected");
			}

			return retVal;
		}	//	end this.checkAdStatusDivHelper

	/**
	 * Loads bait ads, checks to see if they were blocked, and executes the appropriate action.
	 * @param  {function} noBlockerFunc The function to execute if no ad blocker is detected.
	 * @param  {function} blockerFunc   The function to execute if an ad blocker is detected.
	 * @return {void}
	 */
	this.executeFunc = function(noBlockerFunc, blockerFunc) {
		var self = this;	//	We're gonna want this this inside the .ready
							//	function.  Not that this.  So save this this
							//	and use it instead of that this.

		jQuery(document).ready(function () {
			self.loadFakeAds();

			//	Okay, now we want to detect the Ad Status.
			//	However, for God only knows what reason, sometimes
			//	this executes before loadFakeAds has finished appending and
			//	inserting the fake ads.  It shouldn't.  But it does.
			//
			//	And to make things worse, it doesn't do it on YOUR web browser,
			//	only the browsers of other unfortunate souls.  So you can't test.
			//	You can't experiment.  You can't find the cause.  Only treat the
			//	symptom.  So let's treat it.
			//
			//	We need a little timeout to cool our heels before we get
			//	cracking.
			//
			//
			//
			//	I know what you're thinking... this is bullshit.  John's an idiot.
			//	There's no way on this Earth that this needs a timeout.
			//
			//	Well, this is bullshit... and John may very well be an idiot.  But
			//	there needs to be a timeout here.  If you decide to tinker with
			//	this, be VERY CAREFUL!  Or, you know... don't.
			setTimeout(function() {
				var res = self.checkAdStatus();
				
				if (!res.blockerDetected) {
					noBlockerFunc(res);
				}
				else {
					blockerFunc(res);
				}
			}, 500);
		});
	};

	/**
	 * Defers calling of a function until jQuery has loaded or it is impractical to
	 * wait any longer.
	 * @param  function funcToRun    The function to run once jQuery has loaded.
	 * @param  int recurseDepth 	 (optional) The number of times left in recursion. Usually should be omitted.
	 * @param  int totalTime    	 (optional) The number of milliseconds waited so far. Usually should be omitted.
	 * @return int                   The total time spent waiting in milliseconds AFTER RECURSION HAS FINISHED. Not available during recursion.  -1 If defer failed
	 */
	this.jQueryDefer = function( funcToRun, recurseDepth, totalTime ) {
		//	Setup defaults
		if( typeof recurseDepth === 'undefined' ) {
			recurseDepth = 100;
		}
		if( typeof totalTime === 'undefined' ) {
			totalTime = 0;
		}
		var waitTime = 25;


		//	Does jQuery exist?
		if( typeof jQuery !== 'undefined' ) {
			//	Yes, it does, so log any fun messages and run the function.
			if( totalTime > 0 ) {
				self.debugMsg( "Asynchronous jQuery detected. ABD waited " + totalTime/1000 + " seconds for jQuery to load." );
			}
			funcToRun();
			return totalTime;
		}
		else if( recurseDepth > 0 ) {
			//	No, it doesn't, and we still have time to wait. Recurse another level.
			setTimeout( function() {
				self.jQueryDefer( funcToRun, --recurseDepth, totalTime + waitTime );
				},
			waitTime );
		}
		else {
			//	No, it doesn't, and we've waited long enough.  Throw an error and die.
			self.debugMsg( "Cannot run!  jQuery didn't load.  Try adding an exception for jQuery in any asynchronous JavaScript plugins. Total wait time: " + totalTime/1000 + " seconds." );
			return -1;
		}
	}

	/**
	 * Determines whether the browser supports either the sandbox or the security
	 * attribute for iframes.  This is important for detecting frame busting in
	 * the bait ad iframe.
	 */
	this.iframeSecurityPresent = function() {
		var frm = document.createElement('iframe');
		if( ('sandbox' in frm) || ('security' in frm) ) {
			return true;
		}

		return false;
	}

	////////////////
	//	Constructor
	////////////////
	this.allonsy = function() {
		this.executeFunc( options.noBlockerFunc, options.blockerFunc );
	}
	//	Wait until jQuery is loaded, then call this.executeFunc which is wrapped
	//	in this.allonsy so we can pass parameters.
	this.jQueryDefer( this.allonsy );

}	//	end Abd_Detector


//	Let's run the damn thing and see what we can catch
//	Run the ad blocker.
Abd_Detector({
	//	What do we want to do if no ad blockers are detected?
	noBlockerFunc: function(res) {
		jQuery('div.ABD_display_noadblock').show();
		jQuery('div.ABD_display_adblock').hide();

		//	Add class to body tag
		jQuery('body').addClass('ABD_noadblock');

		//	Throw event
		jQuery(document).trigger('abd_status_change', res);
	},
	//	What do we want to do if we detect an ad blocker?
	blockerFunc: function(res) {
		jQuery('div.ABD_display_noadblock').hide();
		jQuery('div.ABD_display_adblock').show();

		//	Add class to body tag
		jQuery('body').addClass('ABD_adblock');

		//	Throw event		
		jQuery(document).trigger('abd_status_change', res);
	},
	//	Do we want to see debug messages?
	debugMessage: true
});
