// this js class name is hardcoded in flash object :(
var SWFUpload = function () {};
SWFUpload.instances = {};

function Uploader(id, params) {
	this.id = id;
	this._moved = false; // flash was moved outside scroll container

	// normalize params
	if (isNaN(parseInt(params.multiple))) {
		// ensure that maximal file number is greather then zero
		params.multiple = 1;
	}

	params.allowedFilesize = this._normalizeFilesize(params.allowedFilesize);

	// set params to uploader
	this._eventQueue = [];
	this.uploadCancelled = false;

	this.params = params;
	this._ensureDefaultValues();

	this.files_count = 0;
	this.files = new Array();
	this.deleted = new Array()

	this.uploadURL = params.uploadURL;
	this.deleteURL = params.deleteURL;

	this._resetCounters();
}

/* ==== Private methods ==== */
Uploader.prototype._ensureDefaultValues = function() {
	// Upload backend settings
	var $defaults = {
		baseUrl: '',
		uploadURL : '',
		useQueryString : false,
		requeueOnError : false,
		httpSuccess : '',
		filePostName : 'Filedata',
		allowedFiletypes : '*.*',
		allowedFiletypesDescription : 'All Files',
		allowedFilesize : 0, // Default zero means "unlimited"
		multiple : 0,
		fileQueueLimit : 0,
		buttonImageURL : '',
		buttonWidth : 1,
		buttonHeight : 1,
		buttonText : '',
		buttonTextTopPadding : 0,
		buttonTextLeftPadding : 0,
		buttonTextStyle : 'color: #000000; font-size: 16pt;',
		buttonAction : parseInt(this.params.multiple) == 1 ? -100 : -110, //  SELECT_FILE  : -100, SELECT_FILES : -110
		buttonDisabled : true, //false,
		buttonCursor : -1, // ARROW : -1, HAND : -2
		wmode : 'transparent', // "window", "transparent", "opaque"
		buttonPlaceholderId: false
	}

	for (var $param_name in $defaults) {
		if (this.params[$param_name] == null) {
//			console.log('setting default value [', $defaults[$param_name], '] for missing parameter [', $param_name, '] instead of [', this.params[$param_name], ']');
			this.params[$param_name] = $defaults[$param_name];
		}
	}
}

Uploader.prototype._normalizeFilesize = function($file_size) {
	var $normalize_size = parseInt($file_size);
	if (isNaN($normalize_size)) {
		return $file_size;
	}

	// in kilobytes (flash doesn't recognize numbers, that are longer, then 9 digits)
	return $normalize_size / 1024;
}

Uploader.prototype._prepareFiles = function() {
	var ids = '';
	var names = '';
	for (var f = 0; f < this.files.length; f++) {
		if (isset(this.files[f].uploaded) && !isset(this.files[f].temp)) {
			continue;
		}

		ids += this.files[f].id + '|'
		names += this.files[f].name + '|'
	}

	ids = ids.replace(/\|$/, '', ids);
	names = names.replace(/\|$/, '', names);
	document.getElementById(this.id+'[tmp_ids]').value = ids;
	document.getElementById(this.id+'[tmp_names]').value = names;
	document.getElementById(this.id+'[tmp_deleted]').value = this.deleted.join('|');
}

Uploader.prototype._getMicroTime = function() {
	var $now = new Date();
	return Math.round($now.getTime() / 1000); // because miliseconds are returned too
}

Uploader.prototype._getEstimatedTime = function() {
	return Math.ceil((100 - this.ProgressPercent) * this.ProgressTime / this.ProgressPercent);
}

Uploader.prototype._formatTime = function ($seconds) {
	$seconds = parseInt($seconds);

	var $minutes = Math.floor($seconds / 60);
	if ($minutes < 10) $minutes = '0' + $minutes;
	$seconds = $seconds % 60;
	if ($seconds < 10) $seconds = '0' + $seconds;

	return $minutes + ':' + $seconds;
}

Uploader.prototype._formatSize = function (bytes) {
	var kb = Math.round(bytes / 1024);
	if (kb < 1024) return kb+'Kb';
	var mb = Math.round(kb / 1024 * 100)/100;
	return mb+'Mb';
}

Uploader.prototype._executeNextEvent = function () {
	var  f = this._eventQueue ? this._eventQueue.shift() : null;
	if (typeof(f) === 'function') {
		f.apply(this);
	}
};

