function UploadsManager() {
	addLoadEvent(
		function() {
			UploadsManager._initAll();
		}
	);
}

UploadsManager = new UploadsManager();

/* ==== Private Attributes ==== */
UploadsManager._nextId = 0;
UploadsManager._uploadersReady = 0;

UploadsManager._debugMode = false;
UploadsManager._Uploaders = {};
UploadsManager._singleUpload = true;

/* ==== Public Attributes ==== */
UploadsManager.useToolbar = true;
UploadsManager.formContainerId = '';
UploadsManager.useTransparency = true;
UploadsManager.uploadCancelled = false;

/* ==== Private methods ==== */
UploadsManager.setScrollEvents = function() {
	var $scroll_container = this._getScrollContainer();

	addEvent(
		$scroll_container,
		'scroll',
		function(ev) {
			UploadsManager.iterate('syncBrowseButton');
		}
	)
}

UploadsManager._nextFlashId = function() {
	this._nextId++;
	return 'uploaderflash' + this._nextId;
}

UploadsManager._getFormContainer = function() {
	if (UploadsManager.formContainerId) {
		return document.getElementById(UploadsManager.formContainerId);
	}

	return Form.Div;
}


UploadsManager._getScrollContainer = function() {
	if (UploadsManager.formContainerId) {
		return document.body;
	}

	return document.getElementById(Form.Div.id + '_container');
}

UploadsManager._initAll = function() {
	this._createHooks();

	for (var i in this._Uploaders) {
		this._Uploaders[i].init();
	}
}

UploadsManager.iterate = function($method, $timeout) {
	var $me = this;
	var args = Array.prototype.slice.call(arguments); // convert to array

	if ($timeout !== undefined) {
		// 2nd parameter is given
		if ((Object.prototype.toString.call($timeout) === '[object String]') && $timeout.match(/^timeout:([\d]+)$/)) {
			// it's string in format "timeout:<number>"
			$timeout = parseInt(RegExp.$1);
		}
		else {
			// this is not the timeout, but 1st parameter of iteratable method
			$timeout = undefined;
		}
	}

	if ($timeout !== undefined) {
		// make delayed iteration (helps with direct innerHTML assignments in IE)
		args.splice(args.length - 1, 1); // remove timeout

		setTimeout(function() { $me.iterate.apply($me, args); }, $timeout);
		return ;
	}

	args.splice(0, 1); // remove method name

	for (var i in this._Uploaders) {
		this._Uploaders[i][$method].apply(this._Uploaders[i], args);
	}
}

UploadsManager._hasQueue = function() {
	var has_queue = false;

	for (var i in this._Uploaders) {
		var tmp = this._Uploaders[i].hasQueue();
		has_queue = has_queue || tmp;
	}

	return has_queue;
}

UploadsManager._getUploader = function (file) {
	var $flash_id = file.id.match(/(.*)_[\d]+/) ? RegExp.$1 : file.id;

	for (var $uploader_index in this._Uploaders) {
		if (this._Uploaders[$uploader_index].flash_id == $flash_id) {
			return this._Uploaders[$uploader_index];
		}
	}

	return null;
}

UploadsManager._createHooks = function () {
	if (typeof Application != 'undefined') {
		Application.setHook(
			'm:OnAfterFormInit',
			function () {
				UploadsManager.setScrollEvents();
			}
		);
	}

	// hooking to standard toolbar select button to peform auto-upload when Save is clicked
	if (!UploadsManager.useToolbar || !isset(a_toolbar)) {
		return ;
	}

	if (a_toolbar.ButtonExists('select')) {
		var old_onclick = a_toolbar.Buttons['select'].onClick;
		a_toolbar.Buttons['select'].onClick = function() {
			UploadsManager.UploadAll(
				function() {
					if (UploadsManager._debugMode) {
						if (!confirm('Proceed with form submitting?')) {
							return ;
						}
					}

					old_onclick();
				}
			);
		}
	}
}

/* ==== Public methods ==== */
UploadsManager.AddUploader = function(id, params) {
	this._Uploaders[id] = new Uploader(id, params);
}

UploadsManager.RemoveUploader = function(id) {
	this._Uploaders[id].remove();
	delete this._Uploaders[id];
	this.iterate('syncBrowseButton');
}

