/**
 * The functions necessary for editing images.
 *
 * @since 2.9.0
 * @output wp-admin/js/image-edit.js
 */
 /* global imageEditL10n, ajaxurl, confirm */
(function($) {
	/**
	 * Contains all the methods to initialise and control the image editor.
	 *
	 * @namespace imageEdit
	 */
	var imageEdit = window.imageEdit = {
	iasapi : {},
	hold : {},
	postid : '',
	_view : false,
	/**
	 * Handle crop tool clicks.
	 */
	handleCropToolClick: function( postid, nonce, cropButton ) {
		var img = $( '#image-preview-' + postid ),
			selection = this.iasapi.getSelection();
		// Ensure selection is available, otherwise reset to full image.
		if ( isNaN( selection.x1 ) ) {
			this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': img.innerWidth(), 'y2': img.innerHeight(), 'width': img.innerWidth(), 'height': img.innerHeight() } );
			selection = this.iasapi.getSelection();
		}
		// If we don't already have a selection, select the entire image.
		if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) {
			this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true );
			this.iasapi.setOptions( { show: true } );
			this.iasapi.update();
		} else {
			// Otherwise, perform the crop.
			imageEdit.crop( postid, nonce , cropButton );
		}
	},
	/**
	 * Converts a value to an integer.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} f The float value that should be converted.
	 *
	 * @return {number} The integer representation from the float value.
	 */
	intval : function(f) {
		/*
		 * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
		 * worth reminding JavaScript doesn't have a distinct "integer" type.
		 */
		return f | 0;
	},
	/**
	 * Adds the disabled attribute and class to a single form element or a field set.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {jQuery}         el The element that should be modified.
	 * @param {bool|number}    s  The state for the element. If set to true
	 *                            the element is disabled,
	 *                            otherwise the element is enabled.
	 *                            The function is sometimes called with a 0 or 1
	 *                            instead of true or false.
	 *
	 * @returns {void}
	 */
	setDisabled : function( el, s ) {
		/*
		 * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
		 * some text fields  was handled targeting $('input', el). Now we need to handle the
		 * disabled state on buttons too so we can just target `el` regardless if it's a single
		 * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
		 */
		if ( s ) {
			el.removeClass( 'disabled' ).prop( 'disabled', false );
		} else {
			el.addClass( 'disabled' ).prop( 'disabled', true );
		}
	},
	/**
	 * Initializes the image editor.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} postid The post id.
	 *
	 * @returns {void}
	 */
	init : function(postid) {
		var t = this, old = $('#image-editor-' + t.postid),
			x = t.intval( $('#imgedit-x-' + postid).val() ),
			y = t.intval( $('#imgedit-y-' + postid).val() );
		if ( t.postid !== postid && old.length ) {
			t.close(t.postid);
		}
		t.hold.w = t.hold.ow = x;
		t.hold.h = t.hold.oh = y;
		t.hold.xy_ratio = x / y;
		t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
		t.postid = postid;
		$('#imgedit-response-' + postid).empty();
		$('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
			var k = e.keyCode;
			// Key codes 37 thru 40 are the arrow keys.
			if ( 36 < k && k < 41 ) {
				$(this).blur();
			}
			// The key code 13 is the enter key.
			if ( 13 === k ) {
				e.preventDefault();
				e.stopPropagation();
				return false;
			}
		});
	},
	/**
	 * Toggles the wait/load icon in the editor.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} postid The post id.
	 * @param {number} toggle Is 0 or 1, fades the icon in then 1 and out when 0.
	 *
	 * @returns {void}
	 */
	toggleEditor : function(postid, toggle) {
		var wait = $('#imgedit-wait-' + postid);
		if ( toggle ) {
			wait.fadeIn( 'fast' );
		} else {
			wait.fadeOut('fast');
		}
	},
	/**
	 * Shows or hides the image edit help box.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {HTMLElement} el The element to create the help window in.
	 *
	 * @returns {boolean} Always returns false.
	 */
	toggleHelp : function(el) {
		var $el = $( el );
		$el
			.attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
			.parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
		return false;
	},
	/**
	 * Gets the value from the image edit target.
	 *
	 * The image edit target contains the image sizes where the (possible) changes
	 * have to be applied to.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} postid The post id.
	 *
	 * @returns {string} The value from the imagedit-save-target input field when available,
	 *                   or 'full' when not available.
	 */
	getTarget : function(postid) {
		return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
	},
	/**
	 * Recalculates the height or width and keeps the original aspect ratio.
	 *
	 * If the original image size is exceeded a red exclamation mark is shown.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number}         postid The current post id.
	 * @param {number}         x      Is 0 when it applies the y-axis
	 *                                and 1 when applicable for the x-axis.
	 * @param {jQuery}         el     Element.
	 *
	 * @returns {void}
	 */
	scaleChanged : function( postid, x, el ) {
		var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
		warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
		if ( false === this.validateNumeric( el ) ) {
			return;
		}
		if ( x ) {
			h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
			h.val( h1 );
		} else {
			w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
			w.val( w1 );
		}
		if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
			warn.css('visibility', 'visible');
		} else {
			warn.css('visibility', 'hidden');
		}
	},
	/**
	 * Gets the selected aspect ratio.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} postid The post id.
	 *
	 * @returns {string} The aspect ratio.
	 */
	getSelRatio : function(postid) {
		var x = this.hold.w, y = this.hold.h,
			X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
			Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
		if ( X && Y ) {
			return X + ':' + Y;
		}
		if ( x && y ) {
			return x + ':' + y;
		}
		return '1:1';
	},
	/**
	 * Removes the last action from the image edit history.
	 * The history consist of (edit) actions performed on the image.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number} postid  The post id.
	 * @param {number} setSize 0 or 1, when 1 the image resets to its original size.
	 *
	 * @returns {string} JSON string containing the history or an empty string if no history exists.
	 */
	filterHistory : function(postid, setSize) {
		// Apply undo state to history.
		var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
		if ( history !== '' ) {
			// Read the JSON string with the image edit history.
			history = JSON.parse(history);
			pop = this.intval( $('#imgedit-undone-' + postid).val() );
			if ( pop > 0 ) {
				while ( pop > 0 ) {
					history.pop();
					pop--;
				}
			}
			// Reset size to it's original state.
			if ( setSize ) {
				if ( !history.length ) {
					this.hold.w = this.hold.ow;
					this.hold.h = this.hold.oh;
					return '';
				}
				// Restore original 'o'.
				o = history[history.length - 1];
				// c = 'crop', r = 'rotate', f = 'flip'
				o = o.c || o.r || o.f || false;
				if ( o ) {
					// fw = Full image width
					this.hold.w = o.fw;
					// fh = Full image height
					this.hold.h = o.fh;
				}
			}
			// Filter the last step/action from the history.
			for ( n in history ) {
				i = history[n];
				if ( i.hasOwnProperty('c') ) {
					op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
				} else if ( i.hasOwnProperty('r') ) {
					op[n] = { 'r': i.r.r };
				} else if ( i.hasOwnProperty('f') ) {
					op[n] = { 'f': i.f.f };
				}
			}
			return JSON.stringify(op);
		}
		return '';
	},
	/**
	 * Binds the necessary events to the image.
	 *
	 * When the image source is reloaded the image will be reloaded.
	 *
	 * @memberof imageEdit
	 * @since    2.9.0
	 *
	 * @param {number}   postid   The post id.
	 * @param {string}   nonce    The nonce to verify the request.
	 * @param {function} callback Function to execute when the image is loaded.
	 *
	 * @returns {void}
	 */
	refreshEditor : function(postid, nonce, callback) {
		var t = this, data, img;
		t.toggleEditor(postid, 1);
		data = {
			'action': 'imgedit-preview',
			'_ajax_nonce': nonce,
			'postid': postid,
			'history': t.filterHistory(postid, 1),
			'rand': t.intval(Math.random() * 1000000)
		};
		img = $( '![]() ' )
			.on( 'load', { history: data.history }, function( event ) {
				var max1, max2,
					parent = $( '#imgedit-crop-' + postid ),
					t = imageEdit,
					historyObj;
				// Checks if there already is some image-edit history.
				if ( '' !== event.data.history ) {
					historyObj = JSON.parse( event.data.history );
					// If last executed action in history is a crop action.
					if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
						/*
						 * A crop action has completed and the crop button gets disabled
						 * ensure the undo button is enabled.
						 */
						t.setDisabled( $( '#image-undo-' + postid) , true );
						// Move focus to the undo button to avoid a focus loss.
						$( '#image-undo-' + postid ).focus();
					}
				}
				parent.empty().append(img);
				// w, h are the new full size dims
				max1 = Math.max( t.hold.w, t.hold.h );
				max2 = Math.max( $(img).width(), $(img).height() );
				t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
				t.initCrop(postid, img, parent);
				if ( (typeof callback !== 'undefined') && callback !== null ) {
					callback();
				}
				if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
					$('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
				} else {
					$('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
				}
				t.toggleEditor(postid, 0);
			})
			.on('error', function() {
				$('#imgedit-crop-' + postid).empty().append('
' )
			.on( 'load', { history: data.history }, function( event ) {
				var max1, max2,
					parent = $( '#imgedit-crop-' + postid ),
					t = imageEdit,
					historyObj;
				// Checks if there already is some image-edit history.
				if ( '' !== event.data.history ) {
					historyObj = JSON.parse( event.data.history );
					// If last executed action in history is a crop action.
					if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
						/*
						 * A crop action has completed and the crop button gets disabled
						 * ensure the undo button is enabled.
						 */
						t.setDisabled( $( '#image-undo-' + postid) , true );
						// Move focus to the undo button to avoid a focus loss.
						$( '#image-undo-' + postid ).focus();
					}
				}
				parent.empty().append(img);
				// w, h are the new full size dims
				max1 = Math.max( t.hold.w, t.hold.h );
				max2 = Math.max( $(img).width(), $(img).height() );
				t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
				t.initCrop(postid, img, parent);
				if ( (typeof callback !== 'undefined') && callback !== null ) {
					callback();
				}
				if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
					$('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
				} else {
					$('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
				}
				t.toggleEditor(postid, 0);
			})
			.on('error', function() {
				$('#imgedit-crop-' + postid).empty().append('
' + imageEditL10n.error + '