/**
 * Byng User Interface utility class
 *
 *
 * ByngUI is responsible for:
 *
 *			- DOM manipulation
 * 			- Opening, closing and updating elements
 * 			- 
 * 
 *	@author Ollie Maitland
 *	@copyright Byng Systems LLP
 */

/**
 * Put HTML retrieval object in the global scope
 * 
 * @type HtmlGet
 */
var HtmlGetObj;

/**
 * Put XML retrieval object in the global scope 
 * 
 * @type XmlPost
 */
var XmlPostObj;

/**
 * Put ByngXml handler object in the global scope
 * 
 * @type ByngXml
 */
var ByngXml;

/**
 * Framework constants
 * 
 * Holds constants which are used through-out the javascript
 * framework such as standardised DOM element IDs
 * 
 */
 
/**
 * Holds the feedback element id name
 * 
 * @type String
 */
var ELEM_FEEDBACK 	= "feedback";

/**
 * Holds the error element id name
 * 
 * @type String
 */
var ELEM_ERRORS	 	= "errors";

/**
 * Holds the popup wrapper id name
 * 
 * @type String
 */
var ELEM_POPUP_WRAP	= "popup-wrap";

/**
 * Holds the basecode version 
 * 
 * @type String
 */
var BASECODE_VERSION = "0.2.3.0";

/**
 * Holds the basecode root path
 * 
 * @type String
 */
var BASECODE_ROOT	= "/common/" + BASECODE_VERSION + "/js/mootools/";

/**
 * Represents a light UI load
 *
 * @type Int
 */
var UI_FLAG_LIGHT 		= 0;

/**
 * Represents a UI load with AJAX
 *
 * @type Int
 */
var UI_FLAG_AJAX		= 1;

/**
 * Represents a UI load with AIR
 *
 * @type Int
 */
var UI_FLAG_AIR			= 2;

/**
 * ByngUI handling method
 * 
 */
