/**
* GLOBAL CODE
*/

// Define console.log for every browser, so we don't get script errors
if (typeof console == "undefined" || typeof console.log == "undefined")
{
	var console = { log: function() {} };
}

/**
* FORM FUNCTIONS
*/

/**
* @param string action
* @param string name of form, if not passed than the first form will be used!
* @param string optional, GET variables for form submission
* @param boolean optional, validate form? defaults to true..
* @param string optional, target subform name
* @param integer optional, id of the receiver of this form action
*
* @return boolean true if action could not be set! Adjusted to a direct call from onclick="return setFormAction(..)"  to avoid further event processing.
*/
function setFormAction(szAction,szForm,szVarString,bValidate,szTargetSubform,nReceiverID)
{
	// *) Default values
	if (typeof szVarString == "undefined")
		szVarString = '';

	if (typeof bValidate == "undefined")
		bValidate = true;

	if (typeof szTargetSubform == "undefined")
		szTargetSubform = false;

	if (typeof nReceiverID == "undefined")
		nReceiverID = false;

	if (typeof szForm == "undefined")
		szForm = 0;

	// *) Fetch form
	var oForm = document.forms[szForm];
	if (!oForm)
	{
		alert("Error: Could not find form ["+szForm+"]");
		return false;
	}

	var nodeAction = oForm.elements['szAction'];
	if (!nodeAction)
	{
		alert("Error: Could not set action value [form: "+oForm.name+" ("+szForm+"), action: "+szAction+"], action formelement not found!");
		return false;
	}

	// *) Set receiver, if available
	if (nReceiverID != false)
	{
		var nodeReceiver = oForm.elements['nReceiverID'];
		if (nodeReceiver)
			nodeReceiver.value = nReceiverID;
	}

	// *) Adjust variable string
	var szTVarString = szVarString;

	if (!bValidate)
		szTVarString = (szTVarString && szTVarString != '' ? szTVarString + '&' : '') + 'bValidate=0';

	if (szTargetSubform && szTargetSubform != '')
		szTVarString = (szTVarString && szTVarString != '' ? szTVarString + '&' : '') + 'szTargetSubform='+szTargetSubform;

	// *) Adjust form action with custom variable string and target subform
	if (szTVarString && szTVarString != '')
		oForm.action += '?' + szTVarString;

	if (szTargetSubform && szTargetSubform != '')
		oForm.action += '#' + szTargetSubform;

	// *) Set internal action value (hidden input field)
	nodeAction.value = szAction;

	return true;
}

/**
* Submits a form action to another url
*
* Notice: This is just a shortcut to the submitFormAction() function
*
* @param string url to submit to. If a query string exists (?bla=123) than it will be automatically merged with the varstring parameter.
* @param string action, pass false if you do not want it unchanged
* @param string name of form, if not passed than the first form will be used! Pass 0 if you do not want to set this parameter.
* @param string optional, GET variables for form submission
* @param boolean optional, validate form? defaults to true..
*
* @return boolean true if action could not be set or form could not be submitted! Adjusted to a direct call from onclick="return submitFormAction(..)"  to avoid further event processing.
*/
function submitFormActionToURL(szURL,szAction,szForm,szVarString,bValidate)
{
	return submitFormAction(szAction,szForm,szVarString,bValidate,false,true,false,szURL);
}

