/* global plupload, BP_Uploader, _, JSON, Backbone */

window.wp = window.wp || {};
window.bp = window.bp || {};

( function( bp, $ ) {

	// Bail if not set.
	if ( typeof BP_Uploader === 'undefined' ) {
		return;
	}

	/**
	 * Extend the bp global with what we need from the wp one.
	 * and make sure previously defined BuddyPress attributes
	 * are not removed (eg: bp.mentions)
	 */
	_.extend( bp, _.pick( wp, 'Backbone', 'ajax', 'template' ) );

	// Init Models, Collections, Views and the BuddyPress Uploader.
	bp.Models      = bp.Models || {};
	bp.Collections = bp.Collections || {};
	bp.Views       = bp.Views || {};
	bp.Uploader    = {};

	/**
	 * BuddyPress Uploader.
	 *
	 * This is an adapted version of wp.Uploader.
	 */
	bp.Uploader.uploader = function() {
		var self = this,
			isIE = navigator.userAgent.indexOf('Trident/') !== -1 || navigator.userAgent.indexOf('MSIE ') !== -1;

		this.params  = BP_Uploader.settings;
		this.strings = BP_Uploader.strings;

		this.supports = {
			upload: this.params.browser.supported
		};

		this.supported = this.supports.upload;

		if ( ! this.supported ) {
			/*jshint -W020 */
			BP_Uploader = undefined;
			return;
		}

		// Make sure flash sends cookies (seems in IE it does without switching to urlstream mode).
		if ( ! isIE && 'flash' === plupload.predictRuntime( this.params.defaults ) &&
			( ! this.params.defaults.required_features || ! this.params.defaults.required_features.hasOwnProperty( 'send_binary_string' ) ) ) {

			this.params.defaults.required_features = this.params.defaults.required_features || {};
			this.params.defaults.required_features.send_binary_string = true;
		}

		this.uploader = new plupload.Uploader( this.params.defaults );

		/**
		 * After the Uploader has been initialized, initialize some behaviors for the dropzone.
		 *
		 * @event Init
		 * @param {plupload.Uploader} uploader Uploader instance.
		 */
		this.uploader.bind( 'Init', function( uploader ) {
			var container    = $( '#' + self.params.defaults.container ),
			    drop_element = $( '#' + self.params.defaults.drop_element );

			if ( 'html4' === uploader.runtime ) {
				uploader.settings.multipart_params.html4 = true;
			}

			/**
			 * Avatars need to be cropped, by default we are using an original
			 * max width of 450px, but there can be cases when this max width
			 * is larger than the one of the Avatar UI (eg: on mobile). To avoid any
			 * difficulties, we're adding a ui_available_width argument to the bp_params
			 * object and set it according to the container width. This value will be
			 * checked during the upload process to eventually adapt the resized avatar.
			 */
			if ( 'bp_avatar_upload' ===  uploader.settings.multipart_params.action ) {
				 uploader.settings.multipart_params.bp_params.ui_available_width = container.width();
			}

			if ( uploader.features.dragdrop && ! self.params.browser.mobile ) {
				container.addClass( 'drag-drop' );

				drop_element.on( 'dragover.wp-uploader', function() {
					container.addClass( 'drag-over' );
				} );

				drop_element.on( 'dragleave.wp-uploader, drop.wp-uploader', function() {
					container.removeClass( 'drag-over' );
				} );
			} else {
				container.removeClass( 'drag-drop' );
				drop_element.off( '.wp-uploader' );
			}

		} );

		// See https://core.trac.wordpress.org/ticket/37039.
		this.uploader.bind( 'postinit', function( up ) {
			up.refresh();
		});

		// Init BuddyPress Uploader.
		this.uploader.init();

		/**
		 * Feedback callback.
		 *
		 * Add a new message to the errors collection, so it's possible
		 * to give some feedback to the user.
		 *
		 * @param  {string}        message
		 * @param  {object}        data
		 * @param  {plupload.File} file     File that was uploaded.
		 */
		this.feedback = function( message, data, file ) {
			if ( ! _.isNull( file ) && file.item ) {
				file.item.clear();
			}

			bp.Uploader.filesError.unshift( {
				message: message,
				data:    data,
				file:    file
			} );
		};

		/**
		 * After files were filtered and added to the queue, create a model for each.
		 *
		 * @event FilesAdded
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {Array}             files    Array of file objects that were added to queue by the user.
		 */
		this.uploader.bind( 'FilesAdded', function( uploader, files ) {
			var hundredmb = 100 * 1024 * 1024, max = parseInt( uploader.settings.max_file_size, 10 ),
			    _this = this;

			/**
			 * In case the multiple selection is false (eg: avatar) stop the process and send
			 * and event containing a warning.
			 */
			if ( ! uploader.settings.multi_selection && files.length > 1 ) {
				for ( var i in files ) {
					uploader.removeFile( files[i] );
				}

				$( self ).trigger( 'bp-uploader-warning', self.strings.unique_file_warning );
				return;
			}

			_.each( files, function( file ) {
				var attributes;

				// Ignore failed uploads.
				if ( plupload.FAILED === file.status ) {
					return;
				}

				if ( max > hundredmb && file.size > hundredmb && uploader.runtime !== 'html5' ) {
					_this.uploadSizeError( uploader, file, true );
				} else {
					attributes = _.extend( {
						id:        file.id,
						file:      file,
						uploading: true,
						date:      new Date(),
						filename:  file.name
					}, _.pick( file, 'loaded', 'size', 'percent' ) );

					file.item = new bp.Models.File( attributes );
					bp.Uploader.filesQueue.add( file.item );
				}

			} );

			uploader.refresh();
			uploader.start();
		} );

		/**
		 * Update each file item on progress.
		 *
		 * @event UploadProgress
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {Object}            file
		 */
		this.uploader.bind( 'UploadProgress', function( uploader, file ) {
			file.item.set( _.pick( file, 'loaded', 'percent' ) );
		} );

		/**
		 * After a file is successfully uploaded, update its model.
		 *
		 * @event FileUploaded
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {plupload.File}     file     File that was uploaded.
		 * @param {Object}            response Object with response properties.
		 * @return {mixed}
		 */
		this.uploader.bind( 'FileUploaded', function( uploader, file, response ) {
			var message = self.strings.default_error;

			try {
				response = JSON.parse( response.response );
			} catch ( e ) {
				return self.feedback( message, e, file );
			}

			if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) {
				return self.feedback( message, null, file );
			} else if ( ! response.success ) {
				if ( response.data && response.data.message ) {
					message = response.data.message;
				}

				return self.feedback( message, response.data, file );
			}

			_.each(['file','loaded','size','percent'], function( key ) {
				file.item.unset( key );
			} );

			file.item.set( _.extend( response.data, { uploading: false } ) );

			//  Add the file to the Uploaded ones.
			bp.Uploader.filesUploaded.add( file.item );

		} );

		/**
		 * Trigger an event to inform a new upload is being processed.
		 *
		 * Mainly used to remove an eventual warning.
		 *
		 * @event BeforeUpload
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {Array}             files    Array of file objects that were added to queue by the user.
		 */
		this.uploader.bind( 'BeforeUpload', function( uploader, files ) {
			$( self ).trigger( 'bp-uploader-new-upload', uploader, files );
		} );

		/**
		 * Reset the filesQueue once the upload is complete.
		 *
		 * @event BeforeUpload
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {Array}             files    Array of file objects that were added to queue by the user.
		 */
		this.uploader.bind( 'UploadComplete', function( uploader, files ) {
			$( self ).trigger( 'bp-uploader-upload-complete', uploader, files );
			bp.Uploader.filesQueue.reset();
		} );

		/**
		 * Map Plupload errors & Create a warning when plupload failed.
		 *
		 * @event Error
		 * @param {plupload.Uploader} uploader Uploader instance.
		 * @param {Object}            pluploadError Plupload error
		 */
		this.uploader.bind( 'Error', function( uploader, pluploadError ) {
			var message = self.strings.default_error,
				key,
				errors = {
					'FAILED':                 self.strings.upload_failed,
					'FILE_EXTENSION_ERROR':   self.strings.invalid_filetype,
					'IMAGE_FORMAT_ERROR':     self.strings.not_an_image,
					'IMAGE_MEMORY_ERROR':     self.strings.image_memory_exceeded,
					'IMAGE_DIMENSIONS_ERROR': self.strings.image_dimensions_exceeded,
					'GENERIC_ERROR':          self.strings.upload_failed,
					'IO_ERROR':               self.strings.io_error,
					'HTTP_ERROR':             self.strings.http_error,
					'SECURITY_ERROR':         self.strings.security_error,
					'FILE_SIZE_ERROR':        self.strings.file_exceeds_size_limit.replace( '%s' , $( '<span />' ).text( pluploadError.file.name ).html() )
				};

			// Check for plupload errors.
			for ( key in errors ) {
				if ( pluploadError.code === plupload[ key ] ) {
					message = errors[ key ];
					break;
				}
			}

			$( self ).trigger( 'bp-uploader-warning', message );
			uploader.refresh();
		} );
	};

	// Create a very generic Model for files.
	bp.Models.File = Backbone.Model.extend( {
		file: {}
	} );

	// Add Collections to store queue, uploaded files and errors.
	$.extend( bp.Uploader, {
		filesQueue    : new Backbone.Collection(),
		filesUploaded : new Backbone.Collection(),
		filesError    : new Backbone.Collection()
	} );

	// Extend wp.Backbone.View with .prepare() and .inject().
	bp.View = bp.Backbone.View.extend( {
		inject: function( selector ) {
			this.render();
			$(selector).html( this.el );
			this.views.ready();
		},

		prepare: function() {
			if ( ! _.isUndefined( this.model ) && _.isFunction( this.model.toJSON ) ) {
				return this.model.toJSON();
			} else {
				return {};
			}
		}
	} );

	// BuddyPress Uploader main view.
	bp.Views.Uploader = bp.View.extend( {
		className: 'bp-uploader-window',
		template: bp.template( 'upload-window' ),

		defaults: _.pick( BP_Uploader.settings.defaults, 'container', 'drop_element', 'browse_button' ),

		initialize: function() {
			this.warnings = [];
			this.model    = new Backbone.Model( this.defaults );
			this.on( 'ready', this.initUploader );
		},

		initUploader: function() {
			this.uploader = new bp.Uploader.uploader();
			$( this.uploader ).on( 'bp-uploader-warning', _.bind( this.setWarning, this ) );
			$( this.uploader ).on( 'bp-uploader-new-upload', _.bind( this.resetWarning, this ) );
		},

		setWarning: function( event, message ) {
			if ( _.isUndefined( message ) ) {
				return;
			}

			var warning = new bp.Views.uploaderWarning( {
				value: message
			} ).render();

			this.warnings.push( warning );

			this.$el.after( warning.el );
		},

		resetWarning: function() {
			if ( 0 === this.warnings.length ) {
				return;
			}

			// Remove all warning views.
			_.each( this.warnings, function( view ) {
				view.remove();
			} );

			// Reset Warnings.
			this.warnings = [];
		}
	} );

	// BuddyPress Uploader warning view.
	bp.Views.uploaderWarning = bp.View.extend( {
		tagName: 'p',
		className: 'warning',

		initialize: function() {
			this.value = this.options.value;
		},

		render: function() {
			this.$el.html( this.value );
			return this;
		}
	} );

	// BuddyPress Uploader Files view.
	bp.Views.uploaderStatus = bp.View.extend( {
		className: 'files',

		initialize: function() {
			_.each( this.collection.models, this.addFile, this );
			this.collection.on( 'change:percent', this.progress, this );
			bp.Uploader.filesError.on( 'add', this.feedback, this );
		},

		addFile: function( file ) {
			this.views.add( new bp.Views.uploaderProgress( { model: file } ) );
		},

		progress:function( model ) {
			if ( ! _.isUndefined( model.get( 'percent' ) ) ) {
				$( '#' + model.get('id') + ' .bp-progress .bp-bar' ).css( 'width', model.get('percent') + '%' );
			}
		},

		feedback: function( model ) {
			if ( ! _.isUndefined( model.get( 'message' ) ) && ! _.isUndefined( model.get( 'file' ) ) ) {
				$( '#' + model.get( 'file' ).id ).html( model.get( 'message' ) ).addClass( 'error' );
			}
		}
	} );

	// BuddyPress Uploader File progress view.
	bp.Views.uploaderProgress = bp.View.extend( {
		className: 'bp-uploader-progress',
		template: bp.template( 'progress-window' )
	} );

})( window.bp, jQuery );
