/**
 * @author Nathanael Dewhurst
 */

var selected_control;
var current_erroneous_field = null;
var validating_a_control = false;
var vald_dlg_el;
var vald_dlg_buttons;
var controls;
var errors;
var vald_failures = new Array();
var form_submission_attempt = false;
var investigating_error = false;

function setupFormEvents(local_form_id) {
	//Obtain all controls that belong to auto-generated forms on this page
	controls = document.getElementsByClassName("control");

	YAHOO.util.Event.addListener(controls, "blur", function() {
		//Reset the selected_control flag as soon as a selected control loses focus
		//	A non-null value will be restored if another event receives focus, but we want the flag to be null in the event
		//	that the user has navigated to something other than a form control
		selected_control = null;
		YAHOO.util.Dom.replaceClass(YAHOO.util.Dom.getAncestorByTagName(this, "LI"), "focused", "blurred");
	});
	YAHOO.util.Event.addListener(controls, "focus", function() {
		//The focus event of the newly targeted element will fire before the blur event of the previously focused element (non-intuitively).
		//	Therefore, delay the setting of the selected_control flag until after the accompanying blur event has caused the flag to be reset
		//	(i.e. if we don't delay this action, we will set the flag but it will be immediately nullified as a result of the blur event)
		setTimeout("selected_control = '" + this.id + "';", 600);
		YAHOO.util.Dom.replaceClass(YAHOO.util.Dom.getAncestorByTagName(this, "LI"), "blurred", "focused");
	});
	
	//Before setting up more events, select the first control element to ease user-input
	// (this will apply to the first element on the page with the "control" class, which should be the first element auto-generated from the db "forms_*" tables)	
	var first_control = controls[0];
	first_control.focus();
	
	//Keep track of which element currently has focus, if any.
	selected_control = first_control.id;
	
	form_id = local_form_id;
	//Obtain the form element that corresponds to the given form id
	var form_el;
	var forms = document.getElementsByTagName("FORM");

	for(var i = 0; i < forms.length; i++) {
		var form = forms[i];
		if(form.id.match("form__" + form_id + "__")) {
				form_el = form;
		}
	}
	
	formjax = new ajaxObject(null, "forms_ajax.php");
	

	//Setup field validation
	
	//Check all visible controls upon form submission
	YAHOO.util.Event.addListener(form_el, "submit", function(e){
//		alert('submit');
//		if (!validating_a_control) {
		
		form_submission_attempt = true;
			var num_vald_failures = validateAll();
			if (num_vald_failures > 0) {
				//Prevent the form from being submitted
				YAHOO.util.Event.stopEvent(e);
				
				//
				if (num_vald_failures == 1) {
					var failure = vald_failures[0];
					if (failure) 
						validateControl(document.getElementById(vald_failures[0]), false);
				}
				//Toggle the error dialog's buttons on/off and display it (then reverse mods after it is dismissed)
				else {
					//Set an appropriate message to display
					var msg = "<p>There are errors with " + vald_failures.length + " of the fields on this form.</p>\n" +
					"<p>These fields are outlined in red.  Click on the \"X\" symbol next to each field to see specific information explaining how to correct that field.</p>";
					document.getElementById("vald_failure_msg").innerHTML = msg;
					
					//Swap the visibility of the various buttons on the vald dialog
					vald_dlg_buttons[0].style.display = "none";
					vald_dlg_buttons[1].style.display = "none";
					vald_dlg_buttons[2].style.display = "block";
					//Override the auto-focusing of the dialog's "default" button (the now-invisible "fix this error" button)
					setTimeout("vald_dlg_buttons[2].focus();", 500);
					
					//Remove the dialog's balloon tail (it will be re-applied as necessary)
					YAHOO.util.Dom.removeClass(vald_dlg_el, "balloon");
					YAHOO.util.Dom.removeClass(vald_dlg_el, "balloon_tailing_right");  //this is presently superfluous (makes no visible difference to the user), but provides slightly cleaner markup
					
					//Affix the vald dialog to the center of the viewport (as opposed to the relative positioning used for field-specific errors)
					valdDialog.cfg.setProperty("fixedcenter", true);
					
/*					
					
					//Create and properly place the new button if it does not exist
					if (typeof(valdButtons2) == "undefined") {
						var valdButtons2 = [{
							text: "Begin fixing these errors",
							handler: function(){
								this.hide();
								//After the dust has settled, bring focus to the first offending element in the form
								//This must be delayed long enough to occur after the simpleDialog has been hidden and any effects have concluded, 
								//	so pay attention to, for instance, the length of a fade-out effect, which is set when valdDialog is created
								setTimeout("document.getElementById(vald_failures[0]).focus();", 400);
								
								//Revert to the standard button(s) on the vald dialog
								
								
								//							var valdButtons = [{
								//								text: "Fix this error",
								//								handler: handleFix,
								//								isDefault: true
								//							}, {
								//								text: "Ignore for now",
								//								handler: handleIgnore
								//							}];
								//							valdDialog.cfg.queueProperty("buttons", valdButtons);
								//							valdDialog.render(document.body);
								
								//Eventually, but not too soon, declare the end of the "form submission attempt" time period 
								//	(during which we ignore individual validation errors)
								setTimeout("form_submission_attempt = false;", 1000);
								
							},
							isDefault: true
						}];
						valdDialog.cfg.queueProperty("buttons", valdButtons2);
						valdDialog.render(document.body);
						//TODO: Find a way to do this that is less of a hack.  E.g. override/extend the queueProperty method of the valdDialog object.
						//Having used the built-in method to initialize/attach/empower the new button, re-arrange the DOM structure of the vald error dialog
						//	to conform to my style.
						vald_dlg_el = document.getElementById("vald_dialog");
						var button_groups = vald_dlg_el.getElementsByClassName("button-group");
						var normal_buttons = button_groups[0];
						var new_buttons = button_groups[1];
						//Hide the regular set of buttons
						normal_buttons.style.visibility = "hidden";
						//Move the new button next to the normal ones
						normal_buttons.parentNode.appendChild(new_buttons);
						//Trim off the now-unnecessary (and empty) auto-created "bd" (body) and "ft" (footer) elements
						vald_dlg_el.removeChild(vald_dlg_el.getElementsByClassName("bd")[0]);
						vald_dlg_el.removeChild(vald_dlg_el.getElementsByClassName("ft")[0]);
					}
					else {
						//TODO: Find a way to do this that is less of a hack.  E.g. override/extend the queueProperty method of the valdDialog object.
						//Having used the built-in method to initialize/attach/empower the new button, re-arrange the DOM structure of the vald error dialog
						//	to conform to my style.
						vald_dlg_el = document.getElementById("vald_dialog");
						var button_groups = vald_dlg_el.getElementsByClassName("button-group");
						var normal_buttons = button_groups[0];
						var new_buttons = button_groups[1];
						//Hide the regular set of buttons
						normal_buttons.style.visibility = "hidden";
						//Move the new button next to the normal ones
						normal_buttons.parentNode.appendChild(new_buttons);
						//Trim off the now-unnecessary (and empty) auto-created "bd" (body) and "ft" (footer) elements
						vald_dlg_el.removeChild(vald_dlg_el.getElementsByClassName("bd")[0]);
						vald_dlg_el.removeChild(vald_dlg_el.getElementsByClassName("ft")[0]);
					}
*/					
					valdDialog.show();
				}
			}
//		}
	});

	validations_flat = Array();
	mandatories = Array();
	
	formjax.callback = function(responseText) {
		special_validations = JSON.parse(responseText);
		validations_flat = array_merge_recursive(mandatories, special_validations);
		
		//Setup validation events
//		var limit = validations_flat.length;
		for(var vald_subject in validations_flat) {
			var valds = validations_flat[vald_subject];
			if(typeof(valds) == "object") {
//				addBlurListener  ( el , fn , obj , override ) 
//				YAHOO.util.Event.addBlurListener(vald_subject + "__control", function(){
//					validateControl(this);
//				}, 1, 1);		 
				
				YAHOO.util.Event.addListener(document.getElementById(vald_subject + "__control"), "blur", function(){
					//Delay the validation of this form control just enough to allow a potentially near-simultaneous form submission event to
					//	register, so that when we do validate this control, we'll realize there's been an attempted form submission, and cancel this
					//	individual vald accordingly.
					//The same goes for when the user clicks on the error notification icon of another field.  In that case, let them investigate the other error
					//	without being interrupted by the notification of an error on whatever field was focused when they clicked the other one (ND - 2009-03-03)
					setTimeout("validateControl(document.getElementById(\"" + this.id + "\"))", 150);
//					validateControl(this);
				});		 
			}
		}
	}

	formjax4 = new ajaxObject(null, "forms_ajax.php");
	formjax4.callback = function(responseText){
		//Take the list of mandatory fields...
		mandatory_controlids = JSON.parse(responseText);
		
		//...and populate each one's entry with the appropriate error message to display when the field is left empty
		var error_msg = "Please enter/select a value for this field.";
		var error_msg_2 = "This field is mandatory. If you are unable to furnish an answer now, don't worry: your other entries will be saved for future access.";
		
		for (var i = 0; i < mandatory_controlids.length; i++) {
			var mandatory_id = mandatory_controlids[i];
			mandatories[mandatory_id] = Array();
			mandatories[mandatory_id][0] = 
				{
					"regex": ".+",
					"error_msg": error_msg,
					"error_msg_2": error_msg_2,
					"num_failures": 0
				};
		}
		
//		for (var mandatory_id in mandatory_controlids) {
//			mandatories[mandatory_id] = Array();
//			mandatories[mandatory_id][0] = 
//				{
//					"regex": ".+",
//					"error_msg": error_msg,
//					"error_msg_2": error_msg_2,
//					"num_failures": 0
//				};
//		} 
		
		//Now retreive all the other validation rules for this form and add them to the validation array
		params = "fid=" + form_id + "&action=get_validations";
		formjax.update(params, 'POST');
	}
	
	params = "fid=" + form_id + "&action=get_mandatories";
	formjax4.update(params, 'POST');
	
	//Get ready to give subtle feedback to the user as he/she types data into a text-type field
	//It should be OK to only listen for "keyup" events, since this subtle, real-time validation feedback is only really applicable to 
	//	typing situations ...correct me if I'm wrong (ND - 2009-02-03)

		valdMild = function(the_event){
			//Validate the value unless the key that was just pressed was the tab key
			if(the_event.keyCode != 9)
				validateControl(this, 'true'); //Perform "mild" validation every time the value is changed
		}
		
		//Add "mild validation" event handlers to all textareas and text/pasword-type inputs
		YAHOO.util.Event.addListener(document.getElementsByTagName("TEXTAREA"), "keyup", valdMild);
		var text_pwds = YAHOO.util.Dom.getElementsBy(inputIsTextPwd, "INPUT", form_el);
		YAHOO.util.Event.addListener(text_pwds, "keyup", valdMild);
	
	//Create dialog object for validation failure warnings
 	valdDialog = new YAHOO.widget.SimpleDialog("vald_dialog", {  
	    width: "20em",  
	    effect:{effect:YAHOO.widget.ContainerEffect.FADE, 
	            duration:0.25},
		underlay: "none",  
	    fixedcenter:false,
		constraintoviewport:false, 
	    modal:true, 
	    visible:false, 
	    draggable:false,
		close: false,
		zindex: 10 });

	handleFix = function(){
		//Hide the SimpleDialog: 
		this.hide();
		//After the dust has settled, bring focus to the offending element
		//This must be delayed long enough to occur after the simpleDialog has been hidden and any effects have concluded, 
		//	so pay attention to, for instance, the length of a fade-out effect, which is set when valdDialog is created
		setTimeout("focusControl(true)", 400); 
	}
	handleIgnore = function(){
		//Hide the SimpleDialog: 
		this.hide();
		//After the dust has settled, bring focus to the element to which the user navigated prior to the error message's rise
		setTimeout("focusControl(false)", 400);
	}
	var valdButtons = [{
		text: "Fix this error",
		handler: handleFix,
		isDefault: true
	}, {
		text: "Ignore for now",
		handler: handleIgnore
	}, {
		text: "Begin fixing these errors",
		handler: function(){
			this.hide();
			//After the dust has settled, bring focus to the first offending element in the form
			setTimeout("document.getElementById(vald_failures[0]).focus();", 400);
			
			function revertValdDlgButtons(){
				//Revert to the standard button(s) on the vald dialog
				vald_dlg_buttons[0].style.display = "block";
				vald_dlg_buttons[1].style.display = "block";
				vald_dlg_buttons[2].style.display = "none";
			}
			//After the vald dialog has had time to fade out of sight, revert to the standard button(s)
			setTimeout(revertValdDlgButtons, 250);
						
			//Eventually, but not too soon, declare the end of the "form submission attempt" time period 
			//	(during which we ignore individual validation errors)
			setTimeout("form_submission_attempt = false;", 1000);
		}
	}];
	valdDialog.cfg.queueProperty("buttons", valdButtons);
	valdDialog.render(document.body);
	
	vald_dlg_el = document.getElementById("vald_dialog");
	vald_dlg_el.className += " beatbox glow red diff_head balloon";

	var vald_dlg_button_group = vald_dlg_el.getElementsByClassName("button-group")[0];
	vald_dlg_buttons = vald_dlg_button_group.getElementsByTagName("BUTTON");
	vald_dlg_el.innerHTML = "<div class=\"header\"><div class=\"head_left_img_holder\"/></div>" +
								"<p id=\"vald_dialog_header_txt\">Wait!</p>" +
							"</div>" +
				 			"<div class=\"content\">" +
								"<div class=\"left_img_holder\"></div>" +
								"<span id=\"vald_failure_msg\"></span>" +
							"</div>" +
							"<div class=\"footer\"><div class=\"foot_left_img_holder\"/></div><div class=\"balloon_tail\"/></div></div>";
	var footer = vald_dlg_el.getElementsByClassName("footer")[0];
	vald_dlg_button_group.id = "vald_dlg_button_group";
	vald_dlg_buttons[2].style.display = "none";
	footer.appendChild(vald_dlg_button_group);
	
/*
	<div class="beatbox glow" id="vald_dialog">
 		<div class="header hd"><div class="head_left_img_holder"/></div>
 		<div id="" class="content bd">
 			<div class="left_img_holder"/>
			Validation Failure Message
		</div>
		<div class="footer ft"><div class="foot_left_img_holder"/></div> <!-- rounded box divs -->
	 </div>
		
		<div class="ft">
			<span class="button-group">
				<button class="default" type="button">Fix this error</button>
				<button type="button">Ignore for now</button>
			</span>
		</div>
*/		 

	//Equip field message icons to (potentially) display a message, such as a validation failure message, upon user click
	YAHOO.util.Event.addListener(document.getElementsByClassName("msg_icon"), "click", function() {
		if(YAHOO.util.Dom.getAncestorByTagName(this, "LI").className.match("validation_failure")) {
			//Declare that the user is investigating this error, so that no other errors steal the spotlight
			investigating_error = true;
			//Before showing a new one, close any others that may be open
			YAHOO.util.Dom.batch(document.getElementsByClassName("msg_display"), function(el) {hide(el.id)});
			show(getElementsByClassName(this.parentNode, "DIV", "msg_display")[0].id);
		}
	}); 
	
	//Arm close buttons
//	YAHOO.util.Event.addListener(document.getElementsByClassName("close_button"), "click", function() {
//		hide(this.parentNode.id);
//	}); 

	//Get genitor-progeny array
	
	formjax2 = new ajaxObject(null, "forms_ajax.php");

	gen_prog_flat = Array();
	gen_prog_map = Object();
	formjax2.callback = function(responseText) {
		gen_prog_flat = JSON.parse(responseText);
		
		//Setup genitor-progeny events
		var limit = gen_prog_flat.length;
		for(var i = 0; i < limit; i++) {
			var row = gen_prog_flat[i];
			var gen_domid = row["gen_domid"];
			var prog_domid = row["prog_domid"];
			var test = row["test"];
			var existing_progeny = Array();
			var hidden_progeny = Array(prog_domid);
			var progeny_data = {
				"test": test,
				"existing_progeny": existing_progeny,
				"hidden_progeny": hidden_progeny,
				"has_been_displayed": false
			};
			var pushed = false;
			if(!gen_prog_map[gen_domid]) {
				gen_prog_map[gen_domid] = Array();
			}
			gen_prog_map[gen_domid][prog_domid] = progeny_data;
			
			setupGenProg(gen_domid);		 
		}
	}
	params = "fid=" + form_id + "&action=get_gen_prog";
	
	//The ajax object may be busy at the moment with another request, so continue trying every so often until the object is ready for our update action
/*
	var ajax_update = function() {
								if(formjax2.update(params, 'POST') == true) {
									window.clearInterval(ajax_update_loop);
								}
							 };
	var ajax_update_loop = window.setInterval(ajax_update, 100);

*/	

	formjax2.update(params, 'POST');


	//Get data duplication array
	
	//Setup data duplication events


	//Obtain and prepare any default text-type field values to auto-fill only when user gives control focus
	text_auto_vals = Array();
	formjax3 = new ajaxObject(null, "forms_ajax.php");
	formjax3.callback = function(responseText) {
		text_auto_vals = JSON.parse(responseText);
		
		//Setup events to auto-fill text-type fields upon focus
		for(var domid in text_auto_vals) {
			YAHOO.util.Event.addListener(document.getElementById(domid), "focus", function(){
				if (this.value.length <= 0) {
					this.value = text_auto_vals[this.id];
				}
			});		 
		}
	}
	params = "fid=" + form_id + "&action=get_text_auto_vals";	
	
	formjax3.update(params, 'POST');

}

