/**
 * @file js/classes/features/InfiniteScrollingFeature.js
 *
 * Copyright (c) 2016-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 InfiniteScrollingFeature
 * @ingroup js_classes_features
 *
 * @brief Feature that implements infinite scrolling on grids.
 * It doesn't support category grids.
 */
(function($) {


	/**
	 * @constructor
	 * @inheritDoc
	 * @extends $.pkp.classes.features.GeneralPagingFeature
	 */
	$.pkp.classes.features.InfiniteScrollingFeature =
			function(gridHandler, options) {
		this.parent(gridHandler, options);
	};
	$.pkp.classes.Helper.inherits(
			$.pkp.classes.features.InfiniteScrollingFeature,
			$.pkp.classes.features.GeneralPagingFeature);


	//
	// Private properties
	//
	/**
	 * The scrollable element.
	 * @private
	 * @type {jQueryObject}
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			$scrollableElement_ = $();


	/**
	 * The scrolling observer callback function.
	 * @private
	 * @type {Function}
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			observeScrollCallback_ = function() {};


	//
	// Extended methods from GeneralPagingFeature
	//
	/**
	 * @inheritDoc
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.init =
			function() {
		var $scrollableElement = $('div.scrollable', this.getGridHtmlElement());
		if (!$scrollableElement.length) {
			this.gridHandler.publishEvent('pkpObserveScrolling');
			this.gridHandler.publishEvent('pkpRemoveScrollingObserver');
		}
		this.$scrollableElement_ = $scrollableElement;
		this.observeScrollCallback_ = this.gridHandler.callbackWrapper(
				this.observeScroll_, this);
		this.addScrollHandler_();
		this.fixGridHeight_();
		this.addPagingDataToRows_();
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.addFeatureHtml =
			function($gridElement, options) {
		var castOptions = /** @type {{pagingMarkup: string?,
					loadingContainer: string?}} */ (options);
		$gridElement.append(castOptions.pagingMarkup);
		$gridElement.find('.pkp_linkaction_moreItems')
				.click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));
	};


	//
	// Hooks implementation.
	//
	/**
	 * @inheritDoc
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.refreshGrid =
			function(opt_elementId) {
		var options = this.getOptions(), params, $firstRow, $lastRow, page, $gridRow,
				elementId;

		params = this.gridHandler.getFetchExtraParams();
		params[options.pageParamName] = options.currentPage;

		if (opt_elementId && opt_elementId !==
				$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID) {
			// We need to make sure we pass the right page for the element.
			elementId = (/** @type {number} */ (opt_elementId));
			$gridRow = this.gridHandler.getRowByDataId(elementId);
			if ($gridRow.length == 1) {
				params[options.pageParamName] = Number($gridRow.attr('data-paging'));
			}
		}

		params[options.itemsPerPageParamName] = options.currentItemsPerPage;

		this.setGridParams(params);

		return false;
	};


	/**
	 * @inheritDoc
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			replaceElementResponseHandler = function(handledJsonData) {
		var pagingInfo, options, castJsonData, rowMarkup;
		options = this.getOptions();
		castJsonData = /** @type {{pagingInfo: Object,
				deletedRowReplacement: string}} */
				(handledJsonData);

		if (castJsonData.deletedRowReplacement != undefined) {
			rowMarkup = handledJsonData.deletedRowReplacement;
			this.gridHandler.insertOrReplaceElement(rowMarkup);
			this.updatePagingDataInAllRows_();
		}

		this.addScrollHandler_();

		if (castJsonData.pagingInfo != undefined) {
			pagingInfo = handledJsonData.pagingInfo;
			this.setOptions(pagingInfo);

			if (pagingInfo.pagingMarkup != undefined) {
				$('div.gridPagingScrolling', this.getGridHtmlElement()).
						replaceWith(pagingInfo.pagingMarkup);
			}
		}

		this.addPagingDataToRows_();

		this.toggleLoadingContainer_();

		this.getGridHtmlElement().find('.pkp_linkaction_moreItems').
				click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));

		return false;
	};


	//
	// Private helper methods.
	//
	/**
	 * Scroll handler to detect when it's time to request more rows.
	 *
	 * @private
	 *
	 * @param {HTMLElement} sourceElement
	 * @param {Event} event
	 * @return {boolean}
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.observeScroll_ =
			function(sourceElement, event) {
		var options = this.getOptions(), sourceElementHeight,
				bottomLimit, windowDimensions;
		if (options.itemsTotal == this.gridHandler.getRows().length) {
			return false;
		}

		if (!this.getGridHtmlElement().is(':visible')) {
			return false;
		}

		if ($(sourceElement).hasClass('scrollable')) {
			sourceElementHeight = $(sourceElement).height();
			bottomLimit = sourceElement.scrollHeight;
		} else {
			windowDimensions = $.pkp.controllers.SiteHandler.
					prototype.getWindowDimensions();
			sourceElementHeight = windowDimensions.height;
			bottomLimit = this.getGridHtmlElement().offset().top +
					this.getGridHtmlElement().height();
		}

		if (sourceElementHeight + $(sourceElement).scrollTop() >= bottomLimit) {
			// Avoid multiple rows requests.
			if (this.$scrollableElement_.length) {
				this.$scrollableElement_.unbind('scroll');
			} else {
				this.getGridHtmlElement().trigger('pkpRemoveScrollingObserver',
						[this.observeScrollCallback_]);
			}

			this.loadMoreItems_();
		}

		return false;
	};


	/**
	 * Fix the grid height to acomodate the number of initial visible rows.
	 *
	 * @private
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.fixGridHeight_ =
			function() {
		var $scrollableDivs = $('div.scrollable', this.getGridHtmlElement()),
				index, limit, $div, timer, length;

		if ($scrollableDivs.length > 0) {
			timer = setInterval(function() {
				if ($scrollableDivs.is(':visible')) {
					clearInterval(timer);
					length = $scrollableDivs.length;
					for (index = 0, limit = length; index < limit; index++) {
						$div = $($scrollableDivs[index]);
						if ($div.get(0).scrollHeight > $div.height()) {
							$div.css('max-height', $div.get(0).scrollHeight - 10);
						}
					}
				}
			},300);
		}
	};


	/**
	 * Add paging data to the respective rows.
	 *
	 * @private
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.addPagingDataToRows_ =
			function() {
		var $rows, options = this.getOptions();
		$rows = this.gridHandler.getRows().filter('tr:not([data-paging])');
		$rows.attr('data-paging', options.currentPage);
	};


	/**
	 * Update paging data in all grid rows.
	 *
	 * @private
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			updatePagingDataInAllRows_ = function() {
		var $rows, options = this.getOptions(), index, limit, page = 1,
				itemsCount = 1;
		$rows = this.gridHandler.getRows();
		$rows.removeAttr('data-paging');

		for (index = 0, limit = $rows.length; index < limit; index++) {
			$($rows[index]).attr('data-paging', page);
			itemsCount++;
			if (itemsCount > options.currentItemsPerPage) {
				itemsCount = 1;
				page++;
			}
		}
	};


	/**
	 * Add scroll handler to the grid element.
	 *
	 * @private
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.addScrollHandler_ =
			function() {
		var $scrollableElement = this.$scrollableElement_;
		if ($scrollableElement.length) {
			$scrollableElement.
					scroll(this.observeScrollCallback_);
		} else {
			this.getGridHtmlElement().trigger('pkpObserveScrolling',
					[this.observeScrollCallback_]);
		}
	};


	/**
	 * Toggle the scrolling loading element.
	 *
	 * @private
	 *
	 * @param {boolean=} opt_show
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			toggleLoadingContainer_ = function(opt_show) {
		var $loadingElement =
				this.getGridHtmlElement().find('div.gridPagingScrolling div.pkp_loading'),
						$scrollableElement = this.$scrollableElement_,
						scrollTop,
						loadingHeight = $loadingElement.height(),
						scrollTarget;

		if (opt_show) {
			this.getGridHtmlElement().addClass('loading');
			scrollTop = $scrollableElement.scrollTop();
			scrollTarget = /** @type {number} */ (scrollTop + loadingHeight);
			$scrollableElement.scrollTop(scrollTarget);
		} else {
			this.getGridHtmlElement().removeClass('loading');
		}
	};


	/**
	 * Trigger necessary actions for the grid to
	 * load next page items.
	 *
	 * @private
	 *
	 */
	$.pkp.classes.features.InfiniteScrollingFeature.prototype.
			loadMoreItems_ = function() {
		var options = this.getOptions();

		// Show the loading icon.
		this.toggleLoadingContainer_(true);

		options.currentPage = Number($('tr.gridRow',
				this.getGridHtmlElement()).last().attr('data-paging')) + 1;
		this.getGridHtmlElement().trigger('dataChanged',
				[$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID]);
	};


}(jQuery));
