var ticketing = {
	reserve_form: null,
	reserve_table: null,
	date_selector: null,
	add_btn: null,
	clear_btn: null,
	clear_a: null,
	perf_data: null,
	cart_data: null,
	first_data: null,
	types: null,
	categories: null,
	area_label1: null,
	area_label2: null,

	init: function () {
		var date, perf_table, sels_table, opts_table, cart_table, reserve_form, messages, reserve_table, first_table;
		var data_table, perf_data, full_data, prev_data, prev_opts, sels_restorable, restore_data, types, categories;
		var select, a, rows, cells, type, cat, els, el, i, j, k, l;

		/* before doing everything, if this is the second choice, do some processing on the first choice */
		i = className.getElementsByClassName(document, 'div', 'first');
		j = className.getElementsByClassName(document, 'div', 'second');
		if (i.length == 1 && j.length == 1) {
			first_table = className.getElementsByClassName(i[0], 'table', 'reserve_table')[0];
		}
		if (first_table) {
			j = {
				standard: false,
				student: false,
				disabled: false
			};
			rows = first_table.tBodies[0].rows;
			for (i = rows.length - 1;i >= 0; i--) {
				/* turn the first choice categories / prices into coloured boxes */
				el = rows[i].cells[1];
				a = el.innerHTML.match(/^\s*(\S+)\s+(.*)$/);
				if (a && a.length == 3) {
					k = document.createElement('span');
					k.className = 'faux_select';
					k.innerHTML = a[2];
					l = document.createElement('div');
					l.className = a[1] + ' color_box';
					el.innerHTML = '';
					el.appendChild(k);
					el.appendChild(l);
					/*@cc_on @*/
					/*@if (@_win32)
						el.removeChild(l);
						k.className += ' ' + a[1];
					/*@end @*/
				}

				/* find out what ticket types are in the first choice */
				/* this should happen later and use the types we read in, but oh well */
				el = rows[i].cells[0];
				switch (el.innerHTML.toLowerCase()) {
				case 'standard':
					j.standard = true;
					break;
				case 'student':
					j.student = true;
					break;
				case 'disabled seating':
				case 'disabled wheelchair':
					j.disabled = true;
					break;
				}
			}
			this.first_data = j;
		}

		/* find things */
		perf_table = document.getElementById('performance_information');
		sels_table = document.getElementById('previous_selections');
		opts_table = document.getElementById('previous_options');
		cart_table = document.getElementById('cart_totals');
		reserve_form = document.getElementById('reserve_form');
		if (reserve_form) {
			els = reserve_form.parentNode.childNodes;
			for (i = els.length - 1; i >= 0; i--) {
				el = els[i];
				if (className.has(el, 'date')) {
					date = el;
				}
				else if (className.has(el, 'messages')) {
					messages = el;
				}
			}
			reserve_table = reserve_form.getElementsByTagName('table')[0];
			this.reserve_form = reserve_form;
			if (reserve_table) {
				this.reserve_table = reserve_table;
			}
			reserve_form.onsubmit = this.validate;
		}

		/* turn the date list into a select menu */
		if (date) {
			select = document.createElement('select');
			select.id = 'date_selector';
			accessible_select.init(select, this.date_selector_change);
			j = date.getElementsByTagName('ul')[0];
			els = j.getElementsByTagName('li');
			k = 0;
			for (i = 0; i < els.length; i++) {
				a = els[i].getElementsByTagName('a')[0];
				el = document.createElement('option');
				if (a) {
					el.href = a.href;
					el.innerHTML = a.innerHTML;
				}
				else {
					el.innerHTML = els[i].innerHTML;
					k = i;
				}
				select.appendChild(el);
			}
			select.selectedIndex = k;
			date.replaceChild(select, j);
			this.date_selector = select;
		}

		/* grab information from the existing reservation form table */
		if (reserve_table) {
			el = reserve_table.tBodies[0].rows[0];
			/* ticket type names and values */
			types = new Array();
			select = el.cells[0].getElementsByTagName('select')[0];
			for (i = select.options.length - 1; i >= 0; i--) {
				types[i] = {
					value: select.options[i].value,
					name: select.options[i].innerHTML
				};
			}
			this.types = types;

			/* area label text */
			els = el.getElementsByTagName('label');
			this.area_label1 = els[0].innerHTML;
			this.area_label2 = els[1].innerHTML;
		}

		/* read the performance information table, if there is one
		     if the table exists, then the performance is not full
		     otherwise, the performance is full
		*/
		if (perf_table) {
			data_table = new Object();
			categories = new Array();
			rows = perf_table.tBodies[0].rows;
			for (i = rows.length - 1; i >= 0; i--) {
				cells = rows[i].cells;
				cat = cells[0].innerHTML;
				data_table[cat] = {
					standard: {
						status: cells[1].innerHTML,
						price: cells[2].innerHTML,
						area: cells[3].innerHTML
					},
					student: {
						status: cells[4].innerHTML,
						price: cells[5].innerHTML
					},
					disabled_seating: {
						status: cells[6].innerHTML,
						price: cells[7].innerHTML,
						area: cells[8].innerHTML
					},
					disabled_wheelchair: {
						status: cells[9].innerHTML,
						price: cells[10].innerHTML,
						area: cells[11].innerHTML
					}
				};
				categories[i] = cat;
			}
			this.categories = categories;
			/* now we need to flip the data so it's sorted by type instead of category
			   separate full and available entries
			*/
			perf_data = new Object();
			full_data = new Object();
			for (i = types.length - 1; i >= 0; i--) {
				type = types[i].value;
				perf_data[type] = new Object();
				perf_data[type].__isEmpty = true;
				full_data[type] = new Object();
				full_data[type].__isEmpty = true;
			}
			for (cat in data_table) {
				for (i = types.length - 1; i >= 0; i--) {
					type = types[i].value;
					switch (data_table[cat][type].status.toLowerCase()) {
					case 'available':
						perf_data[type][cat] = data_table[cat][type];
						perf_data[type].__isEmpty = false;
						break;
					case 'full':
						full_data[type][cat] = data_table[cat][type];
						full_data[type].__isEmpty = false;
						break;
					}
				}
			}
			for (type in perf_data) {
				if (perf_data[type].__isEmpty) {
					delete perf_data[type];
				}
				else {
					delete perf_data[type].__isEmpty;
				}
			}
			for (type in full_data) {
				if (full_data[type].__isEmpty) {
					delete full_data[type];
				}
				else {
					delete full_data[type].__isEmpty;
				}
			}
			this.perf_data = perf_data;
		}

		/* read the previous selections table, if there is one
		     if the table doesn't exist, then there are no previous selections
		     if the table exists, then there are previous selections and we'll try to apply them later
		*/
		if (sels_table) {
			prev_data = new Array();
			rows = sels_table.tBodies[0].rows;
			for (i = rows.length - 1; i >= 0; i--) {
				cells = rows[i].cells;
				prev_data[i] = {
					type: cells[0].innerHTML,
					cat: cells[1].innerHTML,
					price: cells[2].innerHTML,
					area1: cells[3].innerHTML,
					area2: cells[4].innerHTML,
					quantity: cells[5].innerHTML
				};
			}
			/* map type names to type values */
			if (types) {
				for (i = prev_data.length - 1; i >= 0; i--) {
					type = null;
					for (j = types.length - 1; j >= 0; j--) {
						if (types[j].name == prev_data[i].type) {
							type = types[j].value;
							break;
						}
					}
					prev_data[i].type = type;
				}
			}
		}

		/* read the previous options table, if there is one */
		if (opts_table) {
			cells = opts_table.tBodies[0].rows[0].cells;
			prev_opts = {
				standard: (cells[0].innerHTML.toLowerCase() == 'yes'),
				student: (cells[1].innerHTML.toLowerCase() == 'yes'),
				disabled_seating: (cells[2].innerHTML.toLowerCase() == 'yes')
			}
		}

		/* read the cart totals table, if there is one */
		if (cart_table) {
			a = {
				a: null,
				b: null,
				c: null
			};
			rows = cart_table.tBodies[0].rows;
			for (i = rows.length - 1; i >= 0; i--) {
				cells = rows[i].cells;
				j = parseInt(cells[0].innerHTML, 10);
				k = parseInt(cells[1].innerHTML, 10);
				l = parseInt(cells[2].innerHTML, 10) + parseInt(cells[3].innerHTML, 10);

				if (j > 0 || (k > 0 && l > 0)) {
					/* standard, or student and disabled */
					a.a = {
						standard: j,
						student: k,
						disabled: l
					};
				}
				else if (k > 0) {
					/* just student */
					a.b = {
						standard: 0,
						student: k,
						disabled: 0
					};
				}
				else {
					/* just disabled */
					a.c = {
						standard: 0,
						student: 0,
						disabled: l
					};
				}
			}
			this.cart_data = a;
		}

		/* carry on if the reservation form table exists (the performance is not full) */
		if (reserve_table) {
			/* take out existing reservation form table rows
			   need to do this this way for explorer
			*/
			j = reserve_table.tBodies[0];
			for (i = j.rows.length - 1; i >= 0; i--) {
				j.deleteRow(i);
			}

			/* add a column for the "delete" buttons */
			el = document.createElement('th');
			el.className = 'del';
			reserve_table.tHead.rows[0].appendChild(el);

			/* if there are previous selections, try to add those now
			   otherwise add the first row */
			if (prev_data) {
				/* figure out whether the previous selections, can be restored */
				restore_data = new Array();
				sels_restorable = true;
				for (i = prev_data.length - 1; i >= 0; i--) {
					cat = null;
					k = false;

					if (perf_data[prev_data[i].type]) {
						type = prev_data[i].type;

						for (j in perf_data[type]) {
							if (perf_data[type][j].price == prev_data[i].price) {
								cat = j;
								a = perf_data[type][cat].area;
								if (a && prev_data[i].area1 && prev_data[i].area2) {
									k = ((a.indexOf(prev_data[i].area1) != -1) && (a.indexOf(prev_data[i].area2) != -1));
								}
								else {
									k = (!a && !prev_data[i].area1 && !prev_data[i].area2);
								}
								break;
							}
						}
					}

					if (cat && k) {
						/* this is the data we'll use to restore the previous selections
						   the category can be different because we're matching by price
						*/
						restore_data[i] = {
							type: prev_data[i].type,
							cat: cat,
							area1: prev_data[i].area1,
							area2: prev_data[i].area2,
							quantity: prev_data[i].quantity
						}
					}
					else {
						sels_restorable = false;
						break;
					}
				}

				if (sels_restorable) {
					for (i = 0; i < restore_data.length; i++) {
						this.add_row(restore_data[i]);
					}
					/* only restore previous options if we can restore the selections */
					if (prev_opts) {
						reserve_form.ticket_option_standard.checked = prev_opts.standard;
						reserve_form.ticket_option_student.checked = prev_opts.student;
						reserve_form.ticket_option_disabled_seating.checked = prev_opts.disabled_seating;
					}
				}
				else {
					/* we can't restore the previous selections, so print a message */
					messages.innerHTML += '<br />' + reserve_tickets_messages.cannot_keep_selections;
					this.add_row();
				}
			}
			else {
				this.add_row();
			}

			/* list out all the full ticket types/categories */
			k = '';
			for (i = 0; i < types.length; i++) {
				type = types[i].value;
				if (full_data[type]) {
					k += types[i].name + ' ';
					for (j = 0; j < categories.length; j++) {
						cat = categories[j];
						if (full_data[type][cat]) {
							k += '$' + full_data[type][cat].price + ', ';
						}
					}
					k = k.replace(/, $/, '; ');
				}
			}
			if (k.length > 0) {
				messages.innerHTML += '<br />' + reserve_tickets_messages.full_prefix + ' ' + k.replace(/; $/, '');
			}

			/* insert the "select more ticket types" button */
			a = document.createElement('a');
			a.href = '#';
			a.className = 'rollover add_row';
			a.onclick = this.add_row;
			i = document.createElement('img');
			i.src = '/images' + location.pathname.substring(0, 3) + '/ticketing/ticket_types_off.gif'; /* hack */
			i.alt = reserve_tickets_messages.select_more_ticket_types;
			el = reserve_form.getElementsByTagName('fieldset')[0];
			el.appendChild(a);
			a.appendChild(i);
			this.add_btn = a;

			/* define our own behaviour for the clear button(s) */
			a = className.getElementsByClassName(reserve_form, 'input', 'clear')[0];
			a.onclick = this.clear;
			this.clear_btn = a;
			a = className.getElementsByClassName(reserve_form, 'a', 'clear')[0];
			if (a) {
				a.onclick = this.clear;
				this.clear_a = a;
			}
		}
	},

	uninit: function () {
		/* i have no idea where to begin to undo everything */
	},

	date_selector_change: function () {
		var form = ticketing.reserve_form;
		var option = this.options[this.selectedIndex];
		if (option.href) {
			form.action = option.href;
			form.submit();
		}
	},

	add_row: function (restore) {
		var data = ticketing.perf_data;
		var types = ticketing.types;
		var tbody = ticketing.reserve_table.tBodies[0];
		var row_num = tbody.rows.length;
		var inputs = new Object();
		var tr, td, select, option, t, label, i;

		tr = document.createElement('tr')
		tr.inputs = inputs;
		tbody.appendChild(tr);

		/* type */

		td = document.createElement('td');
		td.className = 'type';
		tr.appendChild(td);

		select = document.createElement('select');
		select.name = 'ticket_type_' + row_num;
		accessible_select.init(select, ticketing.change_type);
		td.appendChild(select);
		inputs.type = select;

		for (i = 0; i < types.length; i++) {
			t = types[i].value;
			if (data[t]) {
				option = document.createElement('option');
				option.value = t;
				option.innerHTML = types[i].name;
				select.appendChild(option);
			}
		}
		if (restore && restore.cat) {
			ticketing.select_value(select, restore.type);
		}

		/* category */

		td = document.createElement('td');
		td.className = 'cat';
		tr.appendChild(td);

		select = document.createElement('select');
		select.name = 'ticket_cat_' + row_num;
		accessible_select.init(select, ticketing.change_cat);
		td.appendChild(select);
		inputs.cat = select;

		i = document.createElement('div');
		i.className = 'color_box';
		select.color_box = i;
		td.appendChild(i);

		/* get rid of the color box in explorer */
		/*@cc_on @*/
		/*@if (@_win32)
			td.removeChild(i);
			select.color_box = null;
		/*@end @*/

		/* area */

		td = document.createElement('td');
		td.className = 'area';
		tr.appendChild(td);
		td.default_children = new Array();
		i = 0;
		t = new Array();

		/* create the labels and selects now and reuse them later */
		label = document.createElement('label');
		label.innerHTML = ticketing.area_label1;
		select = document.createElement('select');
		select.name = 'ticket_area1_' + row_num;
		label.htmlFor = select.id = Math.random();
		t[0] = label;
		inputs.area1 = select;
		td.default_children[i++] = label;
		td.default_children[i++] = document.createTextNode(' ');
		td.default_children[i++] = select;
		td.default_children[i++] = document.createTextNode(' ');

		label = document.createElement('label');
		label.innerHTML = ticketing.area_label2;
		select = document.createElement('select');
		select.name = 'ticket_area2_' + row_num;
		label.htmlFor = select.id = Math.random();
		t[1] = label;
		inputs.area2 = select;
		td.default_children[i++] = label;
		td.default_children[i++] = document.createTextNode(' ');
		td.default_children[i++] = select;

		clickable_labels.init(t, [inputs.area1, inputs.area2]);

		/* quantity */

		td = document.createElement('td');
		td.className = 'quantity';
		tr.appendChild(td);

		/* del */

		td = document.createElement('td');
		td.className = 'del';
		tr.appendChild(td);

		t = document.createElement('a');
		t.href = '#';
		t.className = 'rollover del_row';
		t.onclick = ticketing.delete_row;
		td.appendChild(t);

		i = document.createElement('img');
		i.src = '/images' + location.pathname.substring(0, 3) + '/ticketing/delete_off.gif'; /* hack */
		i.alt = reserve_tickets_messages.delete_ticket_type;
		t.appendChild(i);

		/* followup */

		/* call the ticket type change handler to continue the rest */
		accessible_select.trigger_onchange(inputs.type, restore);

		ticketing.change_delete_button_visibility();
		if (this.blur) {
			this.blur();
		}
		return false;
	},

	delete_row: function () {
		var tr = this.parentNode.parentNode;
		var tbody = tr.parentNode;
		var rows = tbody.rows;
		var inputs, i, j;

		/* remove the row */
		/* do cleanup on inputs? */
		tbody.removeChild(tr);

		/* fix the numbering on the remaining inputs */
		for (i = rows.length - 1; i >= 0; i--) {
			inputs = rows[i].inputs;
			for (j in inputs) {
				inputs[j].name = inputs[j].name.replace(/_\d+$/, '_' + i);
			}
		}

		ticketing.change_delete_button_visibility();
		ticketing.change_option_visibility();
		if (this.blur) {
			this.blur();
		}
		return false;
	},

	change_type: function (restore) {
		var tr = this.parentNode.parentNode;
		var inputs = tr.inputs;
		var type = inputs.type.options[inputs.type.selectedIndex].value;
		var type_data = ticketing.perf_data[type];
		var categories = ticketing.categories;
		var select, option, cat, td, row_num, i;

		/* populate the category select */
		select = inputs.cat;
		select.innerHTML = '';
		for (i = 0; i < categories.length; i++) {
			cat = categories[i];
			if (type_data[cat]) {
				option = document.createElement('option');
				option.value = cat;
				option.className = cat;
				option.appendChild(document.createTextNode('$' + type_data[cat].price));
				select.appendChild(option);
			}
		}
		if (restore && restore.cat) {
			ticketing.select_value(select, restore.cat);
		}

		/* create the right input field for quantity */

		row_num = tr.rowIndex - 1; /* since there is one heading row, need to minus 1 */
		td = tr.cells[3];
		/* do cleanup on input? */
		td.innerHTML = '';

		switch (type.toLowerCase()) {
		case 'student':
			select = document.createElement('select');
			for (i = 1; i <= 4; i++) {
				option = document.createElement('option');
				option.value = i;
				option.appendChild(document.createTextNode(i));
				select.appendChild(option);
			}
			if (restore && restore.quantity) {
				ticketing.select_value(select, restore.quantity);
			}
			break;

		case 'disabled_seating':
		case 'disabled_wheelchair':
			select = document.createElement('input');
			select.type = 'text';
			select.value = '1';
			select.readOnly = true;
			break;

		default:
			select = document.createElement('input');
			select.type = 'text';
			select.size = '2';
			if (restore && restore.quantity) {
				select.value = restore.quantity;
			}
			break;
		}

		select.name = 'ticket_quantity_' + row_num;
		inputs.quantity = select;
		td.appendChild(select);
		ticketing.change_option_visibility();

		/* call the ticket category change handler to continue the rest */
		accessible_select.trigger_onchange(inputs.cat, restore);
	},

	change_cat: function (restore) {
		var tr = this.parentNode.parentNode;
		var inputs = tr.inputs;
		var type = inputs.type.options[inputs.type.selectedIndex].value;
		var cat = inputs.cat.options[inputs.cat.selectedIndex].value;
		var area = ticketing.perf_data[type][cat].area;
		var first, second, option, td, default_children, i;

		if (inputs.cat.color_box) {
			inputs.cat.color_box.className = 'color_box ' + cat;
		}

		/* create selects for area */

		first = inputs.area1;
		second = inputs.area2;
		td = tr.cells[2];

		if (area) {
			first.innerHTML = '';
			second.innerHTML = '';
			for (i = 0; i < area.length; i++) {
				option = document.createElement('option');
				option.value = area.charAt(i);
				option.appendChild(document.createTextNode(area.charAt(i)));
				first.appendChild(option.cloneNode(true));
				second.appendChild(option);
			}
			if (restore && restore.area1) {
				ticketing.select_value(first, restore.area1);
				ticketing.select_value(second, restore.area2);
			}
			else {
				first.selectedIndex = 0;
				second.selectedIndex = (area.length > 1) ? 1 : 0;
			}

			/* make the labels and selects visible, if they are not already visible */
			if (first.parentNode != td) {
				td.innerHTML = '';
				default_children = td.default_children
				for (i = 0; i < default_children.length; i++) {
					td.appendChild(default_children[i]);
				}
			}
		}
		else {
			/* hide the labels and selects, if they are visible */
			if (inputs.area1.parentNode == td) {
				default_children = td.default_children
				for (i = default_children.length - 1; i >= 0; i--) {
					td.removeChild(default_children[i]);
				}
			}
			
			td.innerHTML = (area == '') ? reserve_tickets_messages.free_seating : '';
			if (type.toLowerCase() == 'disabled_wheelchair' && area == '') {
				td.innerHTML = '';
			}
		}
	},

	clear: function () {
		var rows = ticketing.reserve_table.tBodies[0].rows;
		var select, i;
		for (i = rows.length - 1; i >= 0; i--) {
			select = rows[i].inputs.type;
			select.selectedIndex = 0;
			accessible_select.trigger_onchange(select);
		}

		ticketing.change_option_visibility();
		if (this.blur) {
			this.blur();
		}
		return false;
	},

	change_delete_button_visibility: function () {
		var rows = ticketing.reserve_table.tBodies[0].rows;
		if (rows.length == 1) {
			className.add(rows[0], 'hide_delete');
		}
		else {
			className.remove(rows[0], 'hide_delete');
		}
	},

	change_option_visibility: function () {
		var lis = {
			standard: document.getElementById('ticket_option_standard').parentNode,
			student: document.getElementById('ticket_option_student').parentNode,
			disabled_seating: document.getElementById('ticket_option_disabled_seating').parentNode,
			disabled_wheelchair: null
		};
		var hasType = {
			standard: false,
			student: false,
			disabled_seating: false,
			disabled_wheelchair: false
		};
		var rows = ticketing.reserve_table.tBodies[0].rows;
		var i;
		var isFreeSeating = false;

		for (i = rows.length - 1; i >= 0; i--) {
			hasType[rows[i].inputs.type.value.toLowerCase()] = true;
			if (!isFreeSeating && rows[i].cells[2].className.toLowerCase()=='area' && rows[i].cells[2].innerHTML==reserve_tickets_messages.free_seating){
				isFreeSeating = true;
			}
		}

		for (i in hasType) {
			if (lis[i]) {
				if (!hasType[i]) {
					className.add(lis[i], 'hide');
				}
				else{
					className.remove(lis[i], 'hide');
				}
			}
		}
	},

	select_value: function (select, value) {
		var options = select.options;
		var i;
		for (i = options.length - 1; i >=0; i--) {
			if (options[i].value == value) {
				select.selectedIndex = i;
				break;
			}
		}
	},

	validate: function () {
		var first_data = ticketing.first_data;
		var cart_data = ticketing.cart_data;
		var rows = ticketing.reserve_table.tBodies[0].rows;
		var first_field = ticketing.reserve_form.getElementsByTagName('select')[0]; /* can't use ticket_type_0 because of explorer */
		var student_only = true;
		var total = {
			standard: 0,
			student: 0,
			disabled: 0
		};
		var first_inputs = {
			standard: null,
			student: null,
			disabled: null
		}
		var cart_total = {
			standard: 0,
			student: 0,
			disabled: 0
		}
		var item_type, row, inputs, type, quantity, i, j;

		/* need to know whether this selection is student only or not before checking the selection */
		for (i = rows.length - 1; i >= 0; i--) {
			if (rows[i].inputs.type.value.toLowerCase() != 'student') {
				student_only = false;
				break;
			}
		}

		/* catch errors in this selection first */
		for (i = 0; i < rows.length; i++) {
			row = rows[i];
			inputs = row.inputs;
			type = inputs.type.value.toLowerCase();
			quantity = inputs.quantity.value.replace(/^\s+|\s+$/g, '');

			/* gather information for later */
			if (type.indexOf('disabled') > -1) {
				type = 'disabled';
			}else if (type.indexOf('student') > -1) {
				type = 'student';
			}
			total[type] += parseInt(quantity, 10);
			if (!first_inputs[type]) {
				first_inputs[type] = inputs;
			}

			/* first and second areas should be different, if they're part of the form */
			/*
			if (inputs.area1.form && inputs.area2.form && inputs.area1.options[inputs.area1.selectedIndex].value == inputs.area2.options[inputs.area2.selectedIndex].value) {
				return ticketing.alert_user(reserve_tickets_messages.duplicate_areas, inputs.area2);
			}
			*/

			/* quantity should be a number greater than 0 */
			if (quantity == '') {
				return ticketing.alert_user(reserve_tickets_messages.quantity_blank, inputs.quantity);
			}
			else if (quantity.search(/\D/) != -1 || quantity.search(/[123456789]/) == -1 || quantity >= 40) {
				return ticketing.alert_user(reserve_tickets_messages.quantity_invalid, inputs.quantity);
			}

			/* max of 4 or 2 student tickets */
			if (!student_only && total.student > 4) {
				return ticketing.alert_user(reserve_tickets_messages.too_many_student, inputs.quantity);
			}
			else if (student_only && total.student > 2) {
				return ticketing.alert_user(reserve_tickets_messages.too_many_student_only, inputs.quantity);
			}

			/* max of 1 disabled ticket */
			if (total.disabled > 1) {
				return ticketing.alert_user(reserve_tickets_messages.too_many_disabled, inputs.type);
			}
			// mix ticket seating check
			
			if (type=='student' && !student_only && total.student >=1 && (quantity >= 1 || total.disabled >= 1)){
				if (!ticketing.confirm_user(reserve_tickets_messages.mix_student_tickets, inputs.quantity)){
					return false;
				}
			}
			
		}

		/* if this is a second choice, run compare with the first choice */
		if (first_data) {
			/* if the first choice has standard and student, second choice must have a standard */
			if (first_data.standard && first_data.student && total.standard == 0) {
				return ticketing.alert_user(reserve_tickets_messages.standard_required, first_field);
			}

			/* if the first choice has disabled and student, second choice must have a standard or disabled */
			if (first_data.disabled && first_data.student && total.standard == 0 && total.disabled == 0) {
				return ticketing.alert_user(reserve_tickets_messages.standard_or_disabled_required, first_field);
			}
		}

		/* find out what type of selection this is */
		if (total.standard > 0 && total.standard < 40 || (total.student > 0 && total.disabled > 0)) {
			item_type = 'a';
		}
		else if (total.student > 0) {
			item_type = 'b';
		}
		else {
			item_type = 'c';
		}

		/* calculate totals for cart items, but without the item that will be overridden */
		if (cart_data) {
			for (i in cart_data) {
				if (cart_data[i] && i != item_type) {
					for (type in cart_data[i]) {
						cart_total[type] += cart_data[i][type];
					}
				}
			}
		}

		/* check totals for the cart */

		/* max of 4 student tickets
		   we don't need to check for student-only (max 2) because in that case this selection will replace the student-only item in the cart
		*/
		if (cart_total.student + total.student > 4) {
			return ticketing.alert_user(reserve_tickets_messages.cart_too_many_student, first_inputs.student.quantity);
		}

		/* max of 1 disabled ticket */
		if (cart_total.disabled + total.disabled > 1) {
			return ticketing.alert_user(reserve_tickets_messages.cart_too_many_disabled, first_inputs.disabled.type);
		}

		/* if this selection will override something in their cart, ask the user first */
		if (cart_data && cart_data[item_type] && !confirm(reserve_tickets_messages.replace_cart_confirmation)) {
			if (first_field.focus) {
				first_field.focus();
			}
			return false;
		}

		/* how'd we get here? */
		return true;
	},
	
	confirm_user: function (message, field){
		if (confirm(message)){
			return true;
		}
		if (field.focus) {
			field.focus();
		}
		return false;
	},
	
	alert_user: function (message, field) {
		alert(message);
		if (field.focus) {
			field.focus();
		}
		return false;
	}
};

