/*
 * javascript utility functions.
 * Depends on jquery 1.4.2 or newer.
 * 
 * Copyright 2010, Nobel
 * 
 */


//-- start Extend jQuery 

var jQueryLoad; // the old (real) load function (used internally by the new "$.fn.load" function)
if (!jQueryLoad) {
	jQueryLoad = jQuery.fn.load; 
}

jQuery.fn.extend({

	/**
	 * Sets the content of the current select control. Assumes JSON content in a format like :
	 * <code> 
	 * {"data":[["10","Value 1"],["11","value 2"]]}
	 * </code>
	 * Code sample (minimal): 
	 * 	<code>  
	 * 	  data = {"data":[["10","Value 1"],["11","value 2"]]};
	 *	  $('select[id=SomeId]').setData({data: data});
	 * 	</code> 
	 * @param config.data the JSon data with the new content.
	 * @param config.onFilterOption (optional) the call-back  used to filter the data. Input: the current row; returns true if the element should be included;
	 * @param config.onFillOption (optional, defaults to "defaultFillOptionCallback") the call-back  used to fill an option.
	 * @param config.root (optional, default to "data") the name of the JSon root property.
	 * @param config.optionNoneText (optional) if specified, adds an empty option (value -1) with this text. This will be the first option in the select.
	 * @param config.selectedIDs (optional) the selected IDs.   
	 */
	setData: function(config) {
    	return this.each(
    			function() { 
    				var currentSelect = this;
    			    var fillOptionCallback;
    				if (!config.onFillOption) {
    					fillOptionCallback = defaultFillOptionCallback;
    				} else {
    					fillOptionCallback = config.onFillOption;
    				}
    				if (!config.root) {
    					config.root = "data";
    				}
    				if (!config.data[config.root]) {
    					alert("Internal error. JSon property not found: [" + config.data + "]");
    				}
    				$(this).clear();
    				if (config.optionNoneText) {
    					var noneOption = document.createElement('option');
    					noneOption.value = -1;
    					noneOption.text = config.optionNoneText;
    					addSelectControlOption(this, noneOption);
    				}
    				jQuery.each(config.data[config.root], function() {
    					var skip = false;
    					if (config.onFilterOption) {
    						skip = config.onFilterOption(this) != true;
    					}
    					if (!skip) {
	    					// add new options
	    					var currentOption = document.createElement('option');
	    					// execute the call-back function for the current option
	    					fillOptionCallback(currentOption, this, config.selectedIDs);
	    					// add option to the select
	    					addSelectControlOption(currentSelect, currentOption);
    					}
    				});
				}
    	);
	},
	
	/**
	 * Sets a "busy message" to the specified select control: clears the 
	 * select and adds an option with value "-1" and the text specified by the "busyMessage" parameter. <br/>
	 * Code sample: 
	 * 	<code>  
	 *	 $('select[id=someSelectId').setBusyMessage('Loading...');
	 * 	</code> 
	 * @param busyMessage the message to set.
	 */
	setBusyMessage: function(busyMessage) {
		return this.each(
    			function() { 
					if (!busyMessage) {
						busyMessage = "Loading...";
					}
					$(this).clear();
					var currentOption = document.createElement('option');
					currentOption.value = -1;
					currentOption.text = busyMessage;
					addSelectControlOption(this, currentOption);
    			}
		);
    },
	
	/**
	 * Clears the select with the specified id.
	 * Code sample: 
	 * 	<code>  
	 *	 $('select[id=someSelectId']).clear();
	 * 	</code> 
	 */
	clear: function() {
    	return this.each(
			function() {
				this.options.length = 0; // remove previous options
			}
    	)
    },
    
    /**
     * the old (real) load function (used internally by the new "load" function)
     */
    jQueryLoad: jQueryLoad,
    
    /**
     * Loads the current select control from the server. Assumes JSON content in a format like :
     * <code> 
     * {"data":[["10","Value 1"],["11","value 2"]]}
     * </code>
     * Code sample (minimal): 
     * 	<code>  
     *	 $('select[id=someSelectId']).load({
     *       url: "/someURL.htm",
     *       data: {id: 10}
     *   });
     * 	</code> 
     * @param config.url the URL to load from
     * @param config.data the parameter to send to the specified URL
     * @param config.busyMessage (optional) a message to display until the remote data is received.
     * @param config.root (optional, default to "data") the name of the JSon root property.
     * @param config.selectedIDs (optional) the selected IDs.   
     * @param config.onFillOption (optional, defaults to "defaultFillOptionCallback") the call-back  used to fill an option.
     * @param config.optionNoneText (optional) if specified, adds an empty option (value -1) with this text. This will be the first option in the select.
     * @param config.onSuccess callback for successful processing.
     * @param config.onError callback for error while making the Ajax request.
     * @param compat1 used when calling default jQuery function (as the second parameter).
     * @param compat2 used when calling default jQuery function (as the third parameter).
     */
    load: function(config, compat1, compat2) {
    	return this.each(
			function() { 
				var isSelect = false;
				if (this.type) {
					if (this.type == 'select-one' || this.type == 'select-multiple') {
						isSelect = true;
					}
				}
				if ( !isSelect ) {
					// default
					if (compat1) {
						if (compat2) {
							return $(this).jQueryLoad(config, compat1, compat2);
						} else {
							return $(this).jQueryLoad(config, compat1);
						}
					} else {
						return $(this).jQueryLoad(config);
					}
				}
	        	var currentSelect = this;
	        	if (config.busyMessage) {
	        		$(this).setBusyMessage(config.busyMessage);
	        	} else { 
	        		$(this).clear();
	        	}
	        	$.ajax({
	    			type: "GET",
	    			url: config.url,
	    			data: (config.data),
	    			success: function(data) {
	        			$(currentSelect).setData({
	        					data: data, root: config.root, selectedIDs: config.selectedIDs, 
	        					onFillOption: config.onFillOption, optionNoneText: config.optionNoneText
	        			});
	    				if (config.onSuccess) {
	    					config.onSuccess();
	    				}
	    			},
	    			error: function(data, options, error) {
	    				if (config.onError) {
	    					config.onError(data, options, error);
	    				}
	    			}
	    		});
			}
    	)
    }
});