/**
* @param string action, pass false if you do not want it unchanged
* @param string name of form, if not passed than the first form will be used! Pass 0 if you do not want to set this parameter.
* @param string optional, GET variables for form submission
* @param boolean optional, validate form? defaults to true..
* @param string optional, target subform name
* @param boolean optional, call onsubmit event function? defaults to true
* @param string optional, url to submit to. If a query string exists (?bla=123) than it will be automatically merged with the varstring parameter.
*
* @return boolean true if action could not be set or form could not be submitted! Adjusted to a direct call from onclick="return submitFormAction(..)"  to avoid further event processing.
*/
function submitFormAction(szAction,szForm,szVarString,bValidate,szTargetSubform,bDoOnSubmit,nReceiverID,szURL)
{
	// *) Default values
	if (typeof szVarString == "undefined")
		szVarString = '';

	if (typeof bValidate == "undefined")
		bValidate = true;

	if (typeof szTargetSubform == "undefined")
		szTargetSubform = false;

	if (typeof bDoOnSubmit == "undefined")
		bDoOnSubmit = true;

	if (typeof nReceiverID == "undefined")
		nReceiverID = false;

	if (typeof szForm == "undefined")
		szForm = 0;

	if (typeof szURL == "undefined")
		szURL = false;

	// *) Fetch form
	var oForm = document.forms[szForm];
	if (!oForm)
	{
		alert("Error: Could not find form ["+szForm+"]");
		return true;
	}

	// *) Set receiver, if available
	if (nReceiverID != false)
	{
		var nodeReceiver = oForm.elements['nReceiverID'];
		if (nodeReceiver)
			nodeReceiver.value = nReceiverID;
	}

	// *) Adjust variable string
	var szTVarString = szVarString;

	if (!bValidate)
		szTVarString = (szTVarString && szTVarString != '' ? szTVarString + '&' : '') + 'bValidate=0';

	if (szTargetSubform && szTargetSubform != '')
		szTVarString = (szTVarString && szTVarString != '' ? szTVarString + '&' : '') + 'szTargetSubform='+szTargetSubform;

	// *) Set url to submit to
	if (szURL)
		oForm.action = szURL;

	// *) Adjust form action with custom variable string and target subform
	var nQueryPos = oForm.action.indexOf('?');
	if (nQueryPos != -1)
	{
		if (szTVarString && szTVarString != '')
			oForm.action += '&';
	}
	else
	if (szTVarString && szTVarString != '')
		oForm.action += '?' + szTVarString;

	if (szTargetSubform && szTargetSubform != '')
		oForm.action += '#' + szTargetSubform;

	// *) Set internal action, if available (hidden input value)
	if (szAction != false)
	{
		var nodeAction = oForm.elements.szAction;
		if (!nodeAction)
		{
			alert("Error: Could not set action value!");
			return true;
		}

		nodeAction.value = szAction;
	}

	// *) Submit the form
	if (typeof jQuery != "undefined")
	// => submit via jquery
	{
		var oJQForm = $('form[name="'+oForm.name+'"]');

		// -) Save validation flag as hidden input value
		var oValidate = oJQForm.find('input[name="bValidate"]');
		if (oValidate.length > 0)
			oValidate.val(bValidate ? '1' : '0');
		else
			oJQForm.append('<input type="hidden" name="bValidate" value="'+(bValidate ? '1' : '0')+'" />');

		console.log('Validation flag: '+(bValidate ? 'true' : 'false'));

		// -) Submit form
		oJQForm.submit();

		console.log('Form submitted via jQuery');
	}
	else
	// => submit with own javascript utility functions
	{
		if (bDoOnSubmit)
			raiseEvent('submit',oForm);

		oForm.submit();

		console.log('Form submitted via javascript (onSubmit() '+(bDoOnSubmit ? 'was called' : 'no called')+')');
	}

	return false;
}

/**
* @param object view
* @param string default value
*/
function removeDefault(oView,szDefault)
{
	if (oView.value == szDefault && szDefault != '')
		oView.value = '';
}

/**
* Formats a request string out of form values
* Notice: Uses jquery!
*
* @param string form id - not name because it can not be selected through jquery!
*
* @return string request
*/
function getFormRequest(szForm)
{
	var aInputs  = $('#'+szForm+' input,#'+szForm+' textarea,#'+szForm+' select');
	var szRequest = "";

	aInputs.each(function(i,el)
	{
		if (szRequest != "")
			szRequest += '&';

		szRequest += el.name + '=' + el.value;
	});

	return szRequest;
}

