/**
 * DadaPRO common library:
 * Javascript library with useful methods used throughout the codebase
 * version 1.0.0
 * Depends on jQuery >= 1.2.6
 *
 * A lot of work still remains to do
 */


/**
 * Singleton object definition, contains all the methods
 */
dp_common = {
	modalCounter: 0,
	/**
	 * This function should be called on every 'submit' event: it makes the
	 * gray layer (that MUST have been defined in the footer) appear.
	 * If callback is a function, after the visualization of the layer is called
	 */
	waitForNewPage: function (callback)
	{
		jQuery('#grayLayer').
		css('opacity',0.1). // Almost completely transparent
		fadeIn('fast',callback );
		this.modalCounter += 1;
		return true;
	},
	resumeThisPage: function ()
	{
		this.modalCounter -= 1;
		if (this.modalCounter <= 0)
		{
			jQuery('#grayLayer').
			css('opacity',0.1). // Almost completely transparent
			fadeOut('fast');
			this.modalCounter = 0;
		}
		return true;
	},
   
   /**
    * Binds an user-generated event generated on a set of DOM elements to a
    * function or a method of an object.
    * @param event (string): event to bind; it must be supported
    * 						by jQuery (blur, change, click, dblclick, focus,
    * 						key*, mouse*, resize, scroll, select, submit); other
    * 						events installed by plugins, such as mousewheel, are
    * 						accepted.
    * @param elements (jQuery result): a set of elements that will be the target
    * 						of the generated events (i.e. if the event is
    * 						'click', the elements that when clicked will trigger
    * 						the execution)
    * @param method: (string or function): if string, the name of the method
    * 						that will be invoked when the event triggers,
    * 						otherwise the function that will be executed: in
    * 						both cases, three parameters will be passed: the DOM
    * 						element target of the event, the Event object itself
    * 						and optionally some context data (see below)
    * @param object: (Javascript object): if method is a string, this parameter
    * 						must not be null, and must be an Object having a
    * 						method with that name.
    * @param contextData (any): additional data that will be passed to the
    * 						method, can be anything depending on the context
    * 						needs.
    * @param exclusive (boolean): if true, all the handlers for the given event
    * 						are detached before attaching the new one, so it
    * 						becomes the exclusive handler for the event
    * Examples:
    *   dp_common.bindEvent('click', jQuery('a'), 'anchorClicked', myObject);
    * Every time the user clicks on an anchor tag, the method anchorClicked of
    * myObject will be called. The method will receive the anchor DOM object as
    * first parameter and the Event object as second.
    *   dp_common.bindEvent('blur', jQuery('input .control'), checkFunction, null, 2);
    * Every time an input with 'control' class will lose the focus, the
    * checkFunction function will be called with three parameters: the input,
    * the blur event and 2.
    */
   bindEvent: function(event, elements, method, object, contextData, exclusive)
	{
		if (exclusive)
			elements.unbind(event);
		elements.each(
			function()
			{
				jQuery(this)[event](function(eventObject) {
					if (object != null)
					{
						return object[method](this, eventObject, contextData);
					}
					else
					{
						return method(this, eventObject, contextData);
					}
					} );
			}
		);
	},
	/**
	 * Utility for bindEvent: accepts an array of [event, elements, method,
	 * object, contextData] arrays that will be bound
	 */
	bindEvents: function(eventsList)
	{
		for (var event in eventsList)
		{
			event = eventsList[event];
			dp_common.bindEvent(event[0], event[1], event[2], event[3], event[4], event[5]);
		}
	},
	/**
	 * Searches the specified text in a set of elements, hiding those that do
	 * not contain it and emphasizing all the matches, putting the matched text
	 * inside spans with 'matchesSearch' class.
	 * @param elements (jQuery result): set of elements that will be traversed
	 * @param filter (string)
	 */
	textFilter: function(elements, filter, subquery)
	{
		// Removes previous matches
		elements.find('.matchesSearch').each(function()
											   {
												jQuery(this).replaceWith(jQuery(this).text());
											   })
		if (filter != "")
		{
			if (subquery == null)
			{
				subquery = ' ';
			}
			// Escapes some regexp special characters to prevent faults
			filter = filter.replace("\\", '\\\\').replace("[", "\\[").replace("(", "\\("); 

			var selector = subquery + ':not(:has(*))', // Searches trough leafs belonging to subquery
				regExp = new RegExp("(" + filter + ")", 'ig');
			elements.each(
				function() {
					parent = this;
					parent.style.display = 'none';
					jQuery(selector, this).each(function() {
						var text = jQuery(this).text(),
							newVal = text.replace(regExp, '<span class="matchesSearch">$1</span>');
						if (newVal != text)
						{
							jQuery(this).html(newVal);
							parent.style.display = '';
						}
					});
				}
			);
		}
		else elements.css('display', '');
	},
	/**
	 * Sorts a set of DOM elements.
	 * The function takes an element and sorts all its children. If a child is a
	 * DOM tree and we want to sort over a certain element or attribute, we must
	 * specify a jQuery selector that will be used to get the value to use as
	 * sorting key.
	 * @param root: a jQuery result matching a DOM element parent of all the
	 * 				sortable elements.
	 * @param compareChild: a jQuery selector (string) used to retrieve the
	 * 				values to sort on.
	 * @param order: integer: if > 0 descending otherwise ascending
	 *
	 * Example:
	 * suppse to have a table such as:
	 * <table id="tb1">
	 *   <tr><td>a</td><td class="sort">3</td></tr>
	 *   <tr><td>b</td><td class="sort">2</td></tr>
	 *   <tr><td>b</td><td class="sort">1</td></tr>
	 * </table>
	 *
	 * If we want to sort the rows by the value of the second column we call:
	 *  dp_common.sortHtml(jQuery('#tb1'), 'td .sort', 1)
	 */
	sortHtml: function(root, compareChild, order)
	{
		var comparator = function(el1, el2)
		{
			var val1 = jQuery(compareChild, el1).text().toUpperCase(),
				val2 = jQuery(compareChild, el2).text().toUpperCase();
			if (order > 0)
				return val1 > val2;
			else
				return val1 <= val2;
		},
			list = root.children().get();
		root.children().remove();
		list.sort(comparator);
		jQuery.each(list, function(index, row){root.append(row)});
	},
	/**
	 * Utility function that calls sortHTML for a generic table: element should
	 * be a th or td: the sort is performed on all the element of the same row
	 * under the tbody section (so that the header is not sorted out).
	 * The extra parameter is an array containing two keys:
	 *  - 'table': the table DOM element;
	 *  - 'callback': an optional function that will be called after the sorting
	 *                (with element, evant, table as parameters)
	 */
	sortTable: function (element, event, data)
	{
		var table = data.table,
			callback = data.callback,
			elem = jQuery(element),
			ascending = elem.hasClass('ascending');
		dp_common.waitForNewPage(function(){
			
			jQuery('.sort', table).removeClass('ascending').removeClass('descending');
			var order = null,
				column = elem.parent().children().index(element);
			
			// Selecting order
			if (ascending)
			{
				order = -1;
				elem.addClass('descending');
			}
			else
			{
				order = 1;
				elem.addClass('ascending');		
			}
			
			dp_common.sortHtml(jQuery('tbody', table), 'td:eq(' + column + ')', order);
			if (typeof(callback) == 'function')
				callback(table);
			dp_common.resumeThisPage();
		});

	},
	__stripeRows: function(visibleRows)
	{
		visibleRows.filter(':even').
		removeClass('odd').addClass('even');
					
		visibleRows.filter(':odd').
					removeClass('even').addClass('odd');
		visibleRows.removeClass('last').removeClass('last');
		visibleRows.filter(':first').addClass('first');
		visibleRows.filter(':last').addClass('last');
	},
	/**
	 * Sets 'even' class to all the even visible rows of the given table
	 * (element parameter), furthermore, sets 'odd' class to all the odd rows,
	 * then sets 'first' and 'last' to the starting and ending visible row.
	 * Does not affects invisible rows.
	 * Affects only rows under the 'tbody' sections
	 */
	stripeTable: function(element, event)
	{
		var visibleRows = jQuery('tbody tr:visible', element);
		dp_common.__stripeRows(visibleRows);
		return true;
	},
	/**
	 * Sets 'even' class to all the even visible lines of the given list
	 * (element parameter), furthermore, sets 'odd' class to all the odd lines,
	 * then sets 'first' and 'last' to the starting and ending visible lines.
	 * Does not affects invisible lines.
	 * Affects only lines (li) under the root elment sections
	 */
	stripeList: function(element, event)
	{
		var visibleRows = jQuery(element).children('li:visible');
		dp_common.__stripeRows(visibleRows);
		return true;
	},
	/**
	 * Generic validator: accepts a DOM element that sholud be an input with
	 * the 'wl-validate' custom attribute. This attribute specifies the type of
	 * validation that will be applied to the field and can be one of the
	 * following:
	 * 	* email: the input must contain a valid email address: the check is
	 * 	 		regexp-based, no domain lookup is made. An empty string is also
	 * 	 		a valid value.
	 * 	* email-list: list of email addresses separated by semicolon (;), blank
	 * 			spaces can be put before or after the semicolon
	 * 	* re: the input will be validated against the regular expression given
	 * 			in the 'wl-re' attribute
	 * 	* ip: the input must contain a valid ip address: the check tests each
	 * 	 		part of input string, so there are possible to set ip from
	 * 	 		0.0.0.0	to 255.255.255.255
	 * 	* byte: the input must contain a number between 0 and 255.
	 * 	* ns : the input must contain a valid Name System. The NS is formed by
	 * 	 		threee string separated by two points.The length of the last
	 * 	 		string must be atleast two characters.
	 * 	* aname : the input must contain a valid address name. The address name
	 * 			should not be formed by special characther like :, ', ?, etc.
	 * 	* custom: a custom method, specified by the 'wl-method' attribute will
	 * 	        be called. The object will be the resultHandler parameter (see
	 * 	        below), and only the input will be passed. The method should
	 * 	        return a boolean
	 * If the element has also the attribute 'wl-required', an empty string is
	 * considered an unvalid value.
	 * @param element: the DOM input element to be validated: must have the
	 * 			'wl-validate' attribute.
	 * @param event: the Event triggering the validation. Parameter added so
	 * 			that this method can be used as an event handler in bindEvent
	 * @param resultHandler: an object that will informed about the result of
	 * 			the validation: it has to expose two methods, valid() and
	 * 			invalid(), that will be called with the element as parameter.
	 * 			If the element 'custom' is used as validation type, the
	 * 			resultHandler must also implement the 'wl-method' method.
	 */
	validateInput: function(element, event, resultHandler)
	{
		var type = element.getAttribute('wl-validate'),
			required = element.getAttribute('wl-required'),
			value = null,
			emailRegexp = /^$|([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6})/i,
			regexp, emails, is_valid, i, method, splitted;
		
		switch (element.tagName.toLowerCase())
		{
			case 'input':case 'textarea':
				value = element.value;
				break;
			case 'select':
				value = element.options[element.selectedIndex].value;
				break;
			default:
				alert(element.tagName);
		}
		
		if (required != null && value == '')
		{
			resultHandler.invalid(element);
			return;
		}
		
		
		switch (type)
		{
			case 're':
				// Validate as regular expression
				regexp = new RegExp(element.getAttribute('wl-re'));
				if (value.match(regexp))
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			case 'email':
				// Validate as email
				if (value.match(emailRegexp))
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			case 'email-list':
				emails = value.split(';');
				is_valid = true;
				for (i in emails)
				{
					email = jQuery.trim(emails[i]);
					if (is_valid && !email.match(emailRegexp))
					{
						resultHandler.invalid(element);
						is_valid = false;
					}
				}
				if (is_valid)
					resultHandler.valid(element);
				
				break;
			case 'ip':
				// Validate as ip address
				regexp = /^$|^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
				is_valid = true;
				if (value.match(regexp)) {
					splitted = value.split(".");
					for (i in splitted)	{
						if (splitted[i] > 255) {
						   is_valid = false;
						}
					}
				}
				else
				  is_valid = false;
					
				if (is_valid)
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			case 'byte':
				// Validate as part of ip address
				regexp = /^$|^(\d{1,3})$/;
				is_valid = true;
				if (value.match(regexp)) {
					if (value > 255) {
						is_valid = false;
					}
				}
				else
				  is_valid = false;
					
				if (is_valid)
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;			
			case 'ns':
				// Validate as NS (Name System)
				regexp = /^$|^[a-z0-9\-\_]+\.([a-z0-9\-\_]+\.)*[a-z0-9\-\_]+\.[a-z]{2,4}(\.[a-z]{2})?$/i;
				if (value.match(regexp))
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			case 'aname':
				// Validate as Address Name (A name)
				regexp = /^$|^[a-z0-9-\.\*]/;
				if (value.match(regexp))
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			case 'custom':
				// Custom validation
				method = element.getAttribute('wl-method');
				if (resultHandler[method](element))
					resultHandler.valid(element);
				else
					resultHandler.invalid(element);
				break;
			default:
				// No validation, return
				return;
		}
	},
	/**
	 * Delayed validator: same as generic validator;
	 * The validation stars after 3 sec from event. 
	 */
	delayedValidateInputTimer : -1,
	delayedValidateInput: function(element, event, resultHandler)
	{
		if (dp_common.delayedValidateInputTimer!= -1)
		{
			// C'e' gia' una richiesta in attesa
			clearTimeout(dp_common.delayedValidateInputTimer);
			dp_common.delayedValidateInputTimer = -1;
		}
		dp_common.delayedValidateInputTimer = setTimeout(
				function(){dp_common.validateInput(element, event, resultHandler);}, 3000
				);
	},
	/**
	 * Helper function that binds the 'keyup' event of a set of elements (ideally
	 * input, selects etc.) to the validateInput method and the given result
	 * handle.
	 * Example:
	 * 	dp_common.bindValidation(jQuery('.validable input'), errorEmphasize);
	 * Is equivalent to:
	 *  dp_common.bindEvent('keyup', jQuery('.validable input'), 'validateInput', dp_common, errorEmphasize);
	 *
	 */
	bindValidation: function(selector, resultHandler, confirm)
	{
		this.bindEvent('blur', selector, 'validateInput', this, resultHandler);
		this.bindEvent('keyup', selector, 'delayedValidateInput', this, resultHandler);
		
		if (confirm != null)
		{
			var validatingClosure = function()
			{
				jQuery(selector).each(
					function()
					{
						dp_common.validateInput(this, null, resultHandler);
					}
					
				)
			};
			this.bindEvent('click', confirm, validatingClosure);
		}
	},
	

	doModal: function(element, event, formId)
	{
		var panel = jQuery(formId);
		this.waitForNewPage();
		panel.css({display: '', height:'0px',width:'0px'}).animate({height:'600px', width:'800px'});
	},
	
	exitModal: function(element, event, formId)
	{
		var panel = jQuery(formId);
		panel.animate({height:'0px', width:'0px'}, 'linear', function() {jQuery(this).css('display', 'none'); dp_common.resumeThisPage();});
	},
	
	
	/**
	 * Publish/subscribe user message methods: an object interested in knowing
	 * the messages for the user should register as messageHandler and implement
	 * the 'handleMessage' method properly
	 */
	messageHandlers: [],
	registerMessageHandler: function(handler)
	{
		this.messageHandlers.push(handler);
	},
	info: function(message)
	{
		for (var id in this.messageHandlers)
			this.messageHandlers[id].handleMessage('info', message);
	},
	warning: function(message)
	{
		for (var id in this.messageHandlers)
			this.messageHandlers[id].handleMessage('warning', message);
	},
	error: function(message)
	{
		for (var id in this.messageHandlers)
			this.messageHandlers[id].handleMessage('error', message);
	},
	/**
	 * Publish/subscribe debug tracking methods: an object interested in logging
	 * the debug messages should register as debugLogger and implement the
	 * 'debugLog' method properly
	 */
	debugLoggers: [],
	registerDebugLogger: function(logger)
	{
		this.debugLoggers.push(logger);
	},
	debugLog: function(message)
	{
		for (var id in this.debugLoggers)
			this.debugLoggers[id].debugLog(message);
	},
	/**
	 * Calls using AJAX an AsyncModule page and parses the result, filling the
	 * content of an HTML container (if present) and calling a method of the
	 * passes object (if present) at the end. The method is called with the
	 * eval'd JSON code embedded in the response.
	 * If the response is an array containing any of the following keys:
	 * 'dp_info','dp_warning','dp_error'
	 * the corresponding message signaling method is invoked and the key is
	 * removed from the response.
	 * 
	 * @param url the url of the Async Module
	 * @param params dictionary with the POST parameters of the call
	 * @param container jQuery selector (string or DOM element) to fill with
	 * 		the response of the script
	 * @param object an object which method will be invoked
	 * @param method (string) the name of the method to be called at the end.
	 * 		The only parameter of the method is data, an object obtained by
	 * 		eval'ing the server response. (@see php: AsyncModule::encodedResult)
	 * 
	 */
	asyncLoad: function(url, params, container, object, callback)
	{
		var cb = function(data)
		{
			dp_common.resumeThisPage();
			dp_common.debugLog('input asyncLoad');
			dp_common.debugLog(data);
			var re = /\[H\](.+)\[\/H\]/,
				json = data.match(re),
				response = null;
			if (json != null && json[1] != null)
			{
				eval("var tmp= " + json[1]);
				response = tmp;
  			dp_common.debugLog('tmp asyncLoad');
				dp_common.debugLog(tmp);
				if (response.dp_info != null)
				{
					dp_common.info(response.dp_info);
					response.dp_info = null;
				}
				if (response.dp_warning != null)
				{
					dp_common.warning(response.dp_warning);
					response.dp_warning = null;
				}
				if (response.dp_error != null)
				{
					dp_common.error(response.dp_error);
					response.dp_error = null;
				}
			};
			if (container != null)
				jQuery(container).html(data);
			if (object != null && callback != null)
			{
				object[callback](response);
			}
		}
		this.waitForNewPage();
		jQuery.post(url, params, cb);
	}
}

