/**
 * Copyright (c) 2015
 *
 * @author Joas Schilling <coding@schilljs.com>
 * @author John Molakvoæ <skjnldsv@protonmail.com>
 * @author Roeland Jago Douma <roeland@famdouma.nl>
 * @author Vincent Petry <vincent@nextcloud.com>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* eslint-disable */
import templateResult from './templates/result.handlebars'
import templateResultForm from './templates/result_form.handlebars'
import templateSelection from './templates/selection.handlebars'

(function(OC) {

	/**
	 * @class OC.SystemTags.SystemTagsInputField
	 * @classdesc
	 *
	 * Displays a file's system tags
	 *
	 */
	var SystemTagsInputField = OC.Backbone.View.extend(
		/** @lends OC.SystemTags.SystemTagsInputField.prototype */ {

			_rendered: false,

			_newTag: null,

			_lastUsedTags: [],

			className: 'systemTagsInputFieldContainer',

			template: function(data) {
				return '<input class="systemTagsInputField" type="hidden" name="tags" value=""/>'
			},

			/**
		 * Creates a new SystemTagsInputField
		 *
		 * @param {Object} [options]
		 * @param {string} [options.objectType=files] object type for which tags are assigned to
		 * @param {bool} [options.multiple=false] whether to allow selecting multiple tags
		 * @param {bool} [options.allowActions=true] whether tags can be renamed/delete within the dropdown
		 * @param {bool} [options.allowCreate=true] whether new tags can be created
		 * @param {bool} [options.isAdmin=true] whether the user is an administrator
		 * @param {Function} options.initSelection function to convert selection to data
		 */
			initialize: function(options) {
				options = options || {}

				this._multiple = !!options.multiple
				this._allowActions = _.isUndefined(options.allowActions) || !!options.allowActions
				this._allowCreate = _.isUndefined(options.allowCreate) || !!options.allowCreate
				this._isAdmin = !!options.isAdmin

				if (_.isFunction(options.initSelection)) {
					this._initSelection = options.initSelection
				}

				this.collection = options.collection || OC.SystemTags.collection

				var self = this
				this.collection.on('change:name remove', function() {
				// refresh selection
					_.defer(self._refreshSelection)
				})

				_.defer(_.bind(this._getLastUsedTags, this))

				_.bindAll(
					this,
					'_refreshSelection',
					'_onClickRenameTag',
					'_onClickDeleteTag',
					'_onSelectTag',
					'_onDeselectTag',
					'_onSubmitRenameTag'
				)
			},

			_getLastUsedTags: function() {
				var self = this
				$.ajax({
					type: 'GET',
					url: OC.generateUrl('/apps/systemtags/lastused'),
					success: function(response) {
						self._lastUsedTags = response
					}
				})
			},

			/**
		 * Refreshes the selection, triggering a call to
		 * select2's initSelection
		 */
			_refreshSelection: function() {
				this.$tagsField.select2('val', this.$tagsField.val())
			},

			/**
		 * Event handler whenever the user clicked the "rename" action.
		 * This will display the rename field.
		 */
			_onClickRenameTag: function(ev) {
				var $item = $(ev.target).closest('.systemtags-item')
				var tagId = $item.attr('data-id')
				var tagModel = this.collection.get(tagId)

				var oldName = tagModel.get('name')
				var $renameForm = $(templateResultForm({
					cid: this.cid,
					name: oldName,
					deleteTooltip: t('core', 'Delete'),
					renameLabel: t('core', 'Rename'),
					isAdmin: this._isAdmin
				}))
				$item.find('.label').after($renameForm)
				$item.find('.label, .systemtags-actions').addClass('hidden')
				$item.closest('.select2-result').addClass('has-form')

				$renameForm.find('[title]').tooltip({
					placement: 'bottom',
					container: 'body'
				})
				$renameForm.find('input').focus().selectRange(0, oldName.length)
				return false
			},

			/**
		 * Event handler whenever the rename form has been submitted after
		 * the user entered a new tag name.
		 * This will submit the change to the server.
		 *
		 * @param {Object} ev event
		 */
			_onSubmitRenameTag: function(ev) {
				ev.preventDefault()
				var $form = $(ev.target)
				var $item = $form.closest('.systemtags-item')
				var tagId = $item.attr('data-id')
				var tagModel = this.collection.get(tagId)
				var newName = $(ev.target).find('input').val().trim()
				if (newName && newName !== tagModel.get('name')) {
					tagModel.save({ 'name': newName })
					// TODO: spinner, and only change text after finished saving
					$item.find('.label').text(newName)
				}
				$item.find('.label, .systemtags-actions').removeClass('hidden')
				$form.remove()
				$item.closest('.select2-result').removeClass('has-form')
			},

			/**
		 * Event handler whenever a tag must be deleted
		 *
		 * @param {Object} ev event
		 */
			_onClickDeleteTag: function(ev) {
				var $item = $(ev.target).closest('.systemtags-item')
				var tagId = $item.attr('data-id')
				this.collection.get(tagId).destroy()
				$(ev.target).tooltip('hide')
				$item.closest('.select2-result').remove()
				// TODO: spinner
				return false
			},

			_addToSelect2Selection: function(selection) {
				var data = this.$tagsField.select2('data')
				data.push(selection)
				this.$tagsField.select2('data', data)
			},

			/**
		 * Event handler whenever a tag is selected.
		 * Also called whenever tag creation is requested through the dummy tag object.
		 *
		 * @param {Object} e event
		 */
			_onSelectTag: function(e) {
				var self = this
				var tag
				if (e.object && e.object.isNew) {
				// newly created tag, check if existing
				// create a new tag
					tag = this.collection.create({
						name: e.object.name.trim(),
						userVisible: true,
						userAssignable: true,
						canAssign: true
					}, {
						success: function(model) {
							self._addToSelect2Selection(model.toJSON())
							self._lastUsedTags.unshift(model.id)
							self.trigger('select', model)
						},
						error: function(model, xhr) {
							if (xhr.status === 409) {
							// re-fetch collection to get the missing tag
								self.collection.reset()
								self.collection.fetch({
									success: function(collection) {
									// find the tag in the collection
										var model = collection.where({
											name: e.object.name.trim(),
											userVisible: true,
											userAssignable: true
										})
										if (model.length) {
											model = model[0]
											// the tag already exists or was already assigned,
											// add it to the list anyway
											self._addToSelect2Selection(model.toJSON())
											self.trigger('select', model)
										}
									}
								})
							}
						}
					})
					this.$tagsField.select2('close')
					e.preventDefault()
					return false
				} else {
					tag = this.collection.get(e.object.id)
					this._lastUsedTags.unshift(tag.id)
				}
				this._newTag = null
				this.trigger('select', tag)
			},

			/**
		 * Event handler whenever a tag gets deselected.
		 *
		 * @param {Object} e event
		 */
			_onDeselectTag: function(e) {
				this.trigger('deselect', e.choice.id)
			},

			/**
		 * Autocomplete function for dropdown results
		 *
		 * @param {Object} query select2 query object
		 */
			_queryTagsAutocomplete: function(query) {
				var self = this
				this.collection.fetch({
					success: function(collection) {
						var tagModels = collection.filterByName(query.term.trim())
						if (!self._isAdmin) {
							tagModels = _.filter(tagModels, function(tagModel) {
								return tagModel.get('canAssign')
							})
						}
						query.callback({
							results: _.invoke(tagModels, 'toJSON')
						})
					}
				})
			},

			_preventDefault: function(e) {
				e.stopPropagation()
			},

			/**
		 * Formats a single dropdown result
		 *
		 * @param {Object} data data to format
		 * @returns {string} HTML markup
		 */
			_formatDropDownResult: function(data) {
				return templateResult(_.extend({
					renameTooltip: t('core', 'Rename'),
					allowActions: this._allowActions,
					tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data)[0].innerHTML : null,
					isAdmin: this._isAdmin
				}, data))
			},

			/**
		 * Formats a single selection item
		 *
		 * @param {Object} data data to format
		 * @returns {string} HTML markup
		 */
			_formatSelection: function(data) {
				return templateSelection(_.extend({
					tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data)[0].innerHTML : null,
					isAdmin: this._isAdmin
				}, data))
			},

			/**
		 * Create new dummy choice for select2 when the user
		 * types an arbitrary string
		 *
		 * @param {string} term entered term
		 * @returns {Object} dummy tag
		 */
			_createSearchChoice: function(term) {
				term = term.trim()
				if (this.collection.filter(function(entry) {
					return entry.get('name') === term
				}).length) {
					return
				}
				if (!this._newTag) {
					this._newTag = {
						id: -1,
						name: term,
						userAssignable: true,
						userVisible: true,
						canAssign: true,
						isNew: true
					}
				} else {
					this._newTag.name = term
				}

				return this._newTag
			},

			_initSelection: function(element, callback) {
				var self = this
				var ids = $(element).val().split(',')

				function modelToSelection(model) {
					var data = model.toJSON()
					if (!self._isAdmin && !data.canAssign) {
					// lock static tags for non-admins
						data.locked = true
					}
					return data
				}

				function findSelectedObjects(ids) {
					var selectedModels = self.collection.filter(function(model) {
						return ids.indexOf(model.id) >= 0 && (self._isAdmin || model.get('userVisible'))
					})
					return _.map(selectedModels, modelToSelection)
				}

				this.collection.fetch({
					success: function() {
						callback(findSelectedObjects(ids))
					}
				})
			},

			/**
		 * Renders this details view
		 */
			render: function() {
				var self = this
				this.$el.html(this.template())

				this.$el.find('[title]').tooltip({ placement: 'bottom' })
				this.$tagsField = this.$el.find('[name=tags]')
				this.$tagsField.select2({
					placeholder: t('core', 'Collaborative tags'),
					containerCssClass: 'systemtags-select2-container',
					dropdownCssClass: 'systemtags-select2-dropdown',
					closeOnSelect: false,
					allowClear: false,
					multiple: this._multiple,
					toggleSelect: this._multiple,
					query: _.bind(this._queryTagsAutocomplete, this),
					id: function(tag) {
						return tag.id
					},
					initSelection: _.bind(this._initSelection, this),
					formatResult: _.bind(this._formatDropDownResult, this),
					formatSelection: _.bind(this._formatSelection, this),
					createSearchChoice: this._allowCreate ? _.bind(this._createSearchChoice, this) : undefined,
					sortResults: function(results) {
						var selectedItems = _.pluck(self.$tagsField.select2('data'), 'id')
						results.sort(function(a, b) {
							var aSelected = selectedItems.indexOf(a.id) >= 0
							var bSelected = selectedItems.indexOf(b.id) >= 0
							if (aSelected === bSelected) {
								var aLastUsed = self._lastUsedTags.indexOf(a.id)
								var bLastUsed = self._lastUsedTags.indexOf(b.id)

								if (aLastUsed !== bLastUsed) {
									if (bLastUsed === -1) {
										return -1
									}
									if (aLastUsed === -1) {
										return 1
									}
									return aLastUsed < bLastUsed ? -1 : 1
								}

								// Both not found
								return OC.Util.naturalSortCompare(a.name, b.name)
							}
							if (aSelected && !bSelected) {
								return -1
							}
							return 1
						})
						return results
					},
					formatNoMatches: function() {
						return t('core', 'No tags found')
					}
				})
					.on('select2-selecting', this._onSelectTag)
					.on('select2-removing', this._onDeselectTag)

				var $dropDown = this.$tagsField.select2('dropdown')
				// register events for inside the dropdown
				$dropDown.on('mouseup', '.rename', this._onClickRenameTag)
				$dropDown.on('mouseup', '.delete', this._onClickDeleteTag)
				$dropDown.on('mouseup', '.select2-result-selectable.has-form', this._preventDefault)
				$dropDown.on('submit', '.systemtags-rename-form', this._onSubmitRenameTag)

				this.delegateEvents()
			},

			remove: function() {
				if (this.$tagsField) {
					this.$tagsField.select2('destroy')
				}
			},

			getValues: function() {
				this.$tagsField.select2('val')
			},

			setValues: function(values) {
				this.$tagsField.select2('val', values)
			},

			setData: function(data) {
				this.$tagsField.select2('data', data)
			}
		})

	OC.SystemTags = OC.SystemTags || {}
	OC.SystemTags.SystemTagsInputField = SystemTagsInputField

})(OC)