/**
* Notice: This function needs jQuery (but it could be rewritten without)
*
* Important: This function only checks form elements, that are enabled. If it's disabled, it is ignored!
*
* @param string form name or false to get the first form available
* @param json validation data
* @param boolean optional, only check elements with overridden validations? This is necessary to display manually set validations from the framework. Defaults to false.
*
* @return boolean form valid?
*/
function checkFormFilter(szForm,aValidationData,bOnlyCheckOverridden)
{
	// *) Check input paramters
	if (typeof bOnlyCheckOverridden == 'undefined')
		bOnlyCheckOverridden = false;

	// *) Remove all invalid style classes and error message containers
	var szFormSelector = szForm ? 'form[name="'+szForm+'"] ' : '';

	$(szFormSelector+' tr').removeClass('form_rowinvalid');
	$(szFormSelector+' tr').find('.form_rowerror').remove();

	// *) Loop trough all form elements for validation
	var bValid			 = true,
		bValidOverridden = true,
		aBaseNameDone	 = new Array();

	$(szFormSelector+':input[name*="f_"]').each(function ()
	{
		// *) Check if element is not visible (via display: none)
		if ($(this).attr('type') == 'hidden')
		// => extra check for hidden input fields
		{
			if ($(this).parents(':hidden').length > 0)
			// => parent of hidden input field hides it
			{
				// console.log('Ignoring input field with type=hidden because a parent is hidden: '+$(this).attr('name'));
				return;
			}
		}
		else
		if ($(this).is(':hidden'))
		{
			// console.log('Ignoring invisible input field: '+$(this).attr('name'));
			return;
		}

		// *) Check if element is disabled
		if ($(this).is(':disabled'))
		{
			// console.log('Ignoring disabled input field: '+$(this).attr('name'));
			return;
		}

		// *) Check if validation data is available
		var szName = $(this).attr('name')

		try
		{
			var aData = eval('aValidationData["'+szName+'"]');
			if (typeof aData == "undefined")
			// => not found in validation data
			{
				// Maybe it's a view with multiple elements?
				var nAddPos = szName.lastIndexOf('-');
				if (nAddPos != -1)
				{
					var szCheck = szName.substr(nAddPos+1);
					if (isNumeric(szCheck))
					{
						szName = szName.substr(0,nAddPos);

						// -) Check if this base name was already processed
						if (!in_array(szName,aBaseNameDone))
						{
							aBaseNameDone.push(szName);

							// -) Search for validation, again ...
							var aData = eval('aValidationData["'+szName+'"]');
						}
					}
				}

				// => absolutely no validation found yet!
				if (typeof aData == "undefined")
					return;
			}
		}
		catch(e)
		{
			alert("Error occurred while fetching validation data: "+e.message+" => Ignoring: "+$(this).attr('name')+" ("+szName+")");
			return;
		}

		if (bOnlyCheckOverridden && !aData.bOverrideValidation)
		{
			console.log($(this).attr('name')+' => not checked');
			return;
		}

		// -) Check filter of current form element
		var bElementValid = checkFilter($(this).get(0),
										bOnlyCheckOverridden && aData.bOverrideValidation ? aData.bValid : aData.szFilter,
										bOnlyCheckOverridden && aData.bOverrideValidation && aData.szAlternateFiltErr != null ? aData.szAlternateFiltErr : aData.szFiltErr,
										aData.szErrorTarget,
										true);

		if (!bElementValid && (!bOnlyCheckOverridden || !aData.bOverrideValidation))
		// => flag whole form invalid
			bValid = false;

		if (!bElementValid && bOnlyCheckOverridden)
			bValidOverridden = false;

		console.log($(this).attr('name')+' => '+(bElementValid ? 'valid' : 'not valid')+' ('+aData.szFilter+','+$(this).val()+')');
	});

	console.log((bOnlyCheckOverridden ? bValidOverridden : bValid) ? "form is valid" : "form is not valid");
	return bOnlyCheckOverridden ? bValidOverridden : bValid;
}

/**
* Checks a single form element if its filter is valid.
*
* Notice: This function needs jQuery (but it could be rewritten without)
*
* @param object form element
* @param array validation data, that is generated via the form templates. This data should be available in a variable called like "aFormFilter_formYourName"
*
* @return boolean form element valid? Returns null, if form element not found in validation data or another error occurred!
*/
function checkFilterByData(o,aValidationData)
{
	var oValue = o instanceof jQuery ? o : $(o);

	try
	{
		var aData = eval('aValidationData["'+oValue.attr('name')+'"]');
		if (typeof aData == "undefined")
		// => not found in validation data, so it has no filter and therefor is valid
			return true;
	}
	catch(e)
	{
		alert("Error occurred while fetching validation data!");
		return null;
	}

	return checkFilter(oValue,aData.szFilter,aData.szFiltErr,aData.szErrorTarget);
}