/* ==== Public methods ==== */
Uploader.prototype.init = function() {
	if (this.params.buttonPlaceholderId !== false) {
		// use given container
		var holder = document.getElementById(this.params.buttonPlaceholderId);
	}
	else {
		// create container on the fly
		var holder = document.createElement('DIV');
		document.body.appendChild(holder);
	}

	if (UploadsManager.useTransparency) {
		document.getElementById($form_name).style.display = 'block';
	}

	// moving out progress div to overcome loosing of flash object after setting opacity
	this.div = document.getElementById(this.id+'_progress');
	var clone = this.div.cloneNode(true);
	this.div.parentNode.removeChild(this.div)
	this.div = document.body.appendChild(clone);
	this.IconPath = this.params.IconPath ? this.params.IconPath : '../admin_templates/img/browser/icons';

	this.filename = document.getElementById(this.id+'_progress_filename');
	this.progress = document.getElementById(this.id+'_progress_progress');
	this.elapsed = document.getElementById(this.id+'_progress_elapsed');
	this.remaining = document.getElementById(this.id+'_progress_remaining');
	this.percent = document.getElementById(this.id+'_percent');
	this.done = document.getElementById(this.id+'_done');

	// initialize flash object
	this.flash_id = UploadsManager._nextFlashId();

	// add callbacks for every event, because none of callbacks will work in other case (see swfupload documentation)
	SWFUpload.instances[this.flash_id] = this;
	SWFUpload.instances[this.flash_id].flashReady = function () { UploadsManager.onFlashReady(this.id); };
	SWFUpload.instances[this.flash_id].fileDialogStart = UploadsManager.onHandleEverything;
	SWFUpload.instances[this.flash_id].fileQueued = UploadsManager.onFileQueued;
	SWFUpload.instances[this.flash_id].fileQueueError = UploadsManager.onFileQueueError;
	SWFUpload.instances[this.flash_id].fileDialogComplete = UploadsManager.onHandleEverything;

	SWFUpload.instances[this.flash_id].uploadStart = UploadsManager.onUploadStart;
	SWFUpload.instances[this.flash_id].uploadProgress = UploadsManager.onUploadProgress;
	SWFUpload.instances[this.flash_id].uploadError = UploadsManager.onUploadError;
	SWFUpload.instances[this.flash_id].uploadSuccess = UploadsManager.onHandleEverything;
	SWFUpload.instances[this.flash_id].uploadComplete = UploadsManager.onUploadComplete;
	SWFUpload.instances[this.flash_id].debug = UploadsManager.onDebug;

	this.swf = new SWFObject(this.params.baseUrl + '/swfupload.swf', this.flash_id, this.params.buttonWidth, this.params.buttonHeight, '9', '#FFFFFF');
	this.swf.setAttribute('style', '');
	this.swf.addParam('wmode', escape(this.params.wmode));

	this.swf.addVariable('movieName', escape(this.flash_id));
	this.swf.addVariable('fileUploadLimit', escape(this.params.multiple));
	this.swf.addVariable('fileQueueLimit', escape(this.params.fileQueueLimit));
	this.swf.addVariable('fileSizeLimit',  escape(this.params.allowedFilesize)); // in kilobytes
	this.swf.addVariable('fileTypes',  escape(this.params.allowedFiletypes));
	this.swf.addVariable('fileTypesDescription',  escape(this.params.allowedFiletypesDescription));
	this.swf.addVariable('uploadURL',  escape(this.params.uploadURL));

	// upload button appearance
	this.swf.addVariable('buttonImageURL', escape(this.params.buttonImageURL));
	this.swf.addVariable('buttonWidth', escape(this.params.buttonWidth));
	this.swf.addVariable('buttonHeight', escape(this.params.buttonHeight));
	this.swf.addVariable('buttonText', escape(this.params.buttonText));
	this.swf.addVariable('buttonTextTopPadding', escape(this.params.buttonTextTopPadding));
	this.swf.addVariable('buttonTextLeftPadding', escape(this.params.buttonTextLeftPadding));
	this.swf.addVariable('buttonTextStyle', escape(this.params.buttonTextStyle));
	this.swf.addVariable('buttonAction', escape(this.params.buttonAction));
	this.swf.addVariable('buttonDisabled', escape(this.params.buttonDisabled));
	this.swf.addVariable('buttonCursor', escape(this.params.buttonCursor));

	if (UploadsManager._debugMode) {
		this.swf.addVariable('debugEnabled', escape('true')); // flash var
	}

	if (this.params.buttonPlaceholderId === false || !UploadsManager.useTransparency) {
		// only write flash, when button placeholder is not used
		this.swf.write(holder);
		this.flash = document.getElementById(this.flash_id);
	}

	if (this.params.urls != '') {
		var urls = this.params.urls.split('|');
		var names = this.params.names.split('|');
		var sizes = this.params.sizes.split('|');
		for (var i = 0; i < urls.length; i++) {
			var a_file = {
				id : names[i],
				name : names[i],
				url : urls[i],
				size: sizes[i],
				uploaded : 1
			}
			this.files.push(a_file)
			this.files_count++;
		}
		this.updateInfo();
	}
}