function setupGenProg(gen_domid) {
	var genitor_el = document.getElementById(gen_domid);
	var event_type;
	if((genitor_el.tagName == "INPUT" && genitor_el.type == "text") || genitor_el.tagName == "TEXTAREA") {
		event_type = "blur";
	}
	else {
		event_type = "change";
	}
	YAHOO.util.Event.addListener(genitor_el, event_type, function(){
		var id = this.id;
		if(gen_prog_map[id]) {
			var my_generations = gen_prog_map[id]; 
			var x; 
//			var ajax_objects = Array();
			var i = 0;
			for(var prog_domid in my_generations) {
				var prog_data = my_generations[prog_domid];
			  	if (typeof(prog_data) == "object" && prog_data.existing_progeny) {
				  	var test = prog_data["test"];
				  	var existing_progeny = prog_data["existing_progeny"];
					var hidden_progeny = prog_data["hidden_progeny"];
					var has_been_displayed = prog_data["has_been_displayed"];
				  	
				  	if (this.className.match(/fieldset/i)) {
				  		var inputs = this.getElementsByTagName("INPUT");
				  		var num_inputs = inputs.length;
				  		for (var j = 0; j < num_inputs; j++) {
				  			var input = inputs[j];
				  			if (input.type == "radio") {
				  				if (input.checked) {
				  					x = input.value;
				  					j = num_inputs; //(no need to continue iterating)
				  				}
				  			}
				  		}
				  	}
				  	else {
				  		x = document.getElementById(this.id + "__control").value;
				  	}
				  	if (typeof(x) != "undefined") {
				  		var num_progeny = eval(test); //TODO: is there a safer way to accomplish what I need here?
						var num_existing_progeny = existing_progeny.length;
						
						if (num_progeny != num_existing_progeny) {
							var num_new_progeny = num_progeny - num_existing_progeny;
							if(num_new_progeny > 0) {
								var last_prog_id, last_prog, new_prog;

								if(has_been_displayed === false) {
									if(num_new_progeny > 1) {
										//If we're going to add more than one progeny, and none have ever been displayed, the initial prog node may not have a 
										//	numerical suffix; in that case, knowing that it will not be alone, we tell it to acquire a suffix of "1," for consistency's sake
										if (!hidden_progeny[0].match(/\d+$/)) {
											var hidden_el = document.getElementById(hidden_progeny[0]);
											acclimateCopiedRootEl(hidden_el);
											hidden_progeny[0] = hidden_el.id; //(this is necessarily identical to "hidden_progeny[hidden_progeny.length - 1]")
										}
									}
								}
								else if(num_existing_progeny > 0) {
									//If we're going to add more progeny, and there's currently only one, it may not have a numerical suffix; 
									//	in that case, knowing that it is no longer alone, we tell it to acquire a suffix of "1," for consistency's sake 
									if (num_existing_progeny == 1) {
										exist_el_id = existing_progeny[0];
										if(!exist_el_id.match((/\d+$/))) {
											var existing_el = document.getElementById(exist_el_id);
											acclimateCopiedRootEl(existing_el, true, true);
											existing_progeny[0] = existing_el.id;
										}
									}
									
									last_prog_id = existing_progeny[existing_progeny.length - 1];
								}
/*								
								else if(hidden_progeny.length == 1) {
									//If we're going to add more than one progeny, and there are currently none, the initial prog node may not have a 
									//	numerical suffix; in that case, knowing that it is no longer alone, we tell it to acquire a suffix of "1," for consistency's sake 
									if (num_new_progeny > 1) {
										if (!hidden_progeny[0].match(/\d+$/)) {
											var hidden_el = document.getElementById(hidden_progeny[0]);
											acclimateCopiedRootEl(hidden_el);
											hidden_progeny[0] = hidden_el.id; //(this is necessarily identical to "hidden_progeny[hidden_progeny.length - 1]")
										}
									}
								}
*/
								var redisplayed_progeny = false;
								
								//Keep track of the number of genuinely new (not re-displayed) progeny nodes we create
								var num_brand_new_prog = 0;
								//If no copies of this progeny had ever before been seen, then the original resident of the hidden_progeny
								//	array counts as a brand new progeny node, since that's where virgin progeny are first stored.
								if(has_been_displayed === false)
									num_brand_new_prog = 1;
								
								for (var k = 0; k < num_new_progeny; k++) {
											
									//TODO: keep the progeny node(s) tied to the genitor's value.  Forthermore, try to remove/add progeny nodes in 
									//	an intelligent manner, preserving data entered into prog nodes that are subsequently hidden, then re-displayed... 
									
									//If there are any hidden progeny nodes in the "reserve list," display (one of) those first
									//There should always be at least one entry stored between the two arrays 
									//	(when the form is printed, existing_progeny is empty, while the id of the original progeny element is stored at index 0 of hidden_progeny)
									if(hidden_progeny.length > 0) {
										var one_hidden_prog = hidden_progeny.pop();
										existing_progeny.push(one_hidden_prog);
										display(one_hidden_prog);
										last_prog_id = one_hidden_prog; //Save a reference to the last progeny element displayed, for future copying
										redisplayed_progeny = true;
									}
									//Otherwise, create a new node
									else {
										
										num_brand_new_prog++;
										
										//Obtain the element that is the most recent addition to the progeny
								//		last_prog = document.getElementById(existing_progeny[existing_progeny.length - 1]);
										//Although it is neither obvious nor terribly intuitive, last_prog_id should always be initialized
										last_prog = document.getElementById(last_prog_id);
								
										//Copy that element
										new_prog = last_prog.cloneNode(true);
										//Make it unique
										acclimateCopiedRootEl(new_prog, true, true);
										//Store its id in the local "existing progeny" array
										new_prog_id = new_prog.id;
										existing_progeny.push(new_prog_id);
										//Place it in the DOM tree immediately following the previous progeny node
										//last_prog.parentNode.appendChild(new_prog);
										YAHOO.util.Dom.insertAfter(new_prog, last_prog);
										//Update the (computation-saving) reference to the most recent progeny addition
										last_prog_id = new_prog_id;
									}
								}
								//If there are progeny nodes who lived a former life, were disappeared, and have now been brought back to life,
								//	then be sure to also display any progeny that they themselves may have (let me know if that sounds too confusing).
								if(redisplayed_progeny === true) {
									displayProgeny(id, prog_domid, true);
								}
								
								//Now that we're done adding progeny nodes, give focus to the first genuinely new progeny node 
								//	(i.e. not including re-displayed progeny that may already contain user-entered values), 
								//	or, if no brand new progeny exist, to the first new-ish progeny node.
								var index;
								if(num_brand_new_prog > 0)
									index = existing_progeny.length - num_brand_new_prog;
								else
									index = existing_progeny.length - num_new_progeny; //We know num_new_progeny is greater than 0
								prog_to_highlight = document.getElementById(existing_progeny[index]);
								//Find the first form control element within that progeny node and give it focus
								//prog_to_highlight.focus();
								var form_controls = YAHOO.util.Dom.getElementsBy(
									function(one_el){
										//Return any form control element
										return (one_el.tagName == "INPUT" || one_el.tagName == "TEXTAREA" 
												|| one_el.tagName == "SELECT" || one_el.tagName == "BUTTON");
									},
									"*", prog_to_highlight);
								if(form_controls.length > 0)
									form_controls[0].focus(); 
									
								if (has_been_displayed === false) {
									//Update the flag so future genitor event listeners will know this progeny has seen the light of day
									gen_prog_map[id][prog_domid]["has_been_displayed"] = true;
								}
							}
							else {
								//Delete the newly superfluous progeny nodes.  Decide, somehow, which ones should be removed.
								//For now, I will simply remove the necessary number of nodes, beginning with the youngest sibling and working backward indiscriminately. (ND - 2008-11-07)
								var num_extra_progeny = 0 - num_new_progeny;
								for (var k = 0; k < num_extra_progeny; k++) {
									var penultimate_progeny = existing_progeny.pop();
									//Hide the affected progeny node
									disappear(penultimate_progeny);
									//Move the prog node from the existing prog array to the "hidden progeny" array, a.k.a. the "reserve list"
									hidden_progeny.push(penultimate_progeny);
								}
								
								if(existing_progeny.length == 0) {
									//Hide any sub-progeny of this now-invisible progeny element set
									disappearProgeny(id, prog_domid, true);
								}	
							}
							
							//Now update the two existing progeny arrays in the gen_prog_map accordingly
							gen_prog_map[id][prog_domid]["existing_progeny"] = existing_progeny;
							gen_prog_map[id][prog_domid]["hidden_progeny"] = hidden_progeny;
						}
					}
					
					i++;
				}	
			}
		}
	});
}


