/* global phpbb, plupload, attachInline */

plupload.addI18n(phpbb.plupload.i18n);
phpbb.plupload.ids = [];

(function($) {  // Avoid conflicts with other libraries

'use strict';

/**
 * Set up the uploader.
 */
phpbb.plupload.initialize = function() {
	// Initialize the Plupload uploader.
	phpbb.plupload.uploader.init();

	// Set attachment data.
	phpbb.plupload.setData(phpbb.plupload.data);
	phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData());

	// Only execute if Plupload initialized successfully.
	phpbb.plupload.uploader.bind('Init', function() {
		phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0];
		let $attachRowTemplate = $('#attach-row-tpl');
		$attachRowTemplate.removeClass('attach-row-tpl');
		phpbb.plupload.rowTpl = $attachRowTemplate[0].outerHTML;

		// Hide the basic upload panel and remove the attach row template.
		$('#attach-row-tpl, #attach-panel-basic').remove();
		// Show multi-file upload options.
		$('#attach-panel-multi').show();
	});

	phpbb.plupload.uploader.bind('PostInit', function() {
		// Point out the drag-and-drop zone if it's supported.
		if (phpbb.plupload.uploader.features.dragdrop) {
			$('#drag-n-drop-message').show();
		}

		// Ensure "Add files" button position is correctly calculated.
		if ($('#attach-panel-multi').is(':visible')) {
			phpbb.plupload.uploader.refresh();
		}
		$('[data-subpanel="attach-panel"]').one('click', function() {
			phpbb.plupload.uploader.refresh();
		});
	});
};

/**
 * Unsets all elements in the object uploader.settings.multipart_params whose keys
 * begin with 'attachment_data['
 */
phpbb.plupload.clearParams = function() {
	var obj = phpbb.plupload.uploader.settings.multipart_params;
	for (var key in obj) {
		if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) {
			continue;
		}

		delete phpbb.plupload.uploader.settings.multipart_params[key];
	}
};

/**
 * Update uploader.settings.multipart_params object with new data.
 *
 * @param {object} obj
 */
phpbb.plupload.updateMultipartParams = function(obj) {
	var settings = phpbb.plupload.uploader.settings;
	settings.multipart_params = $.extend(settings.multipart_params, obj);
};

/**
 * Convert the array of attachment objects into an object that PHP would expect as POST data.
 *
 * @returns {object} An object in the form 'attachment_data[i][key]': value as
 * 	expected by the server
 */
phpbb.plupload.getSerializedData = function() {
	var obj = {};
	for (var i = 0; i < phpbb.plupload.data.length; i++) {
		var datum = phpbb.plupload.data[i];
		for (var key in datum) {
			if (!datum.hasOwnProperty(key)) {
				continue;
			}

			obj['attachment_data[' + i + '][' + key + ']'] = datum[key];
		}
	}

	// Insert form data
	var $pluploadForm = $(phpbb.plupload.config.form_hook).first();
	obj.creation_time = $pluploadForm.find('input[type=hidden][name="creation_time"]').val();
	obj.form_token = $pluploadForm.find('input[type=hidden][name="form_token"]').val();

	return obj;
};

/**
 * Get the index from the phpbb.plupload.data array where the given
 * attachment id appears.
 *
 * @param {int} attachId The attachment id of the file.
 * @returns {bool|int} Index of the file if exists, otherwise false.
 */
phpbb.plupload.getIndex = function(attachId) {
	var index = $.inArray(Number(attachId), phpbb.plupload.ids);
	return (index !== -1) ? index : false;
};

/**
 * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays.
 *
 * @param {Array} data	Array containing the new data to use. In the form of
 * array(index => object(property: value). Requires attach_id to be one of the object properties.
 */
phpbb.plupload.setData = function(data) {
	// Make sure that the array keys are reset.
	phpbb.plupload.ids = phpbb.plupload.data = [];
	phpbb.plupload.data = data;

	for (var i = 0; i < data.length; i++) {
		phpbb.plupload.ids.push(Number(data[i].attach_id));
	}
};

/**
 * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects.
 *
 * @param {Array} data			Array containing the new data to use.
 * @param {string} action		The action that required the update. Used to update the inline attachment bbcodes.
 * @param {int} index			The index from phpbb.plupload_ids that was affected by the action.
 * @param {Array} downloadUrl	Optional array of download urls to update.
 */
