/***
 * This is the csresponse handler class
 */

/**
 * This or JS class than handles data exchange between the controller and the browser.
 *
 * the reason for this is to have a unified way of dealing with data exchanged
 * between the client and the controller.
 *
 * The goal: is to avoid code replication
 *
 * by avoiding code replicaiton we mean:
 *  Having a single implementation that deals with common ajax usecases,
 *  for controller and client data exchange and representation.
 *  Prevent multiple implementations of that target this same task.
 *
 * So how does it work:
 *
 * there are some helper functions that should be as similar as possible to standard prototype calls,
 * the helper functions use the CSResponseHandler class that simply extends prototype to use our simple
 * data excahange format
 * (To read about our simple data exchange format, please read CSResponse documentation on the wiki)
 *
 * helper functions:
 *
 *   -csr(service_url, csr_params);
 *
 *   where:
 *      service_url		corresponds to the service url or path, like /example/givemeyourmoney
 *      csr_params		all params that you can define on prototype like: onSuccess, onFailure,..
 *						plus other params:
 *						data: direct POST data to send to the controller
 *						encoded_data: urlencoded or json encoded data to pass to the controller
 *						debug_toolbar: if you want to log info about his action in the toolbar (true is default)
 *						target: if you want to speciy a target ID for where the contents of your response should go
 *
 *   -a_csr();
 *   this function call csr with the given href of a link
 *		example: <a href="/example/give_me_the_money" onclick="javascript:a_csr">
 *		         is equivalent to:
 *		         <a href="/example/give_me_the_money" onclick="javascript:csr('/example/give_me_the_money')">
 *		IT comes very handy when you want to bind a listener to urls
 *		
 */
var CSResponseHandler = {};

/**
 * get a form values in a format ready for encoded_data csr paramater
 */
CSResponseHandler.get_form_object_values_encoded = function (form)
{
	if(!form)
		return false;
	var serialized_data = form.serialize(true);

	for (iterator in serialized_data) {
		if (iterator.indexOf('[') != -1) {

			serialized_data = CSResponseHandler.parse_element_name_to_object(iterator, serialized_data);
		}
	}
	return serialized_data;
}

/**
 * make the element name an object
 */
CSResponseHandler.parse_element_name_to_object  = function(element_name, object) {

	key = element_name.substr(0, element_name.lastIndexOf('['));
	key2 = element_name.substring(element_name.lastIndexOf('[') + 1, element_name.lastIndexOf(']'));

	if (!object[key]) {
		object[key] = {};
	}
	if (!object[key][key2]) {
		object[key][key2] = {};
	}
	if (typeof(object[element_name]) == 'object') {
		for (i in object[element_name]) {
			object[key][key2][i] = object[element_name][i];
		}
	} else {
		object[key][key2] = object[element_name];
	}

	delete(object[element_name]);

	if (key.indexOf('[') != -1) {
		object = CSResponseHandler.parse_element_name_to_object(key, object);
	}
	return object;
}

// if you want to disable hashing globally, you can enable this variable
CSResponseHandler.no_hash = false;



/**
 * hashhandler deals with a hashing so the previous and forward on ajax would still work
 */