function validateControl(subject_el, mild){
	var error_exists = false;

	//It is possible to call this function to perform a "mild" validation, which checks value and only modifies styling, without displaying any alerts/messages.
	//	This is useful for checking values as they are modified.
	if (typeof(mild) == "undefined") 
		mild = false;
	//If the user is trying to pull-up info on an existing error (with another field or this one) by clicking the error icon, don't interrupt him/her with a vald error
	//	dialog.  
//	//Nonetheless, we don't want them to be completely unaware of the (new) error on this field.  Therefore, perform only a mild validation.
//	if(investigating_error == true)
//		mild = true;
	if (!(form_submission_attempt && mild == false) && investigating_error == false) {
		var subject_el_id = subject_el.id;
		
		//Fieldset vald testing
//		if(subject_el_id.match("form2__ins_licenses__yn_licenses_3rd_party"))
//			alert(subject_el_id);
		
		//For "full" validations, do nothing if we're currently displaying a validation error message (which would pertain to another control)
		//	"Mild" validations are permitted to flow through unobstructed
		if (!current_erroneous_field || mild == true) {
			if (mild == false) {
				current_erroneous_field = subject_el_id;
			}
			var base_id = subject_el_id.substring(0, subject_el_id.length - 9); //Container's id is the same as control's except for the lack of "__control" suffix
			var my_validations = validations_flat[base_id];
			//If there are no validations associated with this control, then do not waste time by proceeding
			//	This is a meaningful check when all controls are being fed through here from the validateAll function
			if (typeof(my_validations) != "undefined") {
				var num_valds = count(my_validations);
				var wrapper_el = document.getElementById(base_id);
				var status = "valid";
				for (var i = 0; i < num_valds; i++) {
					var this_vald = my_validations[i];
					try {
					
						//TODO: Get value using abstracting function that handles mini fieldsets with radios?
						//TODO: accomodate all regexes.  Escape special chars, and/or use RegExp object...
						if (!subject_el.value.match(this_vald["regex"])) {
							status = "invalid";
							error_exists = true;

							YAHOO.util.Dom.replaceClass(wrapper_el, "validation_success", "validation_failure");

							//Obtain the label for the field in question
							var label, label_txt;
							if (subject_el.tagName == "FIELDSET") {
								label = document.getElementById(base_id + "__legend");
							}
							else {
								label = document.getElementById(base_id + "__label");
							}
							//Pull the text from within the label/legend element and strip extraneous whitespace from the ends
							label_txt = label ? label.innerHTML.replace(/^\s*(\S+(\s+\S+)*)\s*$/, "$1") : subject_el_id;
							
//							var msg = "<p>There is an error with the following field: <em>\"" + label_txt + "\"</em></p>\n<p>\n" + this_vald["error_msg"] + "</p>";
							//Rather than print the field title, merely point to it graphically.  
							//What about accessability?  The message is still contextually bound to the corresponding erroneous form field. (ND - 2009-02-27)
							var msg = "<p>There is an error with this field.</p>\n<p>\n" + this_vald["error_msg"] + "</p>";
							var num_failures = this_vald["num_failures"];
							if (num_failures > 0) {
								var error_msg_2 = this_vald["error_msg_2"];
								if(error_msg_2)
									msg += "\n<p>" + error_msg_2 + "</p>";
							}
							//Now increment the failure count
							this_vald["num_failures"]++; //Make sure this modifies the original in validations_flat[]
							//Create message display element if it does not already exist (do this now to avoid creating an empty element for every field control,
							//	which clutters up the html source, at the least.
							//Another option is to copy and convert the validation error dialog, but my opinion is that this would require too much unjustified modification (ND - 2009-02-13)
							var msg_disp_id = base_id + "__msg_display";
							if (!document.getElementById(msg_disp_id)) {
								var msg_display_el = document.createElement("DIV");
								msg_display_el.id = msg_disp_id;
								msg_display_el.className = "msg_display beatbox glow red diff_head";
								msg_display_el.innerHTML = "<div id=\"" + base_id + "__close_button\" class=\"close_button\"></div>" +
								"<div class=\"header\"><div class=\"head_left_img_holder\"/></div>" +
								"<p>Field Error</p>" +
								"</div>" +
								"<div class=\"content\">" +
								"<div class=\"left_img_holder\"></div>" +
								"<span id=\"" +
								base_id +
								"__msg_text\"></span>" +
								"</div>" +
								"<div class=\"footer\"><div class=\"foot_left_img_holder\"/></div>";
								
								//Attach the new node to its "message area" parent element
								document.getElementById(base_id + "__msg_area").appendChild(msg_display_el);
								
								//Empower the close button to close the error message display element
								YAHOO.util.Event.addListener(document.getElementById(base_id + "__close_button"), "click", function(){
									hide(this.parentNode.id);
									//Focus the corresponding control upon error message box closure
									var controls = getElementsByClassName(YAHOO.util.Dom.getAncestorByTagName(this, "LI"), "*", "control");
									if(controls.length > 0) controls[0].focus();
									setTimeout("investigating_error = false;", 175);
								});
								
							}
							
							//Now that we're sure the message display element exists in the DOM, inject it with the appropriate message
							document.getElementById(base_id + "__msg_text").innerHTML = msg;
							
							if (mild == false) {
								//Insert the error message into the main vald error dialog
								document.getElementById("vald_failure_msg").innerHTML = msg;
								//Position the main vald error dialog in relation to the offending control element
									valdDialog.cfg.setProperty("fixedcenter", false);
									//Decide whether to place the dialog to the left or right of the control, based on the control's position in the page
									//Default to the right (with tail pointing left), unless the dialog will lie partly outside the viewport
									var control_x = YAHOO.util.Dom.getX(subject_el);
									var control_region = YAHOO.util.Region.getRegion(subject_el);
									var control_width = control_region.right - control_region.left;
									var dlg_region = YAHOO.util.Region.getRegion(vald_dlg_el);
									var dlg_width = dlg_region.right - dlg_region.left;
									YAHOO.util.Dom.addClass(vald_dlg_el, "balloon");
									var extra_space_to_right = YAHOO.util.Dom.getDocumentScrollLeft() + YAHOO.util.Dom.getViewportWidth() - (control_x + control_width - 10 + dlg_width);
									var extra_space_to_left = control_x + control_width - 30 - dlg_width;
									if (extra_space_to_right < 0 && extra_space_to_left > extra_space_to_right) {
										//Place error dialog balloon to the left of the control, with the tail pointing right
										YAHOO.util.Dom.addClass(vald_dlg_el, "balloon_tailing_right");
										// pass 'context' param as an array in this format: [contextElementOrId, overlayCorner, contextElementCorner]
										valdDialog.cfg.setProperty("context", [subject_el, "br", "tr"]);
									}
									else {
										//Place error dialog balloon to the right of the control, with the tail pointing left (default)
										YAHOO.util.Dom.removeClass(vald_dlg_el, "balloon_tailing_right");
										// pass 'context' param as an array in this format: [contextElementOrId, overlayCorner, contextElementCorner]
										valdDialog.cfg.setProperty("context", [subject_el, "bl", "tr"]);
									}	
		
								//Show the main vald error dialog
								valdDialog.show();
								//...and if the dialog is completely or partly outside the viewport, scroll it into view
								scrollIntoViewXY(vald_dlg_el, 15);
							
							}
							//Exit iteration - don't bother the user with any additional errors before giving him/her a chance to correct this one
							i = num_valds;
						}
					} 
					catch (e) {
						errors += "\n\nElement ID: " + subject_el_id + " \nError: " + e;
					}
				}
			}
			//If the control's value has passed all the tests, then we should make sure any "failure" styling is removed
			if (status == "valid") {
				wrapper_el.className = wrapper_el.className.replace("validation_failure", "validation_success");
				//TODO: find a way to bundle this operation into the above replace op.  It seems so inefficient the way it is, but maybe js allows no better way.
				if (!wrapper_el.className.match("validation_success")) 
					wrapper_el.className += " validation_success";
				//If we've passed all the tests, then we're officially leaving the building
				//	i.e. the "current_erroneous_field" var needs to be reset, but we can't rely on the dialog response handlers to do so,
				//	becuase, in light of the valid field entry, no dialog will be displayed.
				current_erroneous_field = null;
			}
		}
		
	}
	validating_a_control = false;
	
	return error_exists;
}


