/**
 * Ajax file uploader plugin with progress bar
 * Requires PHP 5.2 with APC extension enabled on server-side 
 * Requires jQuery for Ajax calls
 */

// create container namespace object
if(typeof AjaxFileUploader == 'undefined') {
	AjaxFileUploader = {};
}

AjaxFileUploader.init = function(file_input_id, progress_script_url, show_progress_bar, config) {
	
	// all errors thrown will be preffixed by this:
	this.errors_preffix = 'File uploader error - ';
	
	// UID delimiter - may be '-', or '_'
	this.uid_delimiter = '';
	
	// set and validate file input - existance, tag name, input type and form
	this.file_input = document.getElementById(file_input_id);
	if(!this.file_input || !this.file_input.type || !this.file_input.form || (this.file_input.tagName.toLowerCase() != 'input') || (this.file_input.type.toLowerCase() != 'file')) {
		throw new Error(this.errors_preffix + 'Invalid file input.');
	}
	
	// set container form
	this.form = this.file_input.form;
	
	// set progress bar script URL
	if(!progress_script_url) {
		throw new Error(this.errors_preffix + 'Progress watcher script not set.');
	}
	this.script_url = progress_script_url;
	
	// see if progress bar has to be shown
	this.show_progress_bar = show_progress_bar;
	
	// set config object to reference when needed
	if( !config ) {
		config = {};
	}
	this.config = {};
	
	// set defaults for config
	this.config.progressContainerClassName = config.progressContainerClassName || 'progressContainer'; // if this set from the start, will identify the proress bar container element
	if(!config.upload_statuses) {
		this.config.upload_statuses = {
			'waiting': 'Waiting to upload',
			'uploading': 'Uploading',
			'error': 'Invalid response from progress watcher',
			'complete': 'Upload Complete'
		}
	}
	this.config.start_tracking_timeout = config.start_tracking_timeout || 500;
	
	// extra data to be sent
	if(typeof config.extra_data != 'undefined') {
		this.config.extra_data = config.extra_data;
	} else {
		this.config.extra_data = {};
	}
	
	// reload on success ? default no
	if(typeof this.config.extra_data['reload_on_success'] != 'undefined' || typeof this.config.extra_data['reload_on_success_no_alert'] != 'undefined') {
		this.reload_on_success =  this.config.extra_data['reload_on_success'] || this.config.extra_data['reload_on_success_no_alert'];
		if( this.config.extra_data['reload_on_success_no_alert'] ) {
			this.reload_no_alert = true;
		} else {
			this.reload_no_alert = false; //just in case
			if(this.config.extra_data['reload_alert_message']) {
				this.reload_alert_msg = this.config.extra_data['reload_alert_message'];
			} else {
				this.reload_alert_msg = 'The new uploaded file will appear in the page after reload.\nPlease press OK to proceed.';
			}
		}
		this.success_msg = this.config.extra_data['success_msg'];	
	} else {
		this.reload_on_success = false;
		this.success_msg = ''; // just in case
		this.reload_no_alert = false; // just in case
	}
	
	//defined callback function
	//the callback function must return true in order for
	//reload mechanism to function
	if(typeof this.config.extra_data['callback_on_success'] != 'undefined' && typeof this.config.extra_data['callback_on_success'] == 'function')
	{
		this.callback_on_success = this.config.extra_data['callback_on_success'];
	}
	else
	{
		this.callback_on_success = false // just in case
	}
	
	if(typeof this.config.extra_data['while_uploading_msg'] != 'undefined') {
		this.uploading_msg = this.config.extra_data['while_uploading_msg'];
	} else {
		this.uploading_msg = 'Uploading file ... please wait!';
	}
	
	if(typeof this.config.extra_data['on_unload_alert'] != 'undefined') {
		this.show_unload_alert = true;
		this.on_unload_message = this.config.extra_data['on_unload_alert'];
	} else {
		this.show_unload_alert = false;
	}
	
	if(typeof config.loader_img != 'undefined') {
		this.loader_img = config.loader_img;
	} else {
		this.loader_img = 'images/upload_loader.gif';
	}
	
	// add the iframe
	if(!document.getElementById('uploadTargetFrame')) {
		this.appendIframe();
	} else {
		this.iframe = document.getElementById('uploadTargetFrame');
	}
	
	if(this.show_progress_bar == 1) {
		
		// add APC input to form
		this.addAPCInput();
		
		// add progress bar in UI
		this.addProgressBar();
		
		// start upload & eventually progress monitoring (if available and functional)
		this.startUpload();
	} else {
		// regular upload
		// add container where the "please wait..." message will be added
		this.addProgressBarContainer();
		$(this.progressContainer).show();
		// submit the form and disable from being submitted again while uploading.
		this.startUpload();
		// manage upload - show message and attach iframe onload handler to restore all when upload has finished.
		this.manageAjaxUpload();
	}
}