/**
* Checks a single form element if its filter is valid.
*
* Notice: This function needs jQuery (but it could be rewritten without)
*
* @param object form element (can be a jquery or not)
* @param string filter. Pass false to set the form element invalid without filter evaluation.
* @param string optional, filter error. Alternatively pass an empty string to avoid an filter message, pass false to avoid any visible error handling. Defaults to false.
* @param string optional, target container to append filter error message. Pass false to avoid displaying of a filter error. Defaults to false.
* @param boolean optional, for internal use only. Just leave this value alone!
*
* @return boolean form element valid?
*/
function checkFilter(o,szFilter,szFiltErr,szErrorTarget,bCallFromForm)
{
	var oValue = o instanceof jQuery ? o : $(o);

	if (typeof szFiltErr == 'undefined')
		szFiltErr = false;

	if (typeof szErrorTarget == 'undefined')
		szErrorTarget = false;

	if (typeof bCallFromForm == 'undefined')
		bCallFromForm = false;

	// *) Check filter
	var szType	= oValue.attr('type'),
		szValue = szType != 'radio' && szType != 'checkbox' ? oValue.val() : $('input[name="'+oValue.attr('name')+'"]:checked').val(),
		bValid	= true;

	if (typeof szFilter == 'string')
	// => evaluate filter
	{
		if (szFilter.length > 0)
		{
			try
			{
				bValid = eval(szFilter);
			}
			catch(e)
			{
				alert("Invalid filter detected on "+oValue.attr('name')+": "+szFilter+" => "+e.message);
				return true;
			}
		}
	}
	else
	if (!szFilter)
	// => don't check filter, always invalid
		bValid = false;

	// *) Update form element according to validation result?
	if (typeof szFilter != 'string' && szFiltErr == false)
		return bValid;

	var oRow = oValue.parents("tr:first");

	if (bValid)
	// => Valid form element
	{
		if (!bCallFromForm)
		{
			oRow.removeClass('form_rowinvalid');
			oRow.find('.form_rowerror').remove();
		}

		return true;
	}

	// => Invalid form element
	oRow.addClass('form_rowinvalid');
	oRow.find('.form_rowerror').remove();

	if (typeof szErrorTarget == 'string' && szErrorTarget.length > 0)
		oRow.find(szErrorTarget).append('<div class="form_rowerror">'+szFiltErr+'</div>');

	return false;
}

/**
* Removes the filter error messages and other highlighting
*
* @param object form element
* @param string error target class
*/
function clearFilterError(o,szErrorTarget)
{
	var oValue	= $(o),
		oRow	= oValue.parents("tr:first");

	oRow.removeClass('form_rowinvalid');
	oRow.find('.form_rowerror').remove();
}

/**
* Uploads a file of the passed form name dynamically by using our AjaxUploadView class
*
* Complete callback example:
*
* function onComplete(oResponse)
* {
*   if (oResponse.bError || !oResponse.bSuccess)
*		return;
*
*   console.log('Uploaded file: '+oResponse.szTempFile+' of view: '+oResponse.szInternalName);
* }
*
* @param string form name
* @param string internal name of the upload view
* @param string file name to upload
* @param string upload script to handle the file upload serverside
* @param function callback invoked before upload is submitted. Pass false to skip.
* @param function callback invoked when upload is complete. Takes one parameter as json object of the upload script's reponse.
* @param string optional, old temp file (for security reasons only the file name) that should be deleted, if the upload was successful. Defaults to false.
*/
function uploadFile(szFormName,szInternalName,szFileName,szUploadScript,funcSubmit,funcComplete,szOldTempFileName)
{
	if (typeof szOldTempFileName == 'undefined')
		szOldTempFileName = false;

	$('form[name="'+szFormName+'"]').iframePostForm(
	{
		iframeID: 'idFrame_'+szInternalName,
		source	: szUploadScript+(szUploadScript.indexOf('?') == -1 ? '?' : '&')+'szFileName='+encodeURIComponent(szFileName)
																				+(szOldTempFileName != false ? '&szOldTempFileName='+encodeURIComponent(szOldTempFileName) : '')
																				+'&_='+getTimestamp(),
		json	: true,
		post	: funcSubmit,
		complete: function(oResponse)
		{
			oResponse.szInternalName = szInternalName;
			oResponse.szFileName	 = szFileName;

			funcComplete(oResponse);
		}
	});
}

/**
* POPUP FUNCTIONS
*/