/* accessible onchange select menu from http://www.themaninblue.com/experiment/AccessibleSelect/ */
var accessible_select = {
	init: function (select, onchange) {
		select.onchange = onchange;

		/* only explorer needs help */
		/*@cc_on @*/
		/*@if (@_win32)
			select.onchange = this.change;
			select.onclick = this.click;
			select.onmousewheel = this.mousewheel;
			select.onfocus = this.focus;
			select.onkeydown = this.keydown;
			select.onblur = this.blur;
			select.real_onchange = onchange;
			select.changed = false;
		/*@end @*/
	},

	uninit: function (select) {
		select.onchange = select.onclick = select.onmousewheel = select.onfocus = select.onkeydown = select.onblur = null;
		select.real_onchange = select.changed = null;
	},

	trigger_onchange: function (select, arg) {
		if (select.real_onchange) {
			select.real_onchange(arg);
		}
		else {
			select.onchange(arg);
		}
	},

	change: function () {
		var undef;
		if (this.changed) {
			this.changed = false;
			this.initSelectedIndex = this.selectedIndex;
			return this.real_onchange();
		}
		return undef;
	},

	click: function () {
		this.changed = true;
	},

	mousewheel: function () {
		this.changed = true; /* this could also be false */
	},

	focus: function () {
		this.initSelectedIndex = this.selectedIndex;
	},

	keydown: function (e) {
		if (!e) {
			e = window.event;
		}
		switch (e.keyCode) {
		case 13: /* enter*/
			if (this.selectedIndex != this.initSelectedIndex) {
				this.changed = true;
				this.onchange();
			}
			break;
		case 27: /* esc */
			this.selectedIndex = this.initSelectedIndex;
			break;
		}
	},

	blur: function () {
		if (this.selectedIndex != this.initSelectedIndex) {
			this.changed = true;
			this.onchange();
		}
	}
};

