;
(function($) {
	// Namespace all events.
	var eventNamespace = 'waitForImages';

	// CSS properties which contain references to images.
	$.waitForImages = {
		hasImageProperties: [
			'backgroundImage',
			'listStyleImage',
			'borderImage',
			'borderCornerImage',
			'cursor'
		],
		hasImageAttributes: ['srcset']
	};

	// Custom selector to find `img` elements that have a valid `src`
	// attribute and have not already loaded.
	$.expr[':'].uncached = function(obj) {
		// Ensure we are dealing with an `img` element with a valid
		// `src` attribute.
		if (!$(obj).is('img[src][src!=""]')) {
			return false;
		}

		// Firefox's `complete` property will always be `true` even if the
		// image has not been downloaded. Doing it this way works in Firefox.
		var img = new Image();
		img.src = obj.src;
		return !img.complete;
	};

	$.fn.waitForImages = function() {

		var allImgsLength = 0;
		var allImgsLoaded = 0;
		var deferred = $.Deferred();

		var finishedCallback;
		var eachCallback;
		var waitForAll;

		// Handle options object (if passed).
		if ($.isPlainObject(arguments[0])) {

			waitForAll = arguments[0].waitForAll;
			eachCallback = arguments[0].each;
			finishedCallback = arguments[0].finished;

		} else {

			// Handle if using deferred object and only one param was passed in.
			if (arguments.length === 1 && $.type(arguments[0]) === 'boolean') {
				waitForAll = arguments[0];
			} else {
				finishedCallback = arguments[0];
				eachCallback = arguments[1];
				waitForAll = arguments[2];
			}

		}

		// Handle missing callbacks.
		finishedCallback = finishedCallback || $.noop;
		eachCallback = eachCallback || $.noop;

		// Convert waitForAll to Boolean
		waitForAll = !!waitForAll;

		// Ensure callbacks are functions.
		if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
			throw new TypeError('An invalid callback was supplied.');
		}

		this.each(function() {
			// Build a list of all imgs, dependent on what images will
			// be considered.
			var obj = $(this);
			var allImgs = [];
			// CSS properties which may contain an image.
			var hasImgProperties = $.waitForImages.hasImageProperties || [];
			// Element attributes which may contain an image.
			var hasImageAttributes = $.waitForImages.hasImageAttributes || [];
			// To match `url()` references.
			// Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri
			var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g;

			if (waitForAll) {

				// Get all elements (including the original), as any one of
				// them could have a background image.
				obj.find('*').addBack().each(function() {
					var element = $(this);

					// If an `img` element, add it. But keep iterating in
					// case it has a background image too.
					if (element.is('img:uncached')) {
						allImgs.push({
							src: element.attr('src'),
							element: element[0]
						});
					}

					$.each(hasImgProperties, function(i, property) {
						var propertyValue = element.css(property);
						var match;

						// If it doesn't contain this property, skip.
						if (!propertyValue) {
							return true;
						}

						// Get all url() of this element.
						while (match = matchUrl.exec(propertyValue)) {
							allImgs.push({
								src: match[2],
								element: element[0]
							});
						}
					});

					$.each(hasImageAttributes, function(i, attribute) {
						var attributeValue = element.attr(attribute);
						var attributeValues;

						// If it doesn't contain this property, skip.
						if (!attributeValue) {
							return true;
						}

						// Check for multiple comma separated images
						attributeValues = attributeValue.split(',');

						$.each(attributeValues, function(i, value) {
							// Trim value and get string before first
							// whitespace (for use with srcset).
							value = $.trim(value).split(' ')[0];
							allImgs.push({
								src: value,
								element: element[0]
							});
						});
					});
				});
			} else {
				// For images only, the task is simpler.
				obj.find('img:uncached')
					.each(function() {
						allImgs.push({
							src: this.src,
							element: this
						});
					});
			}

			allImgsLength = allImgs.length;
			allImgsLoaded = 0;

			// If no images found, don't bother.
			if (allImgsLength === 0) {
				finishedCallback.call(obj[0]);
				deferred.resolveWith(obj[0]);
			}

			$.each(allImgs, function(i, img) {

				var image = new Image();
				var events =
					'load.' + eventNamespace + ' error.' + eventNamespace;

				// Handle the image loading and error with the same callback.
				$(image).one(events, function me(event) {
					// If an error occurred with loading the image, set the
					// third argument accordingly.
					var eachArguments = [
						allImgsLoaded,
						allImgsLength,
						event.type == 'load'
					];
					allImgsLoaded++;

					eachCallback.apply(img.element, eachArguments);
					deferred.notifyWith(img.element, eachArguments);

					// Unbind the event listeners. I use this in addition to
					// `one` as one of those events won't be called (either
					// 'load' or 'error' will be called).
					$(this).off(events, me);

					if (allImgsLoaded == allImgsLength) {
						finishedCallback.call(obj[0]);
						deferred.resolveWith(obj[0]);
						return false;
					}

				});

				image.src = img.src;
			});
		});

		return deferred.promise();

	};
}(jQuery));