/**
* @param string href
* @param integer popup width
* @param integer popup height
* @param boolean popup scrollable?
* @param boolean popup resizable?
*
* @return boolean true if popup could not be opened! Adjusted to a direct call from onclick="return openPopup(..)"  to avoid further event processing.
*/
function openPopup(szFile,nWidth,nHeight,bScrollable,bResizable)
{
	window.open(szFile,'','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars='+(bScrollable ? 'yes' : 'no')+',resizable='+(bResizable ? 'yes' : 'no')+',width='+nWidth+',height='+nHeight+',top=100,left=100');

	return false;
}

/**
* SPECIFIC INPUT FIELD FUNCTIONS
*/

/**
* @param string (internal) name of view
*
* @return array all values of the given multicheckbox view
*/
function getMultiCheckboxValue(szName)
{
	var aResult = new Array();

	$('input[name^="f_'+szName+'-"]').each(function()
	{
		aResult[$(this).val().toString()] = $(this).is(':checked');
	});

	return aResult;
}

/**
* @param string (internal) name of view
*
* @return boolean all checkboxes of this view unchecked?
*/
function isMultiCheckboxUnchecked(szName)
{
	console.log($('input[name^="f_'+szName+'-"]:checked'));

	return $('input[name^="f_'+szName+'-"]:checked').length == 0;
}

/**
* VARIOUS UTILITY FUNCTIONS
*/

/**
* Reloads the page
*
* @param string optional, new variable string
*/
function reloadPage(szNewVarString)
{
	if (typeof szNewVarString != 'undefined')
		window.location.replace(window.location.pathname+'?'+szNewVarString);
	else
		window.location.reload();
}

/**
* Framework action triggered by javascript
*
* @param string action
* @param string optional url, pass false to use current url
* @param int optional framework object receiver id, pass false and the action will be received by the application object of the framework
* @param string optional parameter (formatted like a query string, no question mark needed!)
*
* @return boolean true if action could not be done! Adjusted to a direct call from onclick="return doAction(..)" to avoid further event processing.
*/
function doAction(szAction,szURL,nReceiverID,szParams)
{
	var szActionURL = typeof szURL != 'undefined' && szURL != false ? szURL : window.location.protocol+'//'+window.location.host+window.location.pathname;

	szActionURL += '?szAction='+szAction;

	if (typeof nReceiverID != 'undefined' && nReceiverID != false)
		szActionURL += '&nReceiverID='+nReceiverID;

	if (typeof szParams != 'undefined' && szParams != false)
		szActionURL += '&'+szParams;

	window.location.href = szActionURL;

	return false;
}

/**
* Performs an asyncronous request by using jquery
*
* Notice: This function needs jQuery!
* Notice: The ajax request is automatically done in JSON and with UTF-8!
*
* @param string url
* @param string parameter (formatted like a query string, no question mark needed!)
* @param function invoked when call successful. Pass false to do a syncronous call!
* @param function optional, invoked when an error occurs
* @param function optional, invoked when call completed
*
* @return mixed in async mode it returns if the url has been successfully requested, in sync mode the response data is returned or false if the call produced an error or null if ajax script has never been called.
*/
function doAJAXRequest(szURL,szParams,funcSuccess,funcError,funcComplete)
{
	var bAsync = typeof funcSuccess == 'function';

	var mixedResponse = null;

	$.ajax({type		: 'GET',
			url			: szURL,
			datatype	: 'json',
			data		: szParams,
			processData	: true,
			contentType	: 'application/json; charset=utf-8',
			cache		: false,
			async		: bAsync,
			success		: function(mixedData)
			{
				mixedResponse = mixedData;

				if (bAsync)
					funcSuccess(mixedData);
			},
			error : function(mixedData)
			{
				mixedResponse = false;

				if (bAsync && typeof funcError == 'function')
					funcError(mixedData);
			},
			complete : function(mixedData)
			{
				if (bAsync && typeof funcComplete == 'function')
					funcComplete(mixedData);
			}
	});

	return !bAsync ? mixedResponse : true;
}

/**
* Converts a json string to an object
*
* This function always returns an object. If an error occurs by evaluating the json string, then there
* an object will be formed, that represents an unknown error (oResult.bError = true)
*
* @param string json string
*
* @return object converted json code
*/
function fetchJSON(szJSON)
{
	var oJSON = false;

	try
	{
		oJSON = eval('('+szJSON+')');
	}
	catch (e)
	{
		console.log("Could not fetch json code",szJSON);
		return { 'bError' : true,'aErrors' : false };
	}

	return oJSON;
}