Uploader.prototype.moveOutside = function() {
	// move flash outside scroll_container, but keeps it's position on screen
	if (!UploadsManager.useTransparency || this._moved) {
		// moving only needed when transparency us used (e.g. in admin)
		return ;
	}

	var $new_container = document.createElement('DIV');
	$new_container.id = this.params.buttonPlaceholderId + '_outside';
	$new_container.style.position = 'absolute';

	var $old_container = document.getElementById(this.params.buttonPlaceholderId);
	$new_container.style.top = getRealTop($old_container) + 'px';
	$new_container.style.left = getRealLeft($old_container) + 'px';

	var $holder_dimensions = getDimensions($old_container);
	$new_container.style.width = $holder_dimensions.innerWidth + 'px';
	$new_container.style.height = $holder_dimensions.innerHeight + 'px';

	document.body.appendChild($new_container);

	this.swf.write($new_container); // write flash outside scroll_container
	this.flash = document.getElementById(this.flash_id); // fix reference to flash object

	this._moved = true;
}

Uploader.prototype.remove = function() {
	var id = this._moved ? this.params.buttonPlaceholderId + '_outside' : this.params.buttonPlaceholderId;

	var obj = document.getElementById(id);
	if (obj/* && obj.nodeName == "OBJECT"*/) {
		var u = navigator.userAgent.toLowerCase();
		var p = navigator.platform.toLowerCase();
		var windows = p ? /win/.test(p) : /win/.test(u);
		var $me = this;

		if (document.all && windows) {
			obj.style.display = "none";
			(function(){
				if (obj.readyState == 4) {
					$me.removeObjectInIE(id);
				}
				else {
					setTimeout(arguments.callee, 10);
				}
			})();
		}
		else {
			obj.parentNode.removeChild(obj);
		}
	}
}

Uploader.prototype.removeObjectInIE = function(id) {
	var obj = document.getElementById(id);
	if (obj) {
		for (var i in obj) {
			if (typeof obj[i] == 'function') {
				obj[i] = null;
			}
		}
		obj.parentNode.removeChild(obj);
	}
}

Uploader.prototype.syncBrowseButton = function() {
	// when flash is moved outside scroll_container, keeps it's position on screen during scroll operations
	if (!this._moved) {
		return ;
	}

	var $scroll_container = UploadsManager._getScrollContainer();
	var $scroll_container_top = getRealTop($scroll_container);
	var $scroll_container_left = getRealLeft($scroll_container);

	var $scroll_top = $scroll_container.scrollTop;
	var $scroll_left = $scroll_container.scrollLeft;

	var $old_container = document.getElementById(this.params.buttonPlaceholderId);
	var $new_container = document.getElementById(this.params.buttonPlaceholderId + '_outside');

	var $old_container_top = getRealTop($old_container);
	var $old_container_left = getRealLeft($old_container);

	if ($scroll_container_top <= $old_container_top - $scroll_top) {
		// prevents moving outside $scroll_container
		$new_container.style.top = ($old_container_top - $scroll_top) + 'px';
	}
	else {
		// move browse button outside visible area
		$new_container.style.top = -this.params.buttonHeight + 'px';
	}

	if ($scroll_container_left <= $old_container_left - $scroll_left) {
		// prevents moving outside $scroll_container
		$new_container.style.left = ($old_container_left - $scroll_left) + 'px';
	}
	else {
		// move browse button outside visible area
		$new_container.style.left = -this.params.buttonWidth + 'px';
	}
}