var popup_lite = {
	init: function (button_list, open_left) {
		var button, hash, id, content, content_dup, close, close_dup, i;
		var contents = new Object();
		var data = {
			container: null,
			contents: contents,
			visible: null,
			button: null,
			open_left: open_left,
			delta: null,
			iframe: null
		};

		if (!button_list || !button_list.length) {
			return;
		}

		i = document.createElement('div');
		i.className = 'popup_lite';
		document.body.appendChild(i);
		data.container = i;
		if(navigator.userAgent.indexOf("MSIE")>-1||navigator.appVersion.indexOf("MSIE")>-1){
  		var ieIdx=(navigator.userAgent.indexOf("MSIE")>-1) ? navigator.userAgent.indexOf("MSIE") : ((navigator.appVersion.indexOf("MSIE")>-1) ? navigator.appVersion.indexOf("MSIE") : -1);
  		var ver=parseFloat(navigator.userAgent.substring(ieIdx+5)) || parseFloat(navigator.appVersion.substring(ieIdx+5));
      if(ver<7){
  			i = document.createElement('iframe');
  			i.className = 'popup_lite';
  			document.body.appendChild(i);
  			data.iframe = i;
      }
    }
		close = document.createElement('a');
		close.href = '#';
		close.className = 'close';
		close.appendChild(document.createTextNode(popup_lite_messages.close));

		for (i = button_list.length - 1; i >= 0; i--) {
			button = button_list[i];
			hash = button.href.indexOf('#');
			id = button.href.substring(hash + 1);
			content = document.getElementById(id);

			if (content) {
				if (!data.contents[id]) {
					content_dup = content.cloneNode(true);
					close_dup = close.cloneNode(true);
					close_dup.popup_lite_data = data;
					close_dup.onclick = this.close;

					className.add(content_dup, id);
					className.add(content_dup, 'popup_lite_content');
					content_dup.id = '';
					content_dup.insertBefore(close_dup, content_dup.firstChild);
					content_dup.popup_lite_close = close_dup;
					contents[id] = content_dup;

					className.add(content, 'popup_lite_hide');
				}

				button.popup_lite = id;
				button.popup_lite_data = data;
				button.onclick = this.open;
			}
		}
	},

	uninit: function (button_list) {
		var button, data, id, content, close, i;

		if (!button_list || !button_list.length) {
			return;
		}

		data = button_list[0].popup_lite_data;
		document.body.removeChild(data.container);
		data.container = null;
		if (data.iframe) {
			document.body.removeChild(data.iframe);
			data.iframe = null;
		}

		for (i = button_list.length - 1; i >= 0; i--) {
			button = button_list[i];
			id = button.popup_lite;
			content = data.contents[id];

			if (content) {
				close = content.popup_lite_close;
				close.popup_lite_data = close.onclick = null;

				content.popup_lite_close = null;
				data.contents[id] = null;

				content = document.getElementById(id);
				className.remove(content, 'popup_lite_hide');
			}
			button.popup_lite = button.popup_lite_data = button.onclick = null;
		}
	},

	open: function (e) {
		var id = this.popup_lite;
		var data = this.popup_lite_data;
		var container = data.container;
		var content = data.contents[id];
		var delta = data.delta;
		var iframe = data.iframe;
		var mouse = [0, 0];
		var actual, pos, pageheight, x, y, w, h;

		if (!e) {
			e = window.event;
		}

		/* hack */
		if (className.has(this, 'book_now') && !cookies_enabled) {
			if (this.blur) {
				this.blur();
			}
			alert(global_messages.cookies_required);
			return false;
		}

		if (data.visible == id && data.button == this) {
			/* toggle */
			content.popup_lite_close.onclick();
		}
		else {
			if (data.visible && data.visible != id) {
				popup_lite.hide(data);
			}

			if (!container.firstChild) {
				container.appendChild(content);
			}
			else {
				container.replaceChild(content, container.firstChild);
			}
			className.add(container, 'popup_lite_visible');
			className.add(container, 'popup_lite_' + id);

			if (e.pageX || e.pageY) {
				mouse = [e.pageX, e.pageY];
			}
			else if (e.clientX || e.clientY) {
				/* this will fail if mouse is at (scrollLeft, scrollTop), but oh well */
				mouse = [
					e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
					e.clientY + document.body.scrollTop + document.documentElement.scrollTop
				];
			}

			if (!delta) {
				container.style.left = '-5000px';
				container.style.top = '-5000px';
				actual = dom.find_position(container);
				delta = [
					-5000 - actual[0],
					-5000 - actual[1]
				];
				data.delta = delta;
			}

			x = document.body.offsetHeight;
			y = document.body.scrollHeight;
			pageheight = (x > y) ? x : y;

			w = container.offsetWidth;
			h = container.offsetHeight;

			x = (data.open_left) ? mouse[0] : mouse[0] - w;
			y = (mouse[1] + h < pageheight) ? mouse[1] : pageheight - h;

			container.style.left = (x + delta[0]) + 'px';
			container.style.top = (y + delta[1]) + 'px';
			data.visible = id;
			data.button = this;

			if (iframe) {
				className.add(iframe, 'popup_lite_visible');
				iframe.style.left = container.style.left;
				iframe.style.top = container.style.top;
				iframe.style.width = w + 'px';
				iframe.style.height = h + 'px';
			}

			/* hack */
			if (popup_lite.onopen) {
				popup_lite.onopen(id);
			}
		}

		if (this.blur) {
			this.blur();
		}
		return false;
	},

	close: function () {
		popup_lite.hide(this.popup_lite_data);
		if (this.blur) {
			this.blur();
		}
		return false;
	},

	hide: function (data) {
		var id = data.visible;
		if (id) {
			className.remove(data.container, 'popup_lite_visible');
			className.remove(data.container, 'popup_lite_' + id);
			if (data.iframe) {
				className.remove(data.iframe, 'popup_lite_visible');
			}
			data.visible = null;
			/* hack */
			if (popup_lite.onclose) {
				popup_lite.onclose(id);
			}
		}
	}
};

