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

function Uploader(id, params) {
	this.id = id;

	// 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.flashReady = 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.enableUploadButton();
}

/* ==== 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,
		thumb_format: '',
		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._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() {
	this.IconPath = this.params.IconPath ? this.params.IconPath : '../admin_templates/img/browser/icons';

	// 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.onUploadSuccess;
	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', encodeURIComponent(this.params.wmode));

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

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

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

	var $me = this;

	Application.setHook(
		'm:OnAfterFormInit',
		function () {
			$me.renderBrowseButton();
		}
	)

	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 : 'uploaded_' + crc32(names[i]),
				name : names[i],
				url : urls[i],
				size: sizes[i],
				uploaded : 1,
				progress: 100
			};

			this.files.push(a_file);
			this.files_count++;
		}

		this.updateInfo();
	}
}

Uploader.prototype.enableUploadButton = function() {
	var $me = this;

	// enable upload button, when flash is fully loaded
	this.queueEvent(
		function() {
			setTimeout(
				function () {
					$me.callFlash('SetButtonDisabled', [false]);
				}, 0
			)
		}
	);
}

Uploader.prototype.renderBrowseButton = function() {
	var holder = document.getElementById(this.params.buttonPlaceholderId);
	this.swf.write(holder);

	this.flash = document.getElementById(this.flash_id);
}

Uploader.prototype.remove = function() {
	var id = 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.isImage = function($filename) {
	$filename.match(/\.([^.]*)$/);
	var $ext = RegExp.$1.toLowerCase();

	return $ext.match(/^(bmp|gif|jpg|jpeg|png)$/);
}

Uploader.prototype.getFileIcon = function($filename) {
	$filename.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';
	return this.IconPath + '/' + $icon + '.gif';
}

Uploader.prototype.getQueueElement = function($file) {
	var $ret = '';
	var $icon_image = this.getFileIcon($file.name);
	var $file_label = $file.name + ' (' + this._formatSize($file.size) + ')';
	var $need_preview = false;

	if (isset($file.uploaded)) {
		// add deletion checkbox
		$need_preview = (this.params.thumb_format.length > 0) && this.isImage($file.name);
		$ret += '<div class="left delete-checkbox"><input type="checkbox" class="delete-file-btn" checked/></div>';

		// add icon based on file type
		$ret += '<div class="left">';

		if ($need_preview) {
			$ret += '<a href="' + $file.url + '" target="_new"><img class="thumbnail-image" large_src="' + $file.url + '&thumb=1" src="' + $icon_image + '" alt=""/></a>';
		}
		else {
			$ret += '<img src="' + $icon_image + '"/>';
		}

		$ret += '</div>'

		// add filename + preview link
		$ret += '<div class="left file-label"><a href="' + $file.url + '" target="_new">' + $file_label + '</a></div>';
	}
	else {
		// add icon based on file type
		$ret += '<div class="left"><img src="' + $icon_image + '"/></div>';

		// add filename
		$ret += '<div class="left file-label">' + $file_label + '</div>';

		// add empty progress bar
		$ret += '<div id="' + $file.id + '_progress" class="progress-container left"><div class="progress-empty"><div class="progress-full" style="width: 0%;"></div></div></div>';

		// add cancel upload link
		$ret += '<div class="left"><a href="#" class="cancel-upload-btn">Cancel</a></div>';
	}

	$ret += '<div style="clear: both;"/>';
	$ret = $('<div id="' + $file.id + '_queue_row" class="file' + ($need_preview ? ' preview' : '') + '">' + $ret + '</div>');

	// set click events
	var $me = this;

	$('.delete-file-btn', $ret).click(
		function ($e) {
			$(this).attr('checked', UploadsManager.DeleteFile($me.id, $file.name) ? '' : 'checked');
		}
	);

	$('.cancel-upload-btn', $ret).click(
		function ($e) {
			UploadsManager.CancelFile(UploadsManager._getUploader($file).id, $file.id);
			return false;
		}
	);

	// prepare auto-loading preview
	var $image = $('img.thumbnail-image', $ret);

	if ($image.length > 0) {
		var $tmp_image = new Image();
		$tmp_image.src = $image.attr('large_src');

		$($tmp_image).load (
			function ($e) {
				$image.attr('src', $tmp_image.src).addClass('thumbnail');
			}
		);
	}

	return $ret;
}

Uploader.prototype.updateQueueFile = function($file_index, $delete_file) {
	$queue_container = $( jq('#' + this.id + '_queueinfo') );

	if ($delete_file !== undefined && $delete_file) {
		$( jq('#' + this.files[$file_index].id + '_queue_row') ).remove();

		if (this.files.length == 1) {
			$queue_container.css('margin-top', '0px');
		}
		return ;
	}

	$ret = this.getQueueElement( this.files[$file_index] );
	var $row = $( jq('#' + this.files[$file_index].id + '_queue_row') );

	if ($row.length > 0) {
		// file round -> replace
		$row.replaceWith($ret);
	}
	else {
		// file not found - add
		$( jq('#' + this.id + '_queueinfo') ).append($ret);
		$queue_container.css('margin-top', '8px');
	}
}

Uploader.prototype.updateInfo = function($file_index, $prepare_only) {
	if ($prepare_only === undefined || !$prepare_only) {
		if ($file_index === undefined) {
			for (var f = 0; f < this.files.length; f++) {
				this.updateQueueFile(f);
			}
		}
		else {
			this.updateQueueFile($file_index);
		}
	}

	this._prepareFiles();
}

Uploader.prototype.updateProgressOnly = function ($file_index) {
	var $progress_code = '<div class="progress-empty" title="' + this.files[$file_index].progress + '%"><div class="progress-full" style="width: ' + this.files[$file_index].progress + '%;"></div></div>';

	$('#' + this.files[$file_index].id + '_progress').html($progress_code);
}

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

	var $to_delete = [];

	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]);
			count++;
		}
		else {
			$to_delete.push(f);
		}
	}

	for (var $i = 0; $i < $to_delete.length; $i++) {
		this.updateQueueFile($to_delete[$i], true);
	}

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

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() {
	this.uploadCancelled = false;

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

 	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');
	}

	this.uploadCancelled = true;
}

Uploader.prototype.UploadFileStart = function(file) {
	var $file_index = this.getFileIndex(file);
	this.files[$file_index].progress = 0;
	this.updateProgressOnly($file_index);

	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]);

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

Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) {
	var $file_index = this.getFileIndex(file);
	this.files[$file_index].progress = Math.round(bytesLoaded / bytesTotal * 100);
	this.updateProgressOnly($file_index);
}

Uploader.prototype.UploadSuccess = function(file, serverData, receivedResponse) {
	if (!receivedResponse) {
		return ;
	}

	for (var f = 0; f < this.files.length; f++) {
		if (this.files[f].id == file.id) {
			// new uploaded file name returned by OnUploadFile event
			this.files[f].name = serverData;
		}
	}
}

Uploader.prototype.UploadFileComplete = function(file) {
	// file was uploaded OR file upload was cancelled
	var $file_index = this.getFileIndex(file);

	if ($file_index !== false) {
		// in case if file upload was cancelled, then no info here
		this.files[$file_index].uploaded = 1;
		this.files[$file_index].progress = 100;
		this.files[$file_index].temp = 1;
		this.files[$file_index].url = this.params.tmp_url.replace('#ID#', file.id).replace('#FILE#', encodeURIComponent(file.name)).replace('#FIELD#', this.params.field);
		this.updateInfo($file_index);
	}

	// upload next file in queue
	var $stats = this.callFlash('GetStats');

	if ($stats.files_queued > 0) {
		this.callFlash('StartUpload');
	}
	else {
		UploadsManager.UploadQueueComplete(this);
	}
}

Uploader.prototype.getFileIndex = function(file) {
	for (var f = 0; f < this.files.length; f++) {
		if (this.files[f].id == file.id) {
			return f;
		}
	}

	return false;
}

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);

	if (!this.flashReady) {
		// don't execute any flash-related events, while it's not completely loaded
		return ;
	}

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

Uploader.prototype._executeQueuedEvents = function() {
	var $me = this;

	setTimeout(
		function () {
			$me._executeNextEvent();

			if ($me._eventQueue.length > 0) {
				$me._executeQueuedEvents();
			}

		}, 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() {
	var $me = this;
	this.flashReady = true;

	// process events, queued before flash load
	this._executeQueuedEvents();
}