Uploader.prototype.updateInfo = function() {
	var $o = '';
	var $icon = '';
	var $filename = '';
	var $delete_code = '';

	for (var f = 0; f < this.files.length; f++) {
		this.files[f].name.match(/\.([^.]*)$/);
		var ext = RegExp.$1.toLowerCase();
		$icon = ext.match(/^(ai|avi|bmp|cs|dll|doc|dot|exe|fla|gif|htm|html|jpg|js|mdb|mp3|pdf|ppt|rdp|swf|swt|txt|vsd|xls|xml|zip)$/) ? ext : 'default.icon';
		$icon = '<img src="' + this.IconPath + '/' + $icon + '.gif"/>&nbsp;';

		if (isset(this.files[f].uploaded)) {
			$filename = '<a href="' + this.files[f].url + '" target="_new">' + this.files[f].name + '</a> (' + this._formatSize(this.files[f].size) + ')';
			$delete_code = 'UploadsManager.DeleteFile(\'' + this.id + '\', \'' + this.files[f].name + '\')';
		}
		else {
			$filename = this.files[f].name + ' (' + this._formatSize(this.files[f].size) + ')';
			$delete_code = 'UploadsManager.CancelFile(\'' + UploadsManager._getUploader(this.files[f]).id + '\', \'' + this.files[f].id + '\')';
		}

		$o += '<tr><td>' + $icon + '</td><td style="font: 11px arial, sans-serif;">' + $filename + '&nbsp;[<a href="javascript:' + $delete_code + '">Delete</a>]</td></tr>';
	}

	document.getElementById(this.id+'_queueinfo').innerHTML = '<table cellpadding="0" cellspacing="0">' + $o + '</table>';
	this._prepareFiles();

	// sync position of all uploaders below current, because file queue height change will not affect their positions
	UploadsManager.iterate('syncBrowseButton', 'timeout:0');
}

Uploader.prototype.removeFile = function (file) {
	var n_files = new Array();
	var count = 0;
	var $new_total = 0;

	for (var f = 0; f < this.files.length; f++) {
		if (this.files[f].id != file.id && this.files[f].name != file.id) {
			n_files.push(this.files[f]);
			if (!isset(this.files[f].uploaded)) {
				$new_total += file.size;
			}
			count++;
		}
	}

	if (this.StartTime == 0) {
		// don't update total during upload, because that breaks progress bar
		this.total = $new_total;
	}

	this.files = n_files;
	this.files_count = count;
	this.updateInfo();
}

Uploader.prototype.hasQueue = function() {
	for (var f = 0; f < this.files.length; f++) {
		if (isset(this.files[f].uploaded)) {
			continue;
		}

		return true;
	}

	return false;
}

Uploader.prototype.startUpload = function() {
	UploadsManager.uploadCancelled = this.uploadCancelled = false;

	if (!this.hasQueue()) {
		return;
	}

	if (UploadsManager.useTransparency) {
		Request.setOpacity(30, UploadsManager._getFormContainer());
	}

	if (!document.all) {
		var $winW = window.innerWidth;
		var $winH = window.innerHeight;
	}
	else {
		var $winW = window.document.body.offsetWidth;
		var $winH = window.document.body.offsetHeight;
	}

	var left = Math.round(($winW - 350)/2)+'px';
	var top = Math.round(($winH - 110)/2)+'px';

	this.div.style.top = top;
	this.div.style.left = left;
	this.div.style.display = 'block';

	if (UploadsManager.useTransparency) {
		Request.setOpacity(100, this.div);
	}

	this.StartTime = this._getMicroTime();
	this.ProgressPercent = 0; // progress percent
	this.ProgressTime = new Array();

	this.uploaded = 0;
	this.total = 0;
	for (var f = 0; f < this.files.length; f++) {
		if (isset(this.files[f].uploaded)) {
			// get total bytes of non-uploaded files
			continue;
		}
		this.total += this.files[f].size;
	}

 	this.callFlash('StartUpload');
}

Uploader.prototype.cancelUpload = function() {
	this.callFlash('StopUpload');

	var $stats = this.callFlash('GetStats');
	while ($stats.files_queued > 0) {
		this.callFlash('CancelUpload');

		$stats = this.callFlash('GetStats');
	}

	UploadsManager.uploadCancelled = this.uploadCancelled = true;
}

Uploader.prototype.UploadFileStart = function(file) {
	this.filename.innerHTML = file.name;

	this.callFlash('AddFileParam', [file.id, 'field', this.params.field]);
	this.callFlash('AddFileParam', [file.id, 'id', file.id]);
	this.callFlash('AddFileParam', [file.id, 'flashsid', this.params.flashsid]);

	UploadsManager.iterate('disableBrowse', true); // disable all "Browse" buttons (not just for current uploader)!

	// we can prevent user from adding any files here :)
	this.callFlash('ReturnUploadStart', [true]);
}

Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) {
	this.cur_file_uploaded = bytesLoaded;
	var uploaded = this.uploaded + this.cur_file_uploaded;
	this.ProgressTime = this._getMicroTime() - this.StartTime;

	var speed = 0;
	if (this.ProgressTime > 0) {
		speed = Math.round(uploaded / this.ProgressTime * 100) / 100;
	}

	this.progress.innerHTML = this._formatSize(uploaded) + ' / ' + this._formatSize(this.total) + ' (' + this._formatSize(speed) + '/s)';
	this.ProgressPercent = Math.round(uploaded / this.total * 100);
	this.done.style.width = this.ProgressPercent + '%';
	this.percent.innerHTML = this.ProgressPercent + '%';

	this.elapsed.innerHTML = this._formatTime(this.ProgressTime );
	this.remaining.innerHTML = this._formatTime( this._getEstimatedTime() );
}