//__ end Extend jQuery

//__ start extend java-script

/**
 * Allow array filtering by providing a boolean function(element, index, array)
 */
if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}

//__ end extend java-script

/**
 * Transform an array into another array using a custom function(element, index, array)
 */
function transformArray(array, transform) {
	var len = array.length;
	if (typeof transform != "function") {
  		throw new TypeError();
	}
	var res = new Array();
	var thisp = arguments[1];
	for (var i = 0; i < len; i++) {
		if (i in array) {
			var val = array[i]; // in case transform mutates this
			res.push(transform.call(thisp, val, i, array));
  		}
	}
	return res;
}

/**
 * The default parse function to be used with "load" for select elements.
 */
var defaultFillOptionCallback = function(currentOption, source, selectedIDs) {
	currentOption.value = source[0];
	currentOption.text = source[1];
	// set selected option 
	if (selectedIDs) {
		if (contains (selectedIDs, source[0])) {
			currentOption.selected = true;
		}
	}
};

/**
 * Specific parse function to be used with with "load" for select elements.
 * Will also add the data to the option.
 */
var defaultFillOptionWithDataCallback = function(currentOption, source, selectedIDs) {
	defaultFillOptionCallback(currentOption, source, selectedIDs);
	currentOption.data = source[2];
};

/**
 * Function that adds an option to a select HTML component.
 * @param selectControl the HTML select control.
 * @param selectOption the HTML select option to be added.
 */
function addSelectControlOption(selectControl, selectOption) {
	try {
		selectControl.add(selectOption, null); //add new option to the end
	} catch(e) { //in IE, try the below version instead
		selectControl.add(selectOption); //add new option to the end
	}
}

/**
 * Gets the element with the specified id. 
 * @param id the id of the element to obtain.
 * @return the element with the specified id.
 */
function selectById(id) { 
	var elements = $("select[id='"+id+"']");
	if (elements.length > 0) {
		return elements[0];
	} else {
		return undefined;
	}
}

/**
 * Gets or sets the a select box value. <br />
 * (as set) If 'value' is specified, it sets the select value to that value and returns the new value.
 * (as get) If 'value' is not specified, it gets the select value. 
 * @param id the select element id.
 * @param value the value to set or undefined for get.
 * @return the new value or undefined if the select was not found.
 */
function selectVal(id, value) {
	var elements = $("select[id='"+id+"']");
	if (elements.length > 0) {
		if (value) { // set
			elements.val(value);
			return elements.val();
		} else { // get
			return elements.val();
		}
	} else {
		return undefined;
	}
}

var _uniqueCnt = 1;
function uniqueCnt() {
	 _uniqueCnt++;
	 return _uniqueCnt;
}

/**
 * Gets the select box display text (selected value). 
 * @param selectId the ID of the select.
 * @return the select box display text (selected value).
 */
function selectOptionText(selectId) {
	//	TODO test this code
	return $("select[id=\'" + selectId + "\ option:selected']").text();
}	

