/*
 * Copyright (c) 2014
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

/* global getURLParameter */
/**
 * Utility class for file related operations
 */
(function() {
	var Files = {
		// file space size sync
		_updateStorageStatistics: function(currentDir) {
			var state = Files.updateStorageStatistics;
			if (state.dir){
				if (state.dir === currentDir) {
					return;
				}
				// cancel previous call, as it was for another dir
				state.call.abort();
			}
			state.dir = currentDir;
			state.call = $.getJSON(OC.generateUrl('apps/files/api/v1/stats?dir={dir}', {
				dir: currentDir,
			}), function(response) {
				state.dir = null;
				state.call = null;
				Files.updateMaxUploadFilesize(response);
			});
		},
		// update quota
		updateStorageQuotas: function() {
			Files._updateStorageQuotasThrottled();
		},
		_updateStorageQuotas: function() {
			var state = Files.updateStorageQuotas;
			state.call = $.getJSON(OC.generateUrl('apps/files/api/v1/stats'), function(response) {
				Files.updateQuota(response);
			});
		},
		/**
		 * Update storage statistics such as free space, max upload,
		 * etc based on the given directory.
		 *
		 * Note this function is debounced to avoid making too
		 * many ajax calls in a row.
		 *
		 * @param dir directory
		 * @param force whether to force retrieving
		 */
		updateStorageStatistics: function(dir, force) {
			if (!OC.currentUser) {
				return;
			}

			if (force) {
				Files._updateStorageStatistics(dir);
			}
			else {
				Files._updateStorageStatisticsDebounced(dir);
			}
		},

		updateMaxUploadFilesize:function(response) {
			if (response === undefined) {
				return;
			}
			if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
				$('#free_space').val(response.data.freeSpace);
				$('#upload.button').attr('title', response.data.maxHumanFilesize);
				$('#usedSpacePercent').val(response.data.usedSpacePercent);
				$('#usedSpacePercent').data('mount-type', response.data.mountType);
				$('#usedSpacePercent').data('mount-point', response.data.mountPoint);
				$('#owner').val(response.data.owner);
				$('#ownerDisplayName').val(response.data.ownerDisplayName);
				Files.displayStorageWarnings();
				OCA.Files.App.fileList._updateDirectoryPermissions();
			}
			if (response[0] === undefined) {
				return;
			}
			if (response[0].uploadMaxFilesize !== undefined) {
				$('#upload.button').attr('title', response[0].maxHumanFilesize);
				$('#usedSpacePercent').val(response[0].usedSpacePercent);
				Files.displayStorageWarnings();
			}

		},

		updateQuota:function(response) {
			if (response === undefined) {
				return;
			}
			if (response.data !== undefined
			 && response.data.quota !== undefined
			 && response.data.total !== undefined
			 && response.data.used !== undefined
			 && response.data.usedSpacePercent !== undefined) {
				var humanUsed = OC.Util.humanFileSize(response.data.used, true);
				var humanTotal = OC.Util.humanFileSize(response.data.total, true);
				if (response.data.quota > 0) {
					$('#quota').attr('title', t('files', '{used}%', {used: Math.round(response.data.usedSpacePercent)}));
					$('#quota progress').val(response.data.usedSpacePercent);
					$('#quotatext').html(t('files', '{used} of {quota} used', {used: humanUsed, quota: humanTotal}));
				} else {
					$('#quotatext').html(t('files', '{used} used', {used: humanUsed}));
				}
				if (response.data.usedSpacePercent > 80) {
					$('#quota progress').addClass('warn');
				} else {
					$('#quota progress').removeClass('warn');
				}
			}

		},

		/**
		 * Fix path name by removing double slash at the beginning, if any
		 */
		fixPath: function(fileName) {
			if (fileName.substr(0, 2) == '//') {
				return fileName.substr(1);
			}
			return fileName;
		},

		/**
		 * Checks whether the given file name is valid.
		 * @param name file name to check
		 * @return true if the file name is valid.
		 * Throws a string exception with an error message if
		 * the file name is not valid
		 *
		 * NOTE: This function is duplicated in the filepicker inside core/src/OC/dialogs.js
		 */
		isFileNameValid: function (name) {
			var trimmedName = name.trim();
			if (trimmedName === '.' || trimmedName === '..')
			{
				throw t('files', '"{name}" is an invalid file name.', {name: name});
			} else if (trimmedName.length === 0) {
				throw t('files', 'File name cannot be empty.');
			} else if (trimmedName.indexOf('/') !== -1) {
				throw t('files', '"/" is not allowed inside a file name.');
			} else if (!!(trimmedName.match(OC.config.blacklist_files_regex))) {
				throw t('files', '"{name}" is not an allowed filetype', {name: name});
			}

			return true;
		},
		displayStorageWarnings: function() {
			if (!OC.Notification.isHidden()) {
				return;
			}

			var usedSpacePercent = $('#usedSpacePercent').val(),
				owner = $('#owner').val(),
				ownerDisplayName = $('#ownerDisplayName').val(),
				mountType = $('#usedSpacePercent').data('mount-type'),
				mountPoint = $('#usedSpacePercent').data('mount-point');
			if (usedSpacePercent > 98) {
				if (owner !== OC.getCurrentUser().uid) {
					OC.Notification.show(t('files', 'Storage of {owner} is full, files cannot be updated or synced anymore!',
						{owner: ownerDisplayName}), {type: 'error'}
					);
				} else if (mountType === 'group') {
					OC.Notification.show(t('files',
						'Group folder "{mountPoint}" is full, files cannot be updated or synced anymore!',
						{mountPoint: mountPoint}),
						{type: 'error'}
					);
				} else if (mountType === 'external') {
					OC.Notification.show(t('files',
						'External storage "{mountPoint}" is full, files cannot be updated or synced anymore!',
						{mountPoint: mountPoint}),
						{type : 'error'}
					);
				} else {
					OC.Notification.show(t('files',
						'Your storage is full, files cannot be updated or synced anymore!'),
						{type: 'error'}
					);
				}
			} else if (usedSpacePercent > 90) {
				if (owner !== OC.getCurrentUser().uid) {
					OC.Notification.show(t('files', 'Storage of {owner} is almost full ({usedSpacePercent}%).',
						{
							usedSpacePercent: usedSpacePercent,
							owner: ownerDisplayName
						}),
						{
							type: 'error'
						}
					);
				} else if (mountType === 'group') {
					OC.Notification.show(t('files',
						'Group folder "{mountPoint}" is almost full ({usedSpacePercent}%).',
						{mountPoint: mountPoint, usedSpacePercent: usedSpacePercent}),
						{type : 'error'}
					);
				} else if (mountType === 'external') {
					OC.Notification.show(t('files',
						'External storage "{mountPoint}" is almost full ({usedSpacePercent}%).',
						{mountPoint: mountPoint, usedSpacePercent: usedSpacePercent}),
						{type : 'error'}
					);
				} else {
					OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%).',
						{usedSpacePercent: usedSpacePercent}),
						{type : 'error'}
					);
				}
			}
		},

		/**
		 * Returns the download URL of the given file(s)
		 * @param {string} filename string or array of file names to download
		 * @param {string} [dir] optional directory in which the file name is, defaults to the current directory
		 * @param {boolean} [isDir=false] whether the given filename is a directory and might need a special URL
		 */
		getDownloadUrl: function(filename, dir, isDir) {
			if (!_.isArray(filename) && !isDir) {
				var pathSections = dir.split('/');
				pathSections.push(filename);
				var encodedPath = '';
				_.each(pathSections, function(section) {
					if (section !== '') {
						encodedPath += '/' + encodeURIComponent(section);
					}
				});
				return OC.linkToRemoteBase('webdav') + encodedPath;
			}

			if (_.isArray(filename)) {
				filename = JSON.stringify(filename);
			}

			var params = {
				dir: dir,
				files: filename
			};
			return this.getAjaxUrl('download', params);
		},

		/**
		 * Returns the ajax URL for a given action
		 * @param action action string
		 * @param params optional params map
		 */
		getAjaxUrl: function(action, params) {
			var q = '';
			if (params) {
				q = '?' + OC.buildQueryString(params);
			}
			return OC.filePath('files', 'ajax', action + '.php') + q;
		},

		/**
		 * Fetch the icon url for the mimetype
		 * @param {string} mime The mimetype
		 * @param {Files~mimeicon} ready Function to call when mimetype is retrieved
		 * @deprecated use OC.MimeType.getIconUrl(mime)
		 */
		getMimeIcon: function(mime, ready) {
			ready(OC.MimeType.getIconUrl(mime));
		},

		/**
		 * Generates a preview URL based on the URL space.
		 * @param urlSpec attributes for the URL
		 * @param {number} urlSpec.x width
		 * @param {number} urlSpec.y height
		 * @param {String} urlSpec.file path to the file
		 * @return preview URL
		 * @deprecated used OCA.Files.FileList.generatePreviewUrl instead
		 */
		generatePreviewUrl: function(urlSpec) {
			OC.debug && console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance');
			return OCA.Files.App.fileList.generatePreviewUrl(urlSpec);
		},

		/**
		 * Lazy load preview
		 * @deprecated used OCA.Files.FileList.lazyLoadPreview instead
		 */
		lazyLoadPreview : function(path, mime, ready, width, height, etag) {
			OC.debug && console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
			return FileList.lazyLoadPreview({
				path: path,
				mime: mime,
				callback: ready,
				width: width,
				height: height,
				etag: etag
			});
		},

		/**
		 * Initialize the files view
		 */
		initialize: function() {
			Files.bindKeyboardShortcuts(document, $);

			// drag&drop support using jquery.fileupload
			// TODO use OC.dialogs
			$(document).bind('drop dragover', function (e) {
					e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
				});

			// display storage warnings
			setTimeout(Files.displayStorageWarnings, 100);

			// only possible at the moment if user is logged in or the files app is loaded
			if (OC.currentUser && OCA.Files.App && OC.config.session_keepalive) {
				// start on load - we ask the server every 5 minutes
				var func = _.bind(OCA.Files.App.fileList.updateStorageStatistics, OCA.Files.App.fileList);
				var updateStorageStatisticsInterval = 5*60*1000;
				var updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);

				// TODO: this should also stop when switching to another view
				// Use jquery-visibility to de-/re-activate file stats sync
				if ($.support.pageVisibility) {
					$(document).on({
						'show': function() {
							if (!updateStorageStatisticsIntervalId) {
								updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
							}
						},
						'hide': function() {
							clearInterval(updateStorageStatisticsIntervalId);
							updateStorageStatisticsIntervalId = 0;
						}
					});
				}
			}


			$('#webdavurl').on('click touchstart', function () {
				this.focus();
				this.setSelectionRange(0, this.value.length);
			});

			//FIXME scroll to and highlight preselected file
			/*
			if (getURLParameter('scrollto')) {
				FileList.scrollTo(getURLParameter('scrollto'));
			}
			*/
		},

		/**
		 * Handles the download and calls the callback function once the download has started
		 * - browser sends download request and adds parameter with a token
		 * - server notices this token and adds a set cookie to the download response
		 * - browser now adds this cookie for the domain
		 * - JS periodically checks for this cookie and then knows when the download has started to call the callback
		 *
		 * @param {string} url download URL
		 * @param {Function} callback function to call once the download has started
		 */
		handleDownload: function(url, callback) {
			var randomToken = Math.random().toString(36).substring(2),
				checkForDownloadCookie = function() {
					if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){
						return false;
					} else {
						callback();
						return true;
					}
				};

			if (url.indexOf('?') >= 0) {
				url += '&';
			} else {
				url += '?';
			}
			OC.redirect(url + 'downloadStartSecret=' + randomToken);
			OC.Util.waitFor(checkForDownloadCookie, 500);
		}
	};

	Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250);
	Files._updateStorageQuotasThrottled = _.throttle(Files._updateStorageQuotas, 30000);
	OCA.Files.Files = Files;
})();

