// DOM.event.move
//
// 2.0.0
//
// Stephen Band
//
// Triggers 'movestart', 'move' and 'moveend' events after
// mousemoves following a mousedown cross a distance threshold,
// similar to the native 'dragstart', 'drag' and 'dragend' events.
// Move events are throttled to animation frames. Move event objects
// have the properties:
//
// pageX:
// pageY:     Page coordinates of pointer.
// startX:
// startY:    Page coordinates of pointer at movestart.
// distX:
// distY:     Distance the pointer has moved since movestart.
// deltaX:
// deltaY:    Distance the finger has moved since last event.
// velocityX:
// velocityY: Average velocity over last few events.


( function ( fn ) {
	if ( typeof define === 'function' && define.amd ) {
		define( [], fn );
	} else if ( ( typeof module !== "undefined" && module !== null ) && module.exports ) {
		module.exports = fn;
	} else {
		fn();
	}
} )( function () {
	var assign = Object.assign || window.jQuery && jQuery.extend;

	// Number of pixels a pressed pointer travels before movestart
	// event is fired.
	var threshold = 8;

	// Shim for requestAnimationFrame, falling back to timer. See:
	// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
	var requestFrame = ( function () {
		return (
			window.requestAnimationFrame ||
			window.webkitRequestAnimationFrame ||
			window.mozRequestAnimationFrame ||
			window.oRequestAnimationFrame ||
			window.msRequestAnimationFrame ||
			function ( fn, element ) {
				return window.setTimeout( function () {
					fn();
				}, 25 );
			}
		);
	} )();

	// Shim for customEvent
	// see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
	( function () {
		if ( typeof window.CustomEvent === "function" ) return false;
		function CustomEvent( event, params ) {
			params = params || { bubbles: false, cancelable: false, detail: undefined };
			var evt = document.createEvent( 'CustomEvent' );
			evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
			return evt;
		}

		CustomEvent.prototype = window.Event.prototype;
		window.CustomEvent = CustomEvent;
	} )();

	var ignoreTags = {
		textarea: true,
		input: true,
		select: true,
		button: true
	};

	var mouseevents = {
		move: 'mousemove',
		cancel: 'mouseup dragstart',
		end: 'mouseup'
	};

	var touchevents = {
		move: 'touchmove',
		cancel: 'touchend',
		end: 'touchend'
	};

	var rspaces = /\s+/;


	// DOM Events

	var eventOptions = { bubbles: true, cancelable: true };

	var eventsSymbol = typeof Symbol === "function" ? Symbol( 'events' ) : {};

	function createEvent( type ) {
		return new CustomEvent( type, eventOptions );
	}

	function getEvents( node ) {
		return node[ eventsSymbol ] || ( node[ eventsSymbol ] = {} );
	}

	function on( node, types, fn, data, selector ) {
		types = types.split( rspaces );

		var events = getEvents( node );
		var i = types.length;
		var handlers, type;

		function handler( e ) { fn( e, data ); }

		while ( i-- ) {
			type = types[ i ];
			handlers = events[ type ] || ( events[ type ] = [] );
			handlers.push( [ fn, handler ] );
			node.addEventListener( type, handler );
		}
	}

	function off( node, types, fn, selector ) {
		types = types.split( rspaces );

		var events = getEvents( node );
		var i = types.length;
		var type, handlers, k;

		if ( !events ) { return; }

		while ( i-- ) {
			type = types[ i ];
			handlers = events[ type ];
			if ( !handlers ) { continue; }
			k = handlers.length;
			while ( k-- ) {
				if ( handlers[ k ][ 0 ] === fn ) {
					node.removeEventListener( type, handlers[ k ][ 1 ] );
					handlers.splice( k, 1 );
				}
			}
		}
	}

	function trigger( node, type, properties ) {
		// Don't cache events. It prevents you from triggering an event of a
		// given type from inside the handler of another event of that type.
		var event = createEvent( type );
		if ( properties ) { assign( event, properties ); }
		node.dispatchEvent( event );
	}


	// Constructors

	function Timer( fn ) {
		var callback = fn,
			active = false,
			running = false;

		function trigger( time ) {
			if ( active ) {
				callback();
				requestFrame( trigger );
				running = true;
				active = false;
			}
			else {
				running = false;
			}
		}

		this.kick = function ( fn ) {
			active = true;
			if ( !running ) { trigger(); }
		};

		this.end = function ( fn ) {
			var cb = callback;

			if ( !fn ) { return; }

			// If the timer is not running, simply call the end callback.
			if ( !running ) {
				fn();
			}
			// If the timer is running, and has been kicked lately, then
			// queue up the current callback and the end callback, otherwise
			// just the end callback.
			else {
				callback = active ?
					function () { cb(); fn(); } :
					fn;

				active = true;
			}
		};
	}


	// Functions

	function noop() { }

	function preventDefault( e ) {
		e.preventDefault();
	}

	function isIgnoreTag( e ) {
		return !!ignoreTags[ e.target.tagName.toLowerCase() ];
	}

	function isPrimaryButton( e ) {
		// Ignore mousedowns on any button other than the left (or primary)
		// mouse button, or when a modifier key is pressed.
		return ( e.which === 1 && !e.ctrlKey && !e.altKey );
	}

	function identifiedTouch( touchList, id ) {
		var i, l;

		if ( touchList.identifiedTouch ) {
			return touchList.identifiedTouch( id );
		}

		// touchList.identifiedTouch() does not exist in
		// webkit yet… we must do the search ourselves...

		i = -1;
		l = touchList.length;

		while ( ++i < l ) {
			if ( touchList[ i ].identifier === id ) {
				return touchList[ i ];
			}
		}
	}

	function changedTouch( e, data ) {
		var touch = identifiedTouch( e.changedTouches, data.identifier );

		// This isn't the touch you're looking for.
		if ( !touch ) { return; }

		// Chrome Android (at least) includes touches that have not
		// changed in e.changedTouches. That's a bit annoying. Check
		// that this touch has changed.
		if ( touch.pageX === data.pageX && touch.pageY === data.pageY ) { return; }

		return touch;
	}


	// Handlers that decide when the first movestart is triggered

	function mousedown( e ) {
		// Ignore non-primary buttons
		if ( !isPrimaryButton( e ) ) { return; }

		// Ignore form and interactive elements
		if ( isIgnoreTag( e ) ) { return; }

		on( document, mouseevents.move, mousemove, e );
		on( document, mouseevents.cancel, mouseend, e );
	}

	function mousemove( e, data ) {
		checkThreshold( e, data, e, removeMouse );
	}

	function mouseend( e, data ) {
		removeMouse();
	}

	function removeMouse() {
		off( document, mouseevents.move, mousemove );
		off( document, mouseevents.cancel, mouseend );
	}

	function touchstart( e ) {
		// Don't get in the way of interaction with form elements
		if ( ignoreTags[ e.target.tagName.toLowerCase() ] ) { return; }

		var touch = e.changedTouches[ 0 ];

		// iOS live updates the touch objects whereas Android gives us copies.
		// That means we can't trust the touchstart object to stay the same,
		// so we must copy the data. This object acts as a template for
		// movestart, move and moveend event objects.
		var data = {
			target: touch.target,
			pageX: touch.pageX,
			pageY: touch.pageY,
			identifier: touch.identifier,

			// The only way to make handlers individually unbindable is by
			// making them unique.
			touchmove: function ( e, data ) { touchmove( e, data ); },
			touchend: function ( e, data ) { touchend( e, data ); }
		};

		on( document, touchevents.move, data.touchmove, data );
		on( document, touchevents.cancel, data.touchend, data );
	}

	function touchmove( e, data ) {
		var touch = changedTouch( e, data );
		if ( !touch ) { return; }
		checkThreshold( e, data, touch, removeTouch );
	}

	function touchend( e, data ) {
		var touch = identifiedTouch( e.changedTouches, data.identifier );
		if ( !touch ) { return; }
		removeTouch( data );
	}

	function removeTouch( data ) {
		off( document, touchevents.move, data.touchmove );
		off( document, touchevents.cancel, data.touchend );
	}

	function checkThreshold( e, data, touch, fn ) {
		var distX = touch.pageX - data.pageX;
		var distY = touch.pageY - data.pageY;

		// Do nothing if the threshold has not been crossed.
		if ( ( distX * distX ) + ( distY * distY ) < ( threshold * threshold ) ) { return; }

		triggerStart( e, data, touch, distX, distY, fn );
	}

	function triggerStart( e, data, touch, distX, distY, fn ) {
		var touches = e.targetTouches;
		var time = e.timeStamp - data.timeStamp;

		// Create a movestart object with some special properties that
		// are passed only to the movestart handlers.
		var template = {
			altKey: e.altKey,
			ctrlKey: e.ctrlKey,
			shiftKey: e.shiftKey,
			startX: data.pageX,
			startY: data.pageY,
			distX: distX,
			distY: distY,
			deltaX: distX,
			deltaY: distY,
			pageX: touch.pageX,
			pageY: touch.pageY,
			velocityX: distX / time,
			velocityY: distY / time,
			identifier: data.identifier,
			targetTouches: touches,
			finger: touches ? touches.length : 1,
			enableMove: function () {
				this.moveEnabled = true;
				this.enableMove = noop;
				// e.preventDefault();
			}
		};

		// Trigger the movestart event.
		trigger( data.target, 'movestart', template );

		// Unbind handlers that tracked the touch or mouse up till now.
		fn( data );
	}


	// Handlers that control what happens following a movestart

	function activeMousemove( e, data ) {
		var timer = data.timer;

		data.touch = e;
		data.timeStamp = e.timeStamp;
		timer.kick();
	}

	function activeMouseend( e, data ) {
		var target = data.target;
		var event = data.event;
		var timer = data.timer;

		removeActiveMouse();

		endEvent( target, event, timer, function () {
			// Unbind the click suppressor, waiting until after mouseup
			// has been handled.
			setTimeout( function () {
				off( target, 'click', preventDefault );
			}, 0 );
		} );
	}

	function removeActiveMouse() {
		off( document, mouseevents.move, activeMousemove );
		off( document, mouseevents.end, activeMouseend );
	}

	function activeTouchmove( e, data ) {
		var event = data.event;
		var timer = data.timer;
		var touch = changedTouch( e, event );

		if ( !touch ) { return; }

		// Stop the interface from gesturing
		// e.preventDefault();

		event.targetTouches = e.targetTouches;
		data.touch = touch;
		data.timeStamp = e.timeStamp;

		timer.kick();
	}

	function activeTouchend( e, data ) {
		var target = data.target;
		var event = data.event;
		var timer = data.timer;
		var touch = identifiedTouch( e.changedTouches, event.identifier );

		// This isn't the touch you're looking for.
		if ( !touch ) { return; }

		removeActiveTouch( data );
		endEvent( target, event, timer );
	}

	function removeActiveTouch( data ) {
		off( document, touchevents.move, data.activeTouchmove );
		off( document, touchevents.end, data.activeTouchend );
	}


	// Logic for triggering move and moveend events

	function updateEvent( event, touch, timeStamp ) {
		var time = timeStamp - event.timeStamp;

		event.distX = touch.pageX - event.startX;
		event.distY = touch.pageY - event.startY;
		event.deltaX = touch.pageX - event.pageX;
		event.deltaY = touch.pageY - event.pageY;

		// Average the velocity of the last few events using a decay
		// curve to even out spurious jumps in values.
		event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
		event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
		event.pageX = touch.pageX;
		event.pageY = touch.pageY;
	}

	function endEvent( target, event, timer, fn ) {
		timer.end( function () {
			trigger( target, 'moveend', event );
			return fn && fn();
		} );
	}


	// Set up the DOM

	function movestart( e ) {
		if ( e.defaultPrevented ) { return; }
		if ( !e.moveEnabled ) { return; }

		var event = {
			startX: e.startX,
			startY: e.startY,
			pageX: e.pageX,
			pageY: e.pageY,
			distX: e.distX,
			distY: e.distY,
			deltaX: e.deltaX,
			deltaY: e.deltaY,
			velocityX: e.velocityX,
			velocityY: e.velocityY,
			identifier: e.identifier,
			targetTouches: e.targetTouches,
			finger: e.finger
		};

		var data = {
			target: e.target,
			event: event,
			timer: new Timer( update ),
			touch: undefined,
			timeStamp: e.timeStamp
		};

		function update( time ) {
			updateEvent( event, data.touch, data.timeStamp );
			trigger( data.target, 'move', event );
		}

		if ( e.identifier === undefined ) {
			// We're dealing with a mouse event.
			// Stop clicks from propagating during a move
			on( e.target, 'click', preventDefault );
			on( document, mouseevents.move, activeMousemove, data );
			on( document, mouseevents.end, activeMouseend, data );
		}
		else {
			// In order to unbind correct handlers they have to be unique
			data.activeTouchmove = function ( e, data ) { activeTouchmove( e, data ); };
			data.activeTouchend = function ( e, data ) { activeTouchend( e, data ); };

			// We're dealing with a touch.
			on( document, touchevents.move, data.activeTouchmove, data );
			on( document, touchevents.end, data.activeTouchend, data );
		}
	}

	on( document, 'mousedown', mousedown );
	on( document, 'touchstart', touchstart );
	on( document, 'movestart', movestart );


	// jQuery special events
	//
	// jQuery event objects are copies of DOM event objects. They need
	// a little help copying the move properties across.

	if ( !window.jQuery ) { return; }

	var properties = ( "startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY" ).split( ' ' );

	function enableMove1( e ) { e.enableMove(); }
	function enableMove2( e ) { e.enableMove(); }
	function enableMove3( e ) { e.enableMove(); }

	function add( handleObj ) {
		var handler = handleObj.handler;

		handleObj.handler = function ( e ) {
			// Copy move properties across from originalEvent
			var i = properties.length;
			var property;

			while ( i-- ) {
				property = properties[ i ];
				e[ property ] = e.originalEvent[ property ];
			}

			handler.apply( this, arguments );
		};
	}

	jQuery.event.special.movestart = {
		setup: function () {
			// Movestart must be enabled to allow other move events
			on( this, 'movestart', enableMove1 );

			// Do listen to DOM events
			return false;
		},

		teardown: function () {
			off( this, 'movestart', enableMove1 );
			return false;
		},

		add: add
	};

	jQuery.event.special.move = {
		setup: function () {
			on( this, 'movestart', enableMove2 );
			return false;
		},

		teardown: function () {
			off( this, 'movestart', enableMove2 );
			return false;
		},

		add: add
	};

	jQuery.event.special.moveend = {
		setup: function () {
			on( this, 'movestart', enableMove3 );
			return false;
		},

		teardown: function () {
			off( this, 'movestart', enableMove3 );
			return false;
		},

		add: add
	};
} );


