/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

/**
 * Module: TYPO3/CMS/Backend/DragUploader
 *
 */
define(['jquery',
		'moment',
		'nprogress',
		'TYPO3/CMS/Backend/Modal',
		'TYPO3/CMS/Backend/Notification',
		'TYPO3/CMS/Backend/Severity',
		'TYPO3/CMS/Lang/Lang'
	   ], function($, moment, NProgress, Modal, Notification, Severity) {

	/**
	 * Array of files which are asked for being overridden
	 *
	 * @type {array}
	 */
	var askForOverride = [],
		percentagePerFile = 1;

	/**
	 * File actions
	 */
	var actions = {
		OVERRIDE: 'replace',
		RENAME: 'rename',
		SKIP: 'cancel',
		USE_EXISTING: 'useExisting'
	};

	/*
	 * part 1: a generic jQuery plugin "$.dragUploader"
	 */

	// register the constructor
	/**
	 *
	 * @param {HTMLElement} element
	 * @constructor
	 * @exports TYPO3/CMS/Backend/DragUploader
	 */
	var DragUploaderPlugin = function(element) {
		var me = this;
		me.$body = $('body');
		me.$element = $(element);
		me.$trigger = $(me.$element.data('dropzone-trigger'));
		me.$dropzone = $('<div />').addClass('dropzone').hide();
		me.irreObjectUid = me.$element.data('file-irre-object');
		if (me.irreObjectUid && me.$element.nextAll(me.$element.data('dropzone-target')).length !== 0) {
			me.dropZoneInsertBefore = true;
			me.$dropzone.insertBefore(me.$element.data('dropzone-target'));
		} else {
			me.dropZoneInsertBefore = false;
			me.$dropzone.insertAfter(me.$element.data('dropzone-target'));
		}
		me.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(me.$dropzone);
		me.$fileInput = $('<input type="file" multiple name="files[]" />').addClass('upload-file-picker').appendTo(me.$body);
		me.$fileList = $(me.$element.data('progress-container'));
		me.fileListColumnCount = $('thead tr:first th', me.$fileList).length;
		me.filesExtensionsAllowed = me.$element.data('file-allowed');
		me.fileDenyPattern = me.$element.data('file-deny-pattern') ? new RegExp(me.$element.data('file-deny-pattern'), 'i') : false;
		me.maxFileSize = parseInt(me.$element.data('max-file-size'));
		me.target = me.$element.data('target-folder');

		me.browserCapabilities = {
			fileReader: typeof FileReader !== 'undefined',
			DnD: 'draggable' in document.createElement('span'),
			FormData: !!window.FormData,
			Progress: "upload" in new XMLHttpRequest
		};

		/**
		 *
		 */
		me.showDropzone = function() {
			me.$dropzone.show();
		};

		/**
		 *
		 * @param {Event} event
		 */
		me.hideDropzone = function(event) {
			event.stopPropagation();
			event.preventDefault();
			me.$dropzone.hide();
		};

		/**
		 *
		 * @param {Event} event
		 * @returns {Boolean}
		 */
		me.dragFileIntoDocument = function(event) {
			event.stopPropagation();
			event.preventDefault();
			me.$body.addClass('drop-in-progress');
			me.showDropzone();
			return false;
		};

		/**
		 *
		 * @param {Event} event
		 * @returns {Boolean}
		 */
		me.dragAborted = function(event) {
			event.stopPropagation();
			event.preventDefault();
			me.$body.removeClass('drop-in-progress');
			return false;
		};

		/**
		 *
		 * @param {Event} event
		 * @returns {Boolean}
		 */
		me.ignoreDrop = function(event) {
			// stops the browser from redirecting.
			event.stopPropagation();
			event.preventDefault();
			me.dragAborted(event);
			return false;
		};

		/**
		 *
		 * @param {Event} event
		 */
		me.handleDrop = function(event) {
			me.ignoreDrop(event);
			me.processFiles(event.originalEvent.dataTransfer.files);
			me.$dropzone.removeClass('drop-status-ok');
		};

		/**
		 *
		 * @param {Array} files
		 */
		me.processFiles = function(files) {
			me.queueLength = files.length;

			if (!me.$fileList.is(':visible')) {
				me.$fileList.show();
			}

			NProgress.start();
			percentagePerFile = 1 / files.length;

			// Check for each file if is already exist before adding it to the queue
			var ajaxCalls = [];
			$.each(files, function(i, file) {

				ajaxCalls[i] = $.ajax({
					url: TYPO3.settings.ajaxUrls['file_exists'],
					data: {
						fileName: file.name,
						fileTarget: me.target
					},
					cache: false,
					success: function(response) {
						var fileExists = response !== false;
						if (fileExists) {
							askForOverride.push({
								original: response,
								uploaded: file,
								action: me.irreObjectUid ? actions.USE_EXISTING : actions.SKIP
							});
							NProgress.inc(percentagePerFile);
						} else {
							new FileQueueItem(me, file, 'cancel');
						}
					}
				});
			});

			$.when.apply($, ajaxCalls).done(function () {
				me.drawOverrideModal();
				NProgress.done();
			});

			delete ajaxCalls;
			me.$fileInput.val('');
		};

		/**
		 *
		 * @param {Event} event
		 */
		me.fileInDropzone = function(event) {
			me.$dropzone.addClass('drop-status-ok');
		};

		/**
		 *
		 * @param {Event} event
		 */
		me.fileOutOfDropzone = function(event) {
			me.$dropzone.removeClass('drop-status-ok');
		};

		/**
		 * bind file picker to default upload button
		 *
		 * @param {Object} button
		 */
		me.bindUploadButton = function(button) {
			button.click(function(event) {
				event.preventDefault();
				me.$fileInput.click();
				me.showDropzone();
			});
		};

		if (me.browserCapabilities.DnD) {
			me.$body.on('dragover', me.dragFileIntoDocument);
			me.$body.on('dragend', me.dragAborted);
			me.$body.on('drop', me.ignoreDrop);

			me.$dropzone.on('dragenter', me.fileInDropzone);
			me.$dropzoneMask.on('dragenter', me.fileInDropzone);
			me.$dropzoneMask.on('dragleave', me.fileOutOfDropzone);
			me.$dropzoneMask.on('drop', me.handleDrop);

			me.$dropzone.prepend(
				'<div class="dropzone-hint">' +
					'<div class="dropzone-hint-media">' +
						'<div class="dropzone-hint-icon"></div>' +
					'</div>' +
					'<div class="dropzone-hint-body">' +
						'<h3 class="dropzone-hint-title">' +
							TYPO3.lang['file_upload.dropzonehint.title'] +
						'</h3>' +
						'<p class="dropzone-hint-message">' +
							TYPO3.lang['file_upload.dropzonehint.message'] +
						'</p>' +
					'</div>' +
				'</div>').click(function() {
					me.$fileInput.click()
				});
			$('<span />').addClass('dropzone-close').click(me.hideDropzone).appendTo(me.$dropzone);

			// no filelist then create own progress table
			if (me.$fileList.length === 0) {
				me.$fileList = $('<table />').attr('id', 'typo3-filelist').addClass('table table-striped table-hover upload-queue').html('<tbody></tbody>').hide();
				if (me.dropZoneInsertBefore) {
					me.$fileList.insertAfter(me.$dropzone);
				} else {
					me.$fileList.insertBefore(me.$dropzone);
				}
				me.fileListColumnCount = 7;
			}

			me.$fileInput.on('change', function() {
				me.processFiles(this.files);
			});

			me.bindUploadButton(me.$trigger.length ? me.$trigger : me.$element);
		}

		/**
		 *
		 */
		me.decrementQueueLength = function() {
			if (me.queueLength > 0) {
				me.queueLength--;
				if (me.queueLength === 0) {
					$.ajax({
						url: TYPO3.settings.ajaxUrls['flashmessages_render'],
						cache: false,
						success: function(data) {
							$.each(data, function(index, flashMessage) {
								Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity);
							});
						}
					});
				}
			}
		};

		/**
		 *
		 */
		me.drawOverrideModal = function() {
			var amountOfItems = Object.keys(askForOverride).length;
			if (amountOfItems === 0) {
				return;
			}
			var $modalContent = $('<div/>').append(
				$('<p/>').text(TYPO3.lang['file_upload.existingfiles.description']),
				$('<table/>', {class: 'table table-striped'}).append(
					$('<tr />').append(
						$('<th/>'),
						$('<th/>').text(TYPO3.lang['file_upload.header.originalFile']),
						$('<th/>').text(TYPO3.lang['file_upload.header.uploadedFile']),
						$('<th/>').text(TYPO3.lang['file_upload.header.action'])
					)
				)
			);

			for (var i = 0; i < amountOfItems; ++i) {
				var $record = $('<tr />').append(
					$('<td />').append(
						(askForOverride[i].original.thumbUrl !== ''
							? $('<img />', {src: askForOverride[i].original.thumbUrl, height: 40})
							: $(askForOverride[i].original.icon)
						)
					),
					$('<td />').html(
						askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].uploaded.size)) + ')' +
						'<br>' + moment(askForOverride[i].uploaded.lastModified, 'x').format('YYYY-MM-DD HH:mm')
					),
					$('<td />').html(
						askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].original.size)) + ')' +
						'<br>' + moment(askForOverride[i].original.mtime, 'X').format('YYYY-MM-DD HH:mm')
					),
					$('<td />').append(
						$('<select />', {class: 'form-control t3js-actions', 'data-override': i}).append(
							(me.irreObjectUid ? $('<option/>').val(actions.USE_EXISTING).text(TYPO3.lang['file_upload.actions.use_existing']) : ''),
							$('<option />').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.skip']),
							$('<option />').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.rename']),
							$('<option />').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.override'])
						)
					)
				);
				$modalContent.find('table').append($record);
			}

			var $modal = Modal.confirm(TYPO3.lang['file_upload.existingfiles.title'], $modalContent, Severity.warning, [
				{
					text: $(this).data('button-close-text') || TYPO3.lang['file_upload.button.cancel'] || 'Cancel',
					active: true,
					btnClass: 'btn-default',
					name: 'cancel'
				},
				{
					text: $(this).data('button-ok-text') || TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions',
					btnClass: 'btn-warning',
					name: 'continue'
				}
			], ['modal-inner-scroll']);
			$modal.find('.modal-dialog').addClass('modal-lg');

			$modal.find('.modal-footer').prepend(
				$('<span/>').addClass('form-inline pull-left').append(
					$('<label/>').text(TYPO3.lang['file_upload.actions.all.label']),
					$('<select/>', {class: 'form-control t3js-actions-all'}).append(
						$('<option/>').val('').text(TYPO3.lang['file_upload.actions.all.empty']),
						(me.irreObjectUid ? $('<option/>').val(actions.USE_EXISTING).text(TYPO3.lang['file_upload.actions.all.use_existing']) : ''),
						$('<option/>').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.all.skip']),
						$('<option/>').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.all.rename']),
						$('<option/>').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.all.override'])
					)
				)
			);

			$modal.on('change', '.t3js-actions-all', function() {
				var $me = $(this),
					value = $me.val();

				if (value !== '') {
					// mass action was selected, apply action to every file
					$modal.find('.t3js-actions').each(function (i, select) {
						var $select = $(select),
							index = parseInt($select.data('override'));
						$select.val(value).prop('disabled', 'disabled');
						askForOverride[index].action = $select.val();
					});
				} else {
					$modal.find('.t3js-actions').removeProp('disabled');
				}
			}).on('change', '.t3js-actions', function() {
				var $me = $(this),
					index = parseInt($me.data('override'));
				askForOverride[index].action = $me.val();
			}).on('button.clicked', function(e) {
				if (e.target.name === 'cancel') {
					askForOverride = [];
					Modal.dismiss();
				} else if (e.target.name === 'continue') {
					$.each(askForOverride, function(key, fileInfo) {
						if (fileInfo.action === actions.USE_EXISTING) {
							DragUploader.addFileToIrre(
								me.irreObjectUid,
								fileInfo.original
							);
						} else if (fileInfo.action !== actions.SKIP) {
							new FileQueueItem(me, fileInfo.uploaded, fileInfo.action);
						}
					});
					askForOverride = [];
					Modal.dismiss();
				}
			}).on('hidden.bs.modal', function() {
				askForOverride = [];
			});
		}
	};

	var FileQueueItem = function(dragUploader, file, override) {
		var me = this;
		me.dragUploader = dragUploader;
		me.file = file;
		me.override = override;

		me.$row = $('<tr />').addClass('upload-queue-item uploading');
		me.$iconCol = $('<td />').addClass('col-icon').appendTo(me.$row);
		me.$fileName = $('<td />').text(file.name).appendTo(me.$row);
		me.$progress = $('<td />').attr('colspan', me.dragUploader.fileListColumnCount-2).appendTo(me.$row);
		me.$progressContainer = $('<div />').addClass('upload-queue-progress').appendTo(me.$progress);
		me.$progressBar = $('<div />').addClass('upload-queue-progress-bar').appendTo(me.$progressContainer);
		me.$progressPercentage = $('<span />').addClass('upload-queue-progress-percentage').appendTo(me.$progressContainer);
		me.$progressMessage = $('<span />').addClass('upload-queue-progress-message').appendTo(me.$progressContainer);

		me.updateMessage = function(message) {
			me.$progressMessage.text(message);
		};

		me.removeProgress = function() {
			if (me.$progress) {
				me.$progress.remove();
			}
		};

		me.uploadStart = function() {
			me.$progressPercentage.text('(0%)');
			me.$progressBar.width('1%');
			me.dragUploader.$trigger.trigger('uploadStart', [me]);
		};

		me.uploadError = function(response) {
			me.updateMessage(TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, me.file.name));
			var error = $(response.responseText);
			if (error.is('t3err')) {
				me.$progressPercentage.text(error.text());
			} else {
				me.$progressPercentage.text('(' + response.statusText + ')');
			}
			me.$row.addClass('error');
			me.dragUploader.decrementQueueLength();
			me.dragUploader.$trigger.trigger('uploadError', [me, response]);
		};

		me.updateProgress = function(event) {
			var percentage = Math.round((event.loaded / event.total) * 100) + '%';
			me.$progressBar.outerWidth(percentage);
			me.$progressPercentage.text(percentage);
			me.dragUploader.$trigger.trigger('updateProgress', [me, percentage, event]);
		};

		me.uploadSuccess = function(data) {
			if (data.upload) {
				me.dragUploader.decrementQueueLength();
				me.$row.removeClass('uploading');
				me.$fileName.text(data.upload[0].name);
				me.$progressPercentage.text('');
				me.$progressMessage.text('100%');
				me.$progressBar.outerWidth('100%');

				// replace file icon
				if (data.upload[0].icon) {
					me.$iconCol.html('<a href="#" class="t3js-contextmenutrigger" data-uid="' + data.upload[0].id + '" data-table="sys_file">' + data.upload[0].icon + '&nbsp;</span></a>');
				}

				if (me.dragUploader.irreObjectUid) {
					DragUploader.addFileToIrre(
						me.dragUploader.irreObjectUid,
						data.upload[0]
					);
					setTimeout(function() {
						me.$row.remove();
						if ($('tr', me.dragUploader.$fileList).length === 0) {
							me.dragUploader.$fileList.hide();
							me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]);
						}
					}, 3000);
				} else {
					setTimeout(function() {
						me.showFileInfo(data.upload[0]);
						me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]);
					}, 3000);
				}
			}
		};

		me.showFileInfo = function(fileInfo) {
			me.removeProgress();
			// add spacing cells when clibboard and/or extended view is enabled
			for (i = 7; i < me.dragUploader.fileListColumnCount; i++) {
				$('<td />').text('').appendTo(me.$row);
			}
			$('<td />').text(fileInfo.extension.toUpperCase()).appendTo(me.$row);
			$('<td />').text(fileInfo.date).appendTo(me.$row);
			$('<td />').text(DragUploader.fileSizeAsString(fileInfo.size)).appendTo(me.$row);
			var permissions = '';
			if (fileInfo.permissions.read) {
				permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>';
			}
			if (fileInfo.permissions.write) {
				permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>';
			}
			$('<td />').html(permissions).appendTo(me.$row);
			$('<td />').text('-').appendTo(me.$row);
		};

		me.checkAllowedExtensions = function() {
			if (!me.dragUploader.filesExtensionsAllowed) {
				return true;
			}
			var extension = me.file.name.split('.').pop();
			var allowed = me.dragUploader.filesExtensionsAllowed.split(',');
			if ($.inArray(extension.toLowerCase(), allowed) !== -1) {
				return true;
			}
			return false;
		};

		// position queue item in filelist
		if ($('tbody tr.upload-queue-item', me.dragUploader.$fileList).length === 0) {
			me.$row.prependTo($('tbody', me.dragUploader.$fileList));
			me.$row.addClass('last');
		} else {
			me.$row.insertBefore($('tbody tr.upload-queue-item:first', me.dragUploader.$fileList));
		}

		// set dummy file icon
		me.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other">&nbsp;</span>')

		// check file size
		if (me.dragUploader.maxFileSize > 0 && me.file.size > me.dragUploader.maxFileSize) {
			me.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded']
				.replace(/\{0\}/g, me.file.name)
				.replace(/\{1\}/g, DragUploader.fileSizeAsString(me.dragUploader.maxFileSize)));
			me.$row.addClass('error');

			// check filename/extension against deny pattern
		} else if (me.dragUploader.fileDenyPattern && me.file.name.match(me.dragUploader.fileDenyPattern)) {
			me.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, me.file.name));
			me.$row.addClass('error');

		} else if (!me.checkAllowedExtensions()) {
			me.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected']
					.replace(/\{0\}/g, me.dragUploader.filesExtensionsAllowed)
			);
			me.$row.addClass('error');
		} else {
			me.updateMessage('- ' + DragUploader.fileSizeAsString(me.file.size));

			var formData = new FormData();
			formData.append('file[upload][1][target]', me.dragUploader.target);
			formData.append('file[upload][1][data]', '1');
			formData.append('overwriteExistingFiles', me.override);
			formData.append('redirect', '');
			formData.append('upload_1', me.file);

			var s = $.extend(true, {}, $.ajaxSettings, {
				url: TYPO3.settings.ajaxUrls['file_process'],
				contentType: false,
				processData: false,
				data: formData,
				cache: false,
				type: 'POST',
				success: me.uploadSuccess,
				error: me.uploadError
			});

			s.xhr = function() {
				var xhr = $.ajaxSettings.xhr();
				xhr.upload.addEventListener('progress', me.updateProgress);
				return xhr;
			};

			// start upload
			me.upload = $.ajax(s);
		}
	};

	/**
	 * part 2: The main module of this file
	 * - initialize the DragUploader module and register
	 * the jQuery plugin in the jQuery global object
	 * when initializing the DragUploader module
	 */
	var DragUploader = {};

	DragUploader.options = {
	};

	DragUploader.fileSizeAsString = function(size) {
		var string = '',
			sizeKB = size / 1024;

		if (parseInt(sizeKB) > 1024) {
			var sizeMB = sizeKB / 1024;
			string = sizeMB.toFixed(1) + ' MB';
		} else {
			string = sizeKB.toFixed(1) + ' KB';
		}
		return string;
	};

	DragUploader.addFileToIrre = function(irre_object, file) {
		inline.delayedImportElement(
			irre_object,
			'sys_file',
			file.uid,
			'file'
		);
	};

	DragUploader.initialize = function() {
		var me = this,
			opts = me.options;

		// register the jQuery plugin "DragUploaderPlugin"
		$.fn.dragUploader = function(option) {
			return this.each(function() {
				var $this = $(this),
					data = $this.data('DragUploaderPlugin');
				if (!data) {
					$this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(this)));
				}
				if (typeof option === 'string') {
					data[option]();
				}
			});
		};

		$(function() {
			$('.t3js-drag-uploader').dragUploader();
		});
	};



	/**
	 * part 3: initialize the RequireJS module, require possible post-initialize hooks,
	 * and return the main object
	 */
	var initialize = function() {

		DragUploader.initialize();

		// load required modules to hook in the post initialize function
		if (
			'undefined' !== typeof TYPO3.settings
			&& 'undefined' !== typeof TYPO3.settings.RequireJS
			&& 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules
			&& 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader']
		) {
			$.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], function(pos, moduleName) {
				require([moduleName]);
			});
		}

		// return the object in the global space
		return DragUploader;
	};

	// call the main initialize function and execute the hooks
	return initialize();

});