Uploader.prototype.UploadFileComplete = function(file) {
	this.uploaded += this.cur_file_uploaded;
	for (var f = 0; f < this.files.length; f++) {
		if (this.files[f].id == file.id) {
			this.files[f].uploaded = 1;
			this.files[f].temp = 1;
			this.files[f].url = this.params.tmp_url.replace('#ID#', file.id).replace('#FILE#', file.name).replace('#FIELD#', this.params.field);
		}
	}
	this.updateInfo();

	// upload next file in queue
	var $stats = this.callFlash('GetStats');
	if ($stats.files_queued > 0 && !UploadsManager.uploadCancelled) {
		this.callFlash('StartUpload');
	} else {
		// all files in queue are uploaded OR upload was cancelled globally
		if (UploadsManager.uploadCancelled) {
			// when upload was cancelled globally -> cancel it for any other uploader
			this.cancelUpload();
		}
		UploadsManager.UploadQueueComplete(this);
	}
}

Uploader.prototype.queueEvent = function (function_body) {
	// Warning: Don't call this.debug inside here or you'll create an infinite loop
	var self = this;

	// Queue the event
	this._eventQueue.push(function_body);

	// Execute the next queued event
	setTimeout(
		function () {
			self._executeNextEvent();
		}, 0
	);
};

// Private: callFlash handles function calls made to the Flash element.
// Calls are made with a setTimeout for some functions to work around
// bugs in the ExternalInterface library.
Uploader.prototype.callFlash = function (functionName, argumentArray) {
	argumentArray = argumentArray || [];

	var returnValue;

	if (typeof this.flash[functionName] === 'function') {
		// We have to go through all this if/else stuff because the Flash functions don't have apply() and only accept the exact number of arguments.
		if (argumentArray.length === 0) {
			returnValue = this.flash[functionName]();
		} else if (argumentArray.length === 1) {
			returnValue = this.flash[functionName](argumentArray[0]);
		} else if (argumentArray.length === 2) {
			returnValue = this.flash[functionName](argumentArray[0], argumentArray[1]);
		} else if (argumentArray.length === 3) {
			returnValue = this.flash[functionName](argumentArray[0], argumentArray[1], argumentArray[2]);
		} else {
			throw 'Too many arguments';
		}

		// Unescape file post param values
		if (returnValue != undefined && typeof returnValue.post === 'object') {
			returnValue = this.unescapeFilePostParams(returnValue);
		}

		return returnValue;
	} else {
//		alert('invalid function name: ' + functionName);
		throw "Invalid function name: " + functionName;
	}
};

// Private: unescapeFileParams is part of a workaround for a flash bug where objects passed through ExternalInterface cannot have
// properties that contain characters that are not valid for JavaScript identifiers. To work around this
// the Flash Component escapes the parameter names and we must unescape again before passing them along.
Uploader.prototype.unescapeFilePostParams = function (file) {
	var reg = /[$]([0-9a-f]{4})/i;
	var unescapedPost = {};
	var uk;

	if (file != undefined) {
		for (var k in file.post) {
			if (file.post.hasOwnProperty(k)) {
				uk = k;
				var match;
				while ((match = reg.exec(uk)) !== null) {
					uk = uk.replace(match[0], String.fromCharCode(parseInt("0x" + match[1], 16)));
				}
				unescapedPost[uk] = file.post[k];
			}
		}

		file.post = unescapedPost;
	}

	return file;
};

Uploader.prototype.onFlashReady = function() {
	this.disableBrowse(false);
	UploadsManager.iterate('syncBrowseButton');
}

Uploader.prototype.disableBrowse = function($disabled) {
	if ($disabled === undefined) {
		$disabled = true;
	}

	this.queueEvent(
		function() {
			this.callFlash('SetButtonDisabled', [$disabled]);
		}
	);
}

Uploader.prototype._resetCounters = function() {
	this.StartTime = 0; // time, when upload was started
	this.ProgressPercent = 0; // upload progress in percents
	this.ProgressTime = 0; // flash upload process callback times
	this.total = 0; // total bytes to upload (from all queued files)
	this.uploaded = 0; // total uploaded bytes (from all queued files)
}

Uploader.prototype.finalizeUpload = function() {
	// hide progress bar only of uploader, that completed it's queue
	this.div.style.display = 'none';
	this._resetCounters();
}