// adds hidden APC_UPLOAD_PROGRESS input with unique value - before the first input in the form
AjaxFileUploader.init.prototype.addAPCInput = function() {
	var apc_value = this.generateUid();
	this.apc_input = $('<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="'+apc_value+'"/>').get(0);
	var form_inputs = this.form.getElementsByTagName('input');
	// Note: the form has at least 1 input - the file input so we are assured that this exists (we can insert the hidden APC field before that)
	$(this.apc_input).insertBefore(form_inputs[0]);
}

// generated unique ID - e.g "CAEBA070_3A4E_6C37_8278_EB5B070BD09D"
AjaxFileUploader.init.prototype.generateUid = function() {
	var result, i, j;
	result = '';
	for(j=0; j<32; j++) {
		if( j == 8 || j == 12|| j == 16|| j == 20) {
			result = result + this.uid_delimiter;
		}
		i = Math.floor(Math.random()*16).toString(16).toUpperCase();
		result = result + i;
	}
	return result;
}

/*
 * Adds the following HTML to the structure
  	<span class="progressContainer" style="display: block;">
		<span class="progressBackground">
			<span class="progressBar"/></span>
			<span class="progressMessage"></span>
		</span>
	</span>
*/
AjaxFileUploader.init.prototype.addProgressBar = function() {
	// add progress bar container if necessary
	this.addProgressBarContainer();
	// add the progress bar and message, too
	if($('.progressBackground').size() == 0) {
		// add progress background element
		this.progressBackground = $('<span class="progressBackground"></span>').css({'display':'block', 'height':'10px'}).get(0);
		this.progressContainer.appendChild(this.progressBackground);
		// add progress bar and message elements
		this.progressBar = $('<span class="progressBar"></span>').css({'display':'block'}).get(0);
		this.progressBackground.appendChild(this.progressBar);
		this.progressMessage = $('<span class="progressMessage"></span>').get(0);
		this.progressBackground.appendChild(this.progressMessage);
	}
}

AjaxFileUploader.init.prototype.addProgressBarContainer = function() {
	// locate progress bar container, if any
	var $progressContainer = $('.'+this.config.progressContainerClassName, this.form);
	if($progressContainer.size() > 0) {
		this.progressContainer = $progressContainer.hide().get(0);
	} else {
		this.progressContainer = $('<span class="'+this.config.progressContainerClassName+'" style="display: block; text-align: center;"></span>').get(0);
		// append it to the form
		this.form.appendChild(this.progressContainer);
		$(this.progressContainer).hide();
	}
}

AjaxFileUploader.init.prototype.appendIframe = function() {
	var uploadTargetFrame = document.createElement('div');
	$(uploadTargetFrame).attr('id', 'uploadTargetFrameContainer');
	uploadTargetFrame.innerHTML = '<iframe name="uploadTargetFrame" id="uploadTargetFrame"></iframe>';
	$(uploadTargetFrame).css({
		'width':'0px',
		'height':'0px',
		'border':'0px',
		'display':'none',
		'zIndex':'-1'
	});
	document.body.appendChild(uploadTargetFrame);
	// Modify the target of the form to be the new iframe in order to prevent the page from reloading.
	this.form.setAttribute('target','uploadTargetFrame');
	this.iframe = $('#uploadTargetFrame').get(0);
}