UploadsManager.DeleteFile = function(uploader_id, fname, confirmed) {
	if (!confirmed && !confirm('Are you sure you want to delete this file?')) {
		return;
	}

	var $uploader = this._Uploaders[uploader_id];

	Request.makeRequest(
		$uploader.deleteURL.replace('#FILE#', fname).replace('#FIELD#', $uploader.params.field),
		false, '',
		function(req, fname, $uploader) {
			$uploader.removeFile({id:fname})
			$uploader.deleted.push(fname);
			$uploader.updateInfo();
		},

		function(req, fname, $uploader) {
			alert('Error while deleting file');
		},
		fname, $uploader
	);
}

UploadsManager.StartUpload = function(id) {
	UploadsManager._singleUpload = true;
	this._Uploaders[id].startUpload();
}

UploadsManager.CancelFile = function(id, file_id) {
	this._Uploaders[id].callFlash('CancelUpload', [file_id]);
}

UploadsManager.UploadAll = function(onAllUploaded) {
	if (!this._hasQueue()) {
		onAllUploaded();
		return ;
	}

	UploadsManager._singleUpload = false;
	for (var i in this._Uploaders) {
		// could raise problems, when simultanious uploads from diffrent uploaders are made
		this._Uploaders[i].startUpload();
	}

	this.OnAllUploaded = onAllUploaded;
}

UploadsManager.UploadQueueComplete = function($uploader) {
	var $all_uploaded = true; // all queued files were uploaded
	var $all_processed = true; // all queued files were processed (uploaded OR cancelled)

	for (var i in this._Uploaders) {
		var $queue_owner = this._Uploaders[i].id == $uploader.id;
		if (UploadsManager._singleUpload && !$queue_owner) {
			// count only current uploader when single upload is made
			continue;
		}

		if ($queue_owner) {
			this._Uploaders[i].finalizeUpload();
		}

		var $empty_queue = !this._Uploaders[i].hasQueue();

		$all_uploaded = $all_uploaded && $empty_queue;
		$all_processed = $all_processed && ($empty_queue || this._Uploaders[i].uploadCancelled);
	}

	if ($all_processed) {
		// enable all browse button, when upload is totally completed or cancelled
		UploadsManager.iterate('disableBrowse', false);
	}

	if (UploadsManager.useTransparency && $all_processed) {
		// remove transparency, when all uploaders are done
		Request.setOpacity(100, UploadsManager._getFormContainer());
	}

	if ($all_uploaded && isset(this.OnAllUploaded) && !this.uploadCancelled) {
		this.OnAllUploaded();
	}
}

UploadsManager.CancelUpload = function(id) {
	this._Uploaders[id].cancelUpload();
}

UploadsManager.setDebugMode = function ($enabled) {
	/*for (var $uploader_index in this._Uploaders) {
		this._Uploaders[$uploader_index].clallFlash('SetDebugEnabled', [$enabled]);
	}*/

	this._debugMode = $enabled;
}


/* ==== Flash event handlers ==== */
UploadsManager.onHandleEverything = function () {
	if (UploadsManager._debugMode) {
		console.log('default swf handler');
	}
}

UploadsManager.onUploadStart = function(file) {
	var $uploader = UploadsManager._getUploader(file);

	$uploader.queueEvent(
		function() {
			this.UploadFileStart(file);
		}
	);
}

UploadsManager.onUploadProgress = function(file, bytesLoaded, bytesTotal) {
	var $uploader = UploadsManager._getUploader(file);

	$uploader.queueEvent(
		function() {
			this.UploadProgress(file, bytesLoaded, bytesTotal);
		}
	);
}

UploadsManager.onUploadComplete = function(file) {
	var $uploader = UploadsManager._getUploader(file);

	$uploader.queueEvent(
		function() {
			this.UploadFileComplete(file);
		}
	);
}