var cart = {
	cart_form: null,
	inputs: null,
	perf_student_only: null,
	choice_student_only: null,

	init: function () {
		var cart_form = document.getElementById('cart_form');
		var inputs = new Array();
		var types = new Object();
		var perf_student_only = new Object();
		var choice_student_only = new Object();
		var name_cache = new Object();
		var choice_cache = new Object();
		var perf, item, choice, ticket, type, input, els, el, c, p, i;

		types[my_cart_messages.standard] = 'standard';
		types[my_cart_messages.student] = 'student';
		types[my_cart_messages.disabled_seating] = 'disabled_seating';
		types[my_cart_messages.disabled_wheelchair] = 'disabled_wheelchair';

		els = cart_form.getElementsByTagName('input');
		for (i = 0; i < els.length; i++) {
			input = els[i];
			if (input.type == 'text') {
				j = input.name.match(/^cart_([^_-]+)-([^_]+)_([^_]+)_([^_]+)_quantity$/);
				if (j && j.length == 5) {
					perf = j[1];
					item = j[2];
					choice = j[3];
					ticket = j[4];

					/* we can't use cells[] because the delete button cell spans rows */
					el = className.getElementsByClassName(input.parentNode.parentNode, 'th', 'type')[0];
					type = types[el.innerHTML.toLowerCase()];

					input.cart_info = {
						perf: perf,
						item: item,
						choice: choice,
						type: type,
						ticket: ticket,
						type_el: el
					};

					p = perf + '_' + choice;
					if (typeof perf_student_only[p] == 'undefined') {
						perf_student_only[p] = true;
					}
					c = perf + '_' + item + '_' + choice;
					if (typeof choice_student_only[c] == 'undefined') {
						choice_student_only[c] = true;
					}

					if (type != 'student') {
						perf_student_only[p] = false;
						choice_student_only[c] = false;
					}

					inputs[inputs.length] = input;
				}
			}
			else if (input.type == 'submit') {
				input.onclick = this.click;
			}
		}

		cart.cart_form = cart_form;
		cart.inputs = inputs;
		cart.perf_student_only = perf_student_only;
		cart.choice_student_only = choice_student_only;

		cart_form.onsubmit = this.validate;
	},

	uninit: function () {
		/* ditto for my cart */
	},

	click: function () {
		this.form.button_name = this.name;
		this.form.button_value = this.value;
	},

	validate: function () {
		var cart_form = cart.cart_form;
		var inputs = cart.inputs;
		var perf_student_only = cart.perf_student_only;
		var choice_student_only = cart.choice_student_only;
		var perf_total = new Object();
		var choice_total = new Object();
		var perf_items = new Object();
		var item_types = new Object();
		var second_choice_type;
		var button_name = cart_form.button_name;
		var button_value = cart_form.button_value;
		var is_delete = (button_name.indexOf('delete') > -1);
		var is_update = (button_name == 'cart_next_step' && button_value == 'update');
		var input, perf, item, choice, type, ticket, quantity, c, p, t, i, j;

		/* remove any highlighting we made last time */
		for (i = inputs.length - 1; i >= 0; i--) {
			className.remove(inputs[i].cart_info.type_el, 'alert_user');
		}

		/* catch invalid input errors */
		for (i = 0; i < inputs.length; i++) {
			input = inputs[i];
			perf = input.cart_info.perf;
			item = input.cart_info.item;
			choice = input.cart_info.choice;
			type = input.cart_info.type;
			ticket = input.cart_info.ticket;
			quantity = input.value.replace(/^\s+|\s+$/g, '');

			p = perf + '_' + choice;
			if (!perf_total[p]) {
				perf_total[p] = {
					standard: 0,
					student: 0,
					disabled: 0
				};
			}
			c = perf + '_' + item + '_' + choice;
			if (!choice_total[c]) {
				choice_total[c] = {
					standard: 0,
					student: 0,
					disabled: 0
				};
			}
			t = (type.indexOf('disabled') == -1) ? type : 'disabled';
			perf_total[p][t] += parseInt(quantity, 10);
			choice_total[c][t] += parseInt(quantity, 10);

			j = perf + '_' + choice;
			if (!perf_items[j]) {
				perf_items[j] = new Object();
			}
			perf_items[j][item] = choice_total[c];

			if (!is_delete) {
				/* quantity should be a number greater than 0 */
				if (quantity == '') {
					return cart.alert_user(my_cart_messages.quantity_blank, input);
				}
				else if (quantity.search(/\D/) != -1 || quantity.search(/[123456789]/) == -1 || quantity >= 40) {
					return cart.alert_user(my_cart_messages.quantity_invalid, input);
				}

				/* per choice limits */

				if (type == 'student') {
					/* max of 4 or 2 student tickets */
					if (!choice_student_only[c] && choice_total[c].student > 4) {
						return cart.alert_user(my_cart_messages.too_many_student, input);
					}
					else if (choice_student_only[c] && choice_total[c].student > 2) {
						return cart.alert_user(my_cart_messages.too_many_student_only, input);
					}
				}

				/* max of 1 disabled ticket */
				if (choice_total[c].disabled > 1) {
					return cart.alert_user(my_cart_messages.too_many_disabled, input);
				}

				/* per performance limits, if this is a first choice */

				if (choice == 'first') {
					if (type == 'student') {
						/* max of 4 or 2 student tickets */
						if (!perf_student_only[p] && perf_total[p].student > 4) {
							return cart.alert_user(my_cart_messages.performance_too_many_student, input);
						}
						if (perf_student_only[p] && perf_total[p].student > 2) {
							return cart.alert_user(my_cart_messages.performance_too_many_student_only, input);
						}
					}

					/* max of 1 disabled ticket */
					if (perf_total[p].disabled > 1) {
						return cart.alert_user(my_cart_messages.performance_too_many_disabled, input);
					}
				}
			}
		}

		if (!is_delete && !is_update && !cart_form.cart_agree.checked) {
			return cart.alert_user(my_cart_messages.agree_to_conditions, cart_form.cart_agree);
		}

		/* if the user is deleting a first choice and there is a second choice, make sure the second choice, now first choice, doesn't conflict with the per cart rules, otherwise ask the user to confirm */
		i = button_name.match(/^cart_([^_-]+)-([^_]+)_delete_first$/);
		if (i && i.length == 3) {
			perf = i[1];
			item = i[2];
			if (choice_total[perf + '_' + item + '_second']) {
				/* find out the types of the other items for this performance */
				item_types = {
					a: false,
					b: false,
					c: false
				};
				i = perf + '_first';
				for (j in perf_items[i]) {
					if (perf_items[i][j].standard > 0 || (perf_items[i][j].student > 0 && perf_items[i][j].disabled > 0)) {
						item_types.a = true;
					}
					else if (perf_items[i][j].student > 0) {
						item_types.b = true;
					}
					else {
						item_types.c = true;
					}
				}

				/* find out the type of the second choice for this performance and item */
				i = perf + '_second';
				if (perf_items[i][item].standard > 0 || (perf_items[i][item].student > 0 && perf_items[i][item].disabled > 0)) {
					second_choice_type = 'a';
				}
				else if (perf_items[i][item].student > 0) {
					second_choice_type = 'b';
				}
				else {
					second_choice_type = 'c';
				}

				if (item_types[second_choice_type]) {
					/* by deleting the first choice, the second choice becomes the first and now conflicts with another item */
					if (!confirm(my_cart_messages.delete_first_deletes_all)) {
						return false;
					}
				}
			}
		}

		return true;
	},

	alert_user: function (message, field) {
		if (field.cart_info) {
			className.add(field.cart_info.type_el, 'alert_user');
		}
		alert(message);
		if (field.focus) {
			field.focus();
		}
		return false;
	}
};

