/*
 * Simple UI management functions
 * Requires:
 *	library/functions.js to be loaded
 *	library/xml_manager.js to be loaded
 * 	library/div_manager.js to be loaded and a div object defining for UI elements to be rendered into
 */

function main_menu_manager (div_object, base_path, section_id, subsection_id)
{
	this.div		= div_object;
	this.base_path		= (typeof (base_path) != 'undefined' ? base_path : '/');
	this.section_id		= (typeof (section_id) != 'undefined' ? section_id : 'index');
	this.subsection_id	= (typeof (subsection_id) != 'undefined' ? subsection_id : '');

	this.render 		= main_menu_render;
	this.expand 		= main_menu_expand;
	this.contract 		= main_menu_contract;
	this.flip 		= main_menu_flip;

	this.cache 		= new Object();
	this.current_state	= 'expanded';

	this.xml_obj = new xml_manager (this.base_path + '/library/main_menu.xml');
	this.xml_obj.update_node ('base_path', this.base_path);
	this.xml_obj.update_node ('section_id', this.section_id);
	this.xml_obj.update_node ('subsection_id', this.subsection_id);
}

function main_menu_expand ()
{
	this.current_state = 'expanded';
	this.render();
}

function main_menu_contract ()
{
	this.current_state = 'contracted';
	this.render();
}

function main_menu_flip ()
{
	if (this.current_state == 'expanded')
		this.contract();
	else
		this.expand();
}

function main_menu_render ()
{
	var str 	= '';
	var img_dir	= false;

	if (typeof (this.cache[this.current_state]) != 'undefined')
	{
		str = this.cache[this.current_state];
	}
	else
	{
		// The main_menu.xsl now includes the navigation.xsl 
		// We therefore need to ensure that the xsl renderer knows the relative path for #includes
		this.xml_obj.update_node ('current_state', this.current_state);
		str = this.xml_obj.render_with_xsl (this.base_path + '/library/main_menu.xsl');

		this.cache[this.current_state] = str;
	}

	this.div.set_content (str);
}

function disclaimer_manager (div_object, base_path)
{
	this.div		= div_object;
	this.base_path		= (typeof (base_path) != 'undefined' ? base_path : '/');

	this.render 		= disclaimer_render;
}


function disclaimer_render()
{
	// We probably should convert this to XML, but it's not dynamic...
	var cc_url = 'http://creativecommons.org/licenses/by-nc-sa/2.0/uk/';

	var str =
		'<p align=center><font size=-2>' +
		'<a rel="license" href="' + cc_url + '"><!--' +
		'--><img alt="Creative Commons License" style="border-width: 0" ' +
		'src="' + this.base_path + '/images/CC_byncsa.png"/></a><br/>' + 
		'All work created by myself is licensed under a ' +
		'<a rel="license" href="' + cc_url + '">Creative Commons Attribution-Noncommercial-Share Alike 2.0 UK: England & Wales License</a>.<br/> '+
		'See the <a href="' + this.base_path + '/licencing.html">LICENCING</a> section for more information<br>' +
		'Website best viewed at 1024*768 or higher<br>' +
		'</font></p>';

	this.div.set_content (str);
}

// Required for navigation.xsl support
function flip_nb_images (var_name, img_url, img_count)
{
	var i = 0;

	for (i = 0; i < img_count; i++)
		document[var_name + "_" + i].src = img_url;
}

function hidden_section_manager (hsm_name, str, parent_section, base_path)
{
	// Hidden Section Manager
	// We need to find all sections enclosed in a "<HIDDEN></HIDDEN>" construct and enable the ability to 
	// hide/reveal their contents
	// Not as easy as it should be, thanks to Javascript's crippled RE design...
	// Hidden sections can optionally include a title: <HIDDEN title='x'></HIDDEN>

	// Methods
	this.initialise			= hsm_initialise;
	this.render_all			= hsm_render_all;
	this.render_node		= hsm_render_node;
	this.flip_section_visibility	= hsm_flip_section_visibility;
	this.find_hidden_nodes		= hsm_find_hidden_nodes;

	// Variables
	this.name		= hsm_name;
	this.node_list		= new Array();
	this.xml_list		= new Array();
	this.old_str		= '';
	this.base_path		= '';

	// Initialisation
	this.initialise(str, parent_section, base_path);
}

