define([
	"jquery",
	"underscore",
	"crel",
	"utils",
	"classes/Extension",
	"mousetrap",
	"rangy",
	"text!html/findReplace.html",
	"text!html/findReplaceSettingsBlock.html"
], function($, _, crel, utils, Extension, mousetrap, rangy, findReplaceHTML, findReplaceSettingsBlockHTML) {

	var findReplace = new Extension("findReplace", 'Find and Replace', true, true);
	findReplace.settingsBlock = findReplaceSettingsBlockHTML;
	findReplace.defaultConfig = {
		findReplaceShortcut: 'mod+f'
	};

	findReplace.onLoadSettings = function() {
		utils.setInputValue("#input-find-replace-shortcut", findReplace.config.findReplaceShortcut);
	};

	findReplace.onSaveSettings = function(newConfig, event) {
		newConfig.findReplaceShortcut = utils.getInputTextValue("#input-find-replace-shortcut", event);
	};

	var editor;
	findReplace.onEditorCreated = function(editorParam) {
		editor = editorParam;
	};

	var eventMgr;
	findReplace.onEventMgrCreated = function(eventMgrParam) {
		eventMgr = eventMgrParam;
	};

	var rangeList = [];
	var offsetList = [];
	var highlightCssApplier, selectCssApplier;
	var selectRange;
	function resetHighlight() {
		resetSelect();
		rangeList.forEach(function(rangyRange) {
			try {
				highlightCssApplier.undoToRange(rangyRange);
			}
			catch(e) {
			}
			rangyRange.detach();
		});
		rangeList = [];
	}

	function resetSelect() {
		if(selectRange) {
			try {
				selectRange && selectCssApplier.undoToRange(selectRange);
			}
			catch(e) {}
			selectRange.toBeDetached && selectRange.detach();
			selectRange = undefined;
		}
	}

	var contentElt;
	var $findReplaceElt, $searchForInputElt, $replaceWithInputElt;
	var foundCounterElt, $caseSensitiveElt, $regexpElt;

	var previousText = '';
	var previousCaseSensitive = false;
	var previousUseRegexp = false;
	var shown = false;
	var regex;

	function highlight(force) {
		if(!shown) {
			return;
		}
		var text = $searchForInputElt.val();
		var caseSensitive = $caseSensitiveElt.prop('checked');
		var useRegexp = $regexpElt.prop('checked');
		if(!force && text == previousText && caseSensitive == previousCaseSensitive && useRegexp == previousUseRegexp) {
			return;
		}
		previousText = text;
		previousCaseSensitive = caseSensitive;
		previousUseRegexp = useRegexp;

		resetHighlight();
		var lastOffset = {};
		var lastRange;

		function adjustOffset(offset) {
			if(offset.container === lastOffset.container) {
				// adjust the offset after rangy has modified the text node
				return {
					container: lastRange.endContainer.parentElement.nextSibling,
					offsetInContainer: offset.offsetInContainer - lastOffset.offsetInContainer,
					offset: offset.offset
				};
			}
			return offset;
		}

		offsetList = [];
		var found = 0;
		var textLength = text.length;
		if(textLength) {
			try {
				var flags = caseSensitive ? 'g' : 'gi';
				text = useRegexp ? text : text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
				regex = new RegExp(text, flags);
				editor.getValue().replace(regex, function(match, offset) {
					offsetList.push({
						start: offset,
						end: offset + match.length
					});
				});
				found = offsetList.length;
				// Highly CPU consuming, so add a limit
				if(offsetList.length < 200) {
					var rangeOffsets = [];
					offsetList.forEach(function(offset) {
						rangeOffsets.push(offset.start);
						rangeOffsets.push(offset.end);
					});
					rangeOffsets = editor.selectionMgr.findOffsets(rangeOffsets);
					for(var i = 0; i < rangeOffsets.length; i += 2) {
						var offsetStart = rangeOffsets[i];
						var offsetEnd = rangeOffsets[i + 1];
						var adjustedOffsetStart = adjustOffset(offsetStart);
						var adjustedOffsetEnd = adjustOffset(offsetEnd);
						var rangyRange = rangy.createRange();
						rangyRange.setStart(adjustedOffsetStart.container, adjustedOffsetStart.offsetInContainer);
						rangyRange.setEnd(adjustedOffsetEnd.container, adjustedOffsetEnd.offsetInContainer);
						lastOffset = offsetEnd;
						lastRange = rangyRange;
						highlightCssApplier.applyToRange(rangyRange);
						rangeList[offsetStart.offset] = rangyRange;
					}
					editor.selectionMgr.hasFocus && editor.selectionMgr.updateSelectionRange();
				}
			}
			catch(e) {
			}
		}
		foundCounterElt.innerHTML = found;
	}

	function show() {
		eventMgr.onEditorPopover();
		shown = true;
		$findReplaceElt.show();
		$searchForInputElt.focus()[0].setSelectionRange(0, $searchForInputElt.val().length);
		editor.selectionMgr.adjustTop = 50;
		editor.selectionMgr.adjustBottom = 220;
		highlight(true);
	}

	function hide() {
		shown = false;
		$findReplaceElt.hide();
		resetHighlight();
		editor.selectionMgr.adjustTop = 0;
		editor.selectionMgr.adjustBottom = 0;
		editor.focus();
	}

	findReplace.onEditorPopover = function() {
		hide();
	};

	function find() {
		resetSelect();
		var position = Math.min(editor.selectionMgr.selectionStart, editor.selectionMgr.selectionEnd);
		var offset = _.find(offsetList, function(offset) {
			return offset.start > position;
		});
		if(!offset) {
			offset = _.first(offsetList);
		}
		if(!offset) {
			return;
		}
		selectRange = rangeList[offset.start];
		if(!selectRange) {
			var range = editor.selectionMgr.createRange(offset.start, offset.end);
			selectRange = rangy.createRange();
			selectRange.setStart(range.startContainer, range.startOffset);
			selectRange.setEnd(range.endContainer, range.endOffset);
			selectRange.toBeDetached = true;
		}
		selectCssApplier.applyToRange(selectRange);
		selectRange.start = offset.start;
		selectRange.end = offset.end;
		editor.selectionMgr.setSelectionStartEnd(offset.start, offset.end);
		editor.selectionMgr.updateCursorCoordinates(true);
	}

	function replace() {
		if(!selectRange) {
			return find();
		}
		var replacement = $replaceWithInputElt.val();
		editor.replace(selectRange.start, selectRange.end, replacement);
		setTimeout(function() {
			find();
			$replaceWithInputElt.focus();
		}, 1);
	}

	function replaceAll() {
		var replacement = $replaceWithInputElt.val();
		editor.replaceAll(regex, replacement);
	}

	findReplace.onContentChanged = _.bind(highlight, null, true);
	findReplace.onFileOpen = _.bind(highlight, null, true);

	findReplace.onReady = function() {
		highlightCssApplier = rangy.createCssClassApplier('find-replace-highlight', {
			normalize: false
		});
		selectCssApplier = rangy.createCssClassApplier('find-replace-select', {
			normalize: false
		});
		contentElt = document.querySelector('#wmd-input .editor-content');

		var elt = crel('div', {
			class: 'find-replace'
		});
		$findReplaceElt = $(elt).hide();
		elt.innerHTML = findReplaceHTML;
		document.querySelector('.layout-wrapper-l2').appendChild(elt);
		$('.button-find-replace-dismiss').click(function() {
			hide();
		});
		foundCounterElt = elt.querySelector('.found-counter');
		$caseSensitiveElt = $findReplaceElt.find('.checkbox-case-sensitive').change(_.bind(highlight, null, false));
		$regexpElt = $findReplaceElt.find('.checkbox-regexp').change(_.bind(highlight, null, false));
		$findReplaceElt.find('.search-button').click(find);
		$searchForInputElt = $('#input-find-replace-search-for').keyup(_.bind(highlight, null, false));
		$findReplaceElt.find('.replace-button').click(replace);
		$replaceWithInputElt = $('#input-find-replace-replace-with');
		$findReplaceElt.find('.replace-all-button').click(replaceAll);

		// Key bindings
		$().add($searchForInputElt).add($replaceWithInputElt).keydown(function(evt) {
			if(evt.which === 13) {
				// Enter key
				evt.preventDefault();
				find();
			}
		});

		mousetrap.bind(findReplace.config.findReplaceShortcut, function(e) {
			var newSearch = editor.selectionMgr.getSelectedText();
			if(newSearch) {
				$searchForInputElt.val(newSearch);
			}
			show();
			e.preventDefault();
		});
	};

	return findReplace;
});
