/**
 * @author Robert
 */

function CloneObject(what, recursive, asreference) {
	if($type(what) != 'object')
      return what;
	var h = $H(what);
	h.each(function(v, k){
		if($type(v) === 'object' && recursive === true && !asreference.contains(k)){
			this[k] = new CloneObject(v, recursive, asreference);
		}else{
			this[k] = v;
		}
	}.bind(this));
	return this;
}



var fabrikForm = new Class( {

	initialize : function(id) {
		this.id = id;
		this.options = Object.extend( {
			'admin':false,
			'postMethod':'post',
			'primaryKey':null,
			'error':'',
			'delayedEvents':false,
			'updatedMsg':'Form saved',
			'liveSite':'',
			'pages':[],
			'page_save_groups':[],
			'start_page':0,
			'ajaxValidation':false,
			'customJsAction':'',
			'inPopup':false,
			'formCss':[],
			'plugins':[],
			'ajaxmethod':'post',
			'mooversion':1.1
		}, arguments[1] || {});
		this.options.pages = $H(this.options.pages);
		this.subGroups = $H({});
		this.lang = Object.extend( {
			'validation_altered_content' :'The validation has altered your content:',
			'validating' :'Validating',
			'success' :'Success',
			'nodata': 'No data'
		}, arguments[2] || {});
		this.currentPage = this.options.start_page;
		this.formElements = $H({});
		this.delGroupJS = $H({});
		this.duplicateGroupJS = $H({});
		this.listenTo = $A([]);
		this.bufferedEvents = $A([]);
		this.duplicatedGroups = $H();
		this.clickDeleteGroup = this.deleteGroup.bindAsEventListener(this);
		this.clickDuplicateGroup = this.duplicateGroup.bindAsEventListener(this);
	
		this.fx = {};
		this.fx.elements = [];
		this.fx.validations = {};
		if (!window.ie6) {
			// only attempt this if you are in a popup form.
			if (this.options.inPopup) {
				this.includeFormCss();
			}
			this.includeCustomJsAction();
		}
		if(this.options.inPopup == true) {
			(function(){
				//delay calling the script if we are in a popup
	  		window.addEvent('domready', this.setUpAll.bindAsEventListener(this))
	  	}.bind(this)).delay(1000);
		}else{
			window.addEvent('domready', this.setUpAll.bindAsEventListener(this));	
		}
	},
	
	setUpAll: function()
	{
		this.setUp();
		this.winScroller = new Fx.Scroll(window);
		this.watchAddOptions();
		$H(this.options.hiddenGroup).each(function(v, k){
			if(v == true){
				var subGroup = $('group'+k).getElement('.fabrikSubGroup');
				this.subGroups.set(k, subGroup.cloneWithIds());
				this.hideLastGroup(k, subGroup);
			}
		}.bind(this));
		
		// get an int from which to start incrementing for each repeated group id
		// dont ever decrease this value when deleteing a group as it will cause all sorts of
		// reference chaos with cascading dropdowns etc
		this.repeatGroupMarkers = $H({});
		this.form.getElements('.fabrikGroup').each(function(group){
			var id = group.id.replace('group', '');
			var c = group.getElements('.fabrikSubGroup').length;
			this.repeatGroupMarkers.set(id, c);
		}.bind(this));
		
	},

	watchAddOptions : function() {
		this.fx.addOptions = [];
		this.getForm().getElements('.addoption').each( function(d) {
		var a = d.getParent().getElement('.toggle-addoption');
		var mySlider = new Fx.Slide(d, {
			duration :500
		});
		mySlider.hide();
		a.addEvent('click', function(e) {
		new Event(e).stop();
		mySlider.toggle();
		});
		});
	},

	// Barbara
	includeCustomJsAction : function() {
		// remove element if present from manual closure of mocha window
		this.removeCustomJsAction();
		// inser new elt
		if (this.options.customJsAction != '') {
			// inject js into head
			this.customJsElement = new Element('script', {
				'type' :'text/javascript',
				'src' :this.options.customJsAction,
				'id' :'customJsAction'
			});
			this.customJsElement.injectInside(document.head);
		}
	},

	// Barbara
	removeCustomJsAction : function() {
		var elt = $E('script#customJsAction');
		if ($defined(elt)) {
			elt.remove();
		}
	},

	// Barbara
	includeFormCss : function() {
		// remove element if present from manual closure of mocha window
		this.removeFormCss();
		// insert new elt
		for ( var c = 0; c < this.options.formCss.length; c++) {
			// inject css into head
			var elt = new Element('link', {
				'type' :'text/css',
				'rel' :'stylesheet',
				'href' :this.options.formCss[c],
				'id' :'formCss' + c
			});
			elt.injectInside(document.head);
		}
	},

	// Barbara
	removeFormCss : function() {
		for ( var c = 0; c < 2; c++) {
			// 0 to 2 form specific css files
			var elt = $E('link#formCss' + c);
			if ($defined(elt)) {
				elt.remove();
			}
		}
	},

	setUp : function() {
		this.form = this.getForm();
		this.watchGroupButtons();
		if (this.options.editable) {
			this.watchSubmit();
		}
		this.createPages();
		this.watchClearSession();
	},

	getForm : function() {
		this.form = this.options.editable == true ? $('form_' + this.id)
				: $('details_' + this.id);
		return this.form;
	},

	// id is the element or group to apply the fx TO, triggered from another
	// element
	addElementFX : function(id) {
		id = id.replace('fabrik_trigger_', '');
		if (id.slice(0, 6) == 'group_') {
			id = id.slice(6, id.length);
			var k = id;
			var c = $(id);
		} else {
			id = id.slice(8, id.length);
			k = 'element' + id;
			if (!$(id)) {
				return;
			}
			c = $(id).findClassUp('fabrikElementContainer');
		}
		if (c) {
			// c will be the <li> element - you can't apply fx's to this as it makes the
			// DOM squiffy with
			// multi column rows, so get the li's content and put it inside a div which
			// is injected into c
			// apply fx to div rather than li - damn im good
			if ((c).getTag() == 'li') {
				var fxdiv = new Element('div').adopt(c.getChildren());
				c.empty();
				fxdiv.injectInside(c);
			} else {
				fxdiv = c;
			}
	
			var opts = {
				duration :800,
				transition :Fx.Transitions.Sine.easeInOut
			};
			this.fx.elements[k] = {};
			this.fx.elements[k].css = fxdiv.effect('opacity', opts);
			if ($type(fxdiv) != false) {
				this.fx.elements[k]['slide'] = new Fx.Slide(fxdiv, opts);
			} else {
				this.fx.elements[k]['slide'] = null;
			}
		}
	},

	doElementFX : function(id, method) {
		id = id.replace('fabrik_trigger_', '');
		if (id.slice(0, 6) == 'group_') {
			id = id.slice(6, id.length);
			//wierd fix?
			if (id.slice(0, 6) == 'group_') id = id.slice(6, id.length);
			var k = id;
			var groupfx = true;
		} else {
			groupfx = false;
			id = id.slice(8, id.length);
			k = 'element' + id;
		}
		var fx = this.fx.elements[k];
		if (!fx) {
			return;
		}
		var fxElement = groupfx ? fx.css.element : fx.css.element.findClassUp('fabrikElementContainer');
		switch (method) {
		case 'show':
			fxElement.removeClass('fabrikHide');
			fx.css.set(1);
			fx.css.element.show();
			break;
		case 'hide':
			fxElement.addClass('fabrikHide');
			fx.css.set(0);
			fx.css.element.hide();
			break;
		case 'fadein':
			fxElement.removeClass('fabrikHide');
			if (fx.css.lastMethod !== 'fadein') {
				fx.css.element.show();
				fx.css.start(0, 1);
			}
			break;
		case 'fadeout':
			if (fx.css.lastMethod !== 'fadeout') {
				fx.css.start(1, 0).chain( function() {
				fx.css.element.hide();
				fxElement.addClass('fabrikHide');
				});
			}
			break;
		case 'slide in':
			fx.slide.slideIn();
			break;
		case 'slide out':
			fx.slide.slideOut();
			fxElement.removeClass('fabrikHide');
			break;
		case 'slide toggle':
			fx.slide.toggle();
			//fxElement.toggleClass('fabrikHide');
			break;
		}
		fx.lastMethod = method;
		this.runPlugins('onDoElementFX', null);
	},

	watchClearSession : function() {
		if (this.form && this.form.getElement('.clearSession')) {
			this.form.getElement('.clearSession').addEvent('click', function(e) {
			new Event(e).stop();
				this.form.getElement('input[name=task]').value = 'removeSession';
				this.clearForm();
				this.form.submit();
			}.bind(this));
		}
	},

	createPages : function() {
		if (this.options.pages.keys().length > 1) {
			//wrap each page in its own div
			this.options.pages.each(function(page, i){
				var p = new Element('div', {'class': 'page', 'id':'page_' + i});
				p.injectBefore($('group'+page[0]));
				page.each(function(group){
					p.adopt($('group'+group));
				});
			});
			if ($('fabrikSubmit' + this.id)) {
				$('fabrikSubmit' + this.id).disabled = "disabled";
			}
			this.form.getElement('.fabrikPagePrevious').disabled = "disabled";
			this.form.getElement('.fabrikPageNext').addEvent('click',
					this._doPageNav.bindAsEventListener(this, [ 1 ]));
			this.form.getElement('.fabrikPagePrevious').addEvent('click',
					this._doPageNav.bindAsEventListener(this, [ -1 ]));
			this.setPageButtons();
			this.hideOtherPages();
		}
	},

	_doPageNav : function(e, dir) {
		var url = this.options.liveSite
				+ 'index.php?option=com_fabrik&controller=form&format=raw&task=ajax_validate&form_id='
				+ this.id;
		oPackage.startLoading('form_' + this.id, 'validating');
		var d = $H({});
		// only validate the current groups elements
		var groupId = this.options.pages.get(this.currentPage.toInt());
		this.formElements.each( function(el, key ) {
			if (el.element) {
				var group = el.element.findClassUp('fabrikGroup');
				groupId.each(function(gid){
					if (groupId && group.id == 'group' + gid) {
						d = el.prepereForAjaxPost(d);
					}	
				});
			}
		}.bind(this));
		d = this._prepareRepeatsForAjax(d);
		var myAjax = new Ajax(url, {
			method :this.options.ajaxmethod,
			data :d,
			onComplete : function(r) {
			oPackage.stopLoading();
			r = Json.evaluate(r);
			if (!this._showGroupError(r, d)) {
				this.changePage(dir);
				this.saveGroupsToDb();
			}
			}.bind(this)
		}).request();
	
		var event = new Event(e).stop();
	},

	getPageElements : function() {
		var d = new Hash();
		// get elements to validate
		this.options.pages.get(this.currentPage).each( function(id) {
		var g = $('group' + id);
		this.formElements.each( function(el, k) {
		if (g.getElement('#' + k)) {
			var element = g.getElement('#' + k);
			d.set(k, element.getValue());
		}
		}.bind(this));
		}.bind(this));
		return d;
		},
	
	saveGroupsToDb : function() {
		if(!this.runPlugins('saveGroupsToDb', null)){
			return;
		}
		var orig = this.form.getElement('input[name=format]').value;
		var origprocess = this.form.getElement('input[name=task]').value;
		this.form.getElement('input[name=format]').value = 'raw';
		this.form.getElement('input[name=task]').value = 'savepage';
	
		var url = this.options.liveSite
				+ 'index.php?option=com_fabrik&format=raw&page=' + this.currentPage;
		oPackage.startLoading('form_' + this.id, 'saving page');
		var data = this.getFormData();
		new Ajax(url, {
			method :this.options.ajaxmethod,
			data :data,
			onComplete : function(r) {
				if(!this.runPlugins('onCompleteSaveGroupsToDb', null)){
					return;
				}
				this.form.getElement('input[name=format]').value = orig;
				this.form.getElement('input[name=task]').value = origprocess;
				if (this.options.postMethod == 'ajax') {
					oPackage.sendMessage('form_' + this.id, 'updateRows', 'ok', json);
				}
				oPackage.stopLoading();
			}.bind(this)
		}).request();
	},
	
	changePage : function(dir) {
		if(!this.runPlugins('onChangePage', null)){
			return;
		}
		this.currentPage = this.currentPage.toInt();
		// hide all error messages ($$$ rob why would we want to do that? - commneting out)
		//this.form.getElements('.fabrikError').addClass('fabrikHide');
		if (this.currentPage + dir >= 0 && this.currentPage + dir < this.options.pages.keys().length) {
			this.currentPage = this.currentPage + dir;
		}
		this.setPageButtons();
		$('page_' + this.currentPage).setStyle('display', '');
		this.hideOtherPages();
		if(!this.runPlugins('onEndChangePage', null)){
			return;
		}
	},
	
	/**
	 * hide all groups except those in the active page
	 */
	hideOtherPages:function(){
		this.options.pages.each( function(gids, i) {
			if (i != this.currentPage) {
	  		$('page_' + i).setStyle('display', 'none');
	  	}
		}.bind(this));
	},

	setPageButtons : function() {
		if (this.currentPage == this.options.pages.keys().length - 1) {
			if ($('fabrikSubmit' + this.id))
				$('fabrikSubmit' + this.id).disabled = "";
			this.form.getElement('.fabrikPageNext').disabled = "disabled";
			this.form.getElement('.fabrikPageNext').setStyle('opacity', 0.5);
		} else {
			this.form.getElement('.fabrikPageNext').disabled = "";
			this.form.getElement('.fabrikPageNext').setStyle('opacity', 1);
		}
		if (this.currentPage === 0) {
			this.form.getElement('.fabrikPagePrevious').disabled = "disabled";
			this.form.getElement('.fabrikPagePrevious').setStyle('opacity', 0.5);
		} else {
			this.form.getElement('.fabrikPagePrevious').disabled = "";
			this.form.getElement('.fabrikPagePrevious').setStyle('opacity', 1);
		}
	},

	addElements : function(a) {
		for ( var i = 0; i < a.length; i++) {
			if ($type(a[i]) !== false) {
				this.addElement(a[i], a[i].options.element);
			}
		}
	},

	addElement: function(oEl, elId) {
		elId = elId.replace('[]', '');
		oEl.form = this;
		this.formElements.set(elId, oEl);
	},
	
	// we have to buffer the events in a pop up window as
	// the dom inserted when the window loads appears after the ajax evalscripts
	
	dispatchEvent : function(elementType, elementId, action, js) {
		if (!this.options.delayedEvents) {
			var el = this.formElements.get(elementId);
			if (el && js != '') {
				// el.storeEvent(action, js);
				el.addNewEvent(action, js);
			}
		} else {
			this.bufferEvent(elementType, elementId, action, js);
		}
	},

	bufferEvent : function(elementType, elementId, action, js) {
		this.bufferedEvents.push( [ elementType, elementId, action, js ]);
	},
	
	// call this after the popup window has loaded
	processBufferEvents : function() {
		this.setUp();
		this.options.delayedEvents = false;
		this.bufferedEvents.each( function(r) {
		// refresh the element ref
				var elementId = r[1];
				var el = this.formElements.get(elementId);
				el.element = $(elementId);
				this.dispatchEvent(r[0], elementId, r[2], r[3]);
				}.bind(this));
		},
	
		action : function(task, element) {
		var oEl = this.formElements.get(el);
		eval('oEl.' + task + '()');
	},
	
	triggerEvents: function(el){
		this.formElements.get(el).fireEvents(arguments[1]);
	},

	/**
	 * @param string
	 *          element id to observe
	 * @param string
	 *          error div for element
	 * @param string
	 *          parent element id - eg for datetime's time field this is the date
	 *          fields id
	 */
	watchValidation : function(id, triggerEvent) {
		if (this.options.ajaxValidation == false) {
			return;
		}
		if ($(id).className == 'fabrikSubElementContainer') {
			// check for things like radio buttons & checkboxes
			
			$(id).getElements('.fabrikinput').each(
					function(i) {
					i.addEvent(triggerEvent, this.doElementValidation.bindAsEventListener(this, [true]));
					}.bind(this));
			return;
		}
		$(id).addEvent(triggerEvent, this.doElementValidation.bindAsEventListener(this, [false]));
	},

	// as well as being called from watchValidation can be called from other
	// element js actions, e.g. date picker closing
	doElementValidation : function(event, subEl, replacetxt) {
		if (this.options.ajaxValidation == false) {
			return;
		}
		replacetxt = $type(replacetxt) == false ? '_time' : replacetxt;
		if ($type(event) == 'event' || $type(event) == 'object') { // type object in
			var e = new Event(event);
			var id = e.target.id;
		// for elements with subelements eg checkboxes radiobuttons
			if (subEl == true) {
				id = $(e.target).findClassUp('fabrikSubElementContainer').id;
			}
		} else {
			// hack for closing date picker where it seems the event object isnt
			// available
			id = event;
		}
	// for elements with subelements eg checkboxes radiobuttons
		/*if (subEl == true) {
			id = $(e.target).findClassUp('fabrikSubElementContainer').id;
		}*/
		if($type($(id)) === false){
			return;
		}
		if($(id).getProperty('readonly') === true || $(id).getProperty('readonly') == 'readonly'){
			//stops date element being validated 
			//return;
		}
		var el = this.formElements.get(id);
		if (!el) {
			//silly catch for date elements you cant do the usual method of setting the id in the 
			//fabrikSubElementContainer as its required to be on the date element for the calendar to work
			id = id.replace(replacetxt, '');
			el = this.formElements.get(id);
			if(!el){
				return;
			}
		}
		if(!this.runPlugins('onStartElementValidation', event)){
			return;
		}
		el.setErrorMessage(this.lang.validating, 'fabrikValidating');
	
		var d = $H({});
	
		this.formElements.each( function(el, key) {
			d = el.prepereForAjaxPost(d);
		}.bind(this));
		d = this._prepareRepeatsForAjax(d);
	
		var origid = el.origId ? el.origId : id;
		el.repeatGroupId = el.repeatGroupId ? el.repeatGroupId : 0;
	
		var url = this.options.liveSite
				+ 'index.php?option=com_fabrik&controller=form&format=raw&task=ajax_validate&form_id='
				+ this.id;
		var myAjax = new Ajax(url, {
			method :this.options.ajaxmethod,
			data :d,
			onComplete :this._completeValidaton.bindAsEventListener(this, [ id, origid ])
		}).request();
	},

	_completeValidaton : function(r, id, origid) {
		r = Json.evaluate(r);
		if(!this.runPlugins('onCompleteElementValidation', null)){
			return;
		}
		var el = this.formElements.get(id);
		if ($defined(r.modified[origid])) {
			el.update(r.modified[origid]);
		}
		if ($type(r.errors[origid]) !== false) {
			this._showElementError(r.errors[origid][el.repeatGroupId], id);
		} else {
			this._showElementError([], id);
		}
	},

	_prepareRepeatsForAjax : function(d) {
		this.getForm();
		//ensure we are dealing with a simple object
		if ($type(d) === 'hash'
				|| ($type(d.obj) === 'object' && this.options.mooversion == 1.1)) {
			d = (this.options.mooversion == 1.1) ? d.obj : d.getClean();
		}
		if (this.options.mooversion == '1.1') {
			this.form.getElements('input[name^=fabrik_repeat_group]').each(
					function(e, c) {
					d['fabrik_repeat_group[' + c + ']'] = e.getValue(); // good for mootools
																															// 1.1
					});
		} else {
			d.fabrik_repeat_group = [];
			this.form.getElements('input[name^=fabrik_repeat_group]').each(
					function(e) {
					d.fabrik_repeat_group.push(e.getValue()); // good for mootools 1.2
					});
		}
		return d;
	},

	_showGroupError : function(r, d) {
		var err = false;
		$H(d).each( function(v, k) {
			k = k.replace('[]', '');//for dropdown validations
		if (r.errors[k]) {
			// prepare error so that it only triggers for real errors and not sucess
			// msgs
	
				var msg = '';
				if ($type(r.errors[k]) !== false) {
					for ( var i = 0; i < r.errors[k].length; i++) {
						if (r.errors[k][i] != '') {
							msg += r[i] + '<br />';
						}
					}
				}
				if (msg !== '') {
					tmperr = this._showElementError(r.errors[k], k);
					if (err == false) {
						err = tmperr;
					}
				}
			}
			if (r.modified[k]) {
				var el = this.formElements.get(k);
				if (el) {
					el.update(r.modified[k]);
				}
			}
			}.bind(this));
		return err;
		},
	
		_showElementError : function(r, id) {
		// r should be the errors for the specific element, down to its repeat group
		// id.
		var msg = '';
		if ($type(r) !== false) {
			for ( var i = 0; i < r.length; i++) {
				if (r[i] != '') {
					msg += r[i] + '<br />';
				}
			}
		}
		var classname = (msg === '') ? 'fabrikSuccess' : 'fabrikError';
		if (msg === '')
			msg = this.lang.success;
		this.formElements.get(id).setErrorMessage(msg, classname);
		return (classname === 'fabrikSuccess') ? false : true;
	},

	updateMainError : function() {
		var mainEr = this.form.getElement('.fabrikMainError');
		mainEr.setHTML(this.options.error);
		var activeValidations = this.form.getElements('.fabrikError').filter(
				function(e, index) {
				return !e.hasClass('fabrikMainError');
				});
		if (activeValidations.length > 0 && mainEr.hasClass('fabrikHide')) {
			mainEr.removeClass('fabrikHide');
			var myfx = new Fx.Style(mainEr, 'opacity', {
				duration :500
			}).start(0, 1);
		}
		if (activeValidations.length === 0) {
			myfx = new Fx.Style(mainEr, 'opacity', {
				duration :500,
				onComplete : function() {
				mainEr.addClass('fabrikHide');
				}
			}).start(1, 0);
		}
	},

	runPlugins : function(func, event) {
		var ret = true;
		this.options.plugins.each( function(plugin) {
		if ($type(plugin[func]) != false) {
			if (plugin[func](event) == false) {
				ret = false;
			}
		}
		});
		return ret;
	},

	watchSubmit : function() {
		if (!$('fabrikSubmit' + this.id)) {
			return;
		}
	
		$('fabrikSubmit' + this.id).addEvent('click', function(e) {
		var ret = this.runPlugins('onSubmit', e);
		this.elementsBeforeSubmit(e);
		if (ret == false) {
			new Event(e).stop();
			// update global status error
			this.updateMainError();
		}
		if (ret && this.options.postMethod == 'ajax') {
			//do ajax val only if onSubmit val ok
			if (this.form) {
				oPackage.startLoading('form_' + this.id);
				this.elementsBeforeSubmit(e);
				// get all values from the form
				var data = $H(this.getFormData());
				data = this._prepareRepeatsForAjax(data);
					var myajax = new Ajax(this.form.action, {
						'data' :data,
						'method' :this.options.ajaxmethod,
						onComplete : function(json) {
						var ojson = Json.evaluate(json);
						
						if($type(ojson) === false) {
							return;
						}
						// process errors if there are some
						if ($defined(ojson.errors) && ojson.errors.length != 0) {
							// for every element of the form update error message
						this.formElements.each( function(el, key) {
						var eltId = el.element.id;
						var errMsg = '';
						if ($defined(ojson.errors[eltId])) {
							errMsg = ojson.errors[eltId];
						}
						// and main error message
								this._showElementError(errMsg, key);
								}.bind(this));
						// update global status error
						this.updateMainError();
						// stop spinner
						oPackage.stopLoading('form_' + this.id);
						// this.runPlugins('onAjaxSubmitComplete'); don't run it I guess
					} else {
						// no errors
						oPackage.sendMessage('form_' + this.id, 'updateRows', 'ok', json);
						this.runPlugins('onAjaxSubmitComplete', e);
						// this.clearForm();
					}
					}.bind(this)
					}).request();
				}
			}
			}.bind(this));
		
		},
	
		elementsBeforeSubmit : function(e) {
			e = new Event(e);
			this.formElements.each( function(el, key) {
			if (!el.onsubmit()) {
				e.stop();
			}
		});
	},

	// used to get the querystring data and
	// for any element overwrite with its own data definition
	// required for empty select lists which return undefined as their value if no
	// items
	// available

	getFormData : function() {
		this.getForm();
		var s = this.form.toQueryString();
		var h = {};
		s = s.split('&');
		s.each( function(p) {
		p = p.split('=');
		h[p[0]] = p[1];
		});
		// $$$rob test commenting out - as this messes up for date from ajax popupform
		// in cal
		/*
		 * this.formElements.each(function(el, key){ var v = el.getValue(); if(v !==
		 * false){ h[key] = v; } }.bind(this));
		 */
		return h;
	},

	watchGroupButtons : function() {
		this.unwatchGroupButtons();
		this.form.getElements('.deleteGroup').each( function(g, i) {
			g.addEvent('click', this.clickDeleteGroup);
		}.bind(this));
		this.form.getElements('.addGroup').each( function(g, i) {
			g.addEvent('click', this.clickDuplicateGroup);
		}.bind(this));
		this.form.getElements('.fabrikSubGroup').each( function(subGroup) {
		var r = subGroup.getElement('.fabrikGroupRepeater');
		if (r) {
			subGroup.addEvent('mouseenter', function(e) {
			r.effect('opacity', {
				wait :true,
				duration :200
			}).start(0.2, 1);
			});
			subGroup.addEvent('mouseleave', function(e) {
			r.effect('opacity', {
				wait :true,
				duration :200
			}).start(1, 0.2);
			});
		}
		});
	},

	unwatchGroupButtons : function() {
		this.form.getElements('.deleteGroup').each( function(g, i) {
			g.removeEvent('click', this.clickDeleteGroup);
		}.bind(this));
		this.form.getElements('.addGroup').each( function(g, i) {
			g.removeEvent('click', this.clickDuplicateGroup);
		}.bind(this));
		this.form.getElements('.fabrikSubGroup').each( function(subGroup) {
			subGroup.removeEvents('mouseenter');
			subGroup.removeEvents('mouseleave');
		});
	},

	addGroupJS : function(groupId, e, js) {
		if (e == 'delete') {
			this.delGroupJS.set(groupId, js);
		} else {
			this.duplicateGroupJS.set(groupId, js);
		}
	},

	deleteGroup : function(event) {
		if(!this.runPlugins('onDeleteGroup', event)){
			return;
		}
		var e = new Event(event).stop();
		var group = $(e.target).findClassUp('fabrikGroup');
		var i = group.id.replace('group', '');
		this.duplicatedGroups.remove(i);
		if($('fabrik_repeat_group_' + i + '_counter').value == '0'){
			return;
		}
		var subgroups = group.getElements('.fabrikSubGroup');
		
		var subGroup = $(e.target).findClassUp('fabrikSubGroup');
		this.subGroups.set(i, subGroup.clone());
		if (subgroups.length <= 1) {
			this.hideLastGroup(i, subGroup);
			
		}else{
			
			var toel = subGroup.getPrevious();
			var js = this.delGroupJS.get(i);
			
			var myFx = new Fx.Style(subGroup, 'opacity', {
				duration :300,
				onComplete : function() {
					if (subgroups.length > 1) {
						subGroup.remove();
					}
					
					this.formElements.each(function(e, k){
						if($type($(e.element.id)) == false){
							e.decloned(i);
							this.formElements.remove(k);
						}
					}.bind(this));
					
					eval(js);
				}.bind(this)
			}).start(1, 0);
			if (toel) {
				this.winScroller.toElement(toel);
			}
		}
	// update the hidden field containing number of repeat groups
		$('fabrik_repeat_group_' + i + '_counter').value = $(
				'fabrik_repeat_group_' + i + '_counter').getValue().toInt() - 1;
	},
	
	hideLastGroup:function(groupid, subGroup){
		var sge = subGroup.getElement('.fabrikSubGroupElements');
		sge.setStyle('display', 'none');
		new Element('div', { 'class' :'fabrikNotice' }).appendText(this.lang.nodata).injectAfter(sge);
	},
	
	isFirstRepeatSubGroup:function(group)
	{
		var subgroups = group.getElements('.fabrikSubGroup');
		return subgroups.length == 1 && subgroups[0].getElement('.fabrikNotice');
	},
	
	getSubGroupToClone:function(groupid)
	{
		var group = $('group' + groupid);
		var subgroup = group.getElement('.fabrikSubGroup');
		if (!subgroup) {
			subgroup = this.subGroups.get(groupid);
		}

		var clone = null;
		var found = false;
		if (this.duplicatedGroups.hasKey(groupid)) {
			found = true;
		}
		if (!found) {
			clone = subgroup.cloneNode(true);
			this.duplicatedGroups.set(groupid, clone);
		} else {
			if (!subgroup) {
				clone = this.duplicatedGroups.get(groupid);
			} else {
				clone = subgroup.cloneNode(true);
			}
		}
		return clone;
	},
	
	repeatGetChecked:function(group)
	{
		///stupid fix for radio buttons loosing their checked value
		var tocheck = [];
		group.getElements('.fabrikinput').each(function(i){
			if(i.type == 'radio' && i.getProperty('checked') ){
				tocheck.push(i);
			}
		});
		return tocheck;
	},

	/* duplicates the groups sub group and places it at the end of the group */

	duplicateGroup : function(event) {
		if(!this.runPlugins('onDuplicateGroup', event)){
			return;
		}
		var e = new Event(event);
		e.stop();
		var i = $(e.target).findClassUp('fabrikGroup').id.replace('group', '');
		var js = this.duplicateGroupJS.get(i);
		var group = $('group' + i);
		var c = this.repeatGroupMarkers.get(i);

		$('fabrik_repeat_group_' + i + '_counter').value = $(
				'fabrik_repeat_group_' + i + '_counter').getValue().toInt() + 1;
		
		if (this.isFirstRepeatSubGroup(group)) {
			var subgroups = group.getElements('.fabrikSubGroup');
			//user has removed all repeat groups and now wants to add it back in
			//remove the 'no groups' notice
			subgroups[0].getElement('.fabrikNotice').remove();
			subgroups[0].getElement('.fabrikSubGroupElements').setStyle('display', '');
			return;
		}
		var clone = this.getSubGroupToClone(i);
		var tocheck = this.repeatGetChecked(group);
		
		group.appendChild(clone);
		tocheck.each(function(i){
			i.setProperty('checked', true);
		});
		// remove values and increment ids
		var newElementControllers = [];
		this.subelementCounter = 0;
		var hasSubElements = false;
		var inputs = clone.getElements('.fabrikinput');
		var lastinput = null;
		this.formElements.each( function(el) {
			var formElementFound = false;
			subElementContainer = null;
			var subElementCounter = -1;

			inputs.each( function(input) {
	
				hasSubElements = el.hasSubElements();
	
				//for all instances of the call to findClassUp use el.element rather than input (HMM SEE LINE 912 - PERHAPS WE CAN REVERT TO USING INPUT NOW?)
				// var testid = (hasSubElements) ?
				// input.findClassUp('fabrikSubElementContainer').id : input.id
				//var testid = (hasSubElements) ? el.element.findClassUp('fabrikSubElementContainer').id : input.id;
				var testid = (hasSubElements) ? input.findClassUp('fabrikSubElementContainer').id : input.id;
				
				if (el.options.element == testid) {
					lastinput = input;
					formElementFound = true;
	
					if (hasSubElements) {
						subElementCounter++;
						//the line below meant that we updated the orginal groups id @ line 942 - which in tern meant when we cleared the values we were clearing the orignal elements values 
						//not sure how this fits in with comments above which state we should use el.element.findClassUp('fabrikSubElementContainer');
						//REAL ISSUE WAS THAT inputs  CONTAINED ADD OPTIONS (elementmodel->getAddOptionFields) WHICH HAD ELEMENTS WITH THE CLASS fabrikinput THIS CLASS IS RESERVERED FOR ACTUAL DATA ELEMENTS 
						//subElementContainer = el.element.findClassUp('fabrikSubElementContainer');
						
						subElementContainer = input.findClassUp('fabrikSubElementContainer');
						// clone the first inputs event to all subelements
						input.cloneEvents($(testid).getElement('input'));
	
						// id set out side this each() function
					} else {
						input.cloneEvents(el.element);
	
						// update the element id use el.element.id rather than input.id as that may contain _1 at end of id
						input.id = el.element.id + '_' + c;
	
						// update labels for non sub elements 
						var l = input.findClassUp('fabrikElementContainer').getElement('label');
						if (l) {
							l.setProperty('for', input.id);
						}
					}
	
					input.name = input.name.replace('[0]', '[' + (c) + ']');
				}
			}.bind(this));
	
			if (formElementFound) {
				if (hasSubElements && $type(subElementContainer) != false ) {
					// if we are checking subelements set the container id after they have all
					// been processed
					// otherwise if check only works for first subelement and no further
					// events are cloned
					subElementContainer.id = el.options.element + '_' + c;
				}
				var origelid = el.options.element;
				// clone js element controller, set form to be passed by reference and not cloned
				var newEl = new CloneObject(el, true, ['form']);

				newEl.container = null;
				newEl.repeatGroupId = c;
				newEl.origId = origelid;
				
				if (hasSubElements && $type(subElementContainer) != false ) {
					newEl.element = $(subElementContainer);
					newEl.options.element = subElementContainer.id;
					newEl._getSubElements();
				} else {
					newEl.element = $(lastinput.id);
					newEl.options.element = lastinput.id;
				}
				newEl.reset();
				newElementControllers.push(newEl);
			}
		}.bind(this));
		
		// add new element controllers to form
		this.addElements(newElementControllers);
		this.winScroller.toElement(clone);
		var myFx = new Fx.Style(clone, 'opacity', {
			duration :500
		}).set(0);
		newElementControllers.each( function(newEl) {
			newEl.cloned(c);
		});
		c = c + 1;
		myFx.start(1);
		eval(js);
		this.repeatGroupMarkers.set(i, this.repeatGroupMarkers.get(i) + 1);
		this.unwatchGroupButtons();
		this.watchGroupButtons();
	},

	update : function(o) {
		if(!this.runPlugins('onUpdate', null)){
			return;
		}
		var leaveEmpties = arguments[1] || false;
		var data = o.data;
		this.getForm();
		if (this.form) { // test for detailed view in module???
			var rowidel = this.form.getElement('input[name=rowid]');
			if (rowidel && data.rowid) {
				rowidel.value = data.rowid;
			}
		}
		this.formElements.each( function(el, key) {
			if (key.substring(key.length - 3, key.length) == '_ro') {
				key = key.substring(0, key.length - 3);
			}
			// this if stopped the form updating empty fields. Element update() methods
			// should test for null
			// variables and convert to their correct values
			// if (data[key]) {
			if ($type(data[key]) === false) {
				// only update blanks if the form is updating itself
				// leaveEmpties set to true when this form is called from updateRows
				if (o.id == this.id && !leaveEmpties) {
					el.update('');
				}
			} else {
				el.update(data[key]);
			}
		}.bind(this));
	},

	reset : function() {
		if(!this.runPlugins('onReset', null)){
			return;
		}
		this.formElements.each( function(el, key) {
		el.reset();
		}.bind(this));
	},

	showErrors : function(data) {
		var d = null;
		if (data.id == this.id) {
		// show errors
		var errors = new Hash(data.errors);
		if (errors.keys().length > 0) {
			this.form.getElement('.fabrikMainError').setHTML(this.options.error);
			this.form.getElement('.fabrikMainError').removeClass('fabrikHide');
			errors.each( function(a, key) {
				if ($(key + '_error')) {
					var e = $(key + '_error');
					var msg = new Element('span');
					for ( var x = 0; x < a.length; x++) {
						for ( var y = 0; y < a[x].length; y++) {
							d = new Element('div').appendText(a[x][y]).injectInside(e);
						}
					}
				} else {
					fconsole(key + '_error' + ' not found');
				}
			});
		}
	}
},

	/** add additional data to an element - e.g database join elements */
	appendInfo : function(data) {
		this.formElements.each( function(el, key) {
		if (el.appendInfo) {
			el.appendInfo(data);
		}
		}.bind(this));
	},

	addListenTo : function(blockId) {
	this.listenTo.push(blockId);
	},
	
	clearForm : function() {
		this.getForm();
		if (!this.form) {
			return;
		}
		this.formElements.each( function(el, key) {
			if (key == this.options.primaryKey) {
				this.form.getElement('input[name=rowid]').value = '';
			}
			el.update('');
		}.bind(this));
		// reset errors
		this.form.getElements('.fabrikError').empty();
		this.form.getElements('.fabrikError').addClass('fabrikHide');
	},
		
	receiveMessage : function(senderBlock, task, taskStatus, data) {
		if (this.listenTo.indexOf(senderBlock) != -1) {
			if (task == 'processForm') {
		
			}
			// a row from the table has been loaded
			if (task == 'update') {
				this.update(data);
			}
			if (task == 'clearForm') {
				this.clearForm();
			}
		}
		
		if (senderBlock == 'form_' + this.id) {
			var id = 'fabrik_update_msg_' + this.id;
			var d = new Element('div', {
				'id' :id,
				'class' :'update_msg'
			}).appendText(this.options.updatedMsg);
			try {
				// Barbara : does not work with Mocha window
			d.injectBefore(this.form);
			} catch (err) {
			}
		
			var myfx = new Fx.Style(id, 'opacity', {
				duration :500
			});
			myfx.set(0);
			myfx.start(0, 1).chain( function() {
				this.start(1, 0).chain( function() {
				if ($(id)) {
					$(id).remove();
				}
				});
			});
		}
	
		// a form has been submitted which contains data that should be updated in this
		// form
		// currently for updating database join drop downs, data is used just as a
		// test to see if the dd needs
		// updating. If found a new ajax call is made from within the dd to update
		// itself
		// $$$ hugh - moved showErrors() so it only runs if data.errors has content
		if (task == 'updateRows') {
			if ($H(data.errors).keys().length === 0) {
				this.appendInfo(data);
				this.update(data, true);
			} else {
				this.showErrors(data);
			}
		}
		},
	
		addPlugin : function(plugin) {
		this.options.plugins.push(plugin);
	}

});

var Injector = new Class( {
	injectScript : function(src) {
	var script = new Element('script', {
		'type' :'text/javascript',
		'src' :src
	});
	script.injectInside(document.head);
	}
});

var scriptInjector = new Injector();