// TODO: move to FileList
var createDragShadow = function(event) {
	// FIXME: inject file list instance somehow
	/* global FileList, Files */

	//select dragged file
	var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
	if (!isDragSelected) {
		//select dragged file
		FileList._selectFileEl($(event.target).parents('tr:first'), true, false);
	}

	// do not show drag shadow for too many files
	var selectedFiles = _.first(FileList.getSelectedFiles(), FileList.pageSize());
	selectedFiles = _.sortBy(selectedFiles, FileList._fileInfoCompare);

	if (!isDragSelected && selectedFiles.length === 1) {
		//revert the selection
		FileList._selectFileEl($(event.target).parents('tr:first'), false, false);
	}

	// build dragshadow
	var dragshadow = $('<table class="dragshadow"></table>');
	var tbody = $('<tbody></tbody>');
	dragshadow.append(tbody);

	var dir = FileList.getCurrentDirectory();

	$(selectedFiles).each(function(i,elem) {
		// TODO: refactor this with the table row creation code
		var newtr = $('<tr></tr>')
			.attr('data-dir', dir)
			.attr('data-file', elem.name)
			.attr('data-origin', elem.origin);
		newtr.append($('<td class="filename"></td>').text(elem.name).css('background-size', 32));
		newtr.append($('<td class="size"></td>').text(OC.Util.humanFileSize(elem.size)));
		tbody.append(newtr);
		if (elem.type === 'dir') {
			newtr.find('td.filename')
				.css('background-image', 'url(' + OC.MimeType.getIconUrl('folder') + ')');
		} else {
			var path = dir + '/' + elem.name;
			Files.lazyLoadPreview(path, elem.mimetype, function(previewpath) {
				newtr.find('td.filename')
					.css('background-image', 'url(' + previewpath + ')');
			}, null, null, elem.etag);
		}
	});

	return dragshadow;
};