( function ( $ ) {

	window.theme || ( window.theme = {} );

	var defaultOptions = {
		default_offset_pct: 0.5,
		orientation: 'horizontal',
		before_label: '',
		after_label: '',
		no_overlay: false,
		move_slider_on_hover: false,
		move_with_handle_only: false,
		click_to_move: true,
	}

	/**
	 * Run image comparison
	 *
	 * @since 1.0
	 * @param {jQuery} Selector
	 */
	theme.imageCompare = function ( $el ) {
		$el.each( function () {
			var container = $( this );
			if ( !container.hasClass( 'icomp-image-after' ) && !container.hasClass( 'icomp-image-before' ) ) {
				return;
			}
			var options = $.extend( {}, defaultOptions, theme.parseOptions( container.attr( 'data-icomp-options' ) ) );
			var sliderPct = options.default_offset_pct;
			var sliderOrientation = options.orientation;
			var beforeDirection = ( sliderOrientation === 'vertical' ) ? 'down' : 'left';
			var afterDirection = ( sliderOrientation === 'vertical' ) ? 'up' : 'right';

			container.wrap( "<div class='icomp-wrapper icomp-" + sliderOrientation + "'></div>" );
			if ( !options.no_overlay ) {
				container.append( "<div class='icomp-overlay'></div>" );
				var overlay = container.find( ".icomp-overlay" );
				options.before_label && overlay.append( "<div class='icomp-before-label' data-content='" + options.before_label + "'></div>" );
				options.after_label && overlay.append( "<div class='icomp-after-label' data-content='" + options.after_label + "'></div>" );
			}

			container.append( "<div class='icomp-handle'></div>" );
			var slider = container.find( ".icomp-handle" );

			var beforeImg = container.find( "img:first" );
			slider.append( "<span class='icomp-" + beforeDirection + "-arrow'></span>" );
			beforeImg.addClass( "icomp-before" );

			if ( container.hasClass( 'icomp-image-after' ) ) {
				var afterImg = container.find( "img:last" );
				slider.append( "<span class='icomp-" + afterDirection + "-arrow'></span>" );
				afterImg.addClass( "icomp-after" );
			}

			var calcOffset = function ( dimensionPct ) {
				var w = beforeImg.width();
				var h = beforeImg.height();
				return {
					w: w + "px",
					h: h + "px",
					cw: ( dimensionPct * w ) + "px",
					ch: ( dimensionPct * h ) + "px"
				};
			};

			var adjustContainer = function ( offset ) {
				if ( sliderOrientation === 'vertical' ) {
					beforeImg.css( "clip", "rect(0," + offset.w + "," + offset.ch + ",0)" );
					if ( typeof afterImg != 'undefined' && afterImg.length ) {
						afterImg.css( "clip", "rect(" + offset.ch + "," + offset.w + "," + offset.h + ",0)" );
					}
				}
				else {
					beforeImg.css( "clip", "rect(0," + offset.cw + "," + offset.h + ",0)" );
					if ( typeof afterImg != 'undefined' && afterImg.length ) {
						afterImg.css( "clip", "rect(0," + offset.w + "," + offset.h + "," + offset.cw + ")" );
					}
				}
				container.css( "height", offset.h );
			};

			var adjustSlider = function ( pct ) {
				var offset = calcOffset( pct );
				slider.css( ( sliderOrientation === "vertical" ) ? "top" : "left", ( sliderOrientation === "vertical" ) ? offset.ch : offset.cw );
				adjustContainer( offset );
			};

			// Return the number specified or the min/max number if it outside the range given.
			var minMaxNumber = function ( num, min, max ) {
				return Math.max( min, Math.min( max, num ) );
			};

			// Calculate the slider percentage based on the position.
			var getSliderPercentage = function ( positionX, positionY ) {
				var sliderPercentage = ( sliderOrientation === 'vertical' ) ?
					( positionY - offsetY ) / imgHeight :
					( positionX - offsetX ) / imgWidth;

				return minMaxNumber( sliderPercentage, 0, 1 );
			};

			$( window ).on( "resize.icomp", function ( e ) {
				adjustSlider( sliderPct );
			} );

			var offsetX = 0;
			var offsetY = 0;
			var imgWidth = 0;
			var imgHeight = 0;
			var onMoveStart = function ( e ) {
				if ( ( ( e.distX > e.distY && e.distX < -e.distY ) || ( e.distX < e.distY && e.distX > -e.distY ) ) && sliderOrientation !== 'vertical' ) {
					e.preventDefault();
				}
				else if ( ( ( e.distX < e.distY && e.distX < -e.distY ) || ( e.distX > e.distY && e.distX > -e.distY ) ) && sliderOrientation === 'vertical' ) {
					e.preventDefault();
				}
				container.addClass( "active" );
				offsetX = container.offset().left;
				offsetY = container.offset().top;
				imgWidth = beforeImg.width();
				imgHeight = beforeImg.height();
			};
			var onMove = function ( e ) {
				if ( container.hasClass( "active" ) ) {
					sliderPct = getSliderPercentage( e.pageX, e.pageY );
					adjustSlider( sliderPct );
				}
			};
			var onMoveEnd = function () {
				container.removeClass( "active" );
			};

			var moveTarget = options.move_with_handle_only ? slider : container;
			moveTarget.on( "movestart", onMoveStart );
			moveTarget.on( "move", onMove );
			moveTarget.on( "moveend", onMoveEnd );

			if ( options.move_slider_on_hover ) {
				container.on( "mouseenter", onMoveStart );
				container.on( "mousemove", onMove );
				container.on( "mouseleave", onMoveEnd );
			}

			slider.on( "touchmove", function ( e ) {
				e.preventDefault();
			} );

			container.find( "img" ).on( "mousedown", function ( event ) {
				event.preventDefault();
			} );

			if ( options.click_to_move ) {
				container.on( 'click', function ( e ) {
					offsetX = container.offset().left;
					offsetY = container.offset().top;
					imgWidth = beforeImg.width();
					imgHeight = beforeImg.height();

					sliderPct = getSliderPercentage( e.pageX, e.pageY );
					adjustSlider( sliderPct );
				} );
			}

			$( window ).trigger( "resize.icomp" );
		} );
	}

	$( window ).on( 'alpha_complete', function () {
		if ( alpha_vars.lazyload != '1' || typeof wp.customize == 'function' ) {
			theme.imageCompare( $( '.icomp-container' ) );
		}
	} );

} )( jQuery );