/**
 * Creates an quick search for an existing select. <br/>
 * The existing select is hidden and a new input field is displayed, along with a small 'show all' button. 
 * When the user types some text, a list with all the matching options is displayed. 
 * @param selectId the select id.
 * @param inputClass the CSS class of the new input.
 * @param inputId (optional) the new input id. 
 * @param searchButtonId (optional) the new search button id.
 * @return (void) 
 */
function createAutocomplete(selectId, inputClass, inputId, showAllButton) {
	if (!inputClass) {
		inputClass = "text100";
	}
	if (!inputId) {
		inputId = 'tags' + uniqueCnt();
	} 
	if (!showAllButton) {
		showAllButton = 'searchTags' + uniqueCnt();
	}
	var select = $("select[id=\'" + selectId + "\']");
	if (select.length == 0) {
		return; // do not throw exception, just ignore (this way we can use the function for selects with 'text' display mode)
	}
	var options = $("select[id=\'" + selectId + "\'] option");
	// create a DIV for the input box and the button 
	var div = $("<div></div>").addClass("ui-widget").insertAfter(select);
	 // create the input box
	div.append($("<input></input>").attr("id", inputId).addClass(inputClass).css('z-index', '5000'));
	var input = $("input[id='" + inputId + "']");
	// create the search button
	$("<button>&nbsp;</button>")
		.attr("title", "Show All Items").attr("id", showAllButton).insertAfter( input )
		.addClass("ui-widget-quick-search")
		.click(function(){
			input.autocomplete("search", "");
			input[0].focus();
		 });
	// get all options from the old select
	var items = [];
	options.each(function(i, selected) {
 		items[items.length] = $(selected).text(); 
	});	
	input.autocomplete({ 
			source: items, // all options from the old select
			minLength: 0, // also on 'down arrow key'
			delay: 0, // 'real-time'
			change: function(event, ui) {
					if ( !ui.item ) { 
						select.val(""); // remove invalid value, as it didn't match anything
						$(this).val(selectOptionText(selectId)); // put the selected option into search box because empty string might not be an option
						return false;
					}
			},
			select: function(event, ui) {
					options.each(function(i, selected) {
						if ( ui.item.value == $(selected).text() ) {
							select.val($(selected).val());
							valid = true;
							return false;
						}
					});
			}
	});
	input.width(select.width() - 20);
	input.val(selectOptionText(selectId));
	select.toggle();
}

/**
 * Tests if this string ends with the specified suffix.
 * @param str
 * @param suffix   the suffix
 * @return <code>true</code> if the character sequence represented by the
 *         argument is a suffix of the character sequence represented by
 *         this object; <code>false</code> otherwise. Note that the
 *         result will be <code>true</code> if the argument is the
 *         empty string or is equal to this <code>String</code> object
 *         as determined by the "equals" method.
 */
function endsWith(str, suffix) {
  return str.match(suffix+"$") == suffix;
}

/**
 * Function called to check if an array contains an object.
 */
function contains(array, value){
	for(var i = 0; i < array.length; i++) {
		if(array[i] == value){
			return true;
		}
	}
	return false;
}

/**
 * Function called each time it is important to count the number of page's hits. 
 * @param urlToSend the requested URL.
 */
function incrementPageHits(urlToSend) {
	var requestedUrl = null;
	if (urlToSend) {
		requestedUrl = urlToSend;
	}

	// google analytics
	if (requestedUrl != null) {
		try	{ 
			if (typeof(pageTracker) != "undefined") {
				pageTracker._trackPageview(requestedUrl);
			}
		} catch (ignoreBecauseWeCannotHandleIt) {
			// nothing to do, the catch block is here in order to make sure that the method will be executed to its end
		}
	}
	
	// NS
	setTimeout( function(){
				$.ajax({
			        type: "GET",
			        url:  "/misc/tracking/incrementPageHits.htm",
			    	data: ({"requestedUrl": requestedUrl}),
				    success: function(data) {
						// nothing to do here
				    },
			        error: function(data, options, error) {
				    	// ignore any errors
			    	}
				})
	}, 50); // setTimeout is needed because jquery 1.5 cannot handle asynchronous requests in anchors with "return true";
	return true;
}

/**
 * Function which opens a pop-up window and embeds in that window a flash movie. 
 * This function relies on the fact that the jQuery library have already been imported.
 * @param videoFileLocation the URL of the video file.
 * @param width the video width.
 * @param height the video height.
 * @param popupTitle the popup window title.
 */	
