// Generate/Clone fields
// containerId is the id of the dom element (mostly divs) which contains the fields
// html is the html text template of the form fields, all form fields _must_ have id and name attributes with same value.
// masterId is the template string which will be replaced with suitable integer (e.g. $X). It must be regexp escaped (e.g. "\\$X")
// calendarOptions are the default dict of options for the calendar objects
// IMPORTANT: The date fields, which are represented by the calnedar objects, must have class "date-field"
function Fields(containerId, html, masterId, containerType, counterStart, calendarOptions) {
	this.innerCounter = counterStart || 0; // the next id which will be used to replace masterId
	this.calendarOptions = calendarOptions || {showsTime: true, ifFormat: "%Y/%m/%d %H:%M", daFormat: "%l;%M %p, %e %m,  %Y",	align: "BR", electric: true,	singleClick: false,	button: ".next()"};
	this.containerType = containerType || "div";
	this.html = html; // IMPORTANT: ALL form fields must _HAVE_ id and name attributes (which have same value)
	this.masterId = masterId;
	this.containerId = containerId;
	this.idsList = []; // list of all ids of the fields
	
	idre = new RegExp("id\\s*=\\s*[\"'](\\w+?)"+masterId+"[\"']", 'ig');
	while (m = idre.exec(this.html)) { // find all id attributes and store them in the list
		this.idsList.push(m[1]);
	}
	
	function generate(id, speed) { // generate new form field group
//		alert(['before ', id, this.innerCounter]);
		if (id != 'undefined' && this.innerCounter <= id) {
			this.innerCounter = id;
		} else {
			id = ++ this.innerCounter;
		}		
		speed = speed == undefined ? 0 : speed;
		resHtml = this.html.replace(new RegExp(this.masterId, 'g') , id);
		$('#'+this.containerId).append("<" + this.containerType + " id='" + this.containerId + "_" + id + "' style='display: none'>" + resHtml + "</" + this.containerType + ">");

		if (jQuery.browser.msie && jQuery.browser.version >= 8) {
			tagre = new RegExp("<[^<>\\n]*?\\sid\\s*=\\s*[\"'](\\w+?)[\"'][^<>\\n]*?\\s(onclick|onchange)\\s*=\\s*([\"'])(.+?[^\\\\])(\\3)[^<>\\n]*?>", 'ig');
			while (m = tagre.exec(resHtml)) { // find all elements which has onclik or onchange attributes and bind their events to the DOM
				document.getElementById(m[1])[m[2]] = null; // unset the DOM's event
				$("#"+m[1]).bind(m[2].replace(/^on/, ""), {function_code: m[4]}, function(e) { eval(e.data.function_code); });
			}
		}
	
		this_calendarOptions = this.calendarOptions;
		if (speed != -1) {
			$("#" + this.containerId + "_" + id).show2((this.containerType.toLowerCase() == "tr" ? null : speed), function () {
				if ((df = jQuery("#" + $(this).attr("id") + " .date-field")).size() > 0) df.dynDateTime(this_calendarOptions); // assign new calendar object
			});
		}
	}
	
	function multigenerate(ids) {
		thisObj = this;
		jQuery.each(
			ids.sort(
				function(first, second){
					return first - second;
				}
			), 
			function (i, val) {thisObj.generate(val)}
		);
	}
	
	function moveUp(id){
		id = (new RegExp(".*?(\\d+)").exec(id))[1]; // if you pass an element id, remove all leading non digits
		if (!id || id > this.innerCounter){
			return;
		}
		
		var row = $("#" + this.containerId + "_" + id);
		var prev = row.prev();
		
		if(prev != null && prev.attr('id') != undefined && prev.attr('id').match(this.containerId + "_\\d+")){
			prev.before(row);
		}
	}
	
	function moveDown(id){
		id = (new RegExp(".*?(\\d+)").exec(id))[1]; // if you pass an element id, remove all leading non digits
		if (!id || id > this.innerCounter){
			return;
		}
		
		var row = $("#" + this.containerId + "_" + id);
		var next = row.next();
		
		if(next != null && next.attr('id') != undefined && next.attr('id').match(this.containerId + "_\\d+")){
			next.after(row);
		}
	}
	
	function getIndicesOrder(){
		var order = [];
		var index = 0;
		re = new RegExp(this.containerId + "_(\\d+)");
		var selector = "#"+this.containerId + " " +  containerType;
		$(selector).each(
			function(i, row){
				var match = re.exec(row['id']);
				if(match != null){
					order[index ++] = match[1];
				}
			}
		);
		
		return order;
	}
	
	function clone(id) { // clone exisitng form field group with this id
		//$('#f1_1').clone().appendTo($('#f1')); the jquery way, which is inapplicable
		if (this.innerCounter <= 0) return; // some validations
		id = (new RegExp(".*?(\\d+)").exec(id))[1]; // if you pass an element id, remove all leading non digits
		if (!id || id > this.innerCounter) id = this.innerCounter; // some validations
		this.innerCounter++;
		dom = document.getElementById(this.containerId + "_" + id).cloneNode(true); // clone the container and all childs
		dom.id = this.containerId + "_" + this.innerCounter; // apply new id to the container
		queue = [];
		queue.push(dom.firstChild);
		events_queue = [];
		while (queue.length > 0) { // Breadth-first search
			e = queue.pop();
			do {
				if (e.id && (elementId = jQuery.inArray(true, jQuery.map(this.idsList, function(_){return _ + id == e.id}))) >= 0) { // check whether the id is in the list of ids
					e.id = this.idsList[elementId] + this.innerCounter; // set the new id attribute value
					if (jQuery.browser.msie && jQuery.browser.version >= 8) { // stupid IE8
						if (e.onclick) events_queue.push(["#" + e.id, "click", e.onclick, "#" + this.idsList[elementId] + id]);
						if (e.onchange) events_queue.push(["#" + e.id, "change", e.onchange, "#" + this.idsList[elementId] + id]);
					}
					if (e.name) e.name = e.id; // set name if the element is form field
					tag = e.nodeName.toLowerCase();
					if (tag == "select") { // dom cloning is working well only with input elements, so for select and textarea there ia additional checks and operations
						e.selectedIndex = document.getElementById(this.idsList[elementId] + id).selectedIndex;
					} else if (tag == "textarea") {
						e.value = document.getElementById(this.idsList[elementId] + id).value;
					} else if ($.browser.msie && tag == "input" && e.type == "checkbox" && document.getElementById(this.idsList[elementId] + id).checked) {
						e.checked = true; // ie has a lot of bugs and one of them is that the checkbox is not cloned correctly						
					}
				}
				if (e.firstChild) queue.push(e.firstChild);
			} while (e = e.nextSibling);
		}
		
		dom.style.display = "none";
		document.getElementById(this.containerId).appendChild(dom); // append the created dom
		while (events_queue.length > 0) { // bind all events of the original elements to the new clone
			ev_data = events_queue.pop();
			$(ev_data[0]).unbind(ev_data[1]);
			$(ev_data[0]).bind(ev_data[1], {function_code: ev_data[2]}, function(ev){eval(ev.data.function_code);});
			$(ev_data[3]).bind(ev_data[1], {function_code: ev_data[2]}, function(ev){eval(ev.data.function_code);});
		}
		this_calendarOptions = this.calendarOptions;
		$("#" + dom.id).show2((this.containerType.toLowerCase() == "tr" ? null : "slow"), function () {
			if ((df = jQuery("#" + $(this).attr("id") + " .date-field")).size() > 0) df.dynDateTime(this_calendarOptions); // assign new calendar object
		});
	}
	
	function remove(id) {
		if (this.innerCounter <= 0) return; // some validations
		id = (new RegExp(".*?(\\d+)").exec(id))[1]; // if you pass an element id, remove all leading non digits
		if (!id || id > this.innerCounter) id = this.innerCounter; // some validations
		$("#" + this.containerId + "_" + id).hide2((this.containerType.toLowerCase() == "tr" ? null : null), function () {
			$(this).remove();
		});
	}
	
	function clear(){
		var re = new RegExp('^' + this.containerId + '_(\\d+)$');

		$('#' + this.containerId + ' ' +  this.containerType).filter(function(index){
			return re.exec($(this).attr('id'));
		}).remove();	
		
		this.innerCounter = 0;
	}
	
	function getCount(){
		var re = new RegExp('^' + this.containerId + '_(\\d+)$');
		return $('#' + this.containerId + ' ' +  this.containerType).filter(function(index){
			return re.exec($(this).attr('id'));
		}).length;	
	}
	
	Fields.prototype.generate = generate;
	Fields.prototype.multigenerate = multigenerate;
	Fields.prototype.clone = clone;
	Fields.prototype.remove = remove;
	Fields.prototype.moveUp = moveUp;
	Fields.prototype.moveDown = moveDown;
	Fields.prototype.clear = clear;
	Fields.prototype.getCount = getCount;
	Fields.prototype.getIndicesOrder = getIndicesOrder;
}

function disableChilds(id) { // disable all child elements which has attribute name or are input fields
	if (document.getElementById(id)) {
		$('#'+id+' [name]').attr('disabled', 'disabled');
		$('#'+id+' [type="button"]').hide();
	}
}