function validateAll() {
	var num_failures = 0;
	for(var i = 0; i < controls.length; i++) {
		var one_control = controls[i];
		var error = validateControl(one_control, true);
		if (error == true) {
			vald_failures[num_failures] = one_control.id;
			num_failures++;
		}	
	}
	if(errors.length > 0)
//		alert("Errors: \n" + errors);
	return num_failures;
}


/*
 * @params bool fix_erroneous_control
 * 		dictates whether to select the most recent field to cause a validation error, or, alternatively, to give focus to the control to which the user navigated
 * 		(or, if said field is undeterminable, the first form control on the page)
 */
function focusControl(fix_erroneous_control) {
//	alert(fix_erroneous_control + "\n" + selected_control + "\n" + current_erroneous_field);
	if(fix_erroneous_control && current_erroneous_field) {
		document.getElementById(current_erroneous_field).focus();
	}
	else if(selected_control) {
		document.getElementById(selected_control).focus();
	}
	else {
		//Select the first control element rather than nothing at all
		// (this will apply to the first element on the page with the "control" class, which should be the first element auto-generated from the db "forms_*" tables)
		var controls = document.getElementsByClassName("control");
		if (controls.size > 0) {
			controls[0].select();
		}
	}
	current_erroneous_field = null;
}

//function focus_invalid_field(culprit_id) {
//	//First blur any element that may now have focus (i.e. if the user tabbed to or clicked on another form control), so that the
//	//	"onblur" type of event will not be raised when we bring focus back to the offending element 
//	//	(such a scenario would cause the newly-focused control to be validated, and upon failure the original element would trigger an 
//	//	onblur event again, and so forth until reaching the error message limit, which is not what we want)
//	//TODO: Find the element that has received focus, if any.
//	if(selected_control != null && selected_control != culprit_id) {
////		document.getElementById(selected_control).blur();
//		document.getElementById(selected_control).style.borderColor = "blue";
////		YAHOO.util.Event.removeListener(selected_control, "blur");
////		
////		YAHOO.util.Event.addListener(document.getElementById(vald_subject + "__control"), "blur", function(){
////					setTimeout("validateControl(document.getElementById(\"" + this.id + "\"))", 2000);
////				});	
//	}
//	document.getElementById(culprit_id).focus();	
//	selected_control = culprit_id;	
//}