/**
* Little helper function to format error arrays that are returned from ajax requests
*
* @param array errors
* @param optional, alternate error message, if error array is empty or false. Defaults to "Unknown error!"
* @param optional, format for error messages. Defaults to "[szType] szMessage<br />" (=> [%(szType)s] %(szMessage)s<br />). Uses sprintf() to format the parameters!
*
* @return string formated errors
*/
function formatErrors(aErrors,szAlternateMessage,szFormat)
{
	if ((typeof aErrors != 'array' && typeof aErrors != 'object') || aErrors.length == 0)
		return typeof szAlternateMessage == 'undefined' ? 'Unknown error!' : szAlternateMessage;

	if (typeof szFormat == 'undefined')
		szFormat = '[%(szType)s] %(szMessage)s<br />';

	var szErrors = "";
	for (var i=0;i != aErrors.length;i++)
	{
		var aError = aErrors[i];

		szErrors += sprintf(szFormat,aError);
	}

	return szErrors;
}

/**
* @param string event type (for example: submit, click, ...)
* @param object
*/
function raiseEvent(szType,oObj)
{
	if (document.createEvent)
	{
		var e = document.createEvent("Events");

		e.initEvent(szType,true,true);
		oObj.dispatchEvent(e);
	}
	else if (document.createEventObject)
	{
		var e = document.createEventObject();

		oObj.fireEvent('on'+szType,e);
	}
}

/**
* Joins multiple request strings
*
* @param string multiple request strings
*
* @return string joined request
*/
function joinRequest()
{
	var szRequest = "";

	for (var i=0;i != arguments.length;i++)
	{
		if (szRequest != "")
			szRequest += '&';

		szRequest += arguments[i];
	}

	return szRequest;
}

/**
* Joins multiple arguments to one string
*
* Important: Glue string is currently fixed to ' ' (space)
*
* @param multiple string arguments
*/
function joinTokens()
{
	var szResult = "";

	if (arguments.length > 0)
	for (var i=0;i != arguments.length;i++)
	{
		if (typeof arguments[i] != 'string' && typeof arguments[i] != 'numeric')
		// => invalid token passed
			continue;

		var szToken = arguments[i].toString();
		if (szToken.length == 0)
		// => ignore empty token
			continue;

		if (szResult != "")
			szResult += ' ';

		szResult += szToken;
	}

	return szResult;
}

/**
* @return integer unix timestamp in seconds
*/
function getTimestamp()
{
	var oDate = new Date();

	return Math.round(oDate.getTime());
}

/**
* @param string text
* @param string optional, comma character. Defaults to ','
* @param string optional, thousand separator. Defaults to '.'
*
* @return boolean is passed variable a number?
*/
function isNumeric(szText,szCommaChar,szSepChar)
{
	if (typeof szCommaChar == 'undefined')
		szCommaChar = ',';

	if (typeof szSepChar == 'undefined')
		szSepChar = '.';

	// *) Check for types and invalid values
	if (typeof szText == 'number')
		return true;

	if (!szText || typeof szText != 'string' || szText.length == 0)
		return false;

	// *) Check every char in the string
	var szValidChars   = "0123456789",
		szLastChar	   = false,
		szCurChar	   = null,
		bCommaFound	   = false,
		bDotFound	   = false,
		nNumbersInRow  = 0,
		bFirstNumBlock = true;

	for (var i = 0;i < szText.length;i++)
	{
		szLastChar = szCurChar;
		szCurChar  = szText.charAt(i);

		if (i == 0 && szCurChar == '-')
		// => ignore leading sign
			continue;

		// -) check for number
		if (szValidChars.indexOf(szCurChar) != -1)
		{
			nNumbersInRow++;
			continue;
		}

		// -) check for comma
		if (szCurChar == szCommaChar)
		{
			if (szLastChar == null || szValidChars.indexOf(szLastChar) == -1)
			// => comma before any other value
				return false;

			bCommaFound	  = true;
			nNumbersInRow = 0;

			continue;
		}

		// -) check for thousand seperator
		if (szCurChar == szSepChar)
		{
			if (bCommaFound)
			// => not allowed after comma
				return false;

			if (!bFirstNumBlock && nNumbersInRow != 3)
			// => wrong format for separation
				return false;
			else
			if (bFirstNumBlock && szValidChars.indexOf(szLastChar) == -1)
			// => no number before separator
				return false;

			bDotFound	   = true;
			nNumbersInRow  = 0;
			bFirstNumBlock = false;

			continue;
		}

		// => any other char is not allowed!
		return false;
	}

	return true;
}