var ByngUI  = new Class (
{
	
	/**
	 * Initialise the user interface environment
	 * 
	 */
	initialize : function ( initFlag ) 
	{
		// Set the current page
		this.page = location.pathname;
		
		// Used to track open elements
		this.tracker = new Array;
		
		// Script include tracker
		this.included = new Array (0);
		this.initFlag = initFlag;
		this.initIncludes ();

		switch (initFlag) {
		case UI_FLAG_AJAX:

			/**
			 * Holds the ByngUIResponse object
			 * 
			 * @type ByngUIResponse
			 */
			this.response = new ByngUIResponse ();
			
			/**
			 * Holds the ByngUIDom object
			 * 
			 * @type ByngUIDom
			 */
			this.dom = new ByngUIDom ();
						
		break;
		}
	},
 	
 	/**
 	 * Set the initialisation requirement
 	 * 
 	 * @param Int flag
 	 */
 	setInitFlag : function (flag)
 	{	
 		this.initFlag = flag;
 	},

		 	
 	/**
 	 * Initialise the includes
 	 * 
 	 */
 	initIncludes : function ()
 	{
		/**
		 * Boot strap required files
		 */
		 switch (this.initFlag) {
		 case UI_FLAG_AJAX:		
			 this.include ( BASECODE_ROOT + "ui/byngui.response.js" );
			 this.include ( BASECODE_ROOT + "ui/byngui.dom.js" );	
			 this.include ( BASECODE_ROOT + "ajax/byngxml.js");
			 this.include ( BASECODE_ROOT + "ajax/byngjson.js");
		 break;
		 }
 	},
	
	/**
	 * Instantiate a new XmlPost with actions
	 * 
	 */ 
	setAction : function (action, module, modulePackage) 
	{
		var url = "/ajax/";
		
		if (!modulePackage) modulePackage = "site";
		
		url += modulePackage + '/' + module;
		
		// New XmlPost object with gateway set by module
		XmlPostObj = new XmlPost (url);
		
		XmlPostObj.setAction (action);
		
		return XmlPostObj;
	},
	
	/**
	 * Post a browser http request
	 * 
	 * Create a form in DOM and submit the request
	 * 
	 * @param ByngRequest ByngRequest
	 * 
	 */
	postAction : function (ByngRequest)
	{
		var builder = Byng.input.getBuilder();

		f = builder.formFactory (ByngRequest.toString(), "postAction", ByngRequest.getAction())

		document.getElementsByTagName('head')[0].appendChild(f);

		f.submit();
	},
	
	/**
	 * XmlPost event to the framework 
	 * 
	 * Post an action via AJAX and handle response
	 * 
	 * @param String action
	 * @param String module
	 * @param String modulePackage
	 * @param Array params
	 */ 
	doAction : function (action, module, modulePackage, params)
	{
		if (console && console.log) {
			alert ('Debug');
		}
		
		// If the module and action are to be set then set them
		if (module && action) {
			// Set the action for the command
			this.setAction (action, module, modulePackage);
		}
		
		// If there are supplied parameters add them to the URI
		if (params) {
			// If JS supported Array.pop() with assoc array life would be easier!	
			for (i=0;i <= params.keys.length;i++) {
			
			key = params.keys[i];
			val = params.vals[i];
			
			XmlPostObj.addParam (key, val);
			
			}
		}
		
		XmlPostObj.fetch (this.readResponse);			
		
	},
	
	/**
	 * Submit the popup form
	 * 
	 */
	submitPopup : function (form) 
	{
		form.submit ();
	},	
	
	/**
	 * Set the post action UI handling method
	 * 
	 * The clean up method is called once a reponse is received
	 * from an AJAX request. This should be used to clean-up any UI
	 * 
	 */ 
	setCleanUp : function (handler, params) 
	{
		if (console && console.log) {
			alert ('Debug');
		}
		
		this.cleanHandler 		= handler;
		this.cleanHandlerParams = params;
	},	
	
	/**
	 * Write a response to the screen
	 * 
	 */ 
	writeResponse : function (r) 
	{
		if (console && console.log) {
			alert ('Debug');
		}
		
		var s = new Array (r.length);
	
		for (i=0;i<r.length;i++)
		{
			f = ce('li');
			// r[i] is an XmlTag
			f.innerHTML = r[i].childNodes[0].nodeValue;
			s[i] = f;
		}
		
		return s;
		
	},
	
	/**
	 * Flush an error stack
	 * 
	 */ 
	flushMessages : function (container)
	{
		if (console && console.log) {
			alert ('Debug');
		}
		items = container.getElementsByTagName ("ul");
		
		for (var i=0;i<items.length;i++)
		{
			container.removeChild(items[i]);
		}
	},
	
	/**
	 * Write message to the screen
	 * 
	 */ 
	writeMessages : function (container, branch)
	{
		if (console && console.log) {
			alert ('Debug');
		}
		
		this.container = container;
		
		var messages = ByngUI.writeResponse(branch);
		
		if (messages.length < 1) return;
		
		// Set the Html
		f = this.container.getElementsByTagName("ul");
		
		if (f.length == false)
		{
			f = document.createElement('ul');
			this.container.appendChild (f);
		} else {
			f = f[0];
		}
		
		// Append the feedback elements
		for (var i=0;i<messages.length;i++)
		{
			f.appendChild (messages[i]);
		}			
		
	},
	
	/**
	 * Get a tag value from the response body
	 * 
	 */ 
	getBodyResponse : function (tag)
	{
		if (console && console.log) {
			alert ('Debug');
		}
		return this.xmlTree.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
	},
	
	/**
	 * Read the response from an XmlPost
	 * 
	 * @param XmlTree
	 */ 
	readResponse : function (tree) 
	{
		if (console && console.log) {
			alert ('Debug');
		}
		Byng.ui.xmlTree = new ByngXml (tree);
		
		if (Byng.ui.xmlTree.isValidTag("code") == false) {
			alert ("Invalid XML response from server");
			return false;
		}
		
		Byng.ui.writeMessages (ge(ELEM_FEEDBACK), Byng.ui.xmlTree.getElementsByTagName("feedback"));
		Byng.ui.writeMessages (ge(ELEM_ERRORS), Byng.ui.xmlTree.getElementsByTagName("error"));
			
		// Use XML response to determine true result
		Byng.ui.result = Byng.ui.xmlTree.getChildContent ("code");

		if (ByngUI.result == false) {
			// Response code == 0
		}
		
		// check for debug strings
		debug = Byng.ui.xmlTree.getChildContent ("debug");
		
		if (trim(debug) != "") {
			// if any debug present then tell the class
			alert (debug);
		}
		
		if (Byng.ui.result == Byng.ui.response.REQ_PROMPT) {
			
			// build the prompt dialogue
			Byng.ui.response.buildPrompt (Byng.ui.xmlTree.getChildTag("prompt"));
			
		}
		
		// Check whether action we Run the interface cleaning methods
		if (isFunction (Byng.ui.cleanHandler)) {

			if (Byng.ui.result == 1) {
				Byng.ui.cleanHandler (Byng.ui.cleanHandlerParams, Byng.ui.result);
			}
			
		}
		
	},
			

	/**
	 * Methods to load script dependencies
	 * 
	 * @todo Move into ByngUIDom
	 */
 
	/**
	 * Include a script dynamically
	 * 
	 * @param String path
	 */
	include : function (path)
	{
		if (path.substr(0,1) != '/') alert ("Unable to include external scripts");
		
		// Only include a file once
		if (this.included[path] == true) {
			return;
		}
		
		// Create a new script element
		var script = document.createElement('script');
	    script.defer = true;
	    script.src = path;
	    script.type = 'text/javascript';
	    
	    document.getElementsByTagName('head')[0].appendChild(script);
		
		this.included[path] = true;
	},
 			
	/**
	 * Preload an image
	 * 
	 * @param String src
	 */
	preload : function (src)
	{
		image1 = new Image();
		image1.src = src;
	},
	
	/**
	 * Add load event
	 * 
	 * @param Function fn
	 */
	addLoadEvent : function (fn) 
	{
		window.addEvent('onload',fn);
    },

	/**
	 * Methods to open pages
	 * 
	 * @todo Move to ByngUISend
	 *
	 */
	 
	/**
	 * Open a location from a ByngRequest
	 * 
	 * @param ByngRequest request
	 */
	openLocation : function (request, options)
	{
		if (!options) options = {};
		if (!options.width)  		options.width 		= 600;
		if (!options.height) 		options.height 		= 800;
		if (!options.scrollbars) 	options.scrollbars 	= 0;
		
		window.open( ( $type(request) == 'object' ? request.composeAsString() : request),
													'_blank','toolbar=no' +
													',width='  + options.width +
													',height=' + options.height + 
													',scrollbars=' + options.scrollbars + 													
													',resizable=yes,menubar=yes,location=no');
	},

	/**
	 * Redirect page to new location
	 *
	 * @param ByngRequest request
	 */
	gotoLocation : function (request)
	{
		window.location = ($type(request) == 'object' ? request.composeAsString(false) : request);
	},
	
	/**
	 * String encoding
	 * 
	 * The string encoding functions provide methods to 
	 * access and target DOM elements with an encoded data structure
	 * rather than nomenclature
	 * 
	 */
	 
	/**
	 * Check is a string is a valid declaration
	 * 
	 * @param String s
	 */
	isValidHex : function (s, prefix)
	{
		if (!prefix) prefix = "zz";
		if (s.substr(0,prefix.length) != prefix) return false;
		
		// remove the prefix and _ character
		h = s.substr(prefix.length+1);
		
		s = this.decodeHex (h);
		
		s = s.split("|");
		
		if (s.length < 2) return false;
					
		this.keys   = s[0].split(":");
		this.values = s[1].split(":");

		return true;
	},
	
	/**
	 * Decode a string from hexidecimal
	 * 
	 * @para String str
	 * @return String
	 */
	decodeHex : function (str)
	{
	    str = str.replace(new RegExp("s/[^0-9a-zA-Z]//g"));
	    var result = "";
	    var nextchar = "";
	    for (var i=0; i<str.length; i++){
	        nextchar += str.charAt(i);
	        if (nextchar.length == 2){
	            result += this.ntos(eval('0x'+nextchar));
	            nextchar = "";
	        }
	    }
	    return result;
	},
	
	/**
	 * Convert a number to a hexidecimal letter
	 * 
	 * @param Int n
	 * @return String
	 */
	toHex : function(n)
	{
		var digitArray = new Array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
		
	    var result = ''
	    var start = true;
	    for (var i=32; i>0;){
	        i-=4;	
	        var digit = (n>>i) & 0xf;
	
	        if (!start || digit != 0){
	            start = false;
	            result += digitArray[digit];
	        }
	    }
	
	    return (result==''?'0':result);
	},
	
	/**
	 * Make hex encoded ID tag
	 * 
	 * @param Array Array keys
	 * @param Array Array values
	 * @return String
	 */
	makeIdTag : function (keys, values, prefix)
	{
		return (prefix ? prefix : 'zz') + '_' + this.encodeHex(keys,values);
	},
	
	/**
	 * Get an associative array from an ID string
	 * 
	 * @param String id 
	 */
	fromIdTag : function ( id, prefix )
	{
		if (!$chk(id)) return null;
		if ($type(id) == 'event')   id = id.target;
		if ($type(id) == 'element') id = id.getProperty('id');
		if (!$chk(prefix)) prefix = id.substring(0,id.indexOf("_"));
		if (this.isValidHex( id, prefix ) == true) {
			return this.values.associate(this.keys);
		} else {
			return null;
		}
	},
	
	/**
	 * Encode a string to a hexidecimal string
	 * 
	 * @param Array keys Array keys
	 * @param Array values Corresponding array values
	 * @return String
	 */
	encodeHex : function (keys, values)
	{
		var pipeStr = '';
		
		var pad = function (str, len, pad){
		    var result = str;		
		    for (var i=str.length; i<len; i++){
		        result = pad + result;
		    }
		    return result;
		}
		
		var encode = function (str) {
			var result = "";
		    for (var i=0; i<str.length; i++){
		        result += pad(Byng.ui.toHex(str.charCodeAt(i)&0xff),2,'0');
		    }
		    return result;
		}
		
		for (var i=0;i<keys.length;i++) {
			pipeStr += keys[i]+'|'+values[i];
		}

		return encode(pipeStr);			
	},
	
	/**
	 * Number to string
	 * 
	 * @param Int
	 * @return String
	 */
	ntos : function (n)
	{
	    n=n.toString(16);
	    if (n.length == 1) n="0"+n;
	    n="%"+n;
	    return unescape(n);
	},

	/**
	 * Toggle a DomElement between hidden and shown
	 * 
	 * @param DomElement element
	 * @param Boolean display
	 */
	toggle : function (element, display)  
	{
		if ($type(element) != "element") throw ("Toggled invalid element");
		
		// Are we using the display toggle 
		if ($chk(display)) {
			if (display == true) {

				element.setStyle('display', 'block');
			
				// If the class name is just hide then reset it in DOM
				if (element.hasClass("hide")) {
					element.removeClass('hide');
				}					
			} else {
				element.setStyle('display', 'none');
			}
			return;
		}
		
		// Otherwise determine from the element styles
		if (element.hasClass("hide") || element.getStyle('display') == 'none') {
			element.setStyle('display', 'block');
			
			// If the class name is just hide then reset it in DOM
			if (element.hasClass("hide")) {
				element.removeClass('hide');
			}				
		} else {
			element.setStyle('display', 'none');
		}
				
	},
	
	/**
	 * Select an element from the DOM
	 * 
	 * @param Mixed selector
	 */
	ge : function ( selector, prefix )
	{
		if (typeof selector == "object") {
			var keys = [];
			var values = [];
			$each(selector, function(v,k){
				keys.push(k);
				values.push(v);
			});
			return $(this.makeIdTag(keys,values,prefix));
		} else {
			return $(selector);
		}
	}
	
});
