/*global documents: true define: true*/
/*
* Aloha Image Plugin - Allow image manipulation in Aloha Editor
*
* Author & Copyright (c) 2013 Gentics Software GmbH
* aloha-sales@gentics.com
* Contributors
*		Johannes Schüth - http://jotschi.de
*		Nicolas karageuzian - http://nka.me/
*		Benjamin Athur Lupton - http://www.balupton.com/
*		Thomas Lete
*		Nils Dehl
*		Christopher Hlubek
*		Edward Tsech
*		Haymo Meran
*		Martin Schönberger
*
* Licensed under the terms of http://www.aloha-editor.org/license.php
*/

define([
	// js
	'jquery',
	'aloha/plugin',
	'image/image-floatingMenu',
	'i18n!aloha/nls/i18n',
	'i18n!image/nls/i18n',
	'jqueryui',
	'image/vendor/jcrop/jquery.jcrop.min',
	'image/vendor/mousewheel/mousewheel'
], function AlohaImagePlugin(
	aQuery,
	Plugin,
	ImageFloatingMenu,
	i18nCore,
	i18n
){

	'use strict';

	var jQuery = aQuery;
	var $ = aQuery;
	var GENTICS = window.GENTICS;
	var Aloha = window.Aloha;
	var resizing = false;

	var aspectRatioValue = false;
	var cropRatioValue = false;

	// Attributes manipulation utilities
	// Aloha team may want to factorize, it could be useful for other plugins
	// Prototypes
	String.prototype.toInteger = String.prototype.toInteger || function () {
		return parseInt(String(this).replace(/px$/, '') || 0, 10);
	};
	String.prototype.toFloat = String.prototype.toInteger || function () {
		return parseFloat(String(this).replace(/px$/, '') || 0, 10);
	};
	Number.prototype.toInteger = Number.prototype.toInteger || String.prototype.toInteger;
	Number.prototype.toFloat = Number.prototype.toFloat || String.prototype.toFloat;

	// Insert jQuery Prototypes
	jQuery.extend(true, jQuery.fn, {
		increase: jQuery.fn.increase || function (attr) {
			var	obj = jQuery(this), value, newValue;
			if (!obj.length) {
				return obj;
			}
			value = obj.css(attr).toFloat();
			newValue = Math.round((value || 1) * 1.2);
			// when value is 2, won't increase
			if (value === newValue) {
				newValue++;
			}
			obj.css(attr, newValue);
			return obj;
		},
		decrease: jQuery.fn.decrease || function (attr) {
			var	obj = jQuery(this), value, newValue;
			// Check
			if (!obj.length) {
				return obj;
			}
			// Calculate
			value = obj.css(attr).toFloat();
			newValue = Math.round((value || 0) * 0.8);
			// Apply
			if (value === newValue && newValue > 0) {
				// when value is 2, won't increase
				newValue--;
			}
			obj.css(attr, newValue);
			// Chain
			return obj;
		}
	});

	// Create and register Image Plugin
	return Plugin.create('image', {
		defaultSettings: {
			'maxWidth': 1600,
			'minWidth': 3,
			'maxHeight': 1200,
			'minHeight': 3,
			// This setting will manually correct values that are out of bounds
			'autoCorrectManualInput': true,
			// This setting will define a fixed aspect ratio for all resize actions
			'fixedAspectRatio' : false,
			// When enabled this setting will order the plugin to automatically resize images to given bounds
			'autoResize': false,
			//Image manipulation options - ONLY in default config section
			ui: {
				meta		: true, // If imageResizeWidth and imageResizeHeight are displayed, then you will want to set this to true, so that the width and height text fields are updated automatically.
				crop		: true, // If imageCropButton is displayed, then you have to enable this.
				resizable	: true	// Resizable ui-drag image
			},
			handles     : 'ne, se, sw, nw',   // set handles for resize

			/**
			 * Crop callback is triggered after the user clicked accept to accept his crop
			 * @param image jquery image object reference
			 * @param props cropping properties
			 */
			onCropped: function ($image, props) {
				Aloha.Log.info('Default onCropped invoked', $image, props);
			},

			/**
			 * Reset callback is triggered before the internal reset procedure is applied
			 * if this function returns true, then the reset has been handled by the callback
			 * which means that no other reset will be applied
			 * if false is returned the internal reset procedure will be applied
			 * @param image jquery image object reference
			 * @return true if a reset has been applied, false otherwise
			 */
			onReset: function ($image) {
				Aloha.Log.info('Default onReset invoked', $image);
				return false;
			},

			/**
			 * Example callback method which gets called while the resize process is being executed.
			 */
			onResize: function ($image) {
				Aloha.Log.info('Default onResize invoked', $image);
			},

			/**
			 * Resize callback is triggered after the internal resize procedure is applied.
			 */
			onResized: function ($image) {
				Aloha.Log.info('Default onResized invoked', $image);
			}
		},

		/**
		 * Internal callback hook which gets invoked when cropping has been finished
		 */
		_onCropped: function ($image, props) {

			$('body').trigger('aloha-image-cropped', [$image, props]);

			// After successful cropping, the aspect ratio value has to be recalculated
			if (this.keepAspectRatio) {
				aspectRatioValue = this.imageObj.width() / this.imageObj.height();
			}

			// Call the custom onCropped function
			this.onCropped($image, props);
		},

		/**
		 * Internal callback hook which gets invoked when resetting images
		 */
		_onReset: function ($image) {

			// No default behaviour defined besides event triggering
			$('body').trigger('aloha-image-reset', $image);

			// Call the custom reset function
			return this.onReset($image);
		},

		/**
		 * Internal callback hook which gets invoked while the image is being resized
		 */
		_onResize: function ($image) {

			// No default behaviour defined besides event triggering
			$('body').trigger('aloha-image-resize', $image);

			this._applyValuesToFields($image.width(), $image.height());

			// Call the custom resize function
			this.onResize($image);
		},

		/**
		 * Internal callback hook which gets invoked when the current resizing action has stopped
		 */
		_onResized: function ($image) {

			$('body').trigger('aloha-image-resized', $image);

			this._applyValuesToFields($image.width(), $image.height());

			// Call the custom resize function
			this.onResized($image);
		},

		/**
		 * The image that is currently edited
		 */
		imageObj: null,


		/**
		 * The Jcrop API reference
		 * this is needed to be able to destroy the cropping frame later on
		 * the variable is linked to the api object whilst cropping, or set to null otherwise
		 * strange, but done as documented http://deepliquid.com/content/Jcrop_API.html
		 */
		jcAPI: null,


		/**
		 * State variable for the aspect ratio toggle feature
		 */
		keepAspectRatio: false,

		/**
		 * This will contain an image's original properties to be able to undo previous settings
		 *
		 * when an image is clicked for the first time, a new object will be added to the array
		 * {
		 *		obj : [the image object reference],
		 *		src : [the original src url],
		 *		width : [initial width],
		 *		height : [initial height]
		 * }
		 *
		 * when an image is clicked the second time, the array will be checked for the image object
		 * reference, to prevent double entries
		 */
		restoreProps: [],

		/**
		 * the defined object types to be used for this instance
		 */
		objectTypeFilter: [],

		/**
		 * Plugin initialization method
		 */
		init: function () {

			var plugin = this;

			var imagePluginUrl = Aloha.getPluginUrl('image');

			if ( typeof this.settings.objectTypeFilter != 'undefined' ) {
				this.objectTypeFilter = this.settings.objectTypeFilter;
			}

			// Extend the default settings with the custom ones (done by default)
			plugin.config = plugin.defaultSettings;
			plugin.settings = jQuery.extend(true, plugin.defaultSettings, plugin.settings);

			// Determine the flag and the value of the aspect ratio depending on settings
			if ( typeof this.settings.fixedAspectRatio === 'number' ) {
				aspectRatioValue = cropRatioValue = this.settings.fixedAspectRatio;
				plugin.keepAspectRatio = true;
			} else {
				if ((plugin.settings.fixedAspectRatio) === true) {
					plugin.keepAspectRatio = true;
				} else {
					plugin.keepAspectRatio = false;
				}
			}

			plugin.initializeUI();
			plugin.bindInteractions();
			plugin.subscribeEvents();

		},

		/**
		* Create buttons
		*/
		initializeUI: function () {

			var that = this;

			this.ui = new ImageFloatingMenu();
			this.ui.init(this);
		},
		/**
		 * Bind plugin interactions
		 */
		bindInteractions: function () {
			var	plugin = this;

			if (plugin.settings.ui.resizable) {
				try {
					// this will disable mozillas image resizing facilities
					document.execCommand('enableObjectResizing', false, false);
				} catch (e) {
					Aloha.Log.info(e, 'Could not disable enableObjectResizing');
					// this is just for internet explorer, which will not support disabling enableObjectResizing
				}
			}

			if (plugin.settings.ui.meta) {
				// update image object when src changes
				plugin.ui.imgSrcField.addListener('keyup', function (event) {
					plugin.srcChange();
				});

				plugin.ui.imgSrcField.addListener('blur', function (event) {
					// TODO remove image or do something useful if the user leaves the
					// image without defining a valid image src
					var img = jQuery(plugin.ui.imgSrcField.getTargetObject());
					if (img.attr('src') === '') {
						img.remove();
					} // image removal when src field is blank
				});
			}

			// Override the default method by using the given one
			if (plugin.settings.onCropped && typeof plugin.settings.onCropped === "function") {
				plugin.onCropped = plugin.settings.onCropped;
			}

			// Override the default method by using the given one
			if (plugin.settings.onReset && typeof plugin.settings.onReset === "function") {
				plugin.onReset = plugin.settings.onReset;
			}

			// Override the default method by using the given one
			if (plugin.settings.onResized && typeof plugin.settings.onResized === "function") {
				plugin.onResized = plugin.settings.onResized;
			}

			// Override the default method by using the given one
			if (plugin.settings.onResize && typeof plugin.settings.onResize === "function") {
				plugin.onResize = this.settings.onResize;
			}

		},

		/**
		 * Subscribe to Aloha events and DragAndDropPlugin Event
		 */
		subscribeEvents: function () {
			var	plugin = this;
			var config = this.settings;

			jQuery('img').filter(config.globalselector).unbind();
			jQuery('img').filter(config.globalselector).click(function (event) {
				plugin.clickImage(event);
			});

			Aloha.bind('aloha-drop-files-in-editable', function (event, data) {
				var img, len = data.filesObjs.length, fileObj, config;

				while (--len >= 0) {
					fileObj = data.filesObjs[len];
					if (fileObj.file.type.match(/image\//)) {
						config = plugin.getEditableConfig(data.editable);
						// Prepare
						img = jQuery('<img/>');
						img.css({
							"max-width": that.maxWidth,
							"max-height": that.maxHeight
						});
						img.attr('id', fileObj.id);
						if (typeof fileObj.src === 'undefined') {
							img.attr('src', fileObj.data);
							//fileObj.src = fileObj.data ;
						} else {
							img.attr('src', fileObj.src);
						}
						GENTICS.Utils.Dom.insertIntoDOM(img, data.range, jQuery(Aloha.activeEditable.obj));
					}
				}

			});
			/*
			 * Add the event handler for selection change
			 */
			Aloha.bind('aloha-selection-changed', function (event, rangeObject, originalEvent) {
				var config, foundMarkup;

				if (originalEvent && originalEvent.target) {
					// Check if the element is currently being resized
					if (plugin.settings.ui.resizable && !jQuery(originalEvent.target).hasClass('ui-resizable-handle')) {
						plugin.endResize();
						plugin.imageObj = null;
						Aloha.trigger('aloha-image-unselected');
					}
				}

				if (Aloha.activeEditable !== null) {
					foundMarkup = plugin.findImgMarkup(rangeObject);
					config = plugin.getEditableConfig(Aloha.activeEditable.obj);

					if (typeof config !== 'undefined') {
						plugin.ui._insertImageButton.show();
					} else {
						plugin.ui._insertImageButton.hide();
						return;
					}

					// Enable image specific ui components if the element is an image
					if (foundMarkup) { // TODO : this is always null (below is dead code, moving it to clickImage)
						plugin.ui._insertImageButton.show();
						plugin.ui.setScope();
						if (plugin.settings.ui.meta) {
							plugin.ui.imgSrcField.setTargetObject(foundMarkup, 'src');
							plugin.ui.imgTitleField.setTargetObject(foundMarkup, 'title');
						}
						plugin.ui.imgSrcField.foreground();
						plugin.ui.imgSrcField.focus();
					} else {
						if (plugin.settings.ui.meta) {
							plugin.ui.imgSrcField.setTargetObject(null);
						}
					}
					// TODO this should not be necessary here!
					plugin.ui.doLayout();
				}

			});

			Aloha.bind('aloha-editable-created', function (event, editable) {
				try {
					// this disables mozillas image resizing facilities
					document.execCommand('enableObjectResizing', false, false);
				} catch (e) {
					Aloha.Log.info(e, 'Could not disable enableObjectResizing');
					// this is just for other browsers, which do not support disabling enableObjectResizing
				}

				// Inital click on images will be handled here
				// editable.obj.find('img').attr('_moz_resizing', false);
				// editable.obj.find('img').contentEditable(false);

				editable.obj.delegate('img', 'mouseup', function (event) {
					if (!resizing) {
						plugin.clickImage(event);
						event.stopPropagation();
					}
				});
			});

			plugin._subscribeToResizeFieldEvents();

		},


		/**
		 * Automatically resize the image to fit into defined bounds.
		 * @param doScaleUp if true, small images are scaled up to fit minimum size
		 */
		autoResize: function(doScaleUp) {
			// @todo handle ratio mismatches (eg 4:3 is set but image is 16:9 --> image needs to be cut)

			var that = this;

			var widthField = jQuery("#" + that.ui.imgResizeWidthField.getInputId());
			var heightField = jQuery("#" + that.ui.imgResizeHeightField.getInputId());

			var width = that.imageObj.width();
			var height = that.imageObj.height();
			var resize = false;

			// Only normalize the field values if the image exceeds the defined bounds
			if (width < that.settings.minWidth ||
				width > that.settings.maxWidth ||
				height < that.settings.minHeight ||
				height > that.settings.maxHeight) {
				resize = true;
			}

			var aspectRatio = width / height;

			if (resize) {
				if (width > that.settings.maxWidth) {
					width = that.settings.maxWidth;
					height = width / aspectRatio;
				}

				if (height > that.settings.maxHeight) {
					height = that.settings.maxHeight;
					width = height * aspectRatio;
				}

				if ((width < that.settings.minWidth) && doScaleUp) {
					width = that.settings.minWidth;
					height = width / aspectRatio;
				}

				if ((height < that.settings.minHeight) && doScaleUp) {
					height = that.settings.minHeight;
					width = width * aspectRatio;
				}

				that._applyValuesToFields(width, height);

				that.setSizeByFieldValue();
				return true;
			}
			return false;
		},

		/**
		 * Toggle the keep aspect ratio functionality
		 */
		toggleKeepAspectRatio: function() {

			this.keepAspectRatio = !this.keepAspectRatio;

			// while cropping: calculate the new aspect ratio value for the crop
			if (typeof this.jcAPI !== 'undefined' && this.jcAPI !== null) {
				var selection = this.jcAPI.tellSelect();

				if (!this.keepAspectRatio) {
					cropRatioValue = false;
				} else {
					if (typeof this.settings.fixedAspectRatio !== 'number') {
						if (selection.w / selection.h > 0) {
							cropRatioValue = selection.w / selection.h;
						} else {
							cropRatioValue = this.imageObj.width() / this.imageObj.height();
						}
					} else {
						cropRatioValue = this.settings.fixedAspectRatio;
					}
				}

				this.jcAPI.setOptions(this.keepAspectRatio ? { aspectRatio: cropRatioValue} : { aspectRatio: 0 });
				this.jcAPI.focus();

			// if not in cropping mode: calculate the new aspect ratio value for image resizing
			} else {
				this.endResize();
				if (!this.keepAspectRatio) {
					aspectRatioValue = false;
				} else {
					if ( typeof this.settings.fixedAspectRatio !== 'number' ) {
						aspectRatioValue = this.imageObj.width() / this.imageObj.height();
					} else {
						aspectRatioValue = this.settings.fixedAspectRatio;
					}
				}
				this.startResize();
			}
		},

		/**
		 * Bind interaction events that are invoked on the resize fields
		 */
		_subscribeToResizeFieldEvents: function () {
			var plugin = this;

			/**
			 * Handle the keyup event on the field
			 */
			function handleKeyUpEventOnField(e) {

				// Load the max/min from the data properties of this event
				var minValue = e.data.minValue;
				var maxValue = e.data.maxValue;
				var fieldName = e.data.fieldName;

				// Allow backspace and delete
				if (e.keyCode === 8 || e.keyCode === 46) {

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, $(this).val(), false)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				// 0-9 keys
				} else if (e.keyCode <= 57 && e.keyCode >= 48 || e.keyCode <= 105 && e.keyCode >= 96 ) {

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, $(this).val(), false)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				} else {
					var delta = 0;
					if (e.keyCode === 38 || e.keyCode === 107) {
						delta = +1;
					} else if (e.keyCode === 40 || e.keyCode === 109) {
						delta = -1;
					}
					// Handle key combinations
					if (e.shiftKey || e.metaKey || e.ctrlKey) {
						delta = delta * 10;
					}

					var isDecrement = false;
					if (delta < 0) {
						isDecrement = true;
					}
					var newValue = parseInt($(this).val(), 10) + delta;

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, newValue, isDecrement)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				}

				e.preventDefault();
				return false;
			}

			/**
			 * Handle the mouse wheel event on the field
			 */
			function handleMouseWheelEventOnField(e, delta) {
				var minValue = e.data.minValue;
				var maxValue = e.data.maxValue;
				var fieldName = e.data.fieldName;

				// Handle key combinations
				if (e.shiftKey || e.metaKey || e.ctrlKey) {
					delta = delta * 10;
				}

				var newValue = parseInt($(this).val(), 10) + delta;
				var decrement = false;
				if (delta < 0) {
					decrement = true;
				}

				// Only resize if field values are ok
				if (plugin._updateFields(fieldName, newValue, decrement)) {
					// Check if we are currently in cropping mode
					if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
						plugin.setCropAreaByFieldValue();
					} else {
						plugin.setSizeByFieldValue();
					}
				}
				return false;
			}

			/**
			 * Handle mousewheel,keyup actions on both fields
			 */
			var $heightField = $('#' + plugin.ui.imgResizeHeightField.getInputId());
			var heightEventData = {fieldName: 'height', maxValue: plugin.ui.imgResizeHeightField.maxValue, minValue: plugin.ui.imgResizeHeightField.minValue };
			$heightField.live('keyup', heightEventData, handleKeyUpEventOnField);
			$heightField.live('mousewheel', heightEventData, handleMouseWheelEventOnField);

			var $widthField = $('#' + plugin.ui.imgResizeWidthField.getInputId());
			var widthEventData = {fieldName: 'width', maxValue: plugin.ui.imgResizeWidthField.maxValue, minValue: plugin.ui.imgResizeWidthField.minValue};
			$widthField.live('keyup', widthEventData, handleKeyUpEventOnField);
			$widthField.live('mousewheel', widthEventData, handleMouseWheelEventOnField);

		},

		/**
		 * Manually set the given size for the current image
		 */
		setSize: function (width, height) {

			var plugin = this;
			plugin.imageObj.width(width);
			plugin.imageObj.height(height);
			var $wrapper = plugin.imageObj.closest('.Aloha_Image_Resize');
			$wrapper.height(height);
			$wrapper.width(width);

			plugin._onResize(plugin.imageObj);
			plugin._onResized(plugin.imageObj);
		},

		/**
		 * This method will handle the mouseUp event on images (eg. within editables).
		 * It will if enabled activate the resizing action.
		 */
		clickImage: function (e) {

			var plugin = this;
			plugin.endResize(); // removes previous resize handler
			plugin.imageObj = jQuery(e.target);
			var currentImage = plugin.imageObj;

			// Ignore any images that are part of the ui (e.g. block edit and delete icons)
			if (currentImage.hasClass('aloha-ui')) {
				return;
			}

			plugin.ui.setScope();

			var editable = currentImage.closest('.aloha-editable');

			plugin.ui._imageCnrRatioButton.setState(this.keepAspectRatio);

			// Disabling the content editable. This will disable the resizeHandles in internet explorer
			// already done in resize on a smaller scope, this block next aloha-selection-change event
			// to be thrown
			// editable.contentEditable(false);

			// Store the current props of the image
			plugin.restoreProps.push({
				obj : plugin.imageObj,
				src : plugin.imageObj.attr('src'),
				width : plugin.imageObj.width(),
				height : plugin.imageObj.height()
			});

			// Update the resize input fields with the new width and height
			plugin._applyValuesToFields(plugin.imageObj.width(), plugin.imageObj.height());

			if (plugin.settings.ui.meta) {
				plugin.ui.imgSrcField.setTargetObject(plugin.imageObj, 'src');
				plugin.ui.imgTitleField.setTargetObject(plugin.imageObj, 'title');
			}
			Aloha.Selection.preventSelectionChanged();
			try {
				plugin.ui.imgSrcField.focus();
			} catch(e) {
				// FIXME for some reason execution breaks at this point
			}

			//to handle switching between images, aspect ratio is recalculated on click
			cropRatioValue = aspectRatioValue = plugin.imageObj.width() / plugin.imageObj.height();

			if (plugin.settings.ui.resizable) {
				plugin.startResize();
			}

			if (plugin.settings.autoResize) {
				plugin.autoResize(true);
			}
			Aloha.Selection.preventSelectionChangedFlag = false;
			Aloha.trigger('aloha-image-selected');
		},

		/**
		 * This method extracts determins if the range selection contains an image
		 *
		 * UNUSED as long as clickImage don't change the selection
		 * @see getPluginFocus instead
		 */
		findImgMarkup: function (range) {

			var plugin = this;
			var config = this.config;
			var result, targetObj;

			if (typeof range === 'undefined') {
				range = Aloha.Selection.getRangeObject();
			}

			targetObj = jQuery(range.startContainer);

			try {
				if (Aloha.activeEditable) {
					if ((typeof range.startContainer !== 'undefined' &&
						typeof range.startContainer.childNodes !== 'undefined' &&
						typeof range.startOffset !== 'undefined' &&
						typeof range.startContainer.childNodes[range.startOffset] !== 'undefined' &&
						range.startContainer.childNodes[range.startOffset].nodeName.toLowerCase() === 'img' &&
						range.startOffset + 1 === range.endOffset) ||
						(targetObj.hasClass('Aloha_Image_Resize')))
					{
						result = targetObj.find('img')[0];
						if (! result.css) {
							result.css = '';
						}

						if (! result.title) {
							result.title = '';
						}

						if (! result.src) {
							result.src = '';
						}
						return result;
					}
					else {
						return null;
					}
				}
			} catch (e) {
				Aloha.Log.debug(e, "Error finding img markup.");
			}
			return null;
		},
		/**
		 * Gets the plugin focus target
		 */
		getPluginFocus: function () {
			return this.imageObj;
		},

		/**
		 * Helper function that checks a field-input (of height or width), adjusts
		 * its value to conform to minimum/maximum values, and corrects the other field
		 * if the aspect ratio is to be kept
		 * @param primaryFieldName the field which is currently edited ('height' or 'width')
		 * @param newValue the value as it was entered into the primary field
		 * @param isDecrement true if value is being decreased (by key or mousewheel); prevents decreasing below minimum
		 * @return true if values are correct or have been corrected, and image can be resized
		 */
		_updateFields: function (primaryFieldName, newValue, isDecrement) {
			var plugin = this;

			var $primaryField = null;
			var $secondaryField = null;

			var adjustedAspectRatio = null;
			var primaryMin = null;
			var primaryMax = null;
			var secondaryMin = null;
			var secondaryMax = null;

			// If the aspect-ratio for cropping has been changed from the standard aspect-ratio,
			// it is used for updating the fields (if keeping ratio is activated)
			if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
				adjustedAspectRatio = cropRatioValue ? cropRatioValue : aspectRatioValue;
			} else {
				adjustedAspectRatio = aspectRatioValue;
			}

			// Depending on the field that is edited, primary and secondary values are set
			if (primaryFieldName == 'width') {
				$primaryField = $("#" + plugin.ui.imgResizeWidthField.getInputId());
				$secondaryField = $("#" + plugin.ui.imgResizeHeightField.getInputId());
				adjustedAspectRatio = ( 1 / adjustedAspectRatio );

				primaryMin = plugin.settings.minWidth;
				primaryMax = plugin.settings.maxWidth;
				secondaryMin = plugin.settings.minHeight;
				secondaryMax = plugin.settings.maxHeight;

			} else if (primaryFieldName == 'height') {
				$primaryField = $("#" + plugin.ui.imgResizeHeightField.getInputId());
				$secondaryField = $("#" + plugin.ui.imgResizeWidthField.getInputId());
				adjustedAspectRatio = adjustedAspectRatio;

				primaryMin = plugin.settings.minHeight;
				primaryMax = plugin.settings.maxHeight;
				secondaryMin = plugin.settings.minWidth;
				secondaryMax = plugin.settings.maxWidth;
			} else {
				// If primaryFieldName is neither width nor height, don't update the fields
				return false;
			}

			if (isNaN(newValue)) {
				$primaryField.css('background-color', 'red');
				// If the current value of the field can't be parsed it is not updated
				return false;
			}
			else {
				$primaryField.val(newValue);
			}

			var correctPrimary = true;

			if (newValue > primaryMax) {
				// Auto-correct out of bounds values
				if (plugin.settings.autoCorrectManualInput) {
					$primaryField.val(primaryMax);
					newValue = $primaryField.val();
					$primaryField.css('background-color', '');
					correctPrimary = true;
				// Just notify the user, do nothing
				} else {
					$primaryField.css('background-color', 'red');
					correctPrimary = false;
				}
			} else if (newValue < primaryMin) {
				// Don't let the user decrement values below minimum
				if (isDecrement) {
					$primaryField.val(primaryMin);
					newValue = $primaryField.val();
					correctPrimary = true;
				// Auto-correct out of bounds values
				} else if (plugin.settings.autoCorrectManualInput) {
					$primaryField.css('background-color', 'wheat');
					correctPrimary = false;
				// Just notify the user, do nothing
				} else {
					$primaryField.css('background-color', 'red');
					correctPrimary = false;
				}
			} else {
				$primaryField.css('background-color', '');
				correctPrimary = true;
			}

			var correctSecondary = true;

			// If keep aspect ratio is enabled, the field that is not edited is updated as well
			if ( plugin.keepAspectRatio ) {
				var secondary = Math.round(newValue * adjustedAspectRatio);

				if (secondary > secondaryMax) {
					// Auto-correct out of bounds values
					if (plugin.settings.autoCorrectManualInput) {
						$secondaryField.val(secondaryMax);
						$primaryField.val($secondaryField.val() / adjustedAspectRatio);
						newValue = $primaryField.val();
						$primaryField.css('background-color', '');
						$secondaryField.css('background-color', '');
						correctSecondary = true;
					// Just notify the user, do nothing
					} else {
						secondary.css('background-color', 'red');
						correctSecondary = false;
					}
				} else if (secondary < secondaryMin) {
					// Don't let the user decrement values below minimum
					if (isDecrement) {
						$secondaryField.val(secondaryMin);
						$primaryField.val($secondaryField.val() / adjustedAspectRatio);
						newValue = $primaryField.val();
						correctPrimary = true;
					// Auto-correct out of bounds values
					} else if (plugin.settings.autoCorrectManualInput) {
						$secondaryField.val(secondary);
						$secondaryField.css('background-color', 'wheat');
						correctSecondary = false;
					// Just notify the user, do nothing
					} else {
						$secondaryField.css('background-color', 'red');
						correctSecondary = false;
					}
				} else {
					$secondaryField.val(secondary);
					$secondaryField.css('background-color', '');
					correctSecondary = true;
				}
			}

			// Final check for value of secondary field, in case focus was changed
			if ($secondaryField.val() < secondaryMin) {
				correctSecondary = false;
			}

			// Success if values are correct or have been adjusted accordingly
			if (correctPrimary && correctSecondary) {
				return true;
			}
			return false;
		},

		/**
		 * Helper function that will set the new image size using the field values
		 */
		setSizeByFieldValue: function () {
			var width =  this.ui.imgResizeWidthField.getValue();
			var height = this.ui.imgResizeHeightField.getValue();
			this.setSize(width, height);
		},

		/**
		 * Helper function that sets field values to specified parameters and resets background of fields
		 * @param width new image-width in pixels
		 * @param height new image-height in pixels
		 */
		_applyValuesToFields: function (width, height) {
			this.ui.imgResizeWidthField.setValue(width);
			this.ui.imgResizeHeightField.setValue(height);

			this.ui.imgResizeWidthField.getInputJQuery().css('background-color', '');
			this.ui.imgResizeHeightField.getInputJQuery().css('background-color', '');
		},

		/**
		 * Helper function that will set the new crop area width and height using the field values
		 *
		 * note: because the _updateFields function does not consider the x and y of the croparea yet,
		 * the value of the secondary field shown after setting croparea with manual input may be incorrect;
		 * while this has no influence on the actual crop size, a future implementation should consider this
		 * when calculating the field values
		 */
		setCropAreaByFieldValue: function () {
			var currentCropArea = this.jcAPI.tellSelect();

			var width =  parseInt(this.ui.imgResizeWidthField.getValue(), 10);
			var height = parseInt(this.ui.imgResizeHeightField.getValue(), 10);

			var selection = [currentCropArea['x'], currentCropArea['y'], currentCropArea['x'] + width,currentCropArea['y'] + height];

			this.jcAPI.setSelect(selection);
			this._onCropSelect();
			this.jcAPI.enable();
		},

		/**
		* This method will insert a new image dom element into the dom tree
		*/
		insertImg: function () {
				var range = Aloha.Selection.getRangeObject(),
				config = this.getEditableConfig(Aloha.activeEditable.obj),
				imagePluginUrl = Aloha.getPluginUrl('image'),
				imagestyle, imagetag, newImg;

				if (range.isCollapsed()) {
					// TODO I would suggest to call the srcChange method. So all image src
					// changes are on one single point.
					imagestyle = "max-width: " + config.maxWidth + "; max-height: " + config.maxHeight;
					imagetag = '<img style="' + imagestyle + '" src="' + imagePluginUrl + '/img/blank.jpg" title="" />';
					newImg = jQuery(imagetag);
					// add the click selection handler
					//newImg.click( Aloha.Image.clickImage ); - Using delegate now
					GENTICS.Utils.Dom.insertIntoDOM(newImg, range, jQuery(Aloha.activeEditable.obj));

			} else {
				Aloha.Log.error('img cannot markup a selection');
				// TODO the desired behavior could be me the selected content is
				// replaced by an image.
				// TODO it should be editor's choice, with an NON-Ext Dialog instead of alert

			}
		},

		srcChange: function () {
			// TODO the src changed. I suggest :
			// 1. set an loading image (I suggest set src base64 enc) to show the user
			// we are trying to load an image
			// 2. start a request to get the image
			// 3a. the image is ok change the src
			// 3b. the image is not availbable show an error.
			 this.imageObj.attr('src', this.ui.imgSrcField.getValue()); // (the img tag)
//			 jQuery(img).attr('src', this.imgSrcField.getQueryValue()); // (the query value in the inputfield)
//			 this.imgSrcField.getItem(); // (optinal a selected resource item)
			// TODO additionally implement an srcChange Handler to let implementer
			// customize
		},

		/**
		 * Reposition the crop buttons below the crop area
		 */
		positionCropButtons: function() {

			var jt = jQuery('.jcrop-tracker:first'),
				off = jt.offset(),
				jtt = off.top,
				jtl = off.left,
				jth = jt.height(),
				jtw = jt.width();

			var oldLeft = 0,
				oldTop = 0;

			var btns = jQuery('#aloha-CropNResize-btns');

			// Hack to hide the buttons when the user just clicked into the image
			if (jtt === 0 && jtl === 0) {
				btns.hide();
			}

			// move the icons to the bottom right side
			jtt = parseInt(jtt + jth + 3, 10);
			jtl = parseInt(jtl + (jtw / 2) - (btns.width() / 2) + 10, 10);

			// comparison to old values hinders flickering bug in FF
			if (oldLeft != jtl || oldTop != jtt) {
				btns.offset({top: jtt, left: jtl});
			}

			oldLeft = jtl;
			oldTop = jtt;
		},

		/**
		 * Code imported from CropnResize Plugin
		 *
		 */
		initCropButtons: function() {
			var that = this,
				btns;

			jQuery('body').append(
				'<div id="aloha-CropNResize-btns" display="none">' +
					'<button class="cnr-crop-apply" title="' + i18n.t('Accept') + '"></button>' +
					'<button class="cnr-crop-cancel" title="' + i18n.t('Cancel') + '"></button>' +
				'</div>'
			);

			btns = jQuery('#aloha-CropNResize-btns');

			btns.find('.cnr-crop-apply').click(function () {
				that.acceptCrop();
			});

			btns.find('.cnr-crop-cancel').click(function () {
				that.endCrop();
			});

			this.interval = setInterval(function () {
				that.positionCropButtons();
			}, 10);
		},

		/**
		 * Destroy crop confirm and cancel buttons
		 */
		destroyCropButtons: function () {
			jQuery('#aloha-CropNResize-btns').remove();
			clearInterval(this.interval);
		},

		/**
		 * Helper function that will disable selectability of elements
		 */
		_disableSelection: function (el) {
			el.find('*').attr('unselectable', 'on')
					.css({
					'-moz-user-select':'none',
					'-webkit-user-select':'none',
					'user-select':'none'
					});
				/*
					.each(function() {
					this.onselectstart = function () { return false; };
					});
					*/

		},

		/**
		 * Initiate a crop action
		 */
		crop: function () {
			var plugin = this;
			var config = this.config;
			var ratio = plugin.keepAspectRatio ? aspectRatioValue : false;

			this.ui._imageCropButton.setState(true);

			plugin.initCropButtons();
			if (plugin.settings.ui.resizable) {
				plugin.endResize();
			}

			plugin.jcAPI = jQuery.Jcrop(plugin.imageObj, {
				onSelect : function () {
					plugin._onCropSelect();
					// ugly hack to keep scope :(
					window.setTimeout(function () {
						plugin.ui.setScope();
					}, 10);
				},
				aspectRatio: ratio,
				minSize  : [plugin.settings.minWidth, plugin.settings.minHeight]
			});

			plugin._disableSelection($('.jcrop-holder'));
			plugin._disableSelection($('#imageContainer'));
			plugin._disableSelection($('#aloha-CropNResize-btns'));
			$('body').trigger('aloha-image-crop-start', [plugin.imageObj]);
		},

		/**
		 * Internal on crop select method
		 */
		_onCropSelect: function () {
			var plugin = this;

			// Hide the crop buttons when the one of the handles is clicked
			jQuery('.jcrop-handle').mousedown(function () {
				jQuery('#aloha-CropNResize-btns').hide();
			});

			jQuery('.jcrop-tracker').mousedown(function () {
				jQuery('#aloha-CropNResize-btns').hide();
			});

			// Update the width and height field using the initial active crop area values
			if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
				plugin.positionCropButtons();
				var currentCropArea = plugin.jcAPI.tellSelect();

				if (currentCropArea.h > 0 && currentCropArea.w > 0) {
					jQuery('#aloha-CropNResize-btns').fadeIn('slow');
				}

				plugin._applyValuesToFields(currentCropArea['w'], currentCropArea['h']);
			}

		},

		/**
		 * Terminates a crop
		 */
		endCrop: function () {
			if (this.jcAPI) {
				this.jcAPI.destroy();
				this.jcAPI = null;
			}

			this.destroyCropButtons();
			this.ui._imageCropButton.setState(false);

			if (this.settings.ui.resizable) {
				this.startResize();
			}

			$('body').trigger('aloha-image-crop-stop', [this.imageObj]);

			//after cropping, field values are set to (once again) contain image width/height
			this._applyValuesToFields(this.imageObj.width(), this.imageObj.height());
		},

		/**
		 * Accept the current cropping area and apply the crop
		 */
		acceptCrop: function () {
			this._onCropped(this.imageObj, this.jcAPI.tellSelect());
			this.endCrop();
		},

		/**
		 * This method will activate the jquery-ui resize functionality for the current image
		 */
		startResize: function () {
			var plugin = this;
			var currentImageObj = this.imageObj;
			var ratio = plugin.keepAspectRatio ? aspectRatioValue : false;

			currentImageObj = this.imageObj.css({
				height		: this.imageObj.height(),
				width		: this.imageObj.width(),
				position	: 'relative',
				'max-height': '',
				'max-width'	: ''
			});

			currentImageObj.resizable({
				maxHeight : plugin.settings.maxHeight,
				minHeight : plugin.settings.minHeight,
				maxWidth  : plugin.settings.maxWidth,
				minWidth  : plugin.settings.minWidth,
				aspectRatio : ratio,
				handles: plugin.settings.handles,
				grid : plugin.settings.grid,
				resize: function (event, ui) {
					resizing = true;
					plugin._onResize(plugin.imageObj);
				},
				stop : function (event, ui) {
					resizing = false;

					plugin._onResized(plugin.imageObj);

					// Workaround to finish cropping
					if (this.enableCrop) {
						window.setTimeout(function () {
							plugin.ui.setScope();
							that.done(event);
						}, 10);
					}
				}

			});

			currentImageObj.css('display', 'inline-block');

			// this will prevent the user from resizing an image
			// using IE's resize handles
			// however I could not manage to hide them completely
			jQuery('.ui-wrapper')
				.attr('contentEditable', false)
				.addClass('aloha-image-box-active Aloha_Image_Resize aloha')
				.css({
					position: 'relative',
					display: 'inline-block',
					'float': plugin.imageObj.css('float')
				})
				.bind('resizestart', function (e) {
					e.preventDefault();
				})
				.bind('mouseup', function (e) {
					e.originalEvent.stopSelectionUpdate = true;
				});
		},

		/**
		 * This method will end resizing and toggle buttons accordingly and remove all markup that has been added for cropping
		 */
		endResize: function () {
			// Find the nearest contenteditable and reenable it since resizing is finished
			if (this.imageObj) {
				var editable = this.imageObj.closest('.aloha-editable');
				//this.imageObj.contentEditable(true);
			}

			if (this.imageObj && this.imageObj.is(":ui-resizable")) {
				this.imageObj
					.resizable('destroy')
					.css({
						top	 : 0,
						left : 0
					});
			}
		},

		resetSize: function () {
			var	plugin = this,
				img = new Image();
			img.onload = function () {
				var myimage = plugin.getPluginFocus();
				if (plugin.settings.ui.resizable) {
					plugin.endResize();
				}
				jQuery(myimage).add(myimage.parent()).css({
						'width': img.width + 'px',
						'height': img.height + 'px',
						'max-width': '',
						'max-height': ''
					});
				if (plugin.settings.ui.resizable) {
					plugin.resize();
				}
			};
			img.src = plugin.getPluginFocus().attr('src');
		},

		/**
		 * Reset the image to its original properties
		 */
		reset: function () {
			var externalReset;

			if (this.settings.ui.crop) {
				this.endCrop();
			}

			if (this.settings.ui.resizable) {
				this.endResize();
			}

			externalReset = this._onReset(this.imageObj);

			// if the external reset procedure has already performed a reset, there is no need to apply an internal reset
			if (!externalReset) {
				for (var i = 0;i < this.restoreProps.length;i++) {

					// restore from restoreProps if there is a match
					if (this.imageObj.get(0) === this.restoreProps[i].obj.get(0)) {
						this.imageObj.attr('src', this.restoreProps[i].src);
						this.imageObj.width(this.restoreProps[i].width);
						this.imageObj.height(this.restoreProps[i].height);
						break;
					}
				}
			}

			// readjust inputfields to show correct height/width
			this._applyValuesToFields(this.imageObj.width(), this.imageObj.height());
		}
	});

});