AjaxFileUploader.init.prototype.startUpload = function() {
	
	var _this = this;
	
	// submit attached form
	this.form.submit();
	
	// prevent form from being submitted again while uploading
	
	// save current onsubmit function
	this.form_onsubmit = this.form.onsubmit;
	// override the submit handler
	this.form.onsubmit = function() {
		alert('There is an upload in progress. Please wait until it finishes.');
		return false;
	}
	
	// attach warning to file input ...
    // this.file_input.setAttribute('disabled','disabled');
    $(this.file_input).bind('mousedown', this.warning);
    
    // start progress tracking if the progress bar has to be shown, yeah!
    if(this.show_progress_bar) {
	    // initiate tracking of upload progress
	    // Initiate the bar at 0% and waiting.
		this.updateProgressBar('0%', 'waiting');
	    // IMPORTANT: for local tests, in IE is important to let little time before starting the progress tracking
		setTimeout(function(){_this.runProgressWatcher();}, this.config.start_tracking_timeout);
    }
    
    // set onunload alert if required so
    if( this.show_unload_alert ) {
    	window.onbeforeunload = function (evt) {
			if (typeof evt == 'undefined') {
				evt = window.event;
			}
			if (evt) {
				evt.returnValue = _this.on_unload_message;
			}
			return _this.on_unload_message;
		}
    }
    
}

AjaxFileUploader.init.prototype.updateProgressBar = function updateProgressBar(percent, upload_status, custom_msg) {
	$(this.progressMessage).removeClass('error');
	$(this.progressMessage).removeClass('complete');
	$(this.progressMessage).removeClass('waiting');
	$(this.progressMessage).removeClass('uploading');
	$(this.progressMessage).addClass(upload_status);
	var progress_msg = typeof custom_msg != 'undefined' ? custom_msg : this.config.upload_statuses[upload_status]; 
	$(this.progressMessage).html(progress_msg);
	// The CSS and className will take care of the status indications
	$(this.progressBar).css({'width':percent});
}

AjaxFileUploader.init.prototype.runProgressWatcher = function() {
	var _this = this;
	// create extra data params, if set
	var extra_data = '';
	if(typeof this.config.extra_data != 'undefined') {
		extra_data = '&' + this.serializeObject(this.config.extra_data);
	}
	// show the progress bar first
	$(this.progressContainer).show();
    // Request the progress script using the unique key
    $.ajax({
    	url: _this.script_url,
    	type: 'POST',
    	dataType: 'json',
    	data: 'key=' + _this.apc_input.value + '&ts=' + (new Date()).getTime() + extra_data,
    	success: function(response, status) {
			if( !response ) {
		        // There was an invalid response, or APC / RFC1867 is not enabled, maybe
		        // call common Ajax upload handler
		        _this.manageAjaxUpload();
			} else if(response.error) {
                // An error was reported by the server
                _this.updateProgressBar('0%', 'error', response.error);
                // The request is finished so clear warnings
                _this.clearWarnings();
                _this.restoreFormOnsubmit();
				_this.removeAddedElements();
            } else if(response.done == 1) {
                // The post has completed
                _this.updateProgressBar('100%', 'complete');
                // The request is finished so clear warnings
                _this.clearWarnings();
				_this.restoreFormOnsubmit();
				_this.removeAddedElements();
				_this.showCompletionNotification();
            } else {
            	// not complete yet, must update progress bar to show the current upload progress
            	// Update the progress bar and return the 
                // result. The result will be null so the
                // return will simply stop execution of the
                // rest of the method.
                var crt_percentage = Math.round(response.current/response.total*100);
                _this.updateProgressBar(
                    crt_percentage+'%',
                    'uploading',
					crt_percentage+'% of ' + response.currentFileName + ' uploaded'
                );
                
                // Execute the progress watcher again
                setTimeout(function(){_this.runProgressWatcher.call(_this);}, 1000);
            } 
    	},
    	error: function(XHR, status, err_thrown) {
    		// TODO - to be completed after all works OK
    	}
    });
}

