function substr_count(string,substring,start,length)
{
 var c = 0;
 if(start) { string = string.substr(start); }
 if(length) { string = string.substr(0,length); }
 for (var i=0;i < string.length;i++)
 {
  if(substring == string.substr(i,substring.length))
  c++;
 }
 return c;
}
function number_format(number, decimals, dec_point, thousands_sep) {

    number = (number+'').replace(',', '').replace(' ', '');
    var n = !isFinite(+number) ? 0 : +number, 
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,        dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
        s = '',
        toFixedFix = function (n, prec) {
            var k = Math.pow(10, prec);
            return '' + Math.round(n * k) / k;        };
    // Fix for IE parseFloat(0.55).toFixed(0) = 0;
    s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
    if (s[0].length > 3) {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }    return s.join(dec);
}

/**
 * js wrapper around Zig Portal forms
 * 
 * @author Jordy de Jong <jojong@zigwebsoftware.nl>
 * @copyright Copyright (c) 2010, Zig Websoftware
 */   
var FormWrapper = Class.create({
	
	/**
	 * the DOM node of the form we want to enrich
	 * 
	 * @var object oForm
	 */	
	oForm: null,

	sFormParameters : '',

	/**
	 * the DOM node we want to show the amount of the results in 
	 * 
	 * @var object oResultCounterNode
	 */	
	oResultCounterNode: null,

	/**
	 * at what time did we do our last change?
	 * 
	 * @var object iTimeLastChange
	 */	
	iTimeLastChange: 0,

	/**
	 * the amount of time (in sec) we want to wait before showing results
	 * 
	 * @var integer iTimer
	 */	
	iTimer: 1,
	
	/**
	 * logic for form
	 * 
	 * @var array
	 */
	aLogic : new Array(),
	
	/**
	 * all element listeners
	 * 
	 * @var array
	 */
	aListeners: new Array(),
	
	/**
	 * option for enabling/showing validation fields
	 * 
	 * @var string (enable|show)
	 */
	sValidateOption : 'enable',
	
	/**
	 * classname when field/element is disabled/hidden
	 * 
	 * @var string
	 */
	sValidateClassName : 'disabled',
	
	bGetNextResults : false,
	
	bGetResultsOnInit : true,
	
	bIsInitialCall : true,
	
	oUpdater : null,
	
    /**
     * initialize the wrapper object
     * 
     * @param object oForm the DOM node of the form we want to enrich
     * @return void
     */		
	initialize: function(oForm,aOptions) 
	{
		this.oForm = oForm;
		this.aListeners = new Array();
		
		if (aOptions)
		{
			if(typeof(aOptions.sValidateOption) == 'string')
			{
				this.sValidateOption = aOptions.sValidateOption;
			}
			if(typeof(aOptions.sValidateClassName) == 'string')
			{
				this.sValidateClassName = aOptions.sValidateClassName;
			}
			if (typeof(aOptions.bGetResultsOnInit) == 'boolean')
			{
				this.bGetResultsOnInit = aOptions.getResultsOnInit;
			}
		}
		// by default we want to do ajax calls after initialization to fill resultNode
		
		
		this.addListeners();
		this.addFilters();
		
		Event.observe(window, 'load', function() {
			// set this flag to notify we are calling the listeners because the window is loaded
			this.bIsInitialCall = true;
		 	
			this.handleListeners();
		 	
			// clear the flag
		 	this.bIsInitialCall = false;
		
			this.oUpdater = new PeriodicalExecuter(
				function(oPE)
				{
					if(this.bGetNextResults)
					{
						var iTimeChanged = new Date().getTime();
						var iTimeDifference = iTimeChanged - this.iTimeLastChange;
						if(iTimeDifference > 100)
						{
							this.getResults();
							this.bGetNextResults = false;
						}
					}
			}.bind(this), 1);
		
		}.bind(this));
	},

    /**
     * set the result node
     * 
     * @param object oResultCounterNode the DOM node we want to show the amount of the results in 
     * @return void
     */	
	setResultNode: function(oResultCounterNode)
	{
		this.oResultCounterNode = oResultCounterNode;	
	},

    /**
     * add Javasript filters, looks for "filter" property in html
     * 
     * @param void
     * @return void
     */	
	addFilters: function()
	{	
		// did we specify a filter on some of the form's elements?
		var aChildren = this.oForm.getElementsByClassName('formElement');
		
		for (var i = 0 ; i < aChildren.length ; i++)
		{			
			var oChild = aChildren[i];
			var sFilters = oChild.readAttribute('filter');
			
			if (sFilters !== '' && sFilters != null)
			{	
				var oElement = null;
				
				oElement = $$('#'+oChild.id+' input').first();

				if (oElement == null)
				{
					//console.log('could not apply filter for form row', oChild, 'html element not found');
				}
				else
				{
					var aFilters = sFilters.split(' ');
					
					for (var j = 0 ; j < aFilters.length ; j++)
					{
						var sFilter = aFilters[j];
						
						switch (sFilter)
						{
							case 'StringToLower':
								
								oElement.observe('blur', function(oEvent) {
									oEvent.target.value = oEvent.target.value.toLowerCase();
								}.bind(this));
								
								break;
							case 'StringToUpper':
								
								oElement.observe('blur', function(oEvent) {
									oEvent.target.value = oEvent.target.value.toUpperCase();
								}.bind(this));
								
								break;
							case 'UcFirst':

								oElement.observe('blur', function(oEvent) {
									oEvent.target.value = oEvent.target.value.charAt(0).toUpperCase() + oEvent.target.value.slice(1);
								}.bind(this));
								
								break;		
							case 'Digits':

								oElement.observe('blur', function(oEvent) {
									oEvent.target.value = oEvent.target.value.replace('.',',').split(',').shift();
								}.bind(this));
								
								break;	
							case 'Price':

								oElement.observe('blur', function(oEvent) {
									
									var mValue = oEvent.target.value;
									if(mValue)
									{
										mValue = mValue.replace(/\.(\d{0,2})(?!\d)/i,',$1');
										mValue = mValue.replace('.','');
										mValue = mValue.replace(',','.');
										mValue = number_format(mValue,2,',','.');
									}
									oEvent.target.value = mValue;
								}.bind(this));
								
								break;	
							case 'PhoneNumber':
								
								oElement.observe('blur', function(oEvent) {
									oEvent.target.value = oEvent.target.value.replace(/\+/g, "00");
									oEvent.target.value = oEvent.target.value.replace(/^0031/, "0");
									//only show digits
									oEvent.target.value = oEvent.target.value.replace(/[^0-9]/g, "");
								}.bind(this));
							
								break;
							case 'DotSeparator':

								oElement.observe('blur', function(oEvent) {
									
									var sValue = oEvent.target.value.replace(/[\.\s]*/g,'');
									var sNewValue = '';
									
									for (var i = 0 ; i < sValue.length ; i++)
									{
										sNewValue += sValue.substr(i, 1);
										sNewValue += '.';
									}
									
									oEvent.target.value = sNewValue;
								}.bind(this));
								
								break;
							default:
								//console.log('could not apply uknown filter ', sFilter, ' to form element ', oElement);
						}
					}				
				}
			}
		}
	},

    /**
     * change the timer
     * 
     * @param int iTimer the timer in sec 
     * @return void
     */	
	setTimer: function(iTimer)
	{
		this.iTimer = parseInt(iTimer, 10);
	},

    /**
     * add listeners to form's elements
     * 
     * @param void
     * @return void
     */	
	addListeners: function() 
	{
		var aElements = this.oForm.getElements();
		
		for (var i in aElements)
		{
			var oElement = $(aElements[i]);
			
			switch (oElement.type)
			{
				case 'reset':
					oElement.observe('click', function(oEvent) {
						Form.reset(this.oForm);
					}.bind(this));
				break;			
				case 'checkbox':
				case 'text':
				case 'radio':
				case 'select-one':
				case 'select':	
					var oFunc = function(oElement){
						var oRunLogic = this.runLogic.bind(this);
						oRunLogic(oElement);
						this.getNextResults();
					}.bind(this);
							
					this.aListeners[oElement.name] = oFunc;

					var sEvent = 'click';
					
					switch (oElement.type)
					{
						case 'text':
						case 'select-one':
						case 'select':
							sEvent = 'change';
						break;
					}
					
					oElement.observe(sEvent, function(oEvent) {	
						this.handleListener(oEvent.target);
					}.bind(this));
					
					break;					
				default:
					// don't listen to changes for buttons, submit, hidden, ...
					// ....
			}
		}		
	},
	
	/**
	 * handle listener on a element
	 * 
	 * @var object oElement
	 */
	handleListener: function(oElement)
	{
		if(this.aListeners[oElement.name])
		{
			this.aListeners[oElement.name](oElement);
		}
	},
	
	/**
	 * handle all listeners in this form
	 * 
	 */
	handleListeners: function()
	{
		var aElements = this.oForm.getElements();
		
		//reverse elements so logic is preformed in right order
		aElements.reverse();
		
		for (var i in aElements)
		{
			var oElement = $(aElements[i]);

			switch (oElement.type)
			{
				case 'text':
				case 'checkbox':
				case 'radio':
					this.handleListener(oElement);
				break;
				case 'select-one':
				case 'select':
					this.handleListener(oElement);
				break;						
				default:
			}
		}	
	},
	
	/**
	 * add logic to this form
	 * 
	 * @param string sType
	 * @param object oJson
	 */
	addLogic: function(sType, oJson)
	{
		if(typeof(oJson) == 'string')
		{
			oJson = eval('(' + oJson + ')');
		}

		if(typeof(this.aLogic[oJson.field.fullname]) != 'object')
		{	
			this.aLogic[oJson.field.fullname] = new Array();
		}
		
		this.aLogic[oJson.field.fullname][sType] = oJson;
	},
	
	/**
	 * check if element has logic
	 * 
	 * @var object oElement
	 * 
	 * @return boolean
	 */
	hasLogic: function(oElement)
	{
		return (typeof(this.aLogic[oElement.name]) == 'object');
	},
	
	/**
	 * get logic
	 * 
	 * @var object oElement
	 * 
	 * @return object
	 */	
	getLogic: function(oElement)
	{
		return this.aLogic[oElement.name];
	},

	/**
	 * run al the logic of a specific element
	 * 
	 * @param object oElement
	 */
	runLogic: function(oElement)
	{
		
		if(oElement.disabled){
			return;
		}
		if(oElement.type == 'radio' && !oElement.checked){
			return;
		}
		
		if(this.hasLogic(oElement))
		{
			var aLogic = this.getLogic(oElement);
		
			for(var sType in aLogic)
			{
				var oLogic = aLogic[sType];

				if(typeof(oLogic) == 'object')
				{
					switch(sType.toLowerCase())
					{
						case "validate":
							this.runValidateLogic(oLogic,oElement);
						break;
						case "required":
							this.runRequiredLogic(oLogic,oElement);
						break;
					}
				}
			}
		}
		
		
	},
	
	/**
	 * trigger logic of a specific field
	 * 
	 * @param string sName name of element
	 * @param string mValue
	 */
	triggerLogic : function(sName,sValue)
	{
		oElement 		= document.createElement('input');
		oElement.value 	= sValue;
		oElement.name 	= sName;
		this.runLogic(oElement);
	},
	
	/**
	 *  run validation logic
	 *  
	 *  @var object oLogic
	 *  @var object oElement
	 *  
	 *  @return void
	 */
	runValidateLogic : function(oLogic,oElement)
	{
		
		/**
		 * default we disable/hide fields
		 */
		var bValidate = false;

		/**
		 * a checkbox or radio field has only a value when it is checked 
		 */
		if((oElement.type != 'checkbox' && oElement.type != 'radio') || oElement.checked)
		{
			/**
			 * if the selected value is the same as the one we gave in the logic, enable/show the fields to validate
			 */
			for(var iCounter in oLogic.value)
			{
				if(typeof(oLogic.value[iCounter]) == 'string' && (oElement.value == oLogic.value[iCounter]))
				{
					bValidate = true;
					break;
				}
			}
		}
		else
		{
			/**
			 * default we return when value wasn't found, but when validation has the `fieldsBelongTo` property, 
			 * we are gonna disable those fields
			 */
			if(!oLogic.fieldsBelongTo)
			{
				return;
			}
		}
		
		if(oLogic.fieldsBelongTo)
		{
			var aFieldsToValidate = oLogic.fieldsBelongTo[oElement.value];
		}
		else
		{
			var aFieldsToValidate = oLogic.fieldsToValidate;
		}	
		
		
		if(aFieldsToValidate)
		{
			var aTriggerfields = new Array();
			
			for(var iCounterEl in aFieldsToValidate)
			{
				if(typeof(aFieldsToValidate[iCounterEl]) == 'object')
				{
					switch(this.sValidateOption)
					{
						case 'show':
						case 'hide':
							//hide complete element
							var aFieldsToToggle = $$('#formElement-'+aFieldsToValidate[iCounterEl].name);
							
							if(aFieldsToToggle)
							{
								aFieldsToToggle.each(function(oEl){
									
									if(bValidate)
									{
										oEl.show();
										oEl.removeClassName(this.sValidateClassName);
										aTriggerfields = aTriggerfields.concat(oEl.select('input')).concat(oEl.select('select'));
									}
									else
									{
										oEl.addClassName(this.sValidateClassName);
										oEl.hide();
										
									}
																	
								}.bind(this));
							}
							
						break;
						case 'enable':
						case 'disable':	
							
							//disable element
							var aFieldsToToggle = $$('#formElement-'+aFieldsToValidate[iCounterEl].name + ' input').concat($$('#formElement-'+aFieldsToValidate[iCounterEl].name + ' select'));

							if(aFieldsToToggle)
							{
								aFieldsToToggle.each(function(oEl){
									
									if(bValidate)
									{
										$('formElement-'+aFieldsToValidate[iCounterEl].name).removeClassName(this.sValidateClassName);
										oEl.enable();
										aTriggerfields.push(oEl);
									}
									else
									{
										$('formElement-'+aFieldsToValidate[iCounterEl].name).addClassName(this.sValidateClassName);
										oEl.disable();
										switch(oEl.type)
										{
											case "select":
											break;
											case "radio":
											case "checkbox":
												oEl.checked = false;
											break;
											default:
												oEl.clear(); 
										}
									}
																	
								}.bind(this));
							}
							
							
					}
				}
			}

			//finished, trigger the validated fields to run their logic
			if(aTriggerfields)
			{
				aTriggerfields.reverse();
				
				aTriggerfields.each(function(oInputEl){
					if(typeof(oInputEl) == 'object'){
						this.runLogic(oInputEl);
					}
				}.bind(this));
			}
		}
		
		//now check the fieldsets
		var aFieldsetsToToggle = $$('#'+this.oForm.id+ ' fieldset');
		
		if(aFieldsetsToToggle)
		{
			aFieldsetsToToggle.each(function(oFieldset){
				
				var aFieldsetFields = $$('#'+oFieldset.id+' div.formElement');

				aFieldsetFields.each(function(oFieldsetField){
					if((oFieldsetField.hasClassName(this.sValidateClassName) == false) && (oFieldsetField.style.display != 'none'))
					{
						switch(this.sValidateOption)
						{
							case 'show':
							case 'hide':
								oFieldset.show();
							break;
							case 'enable':
							case 'disable':
							break;
						}			
						
						oFieldset.removeClassName(this.sValidateClassName);
						throw $break;
						
					}else{
						switch(this.sValidateOption)
						{
							case 'show':
							case 'hide':
								oFieldset.hide();
							break;
							case 'enable':
							case 'disable':
							break;
						}

						oFieldset.addClassName(this.sValidateClassName);	
					}
				}.bind(this));
			}.bind(this));
		}
	},
	
	/**
	 * required logic replaces the <sup>*</sup> in labels
	 * 
	 *  @var object oLogic
	 *  @var object oElement
	 *  
	 *  @return void
	 */
	runRequiredLogic : function(oLogic,oElement)
	{
		var bRequired = false;
		
		for(var iCounter in oLogic.value)
		{
			if(typeof(oLogic.value[iCounter]) == 'string' && (oElement.value == oLogic.value[iCounter]))
			{
				bRequired = true;
				break;
			}
		}

		for(var iCounterEl in oLogic.fieldsToRequire)
		{
			var aField = oLogic.fieldsToRequire[iCounterEl];
			if(typeof(aField) == 'object')
			{						
				var oLabel = $$('#'+aField.name+'-label label').first();
				if (typeof(oLabel) == 'object') {
						
					oLabel.innerHTML = oLabel.innerHTML.replace('<sup>*</sup>','');
					oLabel.innerHTML = oLabel.innerHTML.replace('<SUP>*</SUP>','');
						
					if(bRequired)
					{
						oLabel.innerHTML += '<sup>*</sup>';
					}
				}					
			}
		}
	},
		
    /**
     * retrieve results the next time
     * 
     * @return void
     */	
	getNextResults: function()
	{
		var iTimeChanged = new Date().getTime();
		this.iTimeLastChange = iTimeChanged;
		this.bGetNextResults = true;
	},
	
    /**
     * retrieve results
     * 
     * @param int iTimeChanged the time we triggered this getResults() call
     * @return void
     */	
	getResults: function(start)
	{
		// only perform getResults if we supplied a results node
		if (this.oResultCounterNode !== null)
		{
			var sFormParameters = this.oForm.serialize();

			/**
			 * only do a post when we change the form
			 */
			
			if(!this.sFormParameters)
			{
				this.sFormParameters = sFormParameters;
				if(start == null)
				{
					return;
				}
			}
			else if(this.sFormParameters == sFormParameters)
			{
				return;
			}
			else
			{
				this.sFormParameters = sFormParameters;
			}

			this.oResultCounterNode.innerHTML = '<blink>...</blink>';

			// retrieve target url from form
			var sUrl = this.oForm.action;
			
			if (sUrl == '')
			{
				sUrl = location.href;
			}
			
			if(sUrl.match(/http(s?):\/\//))
			{
				sUrl = sUrl.replace(/http(s?):\/\//,'');
				sUrl = sUrl.substring(sUrl.indexOf('/'));
				sUrl = '/output/json'+sUrl;
			}
			else
			{
				sUrl = '/output/json'+sUrl;
			}

			new Ajax.Request(sUrl,
			{
			    method: 'post',
			    parameters: sFormParameters,
			    onSuccess: function(sResult,oJson)
			    {
					if(!oJson)
					{
						oJson = eval('(' + sResult.responseText + ')');
					}

					this.oResultCounterNode.innerHTML = oJson.iAmountOfRecords;
					
					// if the resultNode has a "onUpdate" callback specified, call it
					if (typeof this.oResultCounterNode.onUpdate == 'function') this.oResultCounterNode.onUpdate(oJson.iAmountOfRecords);

			    }.bind(this)
			});
		}
	}
});