phpbb.plupload.update = function(data, action, index, downloadUrl) {

	phpbb.plupload.updateBbcode(action, index);
	phpbb.plupload.setData(data);
	phpbb.plupload.updateRows(downloadUrl);
	phpbb.plupload.clearParams();
	phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData());
};

/**
 * Update the relevant elements and hidden data for all attachments.
 *
 * @param {Array} downloadUrl Optional array of download urls to update.
 */
phpbb.plupload.updateRows = function(downloadUrl) {
	for (var i = 0; i < phpbb.plupload.ids.length; i++) {
		phpbb.plupload.updateRow(i, downloadUrl);
	}
};

/**
 * Insert a row for a new attachment. This expects an HTML snippet in the HTML
 * using the id "attach-row-tpl" to be present. This snippet is cloned and the
 * data for the file inserted into it. The row is then appended or prepended to
 * #file-list based on the attach_order setting.
 *
 * @param {object} file Plupload file object for the new attachment.
 */
phpbb.plupload.insertRow = function(file) {
	var row = $(phpbb.plupload.rowTpl);

	row.attr('id', file.id);
	row.find('.file-name').html(plupload.xmlEncode(file.name));
	row.find('.file-size').html(plupload.formatSize(file.size));

	if (phpbb.plupload.order === 'desc') {
		$('#file-list').prepend(row);
	} else {
		$('#file-list').append(row);
	}
};

/**
 * Update the relevant elements and hidden data for an attachment.
 *
 * @param {int} index The index from phpbb.plupload.ids of the attachment to edit.
 * @param {Array} downloadUrl Optional array of download urls to update.
 */
phpbb.plupload.updateRow = function(index, downloadUrl) {
	var attach = phpbb.plupload.data[index],
		row = $('[data-attach-id="' + attach.attach_id + '"]');

	// Add the link to the file
	if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') {
		var url = downloadUrl[index].replace('&amp;', '&'),
			link = $('<a></a>');

		link.attr('href', url).html(attach.real_filename);
		row.find('.file-name').html(link);
	}

	row.find('textarea').attr('name', 'comment_list[' + index + ']');
	phpbb.plupload.updateHiddenData(row, attach, index);
};

/**
 * Update hidden input data for an attachment.
 *
 * @param {object} row		jQuery object for the attachment row.
 * @param {object} attach	Attachment data object from phpbb.plupload.data
 * @param {int} index		Attachment index from phpbb.plupload.ids
 */
phpbb.plupload.updateHiddenData = function(row, attach, index) {
	row.find('input[type="hidden"]').remove();

	for (var key in attach) {
		if (!attach.hasOwnProperty(key)) {
			return;
		}

		var input = $('<input />')
			.attr('type', 'hidden')
			.attr('name', 'attachment_data[' + index + '][' + key + ']')
			.attr('value', attach[key]);
		$(row).append(input);
	}
};

/**
 * Deleting a file removes it from the queue and fires an AJAX event to the
 * server to tell it to remove the temporary attachment. The server
 * responds with the updated attachment data list so that any future
 * uploads can maintain state with the server
 *
 * @param {object} row		jQuery object for the attachment row.
 * @param {int} attachId	Attachment id of the file to be removed.
 */
phpbb.plupload.deleteFile = function(row, attachId) {
	// If there's no attach id, then the file hasn't been uploaded. Simply delete the row.
	if (typeof attachId === 'undefined') {
		var file = phpbb.plupload.uploader.getFile(row.attr('id'));
		phpbb.plupload.uploader.removeFile(file);

		row.slideUp(100, function() {
			row.remove();
			phpbb.plupload.hideEmptyList();
		});
	}

	var index = phpbb.plupload.getIndex(attachId);
	row.find('.file-status').toggleClass('file-uploaded file-working');

	if (index === false) {
		return;
	}
	var fields = {};
	fields['delete_file[' + index + ']'] = 1;

	var always = function() {
		row.find('.file-status').removeClass('file-working');
	};

	var done = function(response) {
		if (typeof response !== 'object') {
			return;
		}

		// trigger_error() was called which likely means a permission error was encountered.
		if (typeof response.title !== 'undefined') {
			phpbb.plupload.uploader.trigger('Error', { message: response.message });
			// We will have to assume that the deletion failed. So leave the file status as uploaded.
			row.find('.file-status').toggleClass('file-uploaded');

			return;
		}

		// Handle errors while deleting file
		if (typeof response.error !== 'undefined') {
			phpbb.alert(phpbb.plupload.lang.ERROR, response.error.message);

			// We will have to assume that the deletion failed. So leave the file status as uploaded.
			row.find('.file-status').toggleClass('file-uploaded');

			return;
		}

		phpbb.plupload.update(response, 'removal', index);
		// Check if the user can upload files now if he had reached the max files limit.
		phpbb.plupload.handleMaxFilesReached();

		if (row.attr('id')) {
			var file = phpbb.plupload.uploader.getFile(row.attr('id'));
			phpbb.plupload.uploader.removeFile(file);
		}
		row.slideUp(100, function() {
			row.remove();
			// Hide the file list if it's empty now.
			phpbb.plupload.hideEmptyList();
		});
		phpbb.plupload.uploader.trigger('FilesRemoved');
	};

	$.ajax(phpbb.plupload.config.url, {
		type: 'POST',
		data: $.extend(fields, phpbb.plupload.getSerializedData()),
		headers: phpbb.plupload.config.headers
	})
	.always(always)
	.done(done);
};