//options for file drag/drop
//start&stop handlers needs some cleaning up
// TODO: move to FileList class
var dragOptions={
	revert: 'invalid',
	revertDuration: 300,
	opacity: 0.7,
	cursorAt: { left: 24, top: 18 },
	helper: createDragShadow,
	cursor: 'move',

	start: function(event, ui){
		var $selectedFiles = $('td.filename input:checkbox:checked');
		if (!$selectedFiles.length) {
			$selectedFiles = $(this);
		}
		$selectedFiles.closest('tr').addClass('animate-opacity dragging');
		$selectedFiles.closest('tr').filter('.ui-droppable').droppable( 'disable' );
		// Show breadcrumbs menu
		$('.crumbmenu').addClass('canDropChildren');

	},
	stop: function(event, ui) {
		var $selectedFiles = $('td.filename input:checkbox:checked');
		if (!$selectedFiles.length) {
			$selectedFiles = $(this);
		}

		var $tr = $selectedFiles.closest('tr');
		$tr.removeClass('dragging');
		$tr.filter('.ui-droppable').droppable( 'enable' );

		setTimeout(function() {
			$tr.removeClass('animate-opacity');
		}, 300);
		// Hide breadcrumbs menu
		$('.crumbmenu').removeClass('canDropChildren');
	},
	drag: function(event, ui) {
		// Prevent scrolling when hovering .files-controls
		if ($(event.originalEvent.target).parents('.files-controls').length > 0) {
			return
		}

		/** @type {JQuery<HTMLDivElement>} */
		const scrollingArea = FileList.$container;

		// Get the top and bottom scroll trigger y positions
		const containerHeight = scrollingArea.innerHeight() ?? 0
		const scrollTriggerArea = Math.min(Math.floor(containerHeight / 2), 100);
		const bottomTriggerY = containerHeight - scrollTriggerArea;
		const topTriggerY = scrollTriggerArea;

		// Get the cursor position relative to the container
		const containerOffset = scrollingArea.offset() ?? {left: 0, top: 0}
		const cursorPositionY = event.pageY - containerOffset.top

		const currentScrollTop = scrollingArea.scrollTop() ?? 0

		if (cursorPositionY < topTriggerY) {
			scrollingArea.scrollTop(currentScrollTop - 10)
		} else if (cursorPositionY > bottomTriggerY) {
			scrollingArea.scrollTop(currentScrollTop + 10)
		}
	}
};
// sane browsers support using the distance option
if ( $('html.ie').length === 0) {
	dragOptions['distance'] = 20;
}

// TODO: move to FileList class
var folderDropOptions = {
	hoverClass: "canDrop",
	drop: function( event, ui ) {
		// don't allow moving a file into a selected folder
		/* global FileList */
		if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
			return false;
		}

		var $tr = $(this).closest('tr');
		if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
			FileList._showPermissionDeniedNotification();
			return false;
		}
		var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');

		var files = FileList.getSelectedFiles();
		if (files.length === 0) {
			// single one selected without checkbox?
			files = _.map(ui.helper.find('tr'), function(el) {
				return FileList.elementToFile($(el));
			});
		}

		FileList.move(_.pluck(files, 'name'), targetPath);
	},
	tolerance: 'pointer'
};

// for backward compatibility
window.Files = OCA.Files.Files;