/**
* Formats a numeric value with passed comma and thousands separator
*
* @param mixed numeric value
* @param string optional, comma character. Defaults to ','
* @param string optional, thousand separator. Defaults to '.'
*
* Formatted numeric number
*/
function localeNumber(mixedValue,szCommaChar,szSepChar)
{
	// *) Default arguments
	if (typeof szCommaChar == 'undefined')
		szCommaChar = ',';

	if (typeof szSepChar == 'undefined')
		szSepChar = '.';

	var aValue	  = mixedValue.toString().split('.',2),
		szDecimal = aValue.length > 1 ? aValue[1] : '',
		nValue	  = parseInt(aValue[0]),
		szValue;

	if (isNaN(nValue))
		return '';

	var szMinus = nValue < 0 ? '-' : '';

	nValue	= Math.abs(nValue);
	szValue = new String(nValue);

	aValue = [];
	while (szValue.length > 3)
	{
		aValue.unshift(szValue.substr(szValue.length-3));

		szValue = szValue.substr(0,szValue.length-3);
	}

	if (szValue.length > 0)
		aValue.unshift(szValue);

	return szMinus+aValue.join(szSepChar)+(szDecimal.length > 0 ? szCommaChar+szDecimal : '');
}

/**
* Removes all formating (thousand separator) and parses the input variable as a float (which is good for any number to compare afterwards)
*
* Important: This function only works correctly, when the passed separator character is escaped, so it can be a parameter for the RegExp object!
* 			 When you want a dot as thousand separator, you have to pass this string: '\.'
*
* @param mixed numeric value
* @param string optional, comma character. Defaults to ','
* @param string optional, thousand separator. Read the documentation of this function for custom strings. Defaults to '.'.
*
* @return number
*/
function delocaleNumber(mixedValue,szCommaChar,szSepChar)
{
	// *) Default arguments
	if (typeof szCommaChar == 'undefined')
		szCommaChar = ',';

	if (typeof szSepChar == 'undefined' || szSepChar == '.')
		szSepChar = '\\.';

	// *) Check value
	if (typeof mixedValue == 'number' || typeof mixedValue != 'string' || mixedValue.length == 0 || !mixedValue)
		return mixedValue;

	// *) Remove formatting
	var szValue = mixedValue.toString().replace(new RegExp(szSepChar,"g"),"");

	// *) Return correct number
	return parseFloat(szCommaChar != '.' ? szValue.replace(szCommaChar,'.') : szValue);
}

/**
* PHP-like in_array()
*
* @param mixed needle
* @param array haystack
* @param boolean strict comparison mode?
*
* @return boolean needle in haystack array?
*/
function in_array(mixedNeedle,a,bStrict)
{
	var bFound	= false,
		bStrict = !!bStrict,
		mixedKey;

	for (mixedKey in a)
	{
		if ((bStrict && a[mixedKey] === mixedNeedle) || (!bStrict && a[mixedKey] == mixedNeedle))
		{
			bFound = true;
			break;
		}
	}

	return bFound;
}

/**
* @param mixed variable
*
* @return boolean is passed variable an array?
*/
function isArray()
{
	return typeof arguments[0] == 'object' ? arguments[0].toString().match(/array/i) != null : false;
}

/**
* @param string mail address
*
* @return bool is valid mail address?
*/
function isValidMail(szMail)
{
	var oFilter = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i

	return oFilter.test(szMail);
}