var billing = {
	billing_form: null,
	card_type: null,
	hkafmc_plan: null,

	init: function () {
		var form, card_type, hkafmc_plan;

		form = document.getElementById('billing_form');
		if (form) {
                        form.billing_card_type.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_type_blank'
				}
			];
			form.billing_card_type.ignoreFirstOption = true;
                        form.billing_address_city.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'address_city_blank'
				}
			];
			form.billing_address_street2.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'address_street2_blank'
				}
			];
			form.billing_salutation.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'salutation_blank'
				}
			];
			form.billing_surname.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'surname_blank'
				}
			];
			form.billing_givenname.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'givenname_blank'
				}
			];
			form.billing_phone_home.validators = [
				{
					validator: formValidation.firstNotBlankGroupActivator,
					errorKey: 'phone_blank',
					highlightId: 'billing_phone',
					groups: [
						[form.billing_phone_home],
						[form.billing_phone_office],
						[form.billing_phone_mobile]
					]
				}
			];
			form.billing_address_street1.validators = [
				{
					validator: formValidation.notBlankValidator,
					//errorKey: 'address_blank'
					errorKey: 'address_street1_blank'
				}
			];
			form.billing_festivalnumber.validators = [
				{
					validator: formValidation.festivalNumberValidator,
					errorKey: 'festivalnumber_invalid'
				}
			];
			form.billing_email.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'email_blank'
				},
				{
					validator: formValidation.emailValidator,
					errorKey: 'email_invalid'
				}
			];
			form.billing_card_number.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_number_blank'
				},
				{
					validator: formValidation.characterRangeValidator,
					errorKey: 'card_number_invalid',
					ranges: [[48, 57]] /* 0-9 */
				},
				{
					validator: formValidation.lengthValidator,
					errorKey: 'card_number_invalid',
					ranges: [[14, 16]]
				},
				{
					validator: billing.card_number_validator,
					errorKey: 'card_number_invalid',
					type: form.billing_card_type
				}
			];
			form.billing_card_name.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_name_blank'
				}
			];
			form.billing_card_bank.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_bank_blank'
				}
			];
			form.billing_card_month.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_month_blank'
				}
			];
			form.billing_card_month.ignoreFirstOption = true;
			form.billing_card_year.validators = [
				{
					validator: formValidation.notBlankValidator,
					errorKey: 'card_year_blank',
					highlightId: 'billing_card_month'
				},
				{
					validator: formValidation.dateRangeValidator,
					errorKey: 'card_expiry_date_invalid',
					highlightId: 'billing_card_month',
					ranges: [[10,2009], [12,2016]]
				}
			];
			form.billing_card_year.ignoreFirstOption = true;
			
			form.billing_student_donation_amount.validators = [
				{
					validator: formValidation.characterRangeValidator,
					errorKey: 'donation_amount_invalid',
					ranges: [[48, 57]] /* 0-9 */
				}
			];
			form.billing_newworks_donation_amount.validators = [
				{
					validator: formValidation.characterRangeValidator,
					errorKey: 'donation_amount_invalid',
					ranges: [[48, 57]] /* 0-9 */
				}
			];

			formValidation.init(form, 'alert_user', billing_messages);
			form.idToLabel['billing_phone'] = document.getElementById('billing_phone');
			
			
			this.billing_form = form;
		}

		card_type = document.getElementById('billing_card_type');
		hkafmc_plan = document.getElementById('hkafmc_plan');

		agreeto_receive_latestinfo = document.getElementById('billing_agreeto_receive_latestinfo');
		news_email = document.getElementById('billing_news_email');
		news_mail = document.getElementById('billing_news_mail');
		student_donation_amount = document.getElementById('billing_student_donation_amount');
		student_donation = document.getElementById('billing_student_donation');
		newworks_donation_amount = document.getElementById('billing_newworks_donation_amount');
		newworks_donation = document.getElementById('billing_newworks_donation');

		agreeto_receive_latestinfo.onclick = this.is_checked;
		news_email.onclick = this.is_checked;
		news_mail.onclick = this.is_checked;
		
		if( agreeto_receive_latestinfo.checked)
		{
			news_email.disabled = true;
			news_mail.disabled = true;
		}
		else if( news_email.checked || news_mail.checked )
		{
			news_email.disabled = false;
			news_mail.disabled = false;
			agreeto_receive_latestinfo.disabled = true;
		}
		else
		{
			news_email.disabled = false;
			news_mail.disabled = false;
			agreeto_receive_latestinfo.disabled = false;
		}

		student_donation_amount.onblur = this.is_blur;
		newworks_donation_amount.onblur = this.is_blur;
		
		if (card_type && hkafmc_plan) {
			accessible_select.init(card_type, this.change_card_type);
			this.card_type = card_type;
			this.hkafmc_plan = hkafmc_plan;
			accessible_select.trigger_onchange(card_type);
		}
	},

	// on_blur event, add on 21 Sep, 2007
	is_blur: function () {
		if( student_donation_amount.value > 0 )
		{
			student_donation.checked = true;	
		}
		else
		{
			student_donation.checked = false;
		}
		if( newworks_donation_amount.value > 0 )
		{
			newworks_donation.checked = true;
		}
		else
		{
			newworks_donation.checked = false;
		}
		

	},

	//check function , add on 14 Sep, 2007
	is_checked: function () {
		if( agreeto_receive_latestinfo.checked)
		{
			news_email.disabled = true;
			news_mail.disabled = true;
		}
		else if( news_email.checked || news_mail.checked )
		{
			news_email.disabled = false;
			news_mail.disabled = false;
			agreeto_receive_latestinfo.disabled = true;
		}
		else
		{
			news_email.disabled = false;
			news_mail.disabled = false;
			agreeto_receive_latestinfo.disabled = false;
		}
	},

	uninit: function () {
		if (this.billing_form) {
			formValidation.uninit(this.billing_form);
			this.billing_form = null;
		}
		if (this.card_type) {
			accessible_select.uninit(this.card_type);
			this.card_type = null;
			this.hkafmc_plan = null;
		}
	},

	card_number_validator: function (el) {
		var type = this.type;
		var num = formValidation.getFieldValue(el).replace(/\s+/g, '');
		if (type.options[type.selectedIndex].id == 'hkafmc') {
			return (num.indexOf('54027488') == 0 || num.indexOf('54029088') == 0);
		}else{
			return formValidation.checkCreditCard(num, type.options[type.selectedIndex].value);
		}
		return true;
	},
	
	change_card_type: function () {
		if (this.options[this.selectedIndex].id == 'hkafmc') {
			className.add(billing.hkafmc_plan, 'show');
		}
		else {
			className.remove(billing.hkafmc_plan, 'show');
		}
	}
};