function hsm_initialise (str, parent_section, base_path)
{
	var i		= 0;
	var t_str	= '';
	var node	= false;

	this.old_str 	= str;
	this.base_path	= base_path;

	// Needed to ensure uniqueness on the page: there may be multiple sections, each with their own
	// set of hidden elements
	if (typeof(parent_section) != 'undefined')
		this.parent_section = parent_section;
	else
		this.parent_section = 'default';

	// 1: Identify the position (and contents) of all the hidden nodes
	this.find_hidden_nodes ();

	// 2: convert to valid XML (by encapsulating the contents in CDATA) and generate an XML object
	// This implicitly checks for the existence of any nodes...
	for (i = 0; i < this.node_list.length; i++)
	{
		node = this.node_list[i];

		// To minimise processing overheads, we embed additional parameters into the string prior to
		// converting it into XML
		t_str = '<current_state>minimised</current_state>';
		t_str += '<hsm_name>' + this.name + '</hsm_name>';
		t_str += '<div_id>' + this.parent_section + '_' + i + '</div_id>';
		t_str += '<base_path>' + this.base_path + '</base_path>';
		t_str += '<status>minimised</status>';
		t_str = "<content>" + t_str + node.start_tag + '<![CDATA[\n' + node.content + ']]>\n' + node.end_tag + "</content>";

		node.xml_obj 	= new xml_manager (false, t_str);
	}

}

function hsm_find_hidden_nodes ()
{
	var start_re	= /<hidden.*?>/gim;
	var end_re	= /<\/hidden>/gim;
	var case_re	= /hidden/i;
	var node	= false;
	var bool	= true;
	var res		= false;

	this.node_list	= new Array();

	// The multi-line regex parameter applies the regex to each line in the string, instead of ignoring whitespace, 
	// so we're forced to manually search for the start and end points
	// TODO: implement support for recursive HIDDEN sections
	while (true)
	{
		res = start_re.exec(this.old_str);

		// Failing to find items isn't a failure...
		if (res != null)
		{
			node = new Object();

			node.start_tag 			= res[0];
			node.start_tag_index 		= (start_re.lastIndex - node.start_tag.length);
			node.start_content_index 	= start_re.lastIndex;

			// Find the end point and substring out the full contents
			end_re.lastIndex = start_re.lastIndex;

			res = end_re.exec (this.old_str);

			if (res != null)
			{
				// End-tag start index == end of content string
				node.end_tag 		= res[0];
				node.end_tag_index 	= (end_re.lastIndex - node.end_tag.length);
				node.content 		= this.old_str.substring (node.start_content_index, node.end_tag_index);

				// Unlike HTML, XML/XSL is case sensitive: we therefore need to ensure the tags are in the correct case
				// The template assumes all hidden nodes are marked with <HIDDEN>
				node.start_tag = node.start_tag.replace (case_re, 'HIDDEN');
				node.end_tag = node.end_tag.replace (case_re, 'HIDDEN');

				// Used to cache rendered data
				node.expanded_str		= '';
				node.minimised_str		= '';
	
				this.node_list.push (node);
			}
			else
			{
				alert ('Unable to find a closing tag for ' + node.start_tag);
				bool = false;
			}
		}
		else
		{
			bool = false;
		}

		if (!bool)
			break;
	}
}

function hsm_render_all (xsl_stylesheet)
{

	// We need to generate the HTML and embed it into the original string
	var old_start_pos	= 0;
	var old_end_pos		= 0;
	var start_pos 		= 0;
	var end_pos		= 0;
	var t_str		= '';
	var s_str		= '';
	var new_str		= '';
	var div_id		= false;

	if (this.node_list.length > 0)
	{
		for (i = 0; i < this.node_list.length; i++)
		{
			node 	= this.node_list[i];
			div_id	= node.xml_obj.get_child_node_value('div_id');

			// Wrap a DIV around the rendered HTML, so we can collapse/expand the section
			t_str = this.render_node (i, xsl_stylesheet);
			t_str = "<div id='" + div_id + "'>" + t_str + "</div>";
		
			// We "walk" along the original string and glue the bits together to form the new string
			start_pos 	= node.start_tag_index;
			s_str 		= this.old_str.substring(old_start_pos, start_pos);
			new_str 	= new_str + s_str + t_str;

			// Jump over the old "hidden" node for the next substring
			old_start_pos	= node.end_tag_index + node.end_tag.length;
		}

		// Final part of the string
		new_str += this.old_str.substring(old_start_pos, this.old_str.length);
	}
	else
	{
		new_str = this.old_str;
	}

	return new_str;
}