/* This function generates a text string containing all values of the given form (or fieldset) element,
 * 	formatted to be passed as part of a uri in a POST/GET request.  When the function encounters a fieldset
 * 	element, it calls itself recursively to access the form elements within that fieldset, and so on.
 */
function getFormParamString(form_element) {
	//Initialize the string to be passed with the POST request.  Note that we omit the leading "?" because this is added
	//	by the ajaxObject method we use to send the request
	param_string = "";
	function walkChildren(parent){
		var kids = parent.childNodes;
		var num_kids = kids.length;
		for (var i = 0; i < num_kids; i++) {
			var control = kids[i];
			var control_tag_name = control.tagName;
			if (control_tag_name == "INPUT") {
				switch (control.type) {
					case "text":
					case "password":
					case "hidden":
					case "file":
						param_string += control.name + "=" + control.value + "&";
						break;
					case "checkbox":
						if (control.checked) {
							param_string += control.name + "=" + control.value + "&";
						}
						else {
							param_string += control.name + "=&";
						}
						break;
					case "radio":
						if (control.checked) {
							param_string += control.name + "=" + control.value + "&";
						}
						break;
				}
			}
			else 
				if (control_tag_name == "SELECT") {
					var sel = control;
					param_string += sel.name + "=" + sel.options[sel.selectedIndex].value + "&";
				}
				else 
					if (control_tag_name == "TEXTAREA") {
						param_string += control.name + "=" + control.value + "&";
					}
					else if (control_tag_name == "FIELDSET") {
						walkChildren(control);
					}
		}
	}
	walkChildren(form_element);
	return param_string;
}