// Disable the form file element by alerting a warning message
AjaxFileUploader.init.prototype.warning = function(jqEvent) {
    jqEvent.preventDefault();
    alert('There is an upload in progress. Please wait until it finishes.');
}

// Create a function to re-enable the form after the upload
    // is complete. This will be called from within the Ajax
    // event listeners
AjaxFileUploader.init.prototype.clearWarnings = function() {
    // Remove the warning from the form elements
    $(this.file_input).unbind('mousedown');
    
    // Update the ID and form with a new ID number 
    // so that the next upload will not conflict with 
    // this one
//  var uniqueID = this.generateUid();
//	this.apc_input.value = uniqueID;
}

AjaxFileUploader.init.prototype.restoreFormOnsubmit = function() {
//	var form = this.form;
	this.form.onsubmit = this.form_onsubmit;
	this.form_onsubmit = function(){};
}

AjaxFileUploader.init.prototype.removeAddedElements = function() {
	this.form.removeAttribute('target');
	document.body.removeChild($('#uploadTargetFrameContainer').get(0));
	this.apc_input.parentNode.removeChild(this.apc_input);
}

AjaxFileUploader.init.prototype.serializeObject = function(obj) {
	var serialized_obj = '';
	var comma = '';
	for(var p in obj) {
		serialized_obj = comma + p + '=' + encodeURIComponent(obj[p]);
		comma = '&';
	}
	return serialized_obj;
}

AjaxFileUploader.init.prototype.manageAjaxUpload = function() {
	// show Please wait... message + loader image eventually
	$(this.progressContainer).html('<img src="'+this.loader_img+'" style="vertical-align:middle;margin-right:5px;";>' + this.uploading_msg);
	// attach onload /onreadystatechange(for IE) event to iframe
    this.iframe.upl_obj = this;
    this.iframe.onload = this.iframe.onreadystatechange = function() {
    	if( this.readyState && (this.readyState != 'complete') ) {
    		return;
    	}
    	// reset onunload alert
	    if( this.upl_obj.show_unload_alert ) {
    		window.onbeforeunload = function(){};
    	}
    	$(this.upl_obj.progressContainer).html('');
    	this.upl_obj.restoreFormOnsubmit();
    	this.upl_obj.clearWarnings();
    	this.upl_obj.showCompletionNotification(true, this.upl_obj.reload_on_success);
		this.upl_obj.resetUploadForm();
	}
}

// the method can be called within the iframe or not - shows DONE message and reloads the page
AjaxFileUploader.init.prototype.showCompletionNotification = function(from_iframe, do_reload) {
	if(typeof do_reload == 'undefined') {
		do_reload = false;
	}
	if(from_iframe) {
		// show upload result
		var iframe_doc;
		if ( this.iframe.contentDocument ) { // DOM
			iframe_doc = this.iframe.contentDocument;
		} else if ( this.iframe.contentWindow ) { // IE win
			iframe_doc = this.iframe.contentWindow.document;
		}
		
		var upload_result_msg = iframe_doc.getElementById('upload_result').innerHTML;
		$('.progressContainer').html(upload_result_msg);
		
	} else {
		alert('File upload has completed.\nPress OK to reload the page!');
	}
	
	// callback function ?
	var callback_success = true;
	if(this.callback_on_success)
	{
		if(iframe_doc.getElementById('upload_extra_data'))
		{
			var data = iframe_doc.getElementById('upload_extra_data').innerHTML;
		}
		else
		{
			var data = '';
		}
		callback_success = this.callback_on_success(data);
	}
	
	// reload ?
	// alert('do_reload:'+do_reload+', callback success='+callback_success);
	if(do_reload && callback_success) {
		var location_href = '';
		if(from_iframe) {
			if($.trim(upload_result_msg) == this.success_msg) {
				if( !this.reload_no_alert ) {
	    			alert(this.reload_alert_msg);
				}
				location_href = parent.location.href;
				parent.location.href = location_href;
				// parent.location.reload();
			}
		} else {
			location_href = location.href;
			location.href = location_href;
			//location.reload();
		}
	}
}

AjaxFileUploader.init.prototype.resetUploadForm = function() {
	this.form.reset();
}