function hsm_render_node (node_id, xsl_stylesheet)
{
	var node 	= this.node_list[node_id];
	var xo		= node.xml_obj;
	
	var node_state 	= xo.get_child_node_value ('current_state', 'minimised');

	// The default xsl stylesheet can be overridden if required
	if (typeof(xsl_stylesheet) == 'undefined')
		xsl_stylesheet = this.base_path + '/library/hidden_section.xsl';

	// We employ a simple caching mechanism to minimise overheads
	if (node_state == 'minimised' && node.minimised_str != '')
	{
		t_str = node.minimised_str;
	}
	else if (node_state == 'expanded' && node.expanded_str != '')
	{
		t_str = node.expanded_str;
	}
	else
	{
		t_str	= xo.render_with_xsl(xsl_stylesheet);

		if (node_state == 'minimised')
			node.minimised_str = t_str;
		else
			node.expanded_str = t_str;
	}

	return t_str;
}

function hsm_flip_section_visibility (div_id)
{
	// TODO: find out why num_re is being cached across multiple calls, despite being clearly defined as a local
	// variable
	// I suspect it's because the underlying RE pattern is being cached: naughty JavaScript!
	var num_re	= /(\d+)$/;
	var res		= false;
	var my_div	= false;
	var node	= false;
	var node_id	= false;
	var node_state	= 'minimised';
	var t_str	= '';


	my_div = new div_manager (div_id);

	res = num_re.exec(div_id);

	if (res)
		node_id = res[1]; 

	node 		= this.node_list[node_id];
	node_state 	= node.xml_obj.get_child_node_value ('current_state', 'minimised');

	// Invert the state of the XML obj and regenerate the HTML associated with the div
	if (node_state == 'minimised')
		node.xml_obj.update_node ('current_state', 'expanded');
	else
		node.xml_obj.update_node ('current_state', 'minimised');

	t_str = this.render_node (node_id);

	my_div.set_content (t_str);
}

function submit_captcha_form (submit_url)
{
	var bool 	= true;
	var t_val 	= '';
	var regex	= /[&<>]/;

	// Unfortunately, there's issues with nested forms, so we just have to try and make use of the page's generic form
	// This means we have a risk of clashing object names...
	var form_obj	= document.forms[0];

	t_val = form_obj.name.value;

	if (t_val == '' || t_val.length > 128 || t_val.match(regex))
		bool = false;

	t_val = form_obj.email.value;
	if (t_val.length > 128 || t_val.match(regex))
		bool = false;

	t_val = form_obj.comment.value;
	if (t_val == '' || t_val.length > 1024 ||  t_val.match(regex))
		bool = false;

	if (!bool)
		alert ("Unable to submit comment: please check the details provided.  " +
			"Please be aware that comments are restricted to 1024 characters and that the following special characters" +
			" cannot be used:\n\t& < >\n\nIf there is still a problem submitting your comment, please send an email to the" +
			" contact address given on the website's 'Contact' page.");

	if (bool)
	{
		form_obj.method = 'post';
		form_obj.action = submit_url;
		form_obj.submit();
	}

	return bool;
}

function get_element_position (element_obj)
{
	// Adapted from http://www.quirksmode.org/js/findpos.html
	// Element positions are determined relative to the parent, so we need to recurse back up the entire tree
	// Value returned is the (x,y) co-ordinate for the top-left corner of the element
	var pos_obj	= new Object();
	pos_obj.x	= 0;
	pos_obj.y 	= 0;

	if (element_obj.offsetParent) 
	{
		// Note the "eo = eo.op" in the while statement - this is not a bug!
		while (true)
		{
			pos_obj.x	+= element_obj.offsetLeft;
			pos_obj.y 	+= element_obj.offsetTop;

			if (element_obj.offsetParent)
				element_obj = element_obj.offsetParent;
			else
				break;
		} 
	}

	return pos_obj;
}

function submit_form (form_id)
{
	var form_obj	= false;
	form_obj = document.getElementsById (form_id);

	if (form_obj == null)
	{
		alert ('get_div_obj: Unable to find ' + form_id);
	}
	else
	{
		alert (form.action);
	}
}