//This function will clear all user-input data from the given element and/or all of its descendants
//@param root_el this object can be any DOM node within a form
function resetFormControls(root_el){
	var controls = Array();
	if(root_el.tagName == "INPUT" || root_el.tagName == "TEXTAREA" || root_el.tagName == "SELECT") {
		controls[0] = root_el;
	}
	else {
		controls = joinNodeLists(root_el.getElementsByTagName("INPUT"), root_el.getElementsByTagName("TEXTAREA"), root_el.getElementsByTagName("SELECT"));
	}
	for (var i = 0; i < controls.length; i++) {
		var control = controls[i];
		switch(control.tagName) {
			case "INPUT" :
				switch (control.type) {
					case "text":
					case "password":
					case "file":
						control.value = null;
						break;
					case "checkbox":
					case "radio":
						control.checked = false;
						break;
				}
				break;
			case "TEXTAREA" :
				control.value = null;
				break;
			case "SELECT" :
				var options = control.options;
				options[options.selectedIndex].selected = false;
				//(re-)select any options whose "selected" attribute is present/true
				//TODO: test this (especially to see if changes affect original elements rather than copies thereof
				for(var i = 0; i < options.length; i++) {
					var option = options[i];
					if(option.getAttribute("selected"))
						option.selected = true;
				}
				break;
		}
	}
}

