(function()
{
	var moduleName = 'commentsModule';
	var	module = angular.module(moduleName);

	module
		.directive('commentEdit', ['$rootScope', '$timeout', '$document', 'annotationService', 'underscore', commentEditDirective])
		.filter("highlight", ['$sce', highlightFilter]);

	function highlightFilter($sce)
	{
		return function(name, query)
		{
			var q = query.substring(1);
			var highlighted = name.replace(q, '<span class="mention_popover-highlight">' + q + '</span>');
			return $sce.trustAsHtml(highlighted);
		}
	}

	function commentEditDirective($scope, $timeout, $document, annotationService, _)
	{
		var controller = function()
		{
			var ctrl = this;

            this.$onInit = function() {
                $timeout(function()
                {
                    ctrl.convertSourceToDisplay();
                }, 0);
            };

			ctrl.display = '';
			ctrl.cursorPos = 0;
			ctrl.commentIsTooLong = false;
			ctrl.placeholder = lmsg('common.cla_comments.placeholder');
			ctrl.mentions = [];
			ctrl.currentMention = false;
			ctrl.insertMode = false;
			ctrl.insertBackup = '';
			ctrl.insertPos = 0;
			ctrl.currentSearch = '';
			ctrl.mentionLookups = [];
			ctrl.popupSelectedUser = 0;
			ctrl.oldBoxHeight = 0;
			ctrl.oldBoxInnerHeight = 0;
			ctrl.elementId = '';
			ctrl.initEditBoxHidden = false;
			ctrl.annotationPicker = annotationService.getAnnotationPicker();

			ctrl.escapeRegExp = function(str) {
				return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
			};

			// sanitize html elements in text, only mention <spans> should be allowed, they're added afterwards.
			ctrl.sanitizeText = function(string){
				var entities = {
					"&": "&amp;",
					"<": "&lt;",
					">": "&gt;",
					'"': '&quot;',
					"'": '&#039;'

				};

				return String(string).replace(/[&<>"']/g, function (s) {
					return entities[s];
				});


			};

			// Expands to text in @[<id>] form to @<name>
			ctrl.convertSourceToDisplay = function()
			{
				var maxLength = 3000;


				if (ctrl.comment.text.length >= maxLength)
					ctrl.commentIsTooLong = true;
				else
					ctrl.commentIsTooLong = false;

				ctrl.display = ctrl.comment.text;
				ctrl.highlights = ctrl.sanitizeText(ctrl.comment.text);

				// First pass to replace @[<id>] with @<name>
				for (var i = 0; i < ctrl.comment.mentioned.length; i++)
				{
					var token = '@[' + ctrl.comment.mentioned[i].user_id + ']';
					var start = ctrl.display.indexOf(token);
					while (start !== -1)
					{
						ctrl.display = ctrl.display.replace(token, '@' + ctrl.comment.mentioned[i].name);
						ctrl.highlights = ctrl.highlights.replace(token, '<span class="mention">@' + _.escape(ctrl.comment.mentioned[i].name) + '</span>');

						start = ctrl.display.indexOf(token);
					}
				}

				// Second pass to keep track of all mentions for cursor movements and mouse clicks
				ctrl.mentions = [];
				for (i = 0; i < ctrl.comment.mentioned.length; i++)
				{
					token = '@' + ctrl.comment.mentioned[i].name;
					start = ctrl.display.indexOf(token);
					while (start !== -1)
					{
						if (start >= 0)
						{
							ctrl.mentions.push(
								{
									user_id: ctrl.comment.mentioned[i].user_id,
									start: start,
									length: ctrl.comment.mentioned[i].name.length + 1
								});
						}

						start = ctrl.display.indexOf(token, start + 1);
					}
				}
				ctrl.highlights = ctrl.highlights.replace(new RegExp(ctrl.escapeRegExp("\n"), 'gm'), '<br>');
			};

			// Converts to text in @<name> form to @[<id>] using list of known mentioned people
			ctrl.convertDisplayToSource = function(doLookup)
			{
				if (typeof doLookup === 'undefined')
					doLookup = true;

				// Rebuild the comment source using mentions known already
				ctrl.comment.text = ctrl.display;
				for (var i = 0; i < ctrl.comment.mentioned.length; i++)
				{
					ctrl.comment.text = ctrl.comment.text.replace(new RegExp('@' + ctrl.escapeRegExp(ctrl.comment.mentioned[i].name), 'gmi'), '@[' + ctrl.comment.mentioned[i].user_id + ']');
				}

				// Find unmatched mentions (@ followed by two words)
				var re = /@([a-zA-Z0-9]+\s{1}[a-zA-Z0-9]+)/g;
				var m;

				var names = [];
				while ((m = re.exec(ctrl.comment.text)) !== null)
				{
					if (m.index === re.lastIndex)
						re.lastIndex++;

					names.push(m[1]);
				}

				// Request names that match those words
				if ((doLookup) &&
					(names.length > 0))
				{
					annotationService.getNames(names, function(users)
					{
						// Success
						for (var i = 0; i < users.length; i++)
						{
							var found = false;
							for (var j = 0; j < ctrl.comment.mentioned.length; j++)
							{
								if (ctrl.comment.mentioned[j].user_id == users[i].user_id)
								{
									found = true;
									users[i].name = ctrl.comment.mentioned[j].name;
								}
							}

							if (!found)
							{
								ctrl.comment.mentioned.push(users[i]);
							}
						}

						// Try to update the users again
						ctrl.convertDisplayToSource(false);
						ctrl.convertSourceToDisplay();
						$timeout(function()
						{
							$scope.$digest();
						}, 0);
					}, function(msg)
					{
						// Failure
						console.log(msg);
					});
				}
			};

			ctrl.resizeEditBox = function()
			{
				var target = angular.element('#' + ctrl.elementId + ' .js-mention-edit');
				var outerHeight = target.outerHeight();

				if (outerHeight > target[0].scrollHeight){
					target.height(60)
				}

				var borderWidth = parseFloat(target.css("borderTopWidth")) + parseFloat(target.css("borderBottomWidth"));
				// target.height() never changes if inline css height is overidden
				// with !important in external css file. This prevents an infinite loop
				var initialHeight = target.height();

				while (target.outerHeight() < target[0].scrollHeight + borderWidth){
					target.height(target.height() + 1);

				    // check if target.height is bigger than initialHeight (to prevent infinite loop)
					if (target.height() >= initialHeight)
					    break;
				}
				ctrl.oldBoxInnerHeight = target[0].scrollHeight;
				ctrl.oldBoxHeight = target.outerHeight();
			};

			// Intercept keypresses for special keys (arrows, etc)
			ctrl.onKeyDown = function($event)
			{
				ctrl.cursorPos = ctrl.getCursorPos($event);
				var newPos = 0;
				var newMention = false;
				var sel = window.getSelection().toString();
				var pos = ctrl.getCursorPos($event);

				if (!ctrl.insertMode)
				{
					// Process various special keys
					switch ($event.keyCode)
					{
						case 37:  // Left arrow
						{
							if (pos > 0)
							{
								newPos = pos - 1;
								newMention = ctrl.getCurrentMention(newPos);
								if (newMention !== false)
								{
									ctrl.cursorPos = ctrl.mentions[newMention].start + 1;
									ctrl.setCursorPos($event.target, ctrl.cursorPos);
									if ($event.shiftKey)
										$event.target.setSelectionRange(ctrl.cursorPos, ctrl.cursorPos + ctrl.mentions[newMention].length -1 + sel.length);
								}
							}

							break;
						}
						case 39:  // Right arrow
						{
							if (pos <= ctrl.display.length)
							{
								newPos = pos + 1;
								if ($event.shiftKey)
									newPos += sel.length;
								newMention = ctrl.getCurrentMention(newPos);
								if (newMention !== false)
								{
									ctrl.cursorPos = ctrl.mentions[newMention].start + ctrl.mentions[newMention].length - 1;
									ctrl.setCursorPos($event.target, ctrl.cursorPos);
									if ($event.shiftKey)
										$event.target.setSelectionRange(ctrl.cursorPos - ctrl.mentions[newMention].length + 1 - sel.length, ctrl.cursorPos);
								}
							}

							break;
						}
						case 46:  // Delete
						{
							if (sel.length === 0)
							{
								for (var i = 0; i < ctrl.mentions.length; i++)
								{
									if (pos == ctrl.mentions[i].start)
									{
										$event.target.setSelectionRange(ctrl.mentions[i].start, ctrl.mentions[i].start + ctrl.mentions[i].length);
									}
									ctrl.convertDisplayToSource();
									ctrl.convertSourceToDisplay();
								}
							}
							break;
						}
						case 8:  // Backspace
						{
							if (sel.length === 0)
							{
								for (var i = 0; i < ctrl.mentions.length; i++)
								{
									if (pos == ctrl.mentions[i].start + ctrl.mentions[i].length)
									{
										$event.target.setSelectionRange(ctrl.mentions[i].start, ctrl.mentions[i].start + ctrl.mentions[i].length);
									}
									ctrl.convertDisplayToSource();
									ctrl.convertSourceToDisplay();
								}
							}
							break;
						}
					}
					$timeout(function()
					{
						var target = angular.element($event.currentTarget);
						var outerHeight = target.outerHeight();
						if ((target[0].scrollHeight != ctrl.oldBoxInnerHeight) ||
							(outerHeight != ctrl.oldBoxHeight))
						{
							ctrl.resizeEditBox();
						}
					}, 0);
				}
			};

			// See where the cursor ended up after keyboard repeat
			ctrl.onKeyUp = function($event)
			{
				ctrl.cursorPos = ctrl.getCursorPos($event);
				ctrl.currentMention = ctrl.getCurrentMention(ctrl.cursorPos);
			};

			// Find out the real key code
			ctrl.onKeyPress = function($event)
			{
				if (!ctrl.insertMode)
				{
					if (String.fromCharCode($event.charCode) == '@')
					{
						ctrl.insertMode = true;
						$event.preventDefault();

						// Make sure this keypress overwrites any current selection
						ctrl.display = ctrl.display.slice(0, $event.target.selectionStart) + ctrl.display.slice($event.target.selectionEnd);

						ctrl.convertDisplayToSource();
						ctrl.convertSourceToDisplay();

						var pos = ctrl.getCursorPos($event);
						ctrl.insertPos = pos;
						ctrl.popup = ctrl.display.substr(0, pos) + '<span class="js-cursor-marker"> </span>';
						var locator = angular.element('#' + ctrl.elementId).find('.mention-edit-dropdown-locator');
						var position = angular.element('#' + ctrl.elementId).find('.js-mention-edit').position();
						locator.css('top', position.top - 6);
						locator.css('left', position.left - 2);
						$timeout(function()
						{
							var locator = angular.element('#' + ctrl.elementId).find('.mention-edit-dropdown-locator');
							annotationService.open('@', locator, function(text, user_id)
							{
								if (user_id > 0)
								{
									var inMentions = false;
									for (i = 0; i < ctrl.mentions.length; i++)
									{
										if (ctrl.mentions[i].user_id == ctrl.popupSelectedUser)
										{
											inMentions = true;
										}
									}

									if ((!inMentions) &&
										(text.length > 0) &&
										(text !== '@'))
									{
										ctrl.mentions.push(
											{
												user_id: user_id,
												start: ctrl.insertPos,
												length: text.length + 1
											});
										ctrl.comment.mentioned.push({name: text.slice(1), user_id: user_id});
									}
								}

								ctrl.display = ctrl.display.slice(0, ctrl.insertPos) + text + ctrl.display.slice(ctrl.insertPos);
								ctrl.convertDisplayToSource(true);
								ctrl.convertSourceToDisplay();
								ctrl.insertMode = false;
								angular.element('#' + ctrl.elementId).find('.js-mention-edit').focus();
								$timeout(function()
								{
									$scope.$digest();
								});
							});
						},0);
					}
				}
			};

			// Highlight mentions as one item
			ctrl.onClick = function($event)
			{
				var sel = window.getSelection().toString();
				if (sel.length === 0)
					ctrl.cursorPos = ctrl.getCursorPos($event);

				ctrl.currentMention = ctrl.getCurrentMention(ctrl.cursorPos);

				if (ctrl.currentMention !== false) {
					ctrl.setCursorPos($event.target, ctrl.cursorPos);
					$event.target.setSelectionRange(ctrl.mentions[ctrl.currentMention].start, ctrl.mentions[ctrl.currentMention].start + ctrl.mentions[ctrl.currentMention].length);
				}
			};

			// Needed to handle copy/paste/undo/redo
			ctrl.onChange = function($event)
			{
				if (!ctrl.insertMode)
				{
					ctrl.convertDisplayToSource();
					ctrl.convertSourceToDisplay();
				}
			};

			// Find the user the cursor is currently within
			ctrl.getCurrentMention = function(cursorPos)
			{
				var currentMention = false;
				for (var i = 0; i < ctrl.mentions.length; i++)
				{
					if ((cursorPos > ctrl.mentions[i].start) &&
						(cursorPos < ctrl.mentions[i].start + ctrl.mentions[i].length))
					{
						currentMention = i;
					}
				}

				return currentMention;
			};

			ctrl.getCursorPos = function($event)
			{
				// Initialize
				var oField = $event.target;
				var cursorPos = 0;

				// IE Support
				if (document.selection) {

					// Set focus on the element
					oField.focus ();

					// To get cursor position, get empty selection range
					var oSel = document.selection.createRange ();

					// Move selection start to 0 position
					oSel.moveStart ('character', -oField.value.length);

					// The caret position is selection length
					cursorPos = oSel.text.length;
				}

				// Firefox support
				else if (oField.selectionStart || oField.selectionStart == '0')
					cursorPos = oField.selectionStart;
				return cursorPos;
			};

			ctrl.setCursorPos = function(elem, caretPos) {
				if (elem !== null) {
					if (elem.createTextRange) {
						var range = elem.createTextRange();
						range.move('character', caretPos);
						range.select();
					} else {
						if (elem.setSelectionRange) {
							elem.focus();
							elem.setSelectionRange(caretPos, caretPos);
						} else
							elem.focus();
					}
				}
			};
		};

		var link = function(scope, element, attrs, controller)
		{
			controller.elementId = attrs.id;
			// initially hide (only) edit boxes until they've been resized.
			if(controller.elementId.match(/edittext-[0-9]+/i)){
				controller.initEditBoxHidden = true;
			}
			controller.comment.updateDisplay = function()
			{
				controller.convertSourceToDisplay();
			};
		};

		return {
			restrict: 'E',
			replace: true,
			controllerAs: 'mentions',
			bindToController: true,
			templateUrl: '/intranet/comments/html/ui/comment_edit_directive_template.html',
			scope:
				{
					comment: '='
				},
			controller: controller,
			link: link
		};
	}
}());