/**
 * Check the attachment list and hide its container if it's empty.
 */
phpbb.plupload.hideEmptyList = function() {
	if (!$('#file-list').children().length) {
		$('#file-list-container').slideUp(100);
	}
};

/**
 * Update the indices used in inline attachment bbcodes. This ensures that the
 * bbcodes correspond to the correct file after a file is added or removed.
 * This should be called before the phpbb.plupload,data and phpbb.plupload.ids
 * arrays are updated, otherwise it will not work correctly.
 *
 * @param {string} action	The action that occurred -- either "addition" or "removal"
 * @param {int} index		The index of the attachment from phpbb.plupload.ids that was affected.
 */
phpbb.plupload.updateBbcode = function(action, index) {
	var	textarea = $('#message', phpbb.plupload.form),
		text = textarea.val(),
		removal = (action === 'removal');

	// Return if the bbcode isn't used at all.
	if (text.indexOf('[attachment=') === -1) {
		return;
	}

	function runUpdate(i) {
		var regex = new RegExp('\\[attachment=' + i + '\\](.*?)\\[\\/attachment\\]', 'g');
		text = text.replace(regex, function updateBbcode(_, fileName) {
			// Remove the bbcode if the file was removed.
			if (removal && index === i) {
				return '';
			}
			var newIndex = i + ((removal) ? -1 : 1);
			return '[attachment=' + newIndex + ']' + fileName + '[/attachment]';
		});
	}

	// Loop forwards when removing and backwards when adding ensures we don't
	// corrupt the bbcode index.
	var i;
	if (removal) {
		for (i = index; i < phpbb.plupload.ids.length; i++) {
			runUpdate(i);
		}
	} else {
		for (i = phpbb.plupload.ids.length - 1; i >= index; i--) {
			runUpdate(i);
		}
	}

	textarea.val(text);
};

/**
 * Get Plupload file objects based on their upload status.
 *
 * @param {int} status Plupload status - plupload.DONE, plupload.FAILED,
 * plupload.QUEUED, plupload.STARTED, plupload.STOPPED
 *
 * @returns {Array} The Plupload file objects matching the status.
 */
phpbb.plupload.getFilesByStatus = function(status) {
	var files = [];

	$.each(phpbb.plupload.uploader.files, function(i, file) {
		if (file.status === status) {
			files.push(file);
		}
	});
	return files;
};

/**
 * Check whether the user has reached the maximun number of files that he's allowed
 * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise
 * makes sure that the uploader is enabled.
 *
 * @returns {bool} True if the limit has been reached. False if otherwise.
 */
phpbb.plupload.handleMaxFilesReached = function() {
	// If there is no limit, the user is an admin or moderator.
	if (!phpbb.plupload.maxFiles) {
		return false;
	}

	if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) {
		// Fail the rest of the queue.
		phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS);
		// Disable the uploader.
		phpbb.plupload.disableUploader();
		phpbb.plupload.uploader.trigger('Error', { message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS });

		return true;
	} else if (phpbb.plupload.maxFiles > phpbb.plupload.ids.length) {
		// Enable the uploader if the user is under the limit
		phpbb.plupload.enableUploader();
	}
	return false;
};

/**
 * Disable the uploader
 */
