/**
 * jquery-textrange
 *
 * A jQuery plugin for getting, setting and replacing the selected text in input fields and textareas.
 * See the [README](https://github.com/dwieeb/jquery-textrange/blob/1.x/README.md) for usage and examples.
 *
 * (c) 2012-2017 Daniel Imhoff <dwieeb@gmail.com> - dwieeb.com
 */

(function(factory) {

	if (typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	} else if (typeof exports === 'object') {
		factory(require('jquery'));
	} else {
		factory(jQuery);
	}

})(function($) {

	var browserType,

	textrange = {

		/**
		 * $().textrange() or $().textrange('get')
		 *
		 * Retrieves an object containing the start and end location of the text range, the length of the range and the
		 * substring of the range.
		 *
		 * @param (optional) property
		 * @return An object of properties including position, start, end, length, and text or a specific property.
		 */
		get: function(property) {
			return _textrange[browserType].get.apply(this, [property]);
		},

		/**
		 * $().textrange('set')
		 *
		 * Sets the selected text of an object by specifying the start and length of the selection.
		 *
		 * The start and length parameters are identical to PHP's substr() function with the following changes:
		 *  - excluding start will select all the text in the field.
		 *  - passing 0 for length will set the cursor at start. See $().textrange('setcursor')
		 *
		 * @param (optional) start
		 * @param (optional) length
		 *
		 * @see https://secure.php.net/manual/en/function.substr.php
		 */
		set: function(start, length) {
			var s = parseInt(start),
			    l = parseInt(length),
			    e;

			if (typeof start === 'undefined') {
				s = 0;
			} else if (start < 0) {
				s = this[0].value.length + s;
			}

			if (typeof length !== 'undefined') {
				if (length >= 0) {
					e = s + l;
				} else {
					e = this[0].value.length + l;
				}
			}

			_textrange[browserType].set.apply(this, [s, e]);

			return this;
		},

		/**
		 * $().textrange('setcursor')
		 *
		 * Sets the cursor at a position of the text field.
		 *
		 * @param position
		 */
		setcursor: function(position) {
			return this.textrange('set', position, 0);
		},

		/**
		 * $().textrange('replace')
		 * Replaces the selected text in the input field or textarea with text.
		 *
		 * @param text The text to replace the selection with.
		 */
		replace: function(text) {
			_textrange[browserType].replace.apply(this, [String(text)]);

			return this;
		},

		/**
		 * Alias for $().textrange('replace')
		 */
		insert: function(text) {
			return this.textrange('replace', text);
		}
	},

	_textrange = {
		xul: {
			get: function(property) {
				var props = {
					position: this[0].selectionStart,
					start: this[0].selectionStart,
					end: this[0].selectionEnd,
					length: this[0].selectionEnd - this[0].selectionStart,
					text: this.val().substring(this[0].selectionStart, this[0].selectionEnd)
				};

				return typeof property === 'undefined' ? props : props[property];
			},

			set: function(start, end) {
				if (typeof end === 'undefined') {
					end = this[0].value.length;
				}

				this[0].selectionStart = start;
				this[0].selectionEnd = end;
			},

			replace: function(text) {
				var start = this[0].selectionStart;
				var end = this[0].selectionEnd;
				var val = this.val();
				this.val(val.substring(0, start) + text + val.substring(end, val.length));
				this[0].selectionStart = start;
				this[0].selectionEnd = start + text.length;
			}
		},

		msie: {
			get: function(property) {
				var range = document.selection.createRange();

				if (typeof range === 'undefined') {
					var props = {
						position: 0,
						start: 0,
						end: this.val().length,
						length: this.val().length,
						text: this.val()
					};

					return typeof property === 'undefined' ? props : props[property];
				}

				var start = 0;
				var end = 0;
				var length = this[0].value.length;
				var lfValue = this[0].value.replace(/\r\n/g, '\n');
				var rangeText = this[0].createTextRange();
				var rangeTextEnd = this[0].createTextRange();
				rangeText.moveToBookmark(range.getBookmark());
				rangeTextEnd.collapse(false);

				if (rangeText.compareEndPoints('StartToEnd', rangeTextEnd) === -1) {
					start = -rangeText.moveStart('character', -length);
					start += lfValue.slice(0, start).split('\n').length - 1;

					if (rangeText.compareEndPoints('EndToEnd', rangeTextEnd) === -1) {
						end = -rangeText.moveEnd('character', -length);
						end += lfValue.slice(0, end).split('\n').length - 1;
					} else {
						end = length;
					}
				} else {
					start = length;
					end = length;
				}

				var props = {
					position: start,
					start: start,
					end: end,
					length: length,
					text: range.text
				};

				return typeof property === 'undefined' ? props : props[property];
			},

			set: function(start, end) {
				var range = this[0].createTextRange();

				if (typeof range === 'undefined') {
					return;
				}

				if (typeof end === 'undefined') {
					end = this[0].value.length;
				}

				var ieStart = start - (this[0].value.slice(0, start).split("\r\n").length - 1);
				var ieEnd = end - (this[0].value.slice(0, end).split("\r\n").length - 1);

				range.collapse(true);

				range.moveEnd('character', ieEnd);
				range.moveStart('character', ieStart);

				range.select();
			},

			replace: function(text) {
				document.selection.createRange().text = text;
			}
		}
	};

	$.fn.extend({
		textrange: function(arg) {
			var method = 'get';
			var options = {};

			if (typeof this[0] === 'undefined') {
				return this;
			}

			if (typeof arg === 'string') {
				method = arg;
			} else if (typeof arg === 'object') {
				method = arg.method || method;
				options = arg;
			}

			if (typeof browserType === 'undefined') {
				browserType = 'selectionStart' in this[0] ? 'xul' : document.selection ? 'msie' : 'unknown';
			}

			// I don't know how to support this browser. :c
			if (browserType === 'unknown') {
				return this;
			}

			// Focus on the element before operating upon it.
			if (!options.nofocus && document.activeElement !== this[0]) {
				this[0].focus();
			}

			if (typeof textrange[method] === 'function') {
				return textrange[method].apply(this, Array.prototype.slice.call(arguments, 1));
			} else {
				$.error("Method " + method + " does not exist in jQuery.textrange");
			}
		}
	});
});
