(function ($) {
	'use strict';

	$.sceditor.plugins.undo = function () {
		var base = this;
		var editor;
		var charChangedCount = 0;
		var previousValue;

		var undoLimit  = 50;
		var redoStates = [];
		var undoStates = [];
		var ignoreNextValueChanged = false;

		/**
		 * Sets the editor to the specified state.
		 *
		 * @param  {Object} state
		 * @private
		 */
		var applyState = function (state) {
			ignoreNextValueChanged = true;

			previousValue = state.value;

			editor.sourceMode(state.sourceMode);
			editor.val(state.value, false);
			editor.focus();

			if (state.sourceMode) {
				editor.sourceEditorCaret(state.caret);
			} else {
				editor.getRangeHelper().restoreRange();
			}

			ignoreNextValueChanged = false;
		};


		/**
		 * Caluclates the number of characters that have changed
		 * between two strings.
		 *
		 * @param {String} strA
		 * @param {String} strB
		 * @return {String}
		 * @private
		 */
		var simpleDiff = function (strA, strB) {
			var start, end, aLenDiff, bLenDiff,
				aLength = strA.length,
				bLength = strB.length,
				length  = Math.max(aLength, bLength);

			// Calculate the start
			for (start = 0; start < length; start++) {
				if (strA.charAt(start) !== strB.charAt(start)) {
					break;
				}
			}

			// Calculate the end
			aLenDiff = aLength < bLength ? bLength - aLength : 0;
			bLenDiff = bLength < aLength ? aLength - bLength : 0;

			for (end = length - 1; end >= 0; end--) {
				if (strA.charAt(end - aLenDiff) !==
						strB.charAt(end - bLenDiff)) {
					break;
				}
			}

			return (end - start) + 1;
		};

		base.init = function () {
			// The this variable will be set to the instance of the editor
			// calling it, hence why the plugins "this" is saved to the base
			// variable.
			editor = this;

			undoLimit = editor.undoLimit || undoLimit;

			// addShortcut is the easiest way to add handlers to specific
			// shortcuts
			editor.addShortcut('ctrl+z', base.undo);
			editor.addShortcut('ctrl+shift+z', base.redo);
			editor.addShortcut('ctrl+y', base.redo);
		};

		base.undo = function () {
			var state = undoStates.pop();
			var rawEditorValue = editor.val(null, false);

			if (state && !redoStates.length && rawEditorValue === state.value) {
				state = undoStates.pop();
			}

			if (state) {
				if (!redoStates.length) {
					redoStates.push({
						'caret': editor.sourceEditorCaret(),
						'sourceMode': editor.sourceMode(),
						'value': rawEditorValue
					});
				}

				redoStates.push(state);
				applyState(state);
			}

			return false;
		};

		base.redo = function () {
			var state = redoStates.pop();

			if (!undoStates.length) {
				undoStates.push(state);
				state = redoStates.pop();
			}

			if (state) {
				undoStates.push(state);
				applyState(state);
			}

			return false;
		};

		base.signalReady = function () {
			var rawValue = editor.val(null, false);

			// Store the initial value as the last value
			previousValue = rawValue;

			undoStates.push({
				'caret': this.sourceEditorCaret(),
				'sourceMode': this.sourceMode(),
				'value': rawValue
			});
		};

		/**
		 * Handle the valueChanged signal.
		 *
		 * e.rawValue will either be the raw HTML from the WYSIWYG editor with
		 * the rangeHelper range markers inserted, or it will be the raw value
		 * of the source editor (BBCode or HTML depening on plugins).
		 * @return {void}
		 */
		base.signalValuechangedEvent = function (e) {
			var rawValue = e.rawValue;

			if (undoLimit > 0 && undoStates.length > undoLimit) {
				undoStates.shift();
			}

			// If the editor hasn't fully loaded yet,
			// then the previous value won't be set.
			if (ignoreNextValueChanged || !previousValue ||
					previousValue === rawValue) {
				return;
			}

			// Value has changed so remove all redo states
			redoStates.length = 0;
			charChangedCount += simpleDiff(previousValue, rawValue);

			if (charChangedCount < 20) {
				return;
			// ??
			} else if (charChangedCount < 50 && !/\s$/g.test(e.rawValue)) {
				return;
			}

			undoStates.push({
				'caret': editor.sourceEditorCaret(),
				'sourceMode': editor.sourceMode(),
				'value': rawValue
			});

			charChangedCount = 0;
			previousValue = rawValue;
		};
	};
}(jQuery));