/**
 * Captioned Image provides an Aloha block implementation that allows the editor
 * to work with images that have captions, such that an image with its
 * corresponding caption can be aligned together in an editable.
 * It reads and writes to an <img> tag's data-caption and data-align attributes.
 * No formatting inside the caption is allowed; only plain text is permitted.
 * Four possible alignments are possible: none, left, right, center.
 *
 * TODO
 * ----
 * - Implement makeClean
 * - Prevent disallowed content in caption
 */
define([
	'jquery',
	'aloha/core',
	'aloha/plugin',
	'block/block',
	'block/blockmanager',
	'ui/ui',
	'ui/toggleButton',
	'ui/toolbar',
	'util/maps',
	'aloha/contenthandlermanager',
	'aloha/console',
	'align/align-plugin', // Needed to ensure that we have "alignLeft", and
	                      // "alignRight" components.
	// FIXME: use of the css require plugin is deprecated
	'css!captioned-image/css/captioned-image.css'
], function (
	$,
	Aloha,
	Plugin,
	Block,
	BlockManager,
	Ui,
	ToggleButton,
	Toolbar,
	Maps,
	ContentHandlerManager,
	console
) {
	'use strict';

	var defaultRenderCSS = '\
		.captioned-image {\
			text-align: center;\
			padding: 0 1em 1em;\
		}\
		.captioned-image.align-right {\
			float: right;\
			padding-right: 0;\
		}\
		.captioned-image.align-left {\
			float: left;\
			padding-left: 0;\
		}\
		.captioned-image.align-center {\
			display: block;\
			text-align: center;\
		}\
		.captioned-image .caption {\
			padding: 0.5em;\
			font-size: 0.9em;\
			background: rgba(0,0,0,0.8);\
			font-family: Arial;\
			color: #fff;\
			text-align: left;\
			min-width: 100px;\
		}\
		.captioned-image.align-center .caption {\
			margin-left: auto;\
			margin-right: auto;\
		}\
		/* Overrides for when the caption is being edited through Aloha Editor. */\
		.aloha-captioned-image-block .captioned-image {\
			padding: 0;\
		}\
	';

	var settings = ((Aloha.settings &&
	               Aloha.settings.plugins &&
	               Aloha.settings.plugins.captionedImage) || false);

	if (settings.defaultCSS !== false) {
		$('<style type="text/css">').text(defaultRenderCSS).appendTo('head:first');
	}

	var render;
	if (typeof settings.render === 'function') {
		render = settings.render;
	} else {
		render = function (variables, callback, error) {
			var html = '<div class="captioned-image';

			if (variables.align) {
				html += ' align-' + variables.align;
			}

			html += '">'
				 + (variables.image || '<img alt="Captioned image placeholder"/>')
				 + '<div class="caption"';

			if (variables.width) {
				html += ' style="width:' + variables.width + '"';
			}

			html += '>' + (variables.caption || '') + '</div></div>';

			callback({
				content: html,
				image: '>div>img:first',
				caption: '>div>div.caption:first'
			});
		};
	}

	// This is the class that will be set on the image when cleaning up. Set to
	// the empty string if you don't want a class to be set.
	if (typeof settings.captionedImageClass !== 'string') {
		settings.captionedImageClass = 'aloha-captioned-image';
	}

	var components = [];
	function initializeComponents() {
		var left = Ui.getAdoptedComponent('alignLeft');
		var right = Ui.getAdoptedComponent('alignRight');
		var center = Ui.getAdoptedComponent('alignCenter');
		var alignLeft = function () {
			center.setState(false);
			right.setState(false);
			if (BlockManager._activeBlock) {
				var alignment = BlockManager._activeBlock.attr('align');
				BlockManager._activeBlock.attr('align',
					('left' === alignment) ? 'none' : 'left');
				return true;
			}
			return false;
		};
		var alignRight = function () {
			left.setState(false);
			center.setState(false);
			if (BlockManager._activeBlock) {
				var alignment = BlockManager._activeBlock.attr('align');
				BlockManager._activeBlock.attr('align',
					('right' === alignment) ? 'none' : 'right');
				return true;
			}
			return false;
		};
		var alignCenter = function () {
			left.setState(false);
			right.setState(false);
			if (BlockManager._activeBlock) {
				BlockManager._activeBlock.attr('align', 'center');
				return true;
			}
			return false;
		}

		if (left) {
			var clickLeft = left.click;
			left.click = function () {
				if (!alignLeft()) {
					clickLeft();
				}
			};
			components.push(left);
		} else {
			components.push(Ui.adopt('imgAlignLeft', ToggleButton, {
				tooltip: 'Align left',
				text: 'Align left',
				click: alignLeft
			}));
		}

		if (right) {
			var clickRight = right.click;
			right.click = function () {
				if (!alignRight()) {
					clickRight();
				}
			};
			components.push(right);
		} else {
			components.push(Ui.adopt('imgAlignRight', ToggleButton, {
				tooltip: 'Align right',
				text: 'Align right',
				click: alignRight
			}));
		}

		if (center) {
			var clickCenter = center.click;
			center.click = function () {
				if (!alignCenter()) {
					clickCenter();
				}
			};
			components.push(center);
		} else {
			components.push(Ui.adopt('imgAlignCenter', ToggleButton, {
				tooltip: 'Align center',
				text: 'Align center',
				click: alignCenter
			}));
		}

		components.push(Ui.adopt('imgAlignClear', ToggleButton, {
			tooltip: 'Remove alignment',
			text: 'Remove alignment',
			click: function () {
				if (BlockManager._activeBlock) {
					BlockManager._activeBlock.attr('align', 'none');
				}
			}
		}));
	}

	function getImageWidth($img) {
		var width;

		if (typeof $img.attr('width') !== 'undefined') {
			width = parseInt($img.attr('width'), 10);
		} else {
			// NOTE: this assumes the image has already loaded!
			width = parseInt($img.width(), 10);
		}

		if (typeof width === 'number' && !isNaN(width)) {
			width += 'px';
		} else {
			width = 'auto';
		}

		return width;
	}

	var blockAlignment = {};

	function getAlignmentButton(alignment) {
		switch (alignment) {
		case 'left':
			return Ui.getAdoptedComponent('alignLeft');
		case 'center':
			return Ui.getAdoptedComponent('alignCenter');
		case 'right':
			return Ui.getAdoptedComponent('alignRight');
		}
		return null;
	}

	function showComponents() {
		var i;
		for (i = 0; i < components.length; i++) {
			components[i].visible = false; // Force the component to be shown.
			components[i].show();
			components[i].foreground();
		}

		if (!Aloha.activeEditable || !BlockManager._activeBlock) {
			return;
		}

		for (i = 0; i < components.length; i++) {
			components[i].setState(false);
		}

		var alignment = BlockManager._activeBlock.attr('align');
		var component = getAlignmentButton(alignment);

		if (component) {
			component.setState(true);
		}
	}

	function eachBlock($context, fn) {
		var $blocks = $context.find('.aloha-captioned-image-block');
		$blocks.each(function (i, blockElem) {
			var block = BlockManager.getBlock(blockElem);
			if (block) {
				return fn(block, blockElem);
			}
		});
	}

	function cleanBlock(block, blockElem) {
		var $img = block.$_image.clone();
		var caption = block.attr('caption');
		var align = block.attr('align');

		// We only touch the data-caption and data-align attributes o/t img!
		if (caption) {
			$img.attr('data-caption', caption);
		} else {
			$img.removeAttr('data-caption');
		}

		if (align) {
			$img.attr('data-align', align);
		} else {
			$img.removeAttr('data-align');
		}

		if (settings.captionedImageClass) {
			$img.addClass(settings.captionedImageClass);
		}

		// Now replace the entire block with the original image, with
		// potentially updated data-caption, data-align and class
		// attributes.
		$(blockElem).replaceWith($img);
	}

	function cleanEditable($editable) {
		eachBlock($editable, function (block, blockElem) {
			cleanBlock(block, blockElem);
		});
	}

	function wrapNakedCaptionedImages($editable) {
		var selector = settings.selector || 'img.aloha-captioned-image';
		var $imgs = $editable.find(selector);
		var j = $imgs.length;

		while (j--) {
			var $img = $imgs.eq(j);
			var $block = $img.removeClass(settings.captionedImageClass)
							 .wrap('<div class="aloha-captioned-image-block">')
							 .parent();

			// Set user-provided block class, if any.
			if (typeof settings.blockClass === 'string') {
				$block.addClass(settings.blockClass);
			}

			// Through this plug-in, users will be able to change the caption
			// and the alignment, so we only need to grab those two attributes,
			// as well as the original image. We'll then always manipulate the
			// original image, to make sure we don't accidentally erase other
			// attributes.
			// Whenever we need to use other attributes, we'll have to retrieve
			// it from the original image.
			var caption = $img.attr('data-caption');
			var align = $img.attr('data-align');
			caption = (typeof caption !== 'undefined') ? caption : '';
			align = (typeof align !== 'undefined') ? align : false;
			$block.attr('data-caption',        caption)
			      .attr('data-align',          align)
			      .attr('data-width',          getImageWidth($img))
			      .attr('data-original-image', $img[0].outerHTML);
		}

		return $editable.find('.aloha-captioned-image-block');
	}

	function initializeImageBlocks($editable) {
		var $all = wrapNakedCaptionedImages($editable);
		var $blocks = $();
		var j = $all.length;

		// Transform all of the captioned (or captionable!) images into Aloha
		// Blocks.
		while (j--) {
			if (!$all.eq(j).hasClass('aloha-block')) {
				$blocks = $blocks.add($all[j]);
			}
		}

		// Set the block type for these new Aloha Blocks to the right type.
		$blocks.alohaBlock({
			'aloha-block-type': 'CaptionedImageBlock'
		});
	}

	var CaptionedImageBlock = Block.AbstractBlock.extend({
		title: 'Captioned Image',
		onblur: null,
		$_image: null,
		$_caption: null,
		init: function ($element, postProcessCallback) {
			if (this._initialized) {
				return;
			}

			var that = this;

			this.onblur = function () {
				var html = that.$_caption.html();

				if (that.attr('caption') !== html) {
					that.attr('caption', html);
				}

				Toolbar.$surfaceContainer.show();
			};

			this.onkeypress = function(e) {
				// prevent new line in image caption -- no p and br allowed (default)
				//
				// use Aloha.settings.plugins.captionedImage.allowLinebreak = false (or an empty array [ ]) (default)
				// to allow no <br> and <p>
				//
				// use Aloha.settings.plugins.captionedImage.allowLinebreak = ['p', 'br'] to allow <br> and <p>
				// use Aloha.settings.plugins.captionedImage.allowLinebreak = [ 'br' ] to allow just <br> and not <p> (or boolean true)
				// use Aloha.settings.plugins.captionedImage.allowLinebreak = [ 'p' ] to allow just <p>
				var allowLinebreak = false,
					allowNewline = false;

				if (settings &&
					typeof settings.allowLinebreak != 'undefined' &&
					settings.allowLinebreak) {
					allowNewline = true;
					allowLinebreak = settings.allowLinebreak;
				}

				if (settings.allowLinebreak === true) {
					allowLinebreak = [ 'br' ];
				}

				if (jQuery.inArray('p', allowLinebreak) < 0 && jQuery.inArray('br', allowLinebreak) < 0) {
					allowNewline = false;
				}

				if (e.keyCode == 13 && !allowNewline) {
					console.info(this.title, 'No new line or paragraph allowed in image caption. Use: "Aloha.settings.plugins.captionedImage.allowLinebreak = true" to activate.');
					e.preventDefault();
				} else {
					if ((event.shiftKey && jQuery.inArray('br', allowLinebreak) >= 0) ||
						(!event.shiftKey && jQuery.inArray('p', allowLinebreak) < 0)) {
						Aloha.execCommand( 'insertlinebreak', false );
						return false;
					} else if (jQuery.inArray('p', allowLinebreak) >= 0) {
						Aloha.execCommand( 'insertparagraph', false );
						return false;
					}
				}
			};

			render({
				image  : this.attr('original-image'),
				caption: this.attr('caption'),
				align  : this.attr('align'),
				width  : this.attr('width')
			}, function (data) {
				that._processRenderedData(data);
				postProcessCallback();

				Aloha.bind('aloha-editable-activated', function ($event, data) {
					if (data.editable.obj.is(that.$_caption)) {
						Toolbar.$surfaceContainer.hide();

						// add the key handler for enter (no new line allowed in caption)
						Aloha.Markup.addKeyHandler(13, function($event) {
							return that.onkeypress($event);
						});

					}
				});

				Aloha.bind('aloha-editable-deactivated', function ($event, data) {
					//if (data.editable.obj.is(that.$_caption)) {
						// this should work like above at aloha-editable-activated,
						// but it seems there is a proplem in the block implementation / this plugin
						// when iteracting with the caption editable aloha-editable-(de)activated
						// is triggerd 3 times (because there are 3 captioned image block in the main editable)
						// when just activate / deactivate the caption it works with fine (that.$_caption is available for all 3) but
						// when you change the text of the caption and deactivate it that.$_caption is 3 times the same,
						// it's the first caption editable (but this one was not the one I (de)activated)

						// the implementation of Aloha.Markup.addKeyHandler is at the moment ok,
						// but should be improved so that it's possible to bind it to a specific editable
						// right now addKeyHandler is just used here and in the list plugin
						// there is already an issue in the tracker recommending using the hotkey plugin also here
						// we need to discuss this topic ...

						// remove the key handler for enter
						Aloha.Markup.removeKeyHandler(13);
					//}
				});

			}, function (error) {
				postProcessCallback();
			});
		},
		update: function ($element, postProcessCallback) {
			this.$_caption.unbind('blur', this.onblur);
			var that = this;
			render({
				image  : this.attr('original-image'),
				caption: this.attr('caption'),
				align  : this.attr('align'),
				width  : this.attr('width')
			}, function (data) {
				that._processRenderedData(data);
				postProcessCallback();
			}, function (error) {
				postProcessCallback();
			});
		},
		_processRenderedData: function (data) {
			this.$element.html(data.content);
			this.$_image = this.$element.find(data.image);
			this.$_caption = this.$element.find(data.caption);
			this.$_caption.addClass('aloha-captioned-image-caption')
			              .addClass('aloha-editable')
			              .bind('blur', this.onblur);
			this.$element.removeClass('align-left align-right align-center');
			var alignment = this.attr('align');

			if (alignment) {
				this.$element.addClass('align-' + alignment);
			}

			// Indicate which CaptionedImage blocks have an empty caption, so
			// we can hide their caption areas whenever these blocks are not
			// active.
			if (this.attr('caption')) {
				this.$element.removeClass('aloha-captioned-image-block-empty-caption');
			} else {
				this.$element.addClass('aloha-captioned-image-block-empty-caption');
			}
		}
	});

	var CaptionedImage = Plugin.create('captioned-image', {
		init: function () {
			initializeComponents();
			BlockManager.registerBlockType('CaptionedImageBlock', CaptionedImageBlock);
			var j = Aloha.editables.length;
			while (j--) {
				initializeImageBlocks(Aloha.editables[j].obj);
			}

			Aloha.bind('aloha-editable-created', function ($event, editable) {
				initializeImageBlocks(editable.obj);
				editable.obj.delegate('.aloha-captioned-image-block', 'click',
					showComponents);
			});
			Aloha.bind('aloha-editable-destroyed', function ($event, editable) {
				eachBlock(editable.obj, function (block, blockElem) {
					cleanBlock(block, blockElem);
					block.free();
				});
				editable.obj.undelegate('.aloha-captioned-image-block', 'click',
					showComponents);
			});
		},
		makeClean: function ($content) {
			cleanEditable($content);
		}
	});

	return CaptionedImage;
});