phpbb.plupload.disableUploader = function() {
	$('#add_files').addClass('disabled');
	phpbb.plupload.uploader.disableBrowse();
};

/**
 * Enable the uploader
 */
phpbb.plupload.enableUploader = function() {
	$('#add_files').removeClass('disabled');
	phpbb.plupload.uploader.disableBrowse(false);
};

/**
 * Mark all queued files as failed.
 *
 * @param {string} error Error message to present to the user.
 */
phpbb.plupload.markQueuedFailed = function(error) {
	var files = phpbb.plupload.getFilesByStatus(plupload.QUEUED);

	$.each(files, function(i, file) {
		$('#' + file.id).find('.file-progress').hide();
		phpbb.plupload.fileError(file, error);
	});
};

/**
 * Marks a file as failed and sets the error message for it.
 *
 * @param {object} file		Plupload file object that failed.
 * @param {string} error	Error message to present to the user.
 */
phpbb.plupload.fileError = function(file, error) {
	file.status = plupload.FAILED;
	file.error = error;
	$('#' + file.id).find('.file-status')
		.addClass('file-error')
		.attr({
			'data-error-title': phpbb.plupload.lang.ERROR,
			'data-error-message': error
		});
};


/**
 * Set up the Plupload object and get some basic data.
 */
phpbb.plupload.uploader = new plupload.Uploader(phpbb.plupload.config);
phpbb.plupload.initialize();

/**
 * Add a file filter to check for max file sizes per mime type.
 */
plupload.addFileFilter('mime_types_max_file_size', function(types, file, callback) {
	if (file.size !== 'undefined') {
		$(types).each(function(i, type) {
			let extensions = [],
				extsArray = type.extensions.split(',');

			$(extsArray).each(function(i, extension) {
				/^\s*\*\s*$/.test(extension) ? extensions.push("\\.*") : extensions.push("\\." + extension.replace(new RegExp("[" + "/^$.*+?|()[]{}\\".replace(/./g, "\\$&") + "]", "g"), "\\$&"));
			});

			let regex = new RegExp("(" + extensions.join("|") + ")$", "i");

			if (regex.test(file.name)) {
				if (type.max_file_size !== 'undefined' && type.max_file_size) {
					if (file.size > type.max_file_size) {
						phpbb.plupload.uploader.trigger('Error', {
							code: plupload.FILE_SIZE_ERROR,
							message: plupload.translate('File size error.'),
							file: file
						});

						callback(false);
					} else {
						callback(true);
					}
				} else {
					callback(true);
				}

				return false;
			}
		});
	}
});

var $fileList = $('#file-list');

/**
 * Insert inline attachment bbcode.
 */
$fileList.on('click', '.file-inline-bbcode', function(e) {
	var attachId = $(this).parents('.attach-row').attr('data-attach-id'),
		index = phpbb.plupload.getIndex(attachId);

	attachInline(index, phpbb.plupload.data[index].real_filename);
	e.preventDefault();
});

/**
 * Delete a file.
 */
$fileList.on('click', '.file-delete', function(e) {
	var row = $(this).parents('.attach-row'),
		attachId = row.attr('data-attach-id');

	phpbb.plupload.deleteFile(row, attachId);
	e.preventDefault();
});

/**
 * Display the error message for a particular file when the error icon is clicked.
 */
$fileList.on('click', '.file-error', function(e) {
	phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message'));
	e.preventDefault();
});

/**
 * Fires when an error occurs.
 */
phpbb.plupload.uploader.bind('Error', function(up, error) {
	error.file.name = plupload.xmlEncode(error.file.name);

	// The error message that Plupload provides for these is vague, so we'll be more specific.
	if (error.code === plupload.FILE_EXTENSION_ERROR) {
		error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name;
	} else if (error.code === plupload.FILE_SIZE_ERROR) {
		error.message = plupload.translate('File too large:') + ' ' + error.file.name;
	}
	phpbb.alert(phpbb.plupload.lang.ERROR, error.message);
});

/**
 * Fires before a given file is about to be uploaded. This allows us to
 * send the real filename along with the chunk. This is necessary because
 * for some reason the filename is set to 'blob' whenever a file is chunked
 *
 * @param {object} up	The plupload.Uploader object
 * @param {object} file	The plupload.File object that is about to be uploaded
 */