/**
* @param string date
*
* @return bool is valid date?
*/
function isValidDate(szDate)
{
	var aMatches = szDate.match(/^(\d{1,2})(\/|-|\.)(\d{1,2})(\/|-|\.)(\d{4})$/);
	if (aMatches == null || aMatches[1] == null || aMatches[3] == null || aMatches[5] == null)
		return false;

	var nDay   = aMatches[1],
		nMonth = aMatches[3],
		nYear  = aMatches[5];

	if (nDay < 1 || nDay > 31 || nMonth < 1 || nMonth > 12 || nYear <= 0)
		return false;

	if ((nMonth == 4 || nMonth == 6 || nMonth == 9 || nMonth == 11) && nDay == 31)
		return false;

	if (nMonth == 2)
	// => check for leap year
	{
		var bLeapYear = nYear % 4 == 0 && (nYear % 100 != 0 || nYear % 400 == 0);
		if (nDay > 29 || (nDay == 29 && !bLeapYear))
			return false;
	}

	return true;
}

/**
* Spam protection for mail addresses
*
* @param string name part of the mail address (everthing before the "at")
* @param string domain name part (everthing between "at" and the last dot)
* @param string domain suffix (com, net, at, ...)
* @param boolean optional, write into document? Defaults to false.
*
* @return string mail address
*/
function protectMail(szName,szDomainName,szEnd,bWrite)
{
	if (typeof bWrite == 'undefined')
		bWrite = false;

	var szMail = szName+'@'+szDomainName+'.'+szEnd;

	if (bWrite)
		document.write(szMail);

	return szMail;
}

/**
* PROTOTYPE FUNCTIONS
*/
String.prototype.capitalize = function()
{
	return this.charAt(0).toUpperCase() + this.slice(1);
}

/**
* EXTERNAL CODE
*
* Various useful open source functions
*/

/**
* sprintf()
*
* Version: 0.7-beta1
* Source: http://www.diveintojavascript.com/projects/javascript-sprintf
*/
var sprintf = (function() {
	function get_type(variable) {
		return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
	}
	function str_repeat(input, multiplier) {
		for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
		return output.join('');
	}

	var str_format = function() {
		if (!str_format.cache.hasOwnProperty(arguments[0])) {
			str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
		}
		return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
	};

	str_format.format = function(parse_tree, argv) {
		var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
		for (i = 0; i < tree_length; i++) {
			node_type = get_type(parse_tree[i]);
			if (node_type === 'string') {
				output.push(parse_tree[i]);
			}
			else if (node_type === 'array') {
				match = parse_tree[i]; // convenience purposes only
				if (match[2]) { // keyword argument
					arg = argv[cursor];
					for (k = 0; k < match[2].length; k++) {
						if (!arg.hasOwnProperty(match[2][k])) {
							throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
						}
						arg = arg[match[2][k]];
					}
				}
				else if (match[1]) { // positional argument (explicit)
					arg = argv[match[1]];
				}
				else { // positional argument (implicit)
					arg = argv[cursor++];
				}

				if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
					throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
				}
				switch (match[8]) {
					case 'b': arg = arg.toString(2); break;
					case 'c': arg = String.fromCharCode(arg); break;
					case 'd': arg = parseInt(arg, 10); break;
					case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
					case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
					case 'o': arg = arg.toString(8); break;
					case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
					case 'u': arg = Math.abs(arg); break;
					case 'x': arg = arg.toString(16); break;
					case 'X': arg = arg.toString(16).toUpperCase(); break;
				}
				arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
				pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
				pad_length = match[6] - String(arg).length;
				pad = match[6] ? str_repeat(pad_character, pad_length) : '';
				output.push(match[5] ? arg + pad : pad + arg);
			}
		}
		return output.join('');
	};

	str_format.cache = {};

	str_format.parse = function(fmt) {
		var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
		while (_fmt) {
			if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
				parse_tree.push(match[0]);
			}
			else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
				parse_tree.push('%');
			}
			else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
				if (match[2]) {
					arg_names |= 1;
					var field_list = [], replacement_field = match[2], field_match = [];
					if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
						field_list.push(field_match[1]);
						while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
							if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else {
								throw('[sprintf] huh?');
							}
						}
					}
					else {
						throw('[sprintf] huh?');
					}
					match[2] = field_list;
				}
				else {
					arg_names |= 2;
				}
				if (arg_names === 3) {
					throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
				}
				parse_tree.push(match);
			}
			else {
				throw('[sprintf] huh?');
			}
			_fmt = _fmt.substring(match[0].length);
		}
		return parse_tree;
	};

	return str_format;
})();

var vsprintf = function(fmt, argv) {
	argv.unshift(fmt);
	return sprintf.apply(null, argv);
};

