// Lazy Loading module for Slider Pro.
// 
// Adds the possibility to delay the loading of the images until the slides/thumbnails
// that contain them become visible. This technique improves the initial loading
// performance.
;(function( window, $ ) {

	"use strict";

	var NS = 'LazyLoading.' + $.SliderPro.namespace;

	var LazyLoading = {

		allowLazyLoadingCheck: true,

		initLazyLoading: function() {
			var that = this;

			// The 'resize' event is fired after every update, so it's possible to use it for checking
			// if the update made new slides become visible
			// 
			// Also, resizing the slider might make new slides or thumbnails visible
			this.on( 'sliderResize.' + NS, $.proxy( this._lazyLoadingOnResize, this ) );

			// Check visible images when a new slide is selected
			this.on( 'gotoSlide.' + NS, $.proxy( this._checkAndLoadVisibleImages, this ) );

			// Check visible thumbnail images when the thumbnails are updated because new thumbnail
			// might have been added or the settings might have been changed so that more thumbnail
			// images become visible
			// 
			// Also, check visible thumbnail images after the thumbnails have moved because new thumbnails might
			// have become visible
			this.on( 'thumbnailsUpdate.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, $.proxy( this._checkAndLoadVisibleThumbnailImages, this ) );
		},

		_lazyLoadingOnResize: function() {
			var that = this;

			if ( this.allowLazyLoadingCheck === false ) {
				return;
			}

			this.allowLazyLoadingCheck = false;
			
			this._checkAndLoadVisibleImages();

			if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) {
				this._checkAndLoadVisibleThumbnailImages();
			}

			// Use a timer to deffer the loading of images in order to prevent too many
			// checking attempts
			setTimeout(function() {
				that.allowLazyLoadingCheck = true;
			}, 500 );
		},

		// Check visible slides and load their images
		_checkAndLoadVisibleImages: function() {
			if ( this.$slider.find( '.sp-slide:not([ data-loaded ])' ).length === 0 ) {
				return;
			}

			var that = this,

				// Use either the middle position or the index of the selected slide as a reference, depending on
				// whether the slider is loopable
				referencePosition = this.settings.loop === true ? this.middleSlidePosition : this.selectedSlideIndex,

				// Calculate how many slides are visible at the sides of the selected slide
				visibleOnSides = Math.ceil( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10) - this.averageSlideSize ) / 2 / this.averageSlideSize ),

				// Calculate the indexes of the first and last slide that will be checked
				from = this.settings.centerSelectedSlide === true ? Math.max( referencePosition - visibleOnSides - 1, 0 ) : Math.max( referencePosition - 1, 0 ),
				to = this.settings.centerSelectedSlide === true ? Math.min( referencePosition + visibleOnSides + 1, this.getTotalSlides() - 1 ) : Math.min( referencePosition + visibleOnSides * 2 + 1, this.getTotalSlides() - 1  ),
				
				// Get all the slides that need to be checked
				slidesToCheck = this.slidesOrder.slice( from, to + 1 );

			// Loop through the selected slides and if the slide is not marked as having
			// been loaded yet, loop through its images and load them.
			$.each( slidesToCheck, function( index, element ) {
				var slide = that.slides[ element ],
					$slide = slide.$slide;

				if ( typeof $slide.attr( 'data-loaded' ) === 'undefined' ) {
					$slide.attr( 'data-loaded', true );

					$slide.find( 'img[ data-src ]' ).each(function() {
						var image = $( this );
						that._loadImage( image, function( newImage ) {
							if ( newImage.hasClass( 'sp-image' ) ) {
								slide.$mainImage = newImage;
								slide.resizeMainImage( true );
							}
						});
					});
				}
			});
		},

		// Check visible thumbnails and load their images
		_checkAndLoadVisibleThumbnailImages: function() {
			if ( this.$slider.find( '.sp-thumbnail-container:not([ data-loaded ])' ).length === 0 ) {
				return;
			}

			var that = this,
				thumbnailSize = this.thumbnailsSize / this.thumbnails.length,

				// Calculate the indexes of the first and last thumbnail that will be checked
				from = Math.floor( Math.abs( this.thumbnailsPosition / thumbnailSize ) ),
				to = Math.floor( ( - this.thumbnailsPosition + this.thumbnailsContainerSize ) / thumbnailSize ),

				// Get all the thumbnails that need to be checked
				thumbnailsToCheck = this.thumbnails.slice( from, to + 1 );

			// Loop through the selected thumbnails and if the thumbnail is not marked as having
			// been loaded yet, load its image.
			$.each( thumbnailsToCheck, function( index, element ) {
				var $thumbnailContainer = element.$thumbnailContainer;

				if ( typeof $thumbnailContainer.attr( 'data-loaded' ) === 'undefined' ) {
					$thumbnailContainer.attr( 'data-loaded', true );

					$thumbnailContainer.find( 'img[ data-src ]' ).each(function() {
						var image = $( this );

						that._loadImage( image, function() {
							element.resizeImage();
						});
					});
				}
			});
		},

		// Load an image
		_loadImage: function( image, callback ) {
			// Create a new image element
			var newImage = $( new Image() );

			// Copy the class(es) and inline style
			newImage.attr( 'class', image.attr( 'class' ) );
			newImage.attr( 'style', image.attr( 'style' ) );

			// Copy the data attributes
			$.each( image.data(), function( name, value ) {
				newImage.attr( 'data-' + name, value );
			});

			// Copy the width and height attributes if they exist
			if ( typeof image.attr( 'width' ) !== 'undefined') {
				newImage.attr( 'width', image.attr( 'width' ) );
			}

			if ( typeof image.attr( 'height' ) !== 'undefined') {
				newImage.attr( 'height', image.attr( 'height' ) );
			}

			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
				newImage.attr( 'alt', image.attr( 'alt' ) );
			}

			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
				newImage.attr( 'title', image.attr( 'title' ) );
			}

			// Assign the source of the image
			newImage.attr( 'src', image.attr( 'data-src' ) );
			newImage.removeAttr( 'data-src' );

			// Add the new image in the same container and remove the older image
			newImage.insertAfter( image );
			image.remove();
			image = null;
			
			if ( typeof callback === 'function' ) {
				callback( newImage );
			}
		},

		// Destroy the module
		destroyLazyLoading: function() {
			this.off( 'update.' + NS );
			this.off( 'gotoSlide.' + NS );
			this.off( 'sliderResize.' + NS );
			this.off( 'thumbnailsUpdate.' + NS );
			this.off( 'thumbnailsMoveComplete.' + NS );
		}
	};

	$.SliderPro.addModule( 'LazyLoading', LazyLoading );

})( window, jQuery );