phpbb.plupload.uploader.bind('BeforeUpload', function(up, file) {
	if (phpbb.plupload.handleMaxFilesReached()) {
		return;
	}

	phpbb.plupload.updateMultipartParams({ real_filename: file.name });
});

/**
 * Fired when a single chunk of any given file is uploaded. This parses the
 * response from the server and checks for an error. If an error occurs it
 * is reported to the user and the upload of this particular file is halted
 *
 * @param {object} up		The plupload.Uploader object
 * @param {object} file		The plupload.File object whose chunk has just
 * 	been uploaded
 * @param {object} response	The response object from the server
 */
phpbb.plupload.uploader.bind('ChunkUploaded', function(up, file, response) {
	if (response.chunk >= response.chunks - 1) {
		return;
	}

	var json = {};
	try {
		json = $.parseJSON(response.response);
	} catch (e) {
		file.status = plupload.FAILED;
		up.trigger('FileUploaded', file, {
			response: JSON.stringify({
				error: {
					message: 'Error parsing server response.'
				}
			})
		});
	}

	// If trigger_error() was called, then a permission error likely occurred.
	if (typeof json.title !== 'undefined') {
		json.error = { message: json.message };
	}

	if (json.error) {
		file.status = plupload.FAILED;
		up.trigger('FileUploaded', file, {
			response: JSON.stringify({
				error: {
					message: json.error.message
				}
			})
		});
	}
});

/**
 * Fires when files are added to the queue.
 */
phpbb.plupload.uploader.bind('FilesAdded', function(up, files) {
	// Prevent unnecessary requests to the server if the user already uploaded
	// the maximum number of files allowed.
	if (phpbb.plupload.handleMaxFilesReached()) {
		return;
	}

	// Switch the active tab if the style supports it
	if (typeof activateSubPanel === 'function') {
		activateSubPanel('attach-panel'); // jshint ignore: line
	}

	// Show the file list if there aren't any files currently.
	var $fileListContainer = $('#file-list-container');
	if (!$fileListContainer.is(':visible')) {
		$fileListContainer.show(100);
	}

	$.each(files, function(i, file) {
		phpbb.plupload.insertRow(file);
	});

	up.bind('UploadProgress', function(up, file) {
		$('.file-progress-bar', '#' + file.id).css('width', file.percent + '%');
		$('#file-total-progress-bar').css('width', up.total.percent + '%');
	});

	// Do not allow more files to be added to the running queue.
	phpbb.plupload.disableUploader();

	// Start uploading the files once the user has selected them.
	up.start();
});


/**
 * Fires when an entire file has been uploaded. It checks for errors
 * returned by the server otherwise parses the list of attachment data and
 * appends it to the next file upload so that the server can maintain state
 * with regards to the attachments in a given post
 *
 * @param {object} up		The plupload.Uploader object
 * @param {object} file		The plupload.File object that has just been
 * 	uploaded
 * @param {string} response	The response string from the server
 */
phpbb.plupload.uploader.bind('FileUploaded', function(up, file, response) {
	var json = {},
		row = $('#' + file.id),
		error;

	// Hide the progress indicator.
	row.find('.file-progress').hide();

	try {
		json = JSON.parse(response.response);
	} catch (e) {
		error = 'Error parsing server response.';
	}

	// If trigger_error() was called, then a permission error likely occurred.
	if (typeof json.title !== 'undefined') {
		error = json.message;
		up.trigger('Error', { message: error });

		// The rest of the queue will fail.
		phpbb.plupload.markQueuedFailed(error);
	} else if (json.error) {
		error = json.error.message;
	}

	if (typeof error !== 'undefined') {
		phpbb.plupload.fileError(file, error);
	} else if (file.status === plupload.DONE) {
		file.attachment_data = json.data[0];

		row.attr('data-attach-id', file.attachment_data.attach_id);
		row.find('.file-inline-bbcode').show();
		row.find('.file-status').addClass('file-uploaded');
		phpbb.plupload.update(json.data, 'addition', 0, [json.download_url]);
	}
});

/**
 * Fires when the entire queue of files have been uploaded.
 */
phpbb.plupload.uploader.bind('UploadComplete', function() {
	// Hide the progress bar
	setTimeout(function() {
		$('#file-total-progress-bar').fadeOut(500, function() {
			$(this).css('width', 0).show();
		});
	}, 2000);

	// Re-enable the uploader
	phpbb.plupload.enableUploader();
});

})(jQuery); // Avoid conflicts with other libraries
