//
// Only load once.
//
if (typeof require_external_js == 'undefined') {
	var require_external_js = {};
}
if (!require_external_js['/ixa/scripts/formsclassparser.js']) {
	require_external_js['/ixa/scripts/formsclassparser.js'] = 1;

//
// Forms ClassParser on Javascript
//
// The Basic design is to tag elements with class="validate_something"
// which will attach event to its onblur, onchange, onclick and its forms on*
// for validation and other uses
//
// First up, we need a rules system
//
// The most basic rule, is 'run on event'. This is something along the lines
// of
//   elem.class="validate_run" => elem.onevent = function run() {}
//
// Next we have to cater for the times where multiple runs are registered
// for a single event. This is something along the lines of
//   elem.class="validate_run1 validate_run2"
//     => elem.onevent = function runall () { run1(); run2() }
//
// A common scenario is to run on the form.onsubmit(). Here the event is run
// on the forms event, not the elements, and also run conditionally
//   elem1.class="validate_formrun1 validate_formrun2", 
//   elem2.class="validate_formrun1"
//     => elem.form.onsubmit = function runall () {
//          form.originalonsubmit()
//          && elem1.run1() && elem1.run2()
//          && elem2.run1()
//        }
//
// This can be generalized into grouping the elements. Here the elements are
// tagged, then grouped, and on a single event all thier events are fired
//   elem1.class="validate_group1run1",
//   elem2.class="validate_group1run1"
//   => group1.runall = function () {
//       elem1.run1();
//       elem2.run1();
//   }
//
// Note, here group1.runall() has to be fired from some other event, such
// as
//   elemmaster.onevent = group1.runall()
//
// Note, for groups, you want to have the option of run1();run2(), or
// run1() && run2(). Possibly, return 0 always "stops" the flow, and
// return 1 or return or return null continues it.
//
// So, we have
//   function concatfunction () {
//    run1()!=0 && run2()!=0 && run3!=0
//   }
//
// Some events must be run prior, or after certain events, so some order
// must be maintained. For simplicity, there should at least be an option
// for preevent, onevent, postevent to control what is run
//   validate_run1.type = onevent,
//   validate_run2.type = postevent,
//   validate_run3.type = preevent
//   => element.onevent = function () {
//        elem.run3() ; elem.run1() ; elem.run2()
//      }
//
// Another common scenario is to run on the window.onload(). Here the event
// is run at the start, or just there and then
//   elem1.class="validate_loadrun1",
//   elem2.class="validate_loadrun2"
//     => elem.loadrun1() && elem.loadrun1()
//
// Set Up, Add to headers
// 	<script language="JavaScript" src="/ixa/scripts/dates.js"></script>
// 	<script language="JavaScript" src="/ixa/scripts/sprintf.js"></script>
// 	<script language="JavaScript" src="/ixa/scripts/formsclassparser.js"></script>
// 	<script language="JavaScript" src="/ixa/scripts/validate.js"></script>
//
// Next add to onload FormsClassParser.apply()
//
// Make sure your forms have names
//

document.write('<script language="JavaScript" src="/ixa/scripts/general.js"></script>');

var FormsClassParser = {
	lists : [],

	//
	// Convert rules into a more readily processed version
	//
	compileRules : function (rules) {
		var compiledRules = {};
		for (var currentClass in rules) {
			for (var eventList in rules[currentClass]) {
				var eventObject = rules[currentClass][eventList];
				var eventArray = eventList.split(/,/);
				for (var i = 0; i < eventArray.length; i++) {
					var event = eventArray[i];
					if (event.match(/^(pre|post|on)load/) && typeof eventObject == 'function') {
						compiledRules[currentClass] = compiledRules[currentClass] || {};
						compiledRules[currentClass].load = compiledRules[currentClass].load || {};
						compiledRules[currentClass].load.load = {
							event : event,
							run : EventQueue.once_only(currentClass, eventObject)
						};
					}
					else if (
						event.match(/^(pre|on|post)(form)?/)
						&& typeof eventObject == 'function'
					) {
						compiledRules[currentClass] = compiledRules[currentClass] || {};
						var eventType = RegExp.$1;
						var method = RegExp.$2 || 'direct';
						compiledRules[currentClass][method] = compiledRules[currentClass][method] || {};
						compiledRules[currentClass][method][event.replace(
							/^(pre|on|post)(form)?/
						,'')] = {
							event : event.replace(/^(pre|on|post)form/,'$1'),
							run : eventObject
						};
					}
				}
			}
		}
		return compiledRules;
	},
	
	//
	// Prepare global hashes.
	//
	hardFuncQueue : {},
	elementEventQueues : {},
	formEventQueues : {},
	element_id : 0,

	//
	// Attaches the events based on compiled rules
	//
	compileDocument : function (rules,top) {
		var loadEventQueues = new EventQueue();
		//
		// Go through everything
		//
		top = top ? top : document;
		var elements = getElementsByClassName('','*',top);
		for (var i = 0; i < elements.length; i++) {
			var element = elements[i];
			var elem_id = element.element_id = (element.element_id || ++this.element_id);
			element.className = element.className.replace(/\n|\t/g,' ');

			var classNames = element.className.split(/(?:\bvalue|\bcheck_value|\bif|\bunless|\bselect|\btext)?(?:::[^ ]+)?(?: |$)/);
			for (var j = 0; j < classNames.length; j++) {
				var className = classNames[j];
				if (!rules[className]) {
					continue;
				}
				var classRules = rules[className];

				//
				// Process Load Methods
				//
				if (classRules.load) {
					for (var event in classRules.load) {
						loadEventQueues.addEventFunction(
							element,
							classRules.load[event].event,
							classRules.load[event].run,
							className
						);
					}
				}

				//
				// Process Direct Methods
				//
				if (classRules.direct) {
					for (var event in classRules.direct) {
						this.elementEventQueues[elem_id] = this.elementEventQueues[elem_id] || {};
						this.elementEventQueues[elem_id][event] = this.elementEventQueues[elem_id][event] || new EventQueue();
						this.elementEventQueues[elem_id][event].addEventFunction(
							element,
							classRules.direct[event].event,
							classRules.direct[event].run,
							className
						);
					}
				}

				//
				// Process Form Methods
				//
				var formname = element.form ? element.form.name
					: (element.tagName.toLowerCase() == 'form' ? element.name : document.forms[0] ? document.forms[0].name : '');
				if (classRules.form && formname) {
					for (var event in classRules.form) {
						this.formEventQueues[formname] = this.formEventQueues[formname] || {};
						this.formEventQueues[formname][event] = this.formEventQueues[formname][event] || new EventQueue();
						this.formEventQueues[formname][event].addEventFunction(
							element,
							classRules.form[event].event,
							classRules.form[event].run,
							className
						);
					}
				}
			}

			//
			// Compile Direct Methods
			//
			for (var event in this.elementEventQueues[elem_id]) {
				this.elementEventQueues[elem_id][event].attachEventFunction(element, 'on' + event);
			}
		}

		//
		// Compile Form Methods
		//
		for (var form in this.formEventQueues) {
			for (var event in this.formEventQueues[form]) {
				this.formEventQueues[form][event].attachEventFunction(
					document[form], 'on' + event
				);
			}
		}

		//
		// Run the onload stuff
		//
		(loadEventQueues.getEventFunction())();
	},
	
	addRules : function (rules) {
		this.lists.push(rules);
	},

	apply : function (top) {
		var rules = {};
		for (var i = 0; i<this.lists.length; i++) {
			for (var j in this.lists[i]) {
				rules[j] = this.lists[i][j];
			}
		}
		this.compileDocument(this.compileRules(rules),top);
	}

};

//
// EventQueue Object is an object where you can add events to the queue
// then generating a function that runs all of them
//
// The format of an element is
//   { elem:elem, order:[0,-1,1], run:function(){} }
//
function EventQueue() {
	var eventQueue = [];

	this.addEventFunction = function (elem, event, run, className) {
		var order = 0;
		if (event.match(/^pre/)) {
			order = -1;
		} else if (event.match(/^post/)) {
			order = 1;
		}
	
		var flag = 0;
		if (className) {
			for (i = 0; i < eventQueue.length; i++) {
				if (eventQueue[i].class_name == className && eventQueue[i].elem.element_id == elem.element_id) {
					eventQueue[i] = {order:order, run:run, elem:elem, class_name:className};
					flag = 1;
				}
			}
		}

		if (!className || !flag) {
			eventQueue.push({order:order, run:run, elem:elem, class_name:className || ''});
		}
	};

	this.eventQueue = eventQueue;

	//
	// This method returns the compiled function that runs the event queue
	// Its special cased for 1 and 2 methods
	//
	this.getEventFunction = function (elem, event) {
		var completeEventQueue = eventQueue.slice();

		if (elem) {
			if (EventQueue.orig_event(elem, event)) {
				//
				// Unshift any saved event on to the completeEventQueue.
				//
				completeEventQueue.unshift({run : EventQueue.orig_event(elem, event), elem : elem, order : -2});
			}
		}

		if (!msie) {
			//
			// Indexing for non-IE browsers.
			//
			for (i = 0; i < completeEventQueue.length; i++) {
				completeEventQueue[i].index = i;
			}
		}
		var array = completeEventQueue.sort(function (a, b) {
			var x = 0;
			if (!msie) {
				x = a.index > b.index ? 0 : -1;
			}
			return (a.order - b.order) + x;
		});

		return function (event) {
			for (var i = 0; i < array.length; i++) {
				if (array[i].run.call(array[i].elem, event || window.event) == 0) {
					return false;
				}
			}
			return true;
		}
	};

	//
	// Usage this.attachEventFunction(form.given_name, 'onblur')
	// Attaches the original event as the first function to run if there
	// is one, then run the other events on the queue. Assigns an id to
	// the element in order to not lose it when we need to go through the
	// HTML again. 
	//
	this.attachEventFunction = function (elem, event) {
		elem[event] = this.getEventFunction(elem, event);
	};
}

EventQueue._orig_event = {};

EventQueue.orig_event = function (elem, event) {
	var elem_id = elem.element_id = (elem.element_id || ++this.element_id);
	if (!elem.hard_event_flags) {
		elem.hard_event_flags = {};
	}
	EventQueue._orig_event[elem_id] = EventQueue._orig_event[elem_id] || {};
	if (!elem.hard_event_flags[event]) {
		EventQueue._orig_event[elem_id][event] = elem[event];
		elem.hard_event_flags[event] = 1;
	}
	return EventQueue._orig_event[elem_id][event];
}

EventQueue.id = 1;

//
// Cast a function to run only once, even if apply() is called more than once
//
EventQueue.once_only = function (className, run) {
	return function () {
		var id = this.eventqueue_id = (this.eventqueue_id || ++EventQueue.id);
		EventQueue.once_only[id] = EventQueue.once_only[id] || {};
		if (EventQueue.once_only[id][className]) {
			return;
		}
		EventQueue.once_only[id][className] = 1;
		return run.call(this);
	}
}

//
// A simple way for concating a method with a previous one
//
EventQueue.bindNewEvent = function (elem, event, newmethod) {
	event = event.replace(/^on/, '');
	if (event.match(/^(pre|post)/)) {
		var prefix = RegExp.$1;
		var event = event.replace(/^pre|post/, '');
	}	
	if (elem.tagName && elem.tagName.toLowerCase() == 'form') {
		var formname = elem.name ? elem.name : '';
		FormsClassParser.formEventQueues[formname] = FormsClassParser.formEventQueues[formname] || {};
		FormsClassParser.formEventQueues[formname][event] = FormsClassParser.formEventQueues[formname][event] || new EventQueue();
		FormsClassParser.formEventQueues[formname][event].addEventFunction(elem, prefix + event, newmethod, '');
		FormsClassParser.formEventQueues[formname][event].attachEventFunction(elem, 'on' + event);
	} else {
		var elem_id = elem.element_id = (elem.element_id || ++FormsClassParser.element_id);
		FormsClassParser.elementEventQueues[elem_id] = FormsClassParser.elementEventQueues[elem_id] || {};
		FormsClassParser.elementEventQueues[elem_id][event] = FormsClassParser.elementEventQueues[elem_id][event] || new EventQueue();
		FormsClassParser.elementEventQueues[elem_id][event].addEventFunction(elem, prefix + event, newmethod, '');
		FormsClassParser.elementEventQueues[elem_id][event].attachEventFunction(elem, 'on' + event);
	}
}

}