function openVideoPopup(videoFileLocation, width, height, popupTitle) {
	var delta = 20; // the difference between flash size and pop-up size
	var popupWindow = window.open('','videoplayer','width=' + (width + delta) +',height=' + (height + delta) +',status=no');
	
    try {
	   popupWindow.focus();
    } catch (canNotSetFocus) {
	   // operation not supported
    }
    
    if (popupTitle) {
    	popupWindow.document.title = popupTitle;
    }
    
    $(popupWindow.document.body).html(
		'<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="' + width + '" height="' + height + '" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" ID="Captivate1">'+
	      '<param name="movie" value="' + videoFileLocation + '">'+
		  '<param name="quality" value="high">'+
		  '<param name="loop" value="0">'+
		  '<embed src="' + videoFileLocation + '" width="' + width + '" height="' + height + '" loop="0" quality="high" pluginspage="http://www.adobe.com/go/getflashplayer" type="application/x-shockwave-flash" menu="false"/>'+
		'</object>');
}

var pageCache = [];
var pageCacheDivID = 1;

/**
 * Function used to execute an AJAX call to an URL and load the content into a DIV element, if it is not already loaded. 
 * This function relies on the fact that the jQuery library have already been imported.
 * NOTE: by default, the loaded content is requested using the 'emptyLayout' decorator parameter. Use config for other values. 
 * @see com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper
 * @param contentLocation the URL where is the content.
 * @param divId the id of the DIV element to populate.
 * @param config the auxiliary configuration data.
 * 		  	@param requestMethod String, optional the HTTP request method. If not provided, the default "GET" method is used.
 * 		  	@param busyMessage String, optional the message to display while loading.
 * 		  	@param errorMessage String, optional the message to display if the loading failed.
 * 		  	@param paramsMap Map, optional the map of parameters to be sent to the server. If not provided, the default "layout=emptyLayout" parameter is used.
 * 			@param onSuccess the callback to be called on success. The callback is necessary because the calls are asynchronous.
 * 					Callback parameters: 
 * 							@param String: The content location. 
 * 							@param boolean: true in case of cache hit
 * 							@param object: the auxiliary configuration data.
 */	
function loadContent(contentLocation, divId, config) {
	var loadingInProgressMessage = config.busyMessage || 'Loading...';
	var loadingFailedMessage = config.errorMessage || 'Loading failed.'; 
	var divElement = $("#" + divId);
	// locate in cache
	for(var i = 0; i < pageCache.length; i++) {
		if (pageCache[i][0] == contentLocation) {
			divElement.html(pageCache[i][1]);
			if (config.onSuccess) {
				config.onSuccess(contentLocation, true, config);
			}
			return; // cache hit
		}
	}
	
	// not in cache
	var id = 'pageCacheDiv_' + (pageCacheDivID);                                                                                                                                        
	pageCacheDivID = pageCacheDivID + 1;                                                                                                                                                
	// just add a DIV, we will set the content later in case the request takes long                                                                                                                                      
    divElement.prepend('<div id="' + id +'"><div/>');                                                                                                                 
	setTimeout(function(){                                                                                                                                                  
			// if the DIV still exists, it means the remote page was not loaded yet, so display a 'loading' message                                                     
	        $('#' + id).prepend(loadingInProgressMessage)                                                                                                   
	        }, 
	    500
	);  
	       
	var paramsMap;
	if (config.paramsMap) {
		paramsMap = config.paramsMap;
		if (!paramsMap.layout) {
			paramsMap.layout = "emptyLayout";
		}
	} else {
		paramsMap = {"layout": "emptyLayout"};
	}
	
	$.ajax({
        type: (config.requestMethod || "GET"),
        data: paramsMap,
        url:  contentLocation,
	    success: function(data) {
			divElement.html(data);
			if (config.onSuccess) {
				config.onSuccess(contentLocation, false, config);
			}
			// add cache entry
			pageCache[pageCache.length] = [];
			pageCache[pageCache.length-1][0] = contentLocation;
			pageCache[pageCache.length-1][1] = data;
	    },
        error: function(data, options, error) {
	    	divElement.html(loadingFailedMessage);
    	}
	});
}

/**
 * Function used for synchronizing the checkbox with the corresponding hidden input.
 * <code><nobel:checkbox checked="${form.isChecked}" name="myName" checkboxName="intermediateMyName"/></code>
 * @see CheckboxTag 
 * @param checkBoxInputElementId the id of the checkbox.
 * @param hiddenInputId the id of the hidden input corresponding to the checkbox.
 */	
function copyCheckboxValue(checkBoxInputElementId, hiddenInputId){
	var checkBoxInputElement = $("input[id='" + checkBoxInputElementId + "']");
	var hiddenInputElement = $("input[id='" + hiddenInputId + "']");
	if (checkBoxInputElement.is(':checked')) {
		hiddenInputElement.val('true');	
	} else {
		hiddenInputElement.val('false'); 
	}
}

