/*
 * Fuel UX Pillbox
 * https://github.com/ExactTarget/fuelux
 *
 * Copyright (c) 2014 ExactTarget
 * Licensed under the BSD New license.
 */

// -- BEGIN UMD WRAPPER PREFACE --

// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js

(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// if AMD loader is available, register as an anonymous module.
		define(['jquery', 'fuelux/dropdown-autoflip'], factory);
	} else if (typeof exports === 'object') {
		// Node/CommonJS
		module.exports = factory(require('jquery'));
	} else {
		// OR use browser globals if AMD is not present
		factory(jQuery);
	}
}(function ($) {
	if (!$.fn.dropdownautoflip) {
		throw new Error('Fuel UX pillbox control requires dropdown-autoflip.');
	}

	// -- END UMD WRAPPER PREFACE --

	// -- BEGIN MODULE CODE HERE --

	var old = $.fn.pillbox;

	// PILLBOX CONSTRUCTOR AND PROTOTYPE

	var Pillbox = function (element, options) {
		this.$element = $(element);
		this.$moreCount = this.$element.find('.pillbox-more-count');
		this.$pillGroup = this.$element.find('.pill-group');
		this.$addItem = this.$element.find('.pillbox-add-item');
		this.$addItemWrap = this.$addItem.parent();
		this.$suggest = this.$element.find('.suggest');
		this.$pillHTML = '<li class="btn btn-default pill">' +
		'	<span></span>' +
		'	<span class="glyphicon glyphicon-close">' +
		'		<span class="sr-only">Remove</span>' +
		'	</span>' +
		'</li>';

		this.options = $.extend({}, $.fn.pillbox.defaults, options);

		if (this.options.readonly === -1) {
			if (this.$element.attr('data-readonly') !== undefined) {
				this.readonly(true);
			}

		} else if (this.options.readonly) {
			this.readonly(true);
		}

		// EVENTS
		this.acceptKeyCodes = this._generateObject(this.options.acceptKeyCodes);
		// Creatie an object out of the key code array, so we dont have to loop through it on every key stroke

		this.$element.on('click.fu.pillbox', '.pill-group > .pill', $.proxy(this.itemClicked, this));
		this.$element.on('click.fu.pillbox', $.proxy(this.inputFocus, this));
		this.$element.on('keydown.fu.pillbox', '.pillbox-add-item', $.proxy(this.inputEvent, this));
		if (this.options.onKeyDown) {
			this.$element.on('mousedown.fu.pillbox', '.suggest > li', $.proxy(this.suggestionClick, this));
		}

		if (this.options.edit) {
			this.$element.addClass('pills-editable');
			this.$element.on('blur.fu.pillbox', '.pillbox-add-item', $.proxy(this.cancelEdit, this));
		}
	};

	Pillbox.prototype = {
		constructor: Pillbox,

		destroy: function () {
			this.$element.remove();
			// any external bindings
			// [none]
			// empty elements to return to original markup
			// [none]
			// returns string of markup
			return this.$element[0].outerHTML;
		},

		items: function () {
			var self = this;

			return this.$pillGroup.children('.pill').map(function () {
				return self.getItemData($(this));
			}).get();
		},

		itemClicked: function (e) {
			var self = this;
			var $target = $(e.target);
			var $item;

			e.preventDefault();
			e.stopPropagation();
			this._closeSuggestions();

			if (!$target.hasClass('pill')) {
				$item = $target.parent();
				if (this.$element.attr('data-readonly') === undefined) {
					if ($target.hasClass('glyphicon-close')) {
						if (this.options.onRemove) {
							this.options.onRemove(this.getItemData($item, {
								el: $item
							}), $.proxy(this._removeElement, this));
						} else {
							this._removeElement(this.getItemData($item, {
								el: $item
							}));
						}

						return false;
					} else if (this.options.edit) {
						if ($item.find('.pillbox-list-edit').length) {
							return false;
						}

						this.openEdit($item);
					}

				}

			} else {
				$item = $target;
			}

			this.$element.trigger('clicked.fu.pillbox', this.getItemData($item));
		},

		readonly: function (enable) {
			if (enable) {
				this.$element.attr('data-readonly', 'readonly');
			} else {
				this.$element.removeAttr('data-readonly');
			}

			if (this.options.truncate) {
				this.truncate(enable);
			}
		},

		suggestionClick: function (e) {
			var $item = $(e.currentTarget);
			var item = {
				text: $item.html(),
				value: $item.data('value')
			};

			e.preventDefault();
			this.$addItem.val('');

			if ($item.data('attr')) {
				item.attr = JSON.parse($item.data('attr'));
			}

			item.data = $item.data('data');

			this.addItems(item, true);

			// needs to be after addItems for IE
			this._closeSuggestions();
		},

		itemCount: function () {
			return this.$pillGroup.children('.pill').length;
		},

		// First parameter is 1 based index (optional, if index is not passed all new items will be appended)
		// Second parameter can be array of objects [{ ... }, { ... }] or you can pass n additional objects as args
		// object structure is as follows (attr and value are optional): { text: '', value: '', attr: {}, data: {} }
		addItems: function () {
			var self = this;
			var items, index, isInternal;

			if (isFinite(String(arguments[0])) && !(arguments[0] instanceof Array)) {
				items = [].slice.call(arguments).slice(1);
				index = arguments[0];
			} else {
				items = [].slice.call(arguments).slice(0);
				isInternal = items[1] && !items[1].text;
			}

			//If first argument is an array, use that, otherwise they probably passed each thing through as a separate arg, so use items as-is
			if (items[0] instanceof Array) {
				items = items[0];
			}

			if (items.length) {
				$.each(items, function (i, value) {
					var data = {
						text: value.text,
						value: (value.value ? value.value : value.text),
						el: self.$pillHTML
					};

					if (value.attr) {
						data.attr = value.attr;
					}

					if (value.data) {
						data.data = value.data;
					}

					items[i] = data;
				});

				if (this.options.edit && this.currentEdit) {
					items[0].el = this.currentEdit.wrap('<div></div>').parent().html();
				}

				if (isInternal) {
					items.pop(1);
				}

				if (self.options.onAdd && isInternal) {
					if (this.options.edit && this.currentEdit) {
						self.options.onAdd(items[0], $.proxy(self.saveEdit, this));
					} else {
						self.options.onAdd(items[0], $.proxy(self.placeItems, this));
					}

				} else {
					if (this.options.edit && this.currentEdit) {
						self.saveEdit(items);
					} else {
						if (index) {
							self.placeItems(index, items);
						} else {
							self.placeItems(items, isInternal);
						}

					}

				}

			}
		},

		//First parameter is the index (1 based) to start removing items
		//Second parameter is the number of items to be removed
		removeItems: function (index, howMany) {
			var self = this;
			var count;
			var $currentItem;

			if (!index) {
				this.$pillGroup.find('.pill').remove();
				this._removePillTrigger({
					method: 'removeAll'
				});
			} else {
				howMany = howMany ? howMany : 1;

				for (count = 0; count < howMany; count++) {
					$currentItem = self.$pillGroup.find('> .pill:nth-child(' + index + ')');

					if ($currentItem) {
						$currentItem.remove();
					} else {
						break;
					}

				}
			}
		},

		//First parameter is index (optional)
		//Second parameter is new arguments
		placeItems: function () {
			var $newHtml = [];
			var items;
			var index;
			var $neighbor;
			var isInternal;

			if (isFinite(String(arguments[0])) && !(arguments[0] instanceof Array)) {
				items = [].slice.call(arguments).slice(1);
				index = arguments[0];
			} else {
				items = [].slice.call(arguments).slice(0);
				isInternal = items[1] && !items[1].text;
			}

			if (items[0] instanceof Array) {
				items = items[0];
			}

			if (items.length) {
				$.each(items, function (i, item) {
					var $item = $(item.el);
					var $neighbor;

					$item.attr('data-value', item.value);
					$item.find('span:first').html(item.text);

					// DOM attributes
					if (item.attr) {
						$.each(item.attr, function (key, value) {
							if (key === 'cssClass' || key === 'class') {
								$item.addClass(value);
							} else {
								$item.attr(key, value);
							}
						});

					}

					if (item.data) {
						$item.data('data', item.data);
					}

					$newHtml.push($item);
				});

				if (this.$pillGroup.children('.pill').length > 0) {
					if (index) {
						$neighbor = this.$pillGroup.find('.pill:nth-child(' + index + ')');

						if ($neighbor.length) {
							$neighbor.before($newHtml);
						} else {
							this.$pillGroup.children('.pill:last').after($newHtml);
						}

					} else {
						this.$pillGroup.children('.pill:last').after($newHtml);
					}

				} else {
					this.$pillGroup.prepend($newHtml);
				}

				if (isInternal) {
					this.$element.trigger('added.fu.pillbox', {
						text: items[0].text,
						value: items[0].value
					});
				}

			}
		},

		inputEvent: function (e) {
			var self = this;
			var text = this.$addItem.val();
			var value;
			var attr;
			var $lastItem;
			var $selection;

			if (this.acceptKeyCodes[e.keyCode]) {
				if (this.options.onKeyDown && this._isSuggestionsOpen()) {
					$selection = this.$suggest.find('.pillbox-suggest-sel');

					if ($selection.length) {
						text = $selection.html();
						value = $selection.data('value');
						attr = $selection.data('attr');
					}

				}

				//ignore comma and make sure text that has been entered (protects against " ,". https://github.com/ExactTarget/fuelux/issues/593), unless allowEmptyPills is true.
				if (text.replace(/[ ]*\,[ ]*/, '').match(/\S/) || (this.options.allowEmptyPills && text.length)) {
					this._closeSuggestions();
					this.$addItem.hide();

					if (attr) {
						this.addItems({
							text: text,
							value: value,
							attr: JSON.parse(attr)
						}, true);
					} else {
						this.addItems({
							text: text,
							value: value
						}, true);
					}

					setTimeout(function () {
						self.$addItem.show().val('').attr({
							size: 10
						});
					}, 0);
				}

				e.preventDefault();
				return true;
			} else if (e.keyCode === 8 || e.keyCode === 46) {
				// backspace: 8
				// delete: 46

				if (!text.length) {
					e.preventDefault();

					if (this.options.edit && this.currentEdit) {
						this.cancelEdit();
						return true;
					}

					this._closeSuggestions();
					$lastItem = this.$pillGroup.children('.pill:last');

					if ($lastItem.hasClass('pillbox-highlight')) {
						this._removeElement(this.getItemData($lastItem, {
							el: $lastItem
						}));
					} else {
						$lastItem.addClass('pillbox-highlight');
					}

					return true;
				}

			} else if (text.length > 10) {
				if (this.$addItem.width() < (this.$pillGroup.width() - 6)) {
					this.$addItem.attr({
						size: text.length + 3
					});
				}

			}

			this.$pillGroup.find('.pill').removeClass('pillbox-highlight');

			if (this.options.onKeyDown) {
				if (e.keyCode === 9 || e.keyCode === 38 || e.keyCode === 40) {
					// tab: 9
					// up arrow: 38
					// down arrow: 40

					if (this._isSuggestionsOpen()) {
						this._keySuggestions(e);
					}

					return true;
				}

				//only allowing most recent event callback to register
				this.callbackId = e.timeStamp;
				this.options.onKeyDown({
					event: e,
					value: text
				}, function (data) {
						self._openSuggestions(e, data);
					});
			}
		},

		openEdit: function (el) {
			var index = el.index() + 1;
			var $addItemWrap = this.$addItemWrap.detach().hide();

			this.$pillGroup.find('.pill:nth-child(' + index + ')').before($addItemWrap);
			this.currentEdit = el.detach();

			$addItemWrap.addClass('editing');
			this.$addItem.val(el.find('span:first').html());
			$addItemWrap.show();
			this.$addItem.focus().select();
		},

		cancelEdit: function (e) {
			var $addItemWrap;
			if (!this.currentEdit) {
				return false;
			}

			this._closeSuggestions();
			if (e) {
				this.$addItemWrap.before(this.currentEdit);
			}

			this.currentEdit = false;

			$addItemWrap = this.$addItemWrap.detach();
			$addItemWrap.removeClass('editing');
			this.$addItem.val('');
			this.$pillGroup.append($addItemWrap);
		},

		//Must match syntax of placeItem so addItem callback is called when an item is edited
		//expecting to receive an array back from the callback containing edited items
		saveEdit: function () {
			var item = arguments[0][0] ? arguments[0][0] : arguments[0];

			this.currentEdit = $(item.el);
			this.currentEdit.data('value', item.value);
			this.currentEdit.find('span:first').html(item.text);

			this.$addItemWrap.hide();
			this.$addItemWrap.before(this.currentEdit);
			this.currentEdit = false;

			this.$addItem.val('');
			this.$addItemWrap.removeClass('editing');
			this.$pillGroup.append(this.$addItemWrap.detach().show());
			this.$element.trigger('edited.fu.pillbox', {
				value: item.value,
				text: item.text
			});
		},

		removeBySelector: function () {
			var selectors = [].slice.call(arguments).slice(0);
			var self = this;

			$.each(selectors, function (i, sel) {
				self.$pillGroup.find(sel).remove();
			});

			this._removePillTrigger({
				method: 'removeBySelector',
				removedSelectors: selectors
			});
		},

		removeByValue: function () {
			var values = [].slice.call(arguments).slice(0);
			var self = this;

			$.each(values, function (i, val) {
				self.$pillGroup.find('> .pill[data-value="' + val + '"]').remove();
			});

			this._removePillTrigger({
				method: 'removeByValue',
				removedValues: values
			});
		},

		removeByText: function () {
			var text = [].slice.call(arguments).slice(0);
			var self = this;

			$.each(text, function (i, text) {
				self.$pillGroup.find('> .pill:contains("' + text + '")').remove();
			});

			this._removePillTrigger({
				method: 'removeByText',
				removedText: text
			});
		},

		truncate: function (enable) {
			var self = this;
			var available, full, i, pills, used;

			this.$element.removeClass('truncate');
			this.$addItemWrap.removeClass('truncated');
			this.$pillGroup.find('.pill').removeClass('truncated');

			if (enable) {
				this.$element.addClass('truncate');

				available = this.$element.width();
				full = false;
				i = 0;
				pills = this.$pillGroup.find('.pill').length;
				used = 0;

				this.$pillGroup.find('.pill').each(function () {
					var pill = $(this);
					if (!full) {
						i++;
						self.$moreCount.text(pills - i);
						if ((used + pill.outerWidth(true) + self.$addItemWrap.outerWidth(true)) <= available) {
							used += pill.outerWidth(true);
						} else {
							self.$moreCount.text((pills - i) + 1);
							pill.addClass('truncated');
							full = true;
						}

					} else {
						pill.addClass('truncated');
					}
				});
				if (i === pills) {
					this.$addItemWrap.addClass('truncated');
				}

			}
		},

		inputFocus: function (e) {
			this.$element.find('.pillbox-add-item').focus();
		},

		getItemData: function (el, data) {
			return $.extend({
				text: el.find('span:first').html()
			}, el.data(), data);
		},

		_removeElement: function (data) {
			data.el.remove();
			delete data.el;
			this.$element.trigger('removed.fu.pillbox', data);
		},

		_removePillTrigger: function (removedBy) {
			this.$element.trigger('removed.fu.pillbox', removedBy);
		},

		_generateObject: function (data) {
			var obj = {};

			$.each(data, function (index, value) {
				obj[value] = true;
			});

			return obj;
		},

		_openSuggestions: function (e, data) {
			var markup = '';
			var $suggestionList = $('<ul>');

			if (this.callbackId !== e.timeStamp) {
				return false;
			}

			if (data.data && data.data.length) {
				$.each(data.data, function (index, value) {
					var val = value.value ? value.value : value.text;

					// markup concatentation is 10x faster, but does not allow data store
					var $suggestion = $('<li data-value="' + val + '">' + value.text + '</li>');

					if (value.attr) {
						$suggestion.data('attr', JSON.stringify(value.attr));
					}

					if (value.data) {
						$suggestion.data('data', value.data);
					}

					$suggestionList.append($suggestion);
				});

				// suggestion dropdown
				this.$suggest.html('').append($suggestionList.children());
				$(document.body).trigger('suggested.fu.pillbox', this.$suggest);
			}
		},

		_closeSuggestions: function () {
			this.$suggest.html('').parent().removeClass('open');
		},

		_isSuggestionsOpen: function () {
			return this.$suggest.parent().hasClass('open');
		},

		_keySuggestions: function (e) {
			var $first = this.$suggest.find('li.pillbox-suggest-sel');
			var dir = e.keyCode === 38;// up arrow
			var $next, val;

			e.preventDefault();

			if (!$first.length) {
				$first = this.$suggest.find('li:first');
				$first.addClass('pillbox-suggest-sel');
			} else {
				$next = dir ? $first.prev() : $first.next();

				if (!$next.length) {
					$next = dir ? this.$suggest.find('li:last') : this.$suggest.find('li:first');
				}

				if ($next) {
					$next.addClass('pillbox-suggest-sel');
					$first.removeClass('pillbox-suggest-sel');
				}

			}
		}
	};

	// PILLBOX PLUGIN DEFINITION

	$.fn.pillbox = function (option) {
		var args = Array.prototype.slice.call(arguments, 1);
		var methodReturn;

		var $set = this.each(function () {
			var $this = $(this);
			var data = $this.data('fu.pillbox');
			var options = typeof option === 'object' && option;

			if (!data) {
				$this.data('fu.pillbox', (data = new Pillbox(this, options)));
			}

			if (typeof option === 'string') {
				methodReturn = data[option].apply(data, args);
			}
		});

		return (methodReturn === undefined) ? $set : methodReturn;
	};

	$.fn.pillbox.defaults = {
		onAdd: undefined,
		onRemove: undefined,
		onKeyDown: undefined,
		edit: false,
		readonly: -1,//can be true or false. -1 means it will check for data-readonly="readonly"
		truncate: false,
		acceptKeyCodes: [
			13,//Enter
			188//Comma
		],
		allowEmptyPills: false

		//example on remove
		/*onRemove: function(data,callback){
			console.log('onRemove');
			callback(data);
		}*/

		//example on key down
		/*onKeyDown: function(event, data, callback ){
			callback({data:[
				{text: Math.random(),value:'sdfsdfsdf'},
				{text: Math.random(),value:'sdfsdfsdf'}
			]});
		}
		*/
		//example onAdd
		/*onAdd: function( data, callback ){
			console.log(data, callback);
			callback(data);
		}*/
	};

	$.fn.pillbox.Constructor = Pillbox;

	$.fn.pillbox.noConflict = function () {
		$.fn.pillbox = old;
		return this;
	};


	// DATA-API

	$(document).on('mousedown.fu.pillbox.data-api', '[data-initialize=pillbox]', function (e) {
		var $control = $(e.target).closest('.pillbox');
		if (!$control.data('fu.pillbox')) {
			$control.pillbox($control.data());
		}
	});

	// Must be domReady for AMD compatibility
	$(function () {
		$('[data-initialize=pillbox]').each(function () {
			var $this = $(this);
			if ($this.data('fu.pillbox')) return;
			$this.pillbox($this.data());
		});
	});

	// -- BEGIN UMD WRAPPER AFTERWORD --
}));
// -- END UMD WRAPPER AFTERWORD --
