/**
 * @file js/controllers/grid/CategoryGridHandler.js
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class CategoryGridHandler
 * @ingroup js_controllers_grid
 *
 * @brief Category grid handler.
 */
(function($) {


	/**
	 * @constructor
	 *
	 * @extends $.pkp.controllers.grid.GridHandler
	 *
	 * @param {jQueryObject} $grid The grid this handler is
	 *  attached to.
	 * @param {Object} options Grid handler configuration.
	 */
	$.pkp.controllers.grid.CategoryGridHandler = function($grid, options) {
		this.parent($grid, options);
	};
	$.pkp.classes.Helper.inherits($.pkp.controllers.grid.CategoryGridHandler,
			$.pkp.controllers.grid.GridHandler);


	//
	// Public methods.
	//
	/**
	 * Get category id prefix.
	 * @return {string} Category id prefix.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryIdPrefix =
			function() {
		return this.getGridIdPrefix() + '-category-';
	};


	/**
	 * Get categories tbody element.
	 * @return {jQueryObject} Categories tbody elements.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategories =
			function() {
		return $('.category_grid_body:not(.empty)',
				this.getHtmlElement());
	};


	/**
	 * Get a category tbody element by category data id.
	 * @param {string} categoryDataId The category data id.
	 * @return {jQueryObject} Category tbody element.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryByDataId =
			function(categoryDataId) {
		return $('#' + this.getCategoryIdPrefix() + categoryDataId);
	};


	/**
	 * Get the category row inside a tbody category element. If none element
	 * is passed, get all grid category rows.
	 * @param {jQueryObject} $opt_category Category tbody element.
	 * @return {jQueryObject} Category rows.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryRow =
			function($opt_category) {
		var $context = this.getHtmlElement();
		if ($opt_category !== undefined) {
			$context = $opt_category;
		}

		return $('tr.category', $context);
	};


	/**
	 * Get rows inside a tbody category element, excluding the category row.
	 * @param {jQueryObject} $category Category tbody element.
	 * @return {jQueryObject} Category rows.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowsInCategory =
			function($category) {
		return $('tr.gridRow', $category).not('.category');
	};


	/**
	 * Get the category empty placeholder.
	 * @param {jQueryObject} $category A grid category element.
	 * @return {jQueryObject} The category empty placeholder.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.
			getCategoryEmptyPlaceholder = function($category) {
		var selector = '#' + $category.attr('id') + '-emptyPlaceholder';
		return $(selector, this.getHtmlElement());
	};


	/**
	 * Get the category data id by the passed category element.
	 * @param {jQueryObject} $category Category element.
	 * @return {string} Category data id.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryDataId =
			function($category) {
		var categoryId = $category.attr('id'),
				startExtractPosition = this.getCategoryIdPrefix().length;

		return /** @type {string} */ (categoryId.slice(startExtractPosition));
	};


	/**
	 * Get the category data id by the passed row element id.
	 * @param {string} gridRowId Category row element id.
	 * @return {string} Category data id.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryDataIdByRowId =
			function(gridRowId) {
		// Remove the category id prefix to avoid getting wrong data.
		gridRowId = gridRowId.replace(this.getCategoryIdPrefix(), ' ');

		// Get the category data id.
		var categoryDataId = gridRowId.match('(.*)-row');
		return $.trim(categoryDataId[1]);
	};


	/**
         * Get the id prefix of the grid row inside a category.
         * @return {string}
         */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowIdPrefix =
			function() {
		return this.getGridIdPrefix() + '-category-';
	};


	/**
	 * Get the grid row by the passed data element id.
	 * @param {number} rowDataId
	 * @return {jQueryObject}
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowByDataId =
			function(rowDataId) {
		this.parent('getRowByDataId', rowDataId);
		return $('#' + this.getRowIdPrefix() + this.currentCategoryId_ +
				'-row-' + rowDataId, this.getHtmlElement());
	};


	/**
	 * Get the data element id of the passed grid row.
	 * @param {jQueryObject} $gridRow The grid row JQuery object.
	 * @return {string} The data element id of the passed grid row.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowDataId =
			function($gridRow) {
		var rowDataId;
		rowDataId = $gridRow.attr('id').
				slice(this.getRowIdPrefix().length);
		rowDataId = rowDataId.match('-row-(.*)');
		return /** @type {string} */ ($.trim(rowDataId[1]));
	};


	/**
	 * Append a category to the end of the list.
	 * @param {jQueryObject} $category Category to append.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.appendCategory =
			function($category) {
		var $gridBody = this.getHtmlElement().find(this.bodySelector);
		$gridBody.append($category);
	};


	/**
	 * Re-sequence all category elements based on the passed sequence map.
	 * @param {Array} sequenceMap A sequence array with the category
	 * element id as value.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.resequenceCategories =
			function(sequenceMap) {
		var categoryId, index, $category;
		for (index in sequenceMap) {
			categoryId = sequenceMap[index];
			$category = $('#' + categoryId);
			this.appendCategory($category);
		}

		this.updateEmptyPlaceholderPosition();
	};


	/**
	 * Move all empty category placeholders to their correct position,
	 * below of each correspondent category element.
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.
			updateEmptyPlaceholderPosition = function() {
		var $categories = this.getCategories(),
				index, limit,
				$category, $emptyPlaceholder;
		for (index = 0, limit = $categories.length; index < limit; index++) {
			$category = $($categories[index]);
			$emptyPlaceholder = this.getCategoryEmptyPlaceholder($category);
			if ($emptyPlaceholder.length > 0) {
				$emptyPlaceholder.insertAfter($category);
			}
		}
	};


	//
	// Extended methods from GridHandler
	//
	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.initialize =
			function(options) {
		// Save the URL to fetch a whole category.
		this.fetchCategoryUrl_ = options.fetchCategoryUrl;

		this.parent('initialize', options);
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getElementsByType =
			function($element) {
		if ($element.hasClass('category_grid_body')) {
			return this.getCategories();
		} else {
			return /** @type {jQueryObject} */ (
					this.parent('getElementsByType', $element));
		}
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.getEmptyElement =
			function($element) {
		if ($element.hasClass('category_grid_body')) {
			// Return the grid empty element placeholder.
			return this.getHtmlElement().find('.empty').not('.category_placeholder');
		} else {
			return /** @type {jQueryObject} */ (
					this.parent('getEmptyElement', $element));
		}
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.refreshGridHandler =
			function(sourceElement, event, opt_elementId) {

		var fetchedAlready = false, elementIds,
				// Hack to avoid closure compiler warnings on type difference
				castElementId = /** @type {{parentElementId: number}} */ (opt_elementId);

		if (opt_elementId !== undefined) {
			// Check if we want to refresh a row inside a category.
			if (castElementId.parentElementId !== undefined) {
				elementIds = {rowId: opt_elementId[0],
					rowCategoryId: castElementId.parentElementId};

				// Store the category id.
				this.currentCategoryId_ = castElementId.parentElementId;

				// Retrieve a single row from the server.
				$.get(this.fetchRowUrl, elementIds,
						this.callbackWrapper(
								this.replaceElementResponseHandler), 'json');
			} else {
				// Retrieve the entire category from the server.
				$.get(this.fetchCategoryUrl_, {rowId: opt_elementId},
						this.callbackWrapper(
								this.replaceElementResponseHandler), 'json');
			}
			fetchedAlready = true;
		}

		this.parent('refreshGridHandler', sourceElement,
				event, opt_elementId, fetchedAlready);
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.deleteElement =
			function($element) {

		var $gridBody, index, limit, $parent, $emptyPlaceholder;

		if ($element.length > 1) {
			// Category and category row have the same element data id,
			// handle this case.
			if ($element.length == 2 &&
					$element.hasClass('category_grid_body') &&
					$element.hasClass('category')) {
				// Always delete the entire category.
				$element = $element.filter('.category_grid_body');
			}

			// Sometimes grid rows inside different categories may have
			// the same id. Try to find the correct one to delete.
			if (this.currentCategoryId_) {
				$gridBody = this.getCategoryByDataId(this.currentCategoryId_);
				for (index = 0, limit = $element.length; index < limit; index++) {
					$parent = $($element[index]).
							parents('#' + $gridBody.attr('id'));
					if ($parent.length === 1) {
						$element = $($element[index]);
						break;
					}
				}
			}
		}

		if ($element.hasClass('category_grid_body')) {
			// Need to delete the category empty placeholder.
			$emptyPlaceholder = this.getCategoryEmptyPlaceholder($element);
			this.unbindPartial($emptyPlaceholder);
			$emptyPlaceholder.remove();
		}

		this.parent('deleteElement', $element);
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.addElement =
			function($element) {
		var $gridBody = null, categoryDataId, $emptyPlaceholder;

		if ($element.hasClass('gridRow')) {
			// New row must be inside a category.
			categoryDataId = /** @type {string} */ (
					this.getCategoryDataIdByRowId(
					/** @type {string} */ ($element.attr('id'))));
			$gridBody = /** @type {jQueryObject} */ (
					this.getCategoryByDataId(categoryDataId));
		}

		// Add the element.
		this.parent('addElement', $element, $gridBody);

		// Make sure the placeholder is the last grid element.
		if ($element.hasClass('category_grid_body')) {
			$emptyPlaceholder = this.getEmptyElement($element);
			this.getHtmlElement().find(this.bodySelector).append($emptyPlaceholder);
		}
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.replaceElement =
			function($existingElement, $newElement) {

		if ($newElement.hasClass('category_grid_body')) {
			// Need to delete the category empty placeholder.
			var $emptyPlaceholder = this.getCategoryEmptyPlaceholder($existingElement);
			this.unbindPartial($emptyPlaceholder);
			$emptyPlaceholder.remove();
		}

		this.parent('replaceElement', $existingElement, $newElement);

	};


	/**
	 * @inheritDoc
	 */
	$.pkp.controllers.grid.CategoryGridHandler.prototype.hasSameNumOfColumns =
			function($row) {
		var $element = $row,
				checkColSpan = false;

		if ($row.hasClass('category_grid_body')) {
			$element = $row.find('tr');
			checkColSpan = true;
		}

		return /** @type {boolean} */ (
				this.parent('hasSameNumOfColumns', $element, checkColSpan));
	};


}(jQuery));