var confirmation = {
	isProcessing: false,
	timeout: null,

	init: function () {
		var els, el, i;
		els = document.getElementsByTagName('a');
		for (i = els.length - 1; i >= 0; i--) {
			el = els[i];
			if (className.has(el, 'rollover')) {
				if (className.has(el, 'edit') || className.has(el, 'cancel')) {
					el.onclick = this.click;
				}
				else if (className.has(el, 'submit')) {
					el.onclick = this.submit;
				}
			}
		}
	},

	uninit: function () {
	},

	click: function () {
		if (confirmation.isProcessing) {
			if (this.blur) {
				this.blur();
			}
			alert(confirmation_messages.processing);
			return false;
		}
		return true;
	},

	submit: function () {
		if (!confirmation.isProcessing) {
			confirmation.isProcessing = true;
			confirmation.timeout = setTimeout(confirmation.clearProcessing, 60000); /* 1 minute */
			return true;
		}
		else {
			if (this.blur) {
				this.blur();
			}
			alert(confirmation_messages.processing);
			return false;
		}
	},

	clearProcessing: function () {
		confirmation.isProcessing = false;
	}
};

var programme_list = {
	visible: null,
	frozen: false,
	sending: null,
	added: null,
	init: function (table) {
		var heading, view_by, select, selectedIndex, rows, row, option, prefix, els, el, span, a, html, i;

		if (!table) {
			return;
		}

		/* show buttons on row mouseover and do ajax for add to planner */
		rows = table.tBodies[0].rows;
		for (i = rows.length - 1; i >= 0; i--) {
			row = rows[i];
			row.onmouseover = this.over;
			row.onmouseout = this.out;
			row.add_to_planner = className.getElementsByClassName(row, 'a', 'add_to_planner')[0];
			row.book_now = className.getElementsByClassName(row, 'a', 'book_now')[0];
			if (row.add_to_planner) {
				row.add_to_planner.onclick = this.add_to_planner;
			}
			if (row.book_now) {
				row.book_now = row.book_now.getElementsByTagName('img')[0];
			}
		}
		/* hack */
		popup_lite.onopen = this.freeze;
		popup_lite.onclose = this.unfreeze;

		/* preload */
		a = location.pathname.substring(0, 3); /* hack */
		i = document.createElement('img');
		i.src = '/images' + a + '/list/sending_list.gif';
		i.alt = programme_list_messages.sending;
		this.sending = i;
		i = document.createElement('img');
		i.src = '/images' + a + '/list/addplanner_in.gif';
		i.alt = programme_list_messages.added_to_planner;
		this.added = i;

		/* build the view/filter selects */
		heading = document.getElementById('flash_heading');
		view_by = document.getElementById('view_by');
		if (heading && view_by) {
			select = document.createElement('select');
			select.id = 'view_by_selector';
			accessible_select.init(select, this.change_view);
			heading.appendChild(select);
			els = view_by.childNodes;
			prefix = programme_list_messages.view_by_prefix;
			for (i = 0; i < els.length; i++) {
				el = els[i];
				if (el.nodeType == 1 && el.nodeName == 'LI') {
					option = document.createElement('option');
					span = el.getElementsByTagName('span')[0];
					if (span) {
						html = span.innerHTML;
						selectedIndex = select.options.length;
					}
					else {
						a = el.getElementsByTagName('a')[0];
						html = a.innerHTML;
						option.href = a.href;
					}
					option.innerHTML = html.replace(prefix, '').toLowerCase();
					select.appendChild(option);
				}
			}
			select.selectedIndex = selectedIndex;

			els = view_by.getElementsByTagName('ul');
			if (els.length > 0) {
				select = document.createElement('select');
				select.id = 'filter_by_selector';
				accessible_select.init(select, this.change_view);
				heading.appendChild(select);
				els = els[0].childNodes;
				for (i = 0; i < els.length; i++) {
					el = els[i];
					if (el.nodeType == 1 && el.nodeName == 'LI') {
						option = document.createElement('option');
						span = el.getElementsByTagName('span')[0];
						if (span) {
							html = span.innerHTML;
							selectedIndex = select.options.length;
						}
						else {
							a = el.getElementsByTagName('a')[0];
							html = a.innerHTML;
							option.href = a.href;
						}
						option.innerHTML = html.toLowerCase();
						select.appendChild(option);
					}
				}
				select.selectedIndex = selectedIndex;
			}

			view_by.parentNode.removeChild(view_by);
		}
	},

	uninit: function (table) {
		var rows, row, i;
		if (!table) {
			return;
		}
		rows = table.tBodies[0].rows;
		for (i = rows.length - 1; i >= 0; i--) {
			row = rows[i];
			if (row.add_to_planner) {
				row.add_to_planner.onclick = null
			}
			row.onmouseover = row.onmouseout = row.add_to_planner = row.book_now = null;
		}

		/* hack */
		popup_lite.onopen = popup_lite.onclose = null;
	},

	over: function () {
		if (!programme_list.frozen) {
			if (programme_list.visible && programme_list.visible != this) {
				className.remove(programme_list.visible, 'show_actions');
			}
			className.add(this, 'show_actions');
			programme_list.visible = this;
		}
	},

	out: function () {
		if (!programme_list.frozen) {
			className.remove(this, 'show_actions');
			programme_list.visible = null;
		}
	},

	freeze: function () {
		programme_list.frozen = true;
		if (programme_list.visible && programme_list.visible.book_now) {
			programme_list.visible.book_now.style.visibility = 'hidden';
		}
	},

	unfreeze: function () {
		programme_list.frozen = false;
		if (programme_list.visible && programme_list.visible.book_now) {
			programme_list.visible.book_now.style.visibility = '';
		}
	},

	change_view: function () {
		var option = this.options[this.selectedIndex];
		if (option.href) {
			location.href = option.href;
		}
	},

	add_to_planner: function () {
		var http_request = null;
		var parent = this.parentNode;
		var url = this.href;
		var timeout, img;

		if (!cookies_enabled) {
			if (this.blur) {
				this.blur();
			}
			alert(global_messages.cookies_required);
			return false;
		}

		if (window.XMLHttpRequest) {
			http_request = new XMLHttpRequest();
			if (http_request.overrideMimeType) {
				/* for (older?) mozilla */
				http_request.overrideMimeType('text/xml');
			}
		}
		else if (window.ActiveXObject) {
			try {
				http_request = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch (e) {
				try {
					http_request = new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch (e) {
					http_request = null;
				}
			}
		}

		if (!http_request) {
			/* fallback to letting the browser retrieve the page */
			return true;
		}

		if (this.blur) {
			this.blur();
		}

		/* replace the button with the sending image */
		img = programme_list.sending.cloneNode(false);
		parent.button = this;
		parent.image = img;
		parent.replaceChild(img, this);

		timeout = setTimeout(function () {
				programme_list.planner_timed_out(http_request, parent);
			}, 60000); /* 1 minute */

		http_request.open('POST', url, true);
		http_request.onreadystatechange = function() {
			programme_list.added_to_planner(http_request, parent, timeout);
		};
		http_request.send(null);

		return false;
	},

	added_to_planner: function (http_request, parent, timeout) {
		var img;
		try {
			if (http_request.readyState == 4) {
				clearTimeout(timeout);
				if (http_request.status == 200) {
					/* replace the sending image with the added image */
					img = parent.image;
					if (img) {
						img.src = programme_list.added.src;
						img.alt = programme_list.added.alt;
					}
				}
				else {
					programme_list.restore_planner_button(parent, programme_list_messages.add_error);
				}
			}
		}
		catch (e) {
			clearTimeout(timeout);
			programme_list.restore_planner_button(parent, programme_list_messages.add_error);
		}
	},

	planner_timed_out: function (http_request, parent) {
		http_request.abort();
		programme_list.restore_planner_button(parent, programme_list_messages.add_error);
	},

	restore_planner_button: function (parent, message) {
		var button = parent.button;
		var image = parent.image;
		if (message) {
			alert(message);
		}
		if (button && image) {
			parent.replaceChild(button, image);
			parent.button = null;
			parent.image = null;
		}
	}
};