var CSHashHandler = Class.create({
	hash: '', // The current hash as known by the HashHandler instance. To compare with the real one pulled from the window.location.hash.
	hash_chunks: {}, // The chunks used to create the current hash.
	hash_targets: {}, // The targets used with each hash_chunk.
	updating: null, // Whether an ajax call is in the process of updating the hash.

	initialize: function () {
		this.updating = false;
		this.hash = '';
		thisObj = this;		
		setTimeout(function () {thisObj.check_hash_change();}, 1000);
	},

	check_hash_change: function() {
		var data_strings = {};
		var params = {};
		var memo = null;
		// If we are not in the middle of updating the hash and the currently stored hash does not match the window.location.hash, process the difference.
		if (!this.updating && this.hash != window.location.hash) {
			updated_hash_chunks = window.location.hash.replace('#', '').split('|'); // Get the hash chunks from window.location.hash
			
			this.updating = true;
			var delegate_update = false;
			if (updated_hash_chunks != '') {
				for (i in updated_hash_chunks) {
					// Stop us from looping trhough any funcitons... we just want properties.
					if (updated_hash_chunks[i] instanceof Function) {
						break;
					}

					var service_url = updated_hash_chunks[i].split('?')[0]; // Get the service URL of this chunk.
					if( updated_hash_chunks[i].split('?')[1] )
						data_strings = updated_hash_chunks[i].split('?')[1].split('&'); // Get the data and params of this chunk.
					else
						data_strings = {};
					var data = {};
					params = {};
					for (j in data_strings) {
						// Stop us from looping trhough any funcitons... we just want properties.
						if (data_strings[j] instanceof Function) {
							break;
						}
						key = data_strings[j].split('=')[0];
						if (key == 'd') {
							data = eval('('+data_strings[j].split('=')[1]+')');
						} else if (key == 'p') {
							params = eval('('+data_strings[j].split('=')[1]+')');
						} else if(key =='du'){
							delegate_update = eval('('+data_strings[j].split('=')[1]+')');
						}
					}

					// As a backup check, see if there is already a stored chunk for this service URL where target is not defined. If so, use the new one.
					//if ((typeof(this.hash_chunks[service_url]) == 'undefined' || this.hash_chunks[service_url] != updated_hash_chunks[i]) && typeof(params.parameters.target) == 'undefined') {
						this.hash_chunks[service_url] = updated_hash_chunks[i];
						// let the observers of the event deal with the screen update. this is a work-around the fact that we can't store the callback functions in the URL.
						memo = $H({'service_url': service_url, 'data':data, 'params': params});
						document.fire('CSHashHandler:updated', memo);
						
					//}
				}
			} else {
				// THE BELOW CODE ISN'T WORKING!
				for (i in this.hash_chunks) {

					if(this.hash_chunks[i].split('?')[1]){
						data_strings = this.hash_chunks[i].split('?')[1].split('&'); // Get the data of this chunk.
					}
					
					
					for (j in data_strings) {
						// Stop us from looping trhough any funcitons... we just want properties.
						if (data_strings[j] instanceof Function) {
							break;
						}
						key = data_strings[j].split('=')[0];
						if (key == 'p') {
							params = eval('('+data_strings[j].split('=')[1]+')');
						}
					}

					// let the observers of the event deal with the screen update. this is a work-around the fact that we can't store the callback functions in the URL.
					memo = $H({'service_url': service_url, 'data':null, 'params': params});
					document.fire('CSHashHandler:updated', memo);
				}
			}
			this.hash = window.location.hash; // Set hash to the current hash value from window.location.
			this.updating = false;
		}

		thisObj = this;
		setTimeout(function () {thisObj.check_hash_change();}, 1000);
	},

	add_hash_chunk: function (service_url, data, params, delegate_update) {
		var get_params_array = [];
		get_params_array.push('d='+Object.toJSON(data));
		get_params_array.push('p='+Object.toJSON(params));
		if(delegate_update){
			get_params_array.push('du='+delegate_update);	
		}

		target = params.target;
		if (target) {
			// Remove any other hash_chunks with this target.
			for (i in this.hash_targets) {
				if (this.hash_targets[i] == target) {
					delete this.hash_targets[i];
					delete this.hash_chunks[i];
				}
			}

			this.hash_targets[service_url] = target; // Store target.
		}

		this.hash_chunks[service_url] = service_url+'?'+get_params_array.join('&'); // Create the new chunk, overwriting the old one keyed to that service_url if it exists.

		this.updating = true; // Set updating to true, we don't want to check while we're in the middle of updating the hash.
		var hash_array = [];
		for (i in this.hash_chunks) {
			hash_array.push(this.hash_chunks[i]);
		}
		this.hash = '#'+hash_array.join('|');
		window.location.hash = this.hash;
		this.updating = false; // Set updating to false again, allowing updates to continue.
	}
});

var global_hash_handler = null;
document.observe("dom:loaded", function() {
	global_hash_handler = new CSHashHandler(); // Create a global_hash_handler, which will immediately start polling for hash changes.
});

/**
 * call a function by argument
 */
var dispatch = function(fn, args) {
	fn = (typeof fn == "function") ? fn : window[fn];  // Allow fn to be a function object or the name of a global function
	return fn.apply(this, args || []);  // args is optional, use an empty array by default
}

var loading_info_box_original_top = null; // Stores the initial offset of the ajax_loading_info box, to compare against.
var sticky_loading_info_box = function(event, id) {
	// IF initial offset not set, get it. We have to temporarily show the element for this to work.
	if (loading_info_box_original_top === null) {
		$(id).show();
		loading_info_box_original_top = $(id).cumulativeOffset().top;
		$(id).hide();
	}
	var document_top = document.viewport.getScrollOffsets().top;
	// Compare document top with original box top to see if we need it to be sticky.
	if (document_top > loading_info_box_original_top){
		$(id).addClassName('stick')
	} else {
		$(id).removeClassName('stick');
	}
}

document.observe('dom:loaded', function() {
	// if no laoding info field nowhere to place the sticky_loading info
	if ($('ajax_loading_info')) {
		// Set event handler to make the ajax_loading_info box sticky if the user scrolls down.
		Event.observe(document, 'scroll', sticky_loading_info_box.bindAsEventListener(this, 'ajax_loading_info'));
		sticky_loading_info_box(null, 'ajax_loading_info'); // Initialy position it
	}

});