UploadsManager.onFileQueued = function(file) {
	var $uploader = UploadsManager._getUploader(file);
//	file = this.unescapeFilePostParams(file);

	$uploader.queueEvent(
		function() {
			if (this.files_count >= this.params.multiple) {
				// new file can exceed allowed file number
				if (this.params.multiple > 1) {
					// it definetly exceed it
					UploadsManager.onFileQueueError(file, -100, this.params.multiple);
					this.callFlash('CancelUpload', [file.id]);
				}
				else {
					// delete file added
					this.files_count++;
					this.files.push(file);
					this.total += file.size;
					if (this.files[0].uploaded) {
						UploadsManager.DeleteFile(UploadsManager._getUploader(file).id, this.files[0].name, true);
					}
					else {
						this.callFlash('CancelUpload', [this.files[0].id]);
					}
				}
			}
			else {
				// new file will not exceed allowed file number
				this.files_count++;
				this.files.push(file);
				this.total += file.size;
			}
			this.updateInfo();
		}
	)
}

UploadsManager.onUploadError = function(file, errorCode, message) {
	var $uploader = UploadsManager._getUploader(file);

	$uploader.queueEvent(
		function() {
			this.removeFile(file);

			switch (errorCode) {
				case -200:
					// HTTP Error
					message = parseInt(message); // HTTP Error Code
					switch (message) {
						case 403:
							message = "You don't have permission to upload";
							break;

						case 413:
							message = 'File size exceeds allowed limit';
							break;

						case 500:
							message = 'Write permissions not set on the server, please contact server administrator';
							break;
					}

					if (isNaN(message)) {
						// message is processed
						alert('Error: ' + message + "\n" + 'Occured on file ' + file.name);
						return ;
					}
					break;

				case -280:
					// File Cancelled
					return ;
					break;

				case -290:
					// Upload Stopped
					UploadsManager.UploadQueueComplete(this);
					return ;
					break;
			}

			// all not processed error messages go here
			alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name);
		}
	);
}

UploadsManager.onFileQueueError = function(file, errorCode, message) {
	switch (errorCode) {
		case -100:
			// maximal allowed file count reached
			alert('Error: Files count exceeds allowed limit' + "\n" + 'Occured on file ' + file.name);
			return ;
			break;

		case -110:
			// maximal allowed filesize reached
			alert('Error: File size exceeds allowed limit' + "\n" + 'Occured on file ' + file.name);
			return ;
			break;

		case -130:
			// maximal allowed filesize reached
			alert('Error: File is not an allowed file type.' + "\n" + 'Occured on file ' + file.name);
			return ;
			break;
	}

	// all not processed error messages go here
	alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name);
}

UploadsManager.onFlashReady = function ($uploader_id) {
	this._Uploaders[$uploader_id].onFlashReady();
	this._uploadersReady++;

	if (this._uploadersReady == this._nextId) {
		// all uploaders are ready
		Application.processHooks('m:OnUploadersReady');
	}
}

UploadsManager.onDebug = function (message) {
	if (!UploadsManager._debugMode) {
		return ;
	}

	var exceptionMessage, exceptionValues = [];

	// Check for an exception object and print it nicely
	if (typeof(message) === 'object' && typeof(message.name) === 'string' && typeof(message.message) === 'string') {
		for (var key in message) {
			if (message.hasOwnProperty(key)) {
				exceptionValues.push(key + ': ' + message[key]);
			}
		}
		exceptionMessage = exceptionValues.join("\n") || '';
		exceptionValues = exceptionMessage.split("\n");
		exceptionMessage = 'EXCEPTION: ' + exceptionValues.join("\nEXCEPTION: ");

		console.log(exceptionMessage);
	} else {
		console.log(message);
	}
};

if (!window.console || !console.firebug) {
	// emulate FireBug Console in other browsers to see flash debug messages
	window.console = {};
	window.console.log = function (message) {
		var console, documentForm;

		try {
			console = document.getElementById('SWFUpload_Console');

			if (!console) {
				documentForm = document.createElement('form');
				document.getElementsByTagName('body')[0].appendChild(documentForm);

				console = document.createElement('textarea');
				console.id = 'SWFUpload_Console';
				console.style.fontFamily = 'monospace';
				console.setAttribute('wrap', 'off');
				console.wrap = 'off';
				console.style.overflow = 'auto';
				console.style.width = '700px';
				console.style.height = '350px';
				console.style.margin = '5px';
				documentForm.appendChild(console);
			}

			console.value += message + "\n";

			console.scrollTop = console.scrollHeight - console.clientHeight;
		} catch (ex) {
			alert('Exception: ' + ex.name + ' Message: ' + ex.message);
		}
	};
}