/**
 * 	Byng User Interface utility class
 *
 *  Contains:
 *		ByngUI - User interface methods
 *		ByngRequest - Encapsulates a request to the BaseCode
 *
 *	@author Ollie Maitland
 *	@copyright Byng Systems LLP
 */

	// Put Ajax controllers in the global scope
	
	/**
	 * @type HtmlGet
	 */
	var HtmlGetObj;
	
	/**
	 * @type XmlPost
	 */
	var XmlPostObj;
	
	/**
	 * @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.1.0";
	
	/**
	 * Holds the basecode root path
	 * 
	 * @type String
	 */
	var BASECODE_ROOT	= "/common/" + BASECODE_VERSION + "/js/";
	
	/**
	 * 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
	 * 
	 */
	function ByngUI (flag) {

		// 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);
		
		
		/**
		 * Initialise the user interface environment
		 * 
		 */
		this.init = function () 
		{

			switch (ByngUI.initFlag) {
			case UI_FLAG_AJAX:

				/**
				 * Holds the ByngUIResponse object
				 * 
				 * @type ByngUIResponse
				 */
				ByngUI.response = new ByngUIResponse ();
				
				/**
				 * Holds the ByngUIDom object
				 * 
				 * @type ByngUIDom
				 */
				ByngUI.dom = new ByngUIDom ();

				/**
				 * Holds the ByngUISend object
				 * 
				 * @type ByngUISend
				 */
				ByngUI.send = new ByngUISend ();
							
			break;
			}
		}
	 	
	 	/**
	 	 * Set the initialisation requirement
	 	 * 
	 	 * @param Int flag
	 	 */
	 	this.setInitFlag = function (flag) 
	 	{	
	 		this.initFlag = flag;
	 	}

		// set the initialise environment flag
		this.setInitFlag(flag);
			 	
	 	/**
	 	 * Initialise the includes
	 	 * 
	 	 */
	 	this.initIncludes = function ()
	 	{
			/**
			 * Boot strap required files
			 */
			 switch (this.initFlag) {
			 case UI_FLAG_AJAX:
				 this.include ( BASECODE_ROOT + "ui/byngui.send.js" );			 
				 this.include ( BASECODE_ROOT + "ui/byngui.response.js" );
				 this.include ( BASECODE_ROOT + "ui/byngui.dom.js" );				 
			 break;
			 }
			 
			 //this.include ( "/common/trunk/js/debug/dump.js" );
	 	}
 	
/**
 *
 *	Methods to interface with the framework
 *	
 *
 */
		
		/**
		 * Instantiate a new XmlPost with actions
		 * 
		 */ 
		this.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;
		}
		
		/**
		 * Set the XmlPostObj in the global scope
		 * 
		 */
		this.setXmlPostObj = function (XmlPostClone) {
			
			XmlPostObj = XmlPostClone;
			
		}
		
		/**
		 * Post a browser http request
		 * 
		 * Create a form in DOM and submit the request
		 * 
		 * @param ByngRequest ByngRequest
		 * 
		 */
		this.postAction = function (ByngRequest)
		{			
			Forms = new FormBuilder ();

			f = Forms.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
		 */ 
		this.doAction = function (action, module, modulePackage, params)
		{
			// 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);			
			
		}
		
		/**
		 * 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
		 * 
		 */ 
		this.setCleanUp = function (handler, params) 
		{
			this.cleanHandler 		= handler;
			this.cleanHandlerParams = params;
		}
		
		/**
		 * Setting innerHtml wrapper
		 */ 
		this.setHtml = function (obj, html)
		{
			obj.innerHTML = html;
		}
				
		
		/**
		 * Write a response to the screen
		 */ 
		this.writeResponse = function (r) {
			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
		 * 
		 */ 
		this.flushMessages = function (container)
		{
			
			items = container.getElementsByTagName ("ul");
			
			for (var i=0;i<items.length;i++)
			{
				container.removeChild(items[i]);
			}
			
		}
		
		/**
		 * Write message to the screen
		 * 
		 */ 
		this.writeMessages = function (container, branch)
		{
			
			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
		 * 
		 */ 
		this.getBodyResponse = function (tag)
		{
			return this.xmlTree.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
		}
		
		/**
		 * Read the response from an XmlPost
		 * 
		 * @param XmlTree
		 */ 
		ByngUI.prototype.readResponse = function (tree) {

			ByngUI.xmlTree = new ByngXml (tree);
			
			if (ByngUI.xmlTree.isValidTag("code") == false) {
				alert ("Invalid XML response from server");
				return false;
			}
			
			ByngUI.writeMessages (ge(ELEM_FEEDBACK), ByngUI.xmlTree.getElementsByTagName("feedback"));
			ByngUI.writeMessages (ge(ELEM_ERRORS), ByngUI.xmlTree.getElementsByTagName("error"));
				
			// Use XML response to determine true result
			ByngUI.result = ByngUI.xmlTree.getChildContent ("code");

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

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

/**
 *	Dependency loading scripts
 *
 *
 */
		/**
		 * Include a script dynamically
		 * 
		 * @param String path
		 */
		this.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;
		}
		
	 	// include the scripts required for this load flag
	 	this.initIncludes ();	
	 			
		/**
		 * Preload an image
		 * 
		 * @param String src
		 */
		this.preload = function (src)
		{
			image1 = new Image();
			image1.src = src;
		}
		
		/**
		 * Add load event
		 * 
		 * @param Function fn
		 */
		this.addLoadEvent = function (fn) {

           if (window.addEventListener) {
                   window.addEventListener("load", fn, false);
           } else if (document.addEventListener) {
                   document.addEventListener("load", fn, false);
           } else if (window.attachEvent) {
                   window.attachEvent("onload", fn);
           } else if (typeof window.onload == "function") {
                   var fnOld = window.onload;
                   window.onload = function(){
                           fnOld();
                           fn();
                   };
           } else {
                   window.onload = fn;
           }
        }
        
	 	// add the ByngUI.init() method to the page onload() event
	 	this.addLoadEvent ( this.init );
/**
 *
 *	Contextual help methods
 *	
 *
 */
		
		// Register the contextual help container
		this.registerContextualHelp = function (help) {
			this.help = help;			
		}
		
		// Load the current contextual help for this page
		this.loadContextualHelp = function () {
			
			if (this.help.style.display == 'block') {
				this.help.style.display = 'none'
				return;
			}
			
			try {
				
			HtmlGetObj = new HtmlGet (this.help, "/ajax/cms/contextual_help/view");
			
			HtmlGetObj.addParam ('page', this.page);
			HtmlGetObj.addParam ('output', "html");
			
			this.help.style.display = 'block';
			
			this.help.innerHTML = "<p class=\"loading\">Loading...</p>";
			
			HtmlGetObj.update();
			
			} catch (e) {
				alert ("Unable to load help: "+e);
			}
		}
		

		

/**
 *
 *	DOM wrappers for user interfaces
 *
 */
		/**
		 * @param ByngRequest
		 */
		this.openLocation = function (ByngRequest)
		{
			window.open(ByngRequest.toString(),'_blank','toolbar=no,width=600,height=800,resizable=yes,menubar=yes,location=no,');
		}

		/**
		 * Redirect page to new location
		 *
		 * @param ByngRequest
		 */
		this.gotoLocation = function (ByngRequest)
		{
			window.location = ByngRequest.toString();
		}
		
		// Fetch the order of a list
		this.getListOrder = function (list, attr, tag) {
			
			if (!tag) tag = 'li';
			
			var items = list.getElementsByTagName(tag);
			
			orderedList = new Array ();
			
			for (var i = 0, n = items.length; i < n; i++) {
				var item = items[i]
	
				// Get the element
				orderedList.push (item.getAttribute(attr));
			}
			
			return orderedList;
		}
		
		this.getChildTags = function (obj, tag) 
		{
			return obj.parentNode.getElementsByTagName(tag)
		}
		
		// Do a method
		this.doForAttribute = function (obj, tracker, method)
		{
			
			var items = this.getChildTags(obj, 'dd');
			
			for (var i = 0, n = items.length; i < n; i++) {
				var item = items[i]
	
				// Get the element
				k = item.getAttribute("trackerId");
				
				if (k == tracker) {
					method (item, 1);
				} else {
					method (item, 0);
				}
				
			}
			
		}
		
		// Move object to the top or end of a list
		this.moveListPosition = function (obj, tracker, tag, position) 
		{
			if (!tag) tag = "dd";
			if (!position) position = "first";
			
			var items = this.getChildTags(obj, tag);
			
			for (var i = 0, n = items.length; i < n; i++) {
				var item = items[i]
	
				// Get the element
				k = item.getAttribute("trackerId");
				
				if (k == tracker) {
					if (position == "first") {
						obj.parentNode.insertBefore (item, items[0]);
					} else {
						obj.parentNode.appendChild (item);
					}
					break; /* Found it so break out of this look before messing up the new order */
				} 
				
			}

		}
		// Move to the top of a list
		this.moveToFirstChild = function (obj, tracker, tag ) 
		{
			
			this.moveListPosition (obj, tracker, tag, "first");
			
		}
		
		// Move object to the end of a list
		this.moveToLastChild = function (obj, tracker, tag) 
		{
			this.moveListPosition (obj, tracker, tag, "last");
		}
		
		// Drop an element out of a list
		this.dropFromList = function (obj, tracker, tag) 
		{
			var items = this.getChildTags(obj, tag);
			for (var i = 0;i < items.length;i++) {
	
				// Get the element
				k = items[i].getAttribute("trackerId");
				
				if (k == tracker) {
					obj.removeChild (items[i]);
				}
			}
			
		}
		
/**
 *
 *	Scriptalicious wrappers
 *
 */
		// Create inPlace editors by tag
		this.createInPlaceEditors = function (obj, tag, baseUrl)
		{	
			
			items = this.getChildTags (obj, tag);
			
			editors = new Array (items.length);
			
			for (var i = 0, n = items.length; i < n; i++) 
			{
			
				if (this.isValidHex(items[i].id))
				{
					
					
					url = baseUrl+"?";
					for (var j=0;j<this.keys.length;j++)
					{
						url += this.keys[j]+'='+this.values[j]+'&';
					}

					editors[i] = new Ajax.InPlaceEditor(items[i].id, url, {rows:4,highlightcolor:'#EEE'});	
				}
				
				
			}
			
			
		}
/**
 *
 *	String encoding
 *
 */
		this.isValidHex = function (s)
		{
			if (s.substr(0,3) != "zz_") return false;
			
			h = s.substr(3);
			
			s = this.decodeHex (h);
			
			s = s.split("|");
						
			this.keys = s[0].split(":");
			this.values = s[1].split(":");

			return true;
		}
		
		/**
		 * Decode a string from hexidecimal
		 * 
		 * @para String str
		 * @return String
		 */
		this.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
		 */
		this.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
		 */
		this.makeIdTag = function (keys, values)
		{
			return 'zz_' + this.encodeHex(keys,values);
		}
		
		/**
		 * Encode a string to a hexidecimal string
		 * 
		 * @param Array keys Array keys
		 * @param Array values Corresponding array values
		 * @return String
		 */
		this.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(ByngUI.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
		 */
		this.ntos = function (n)
		{
		    n=n.toString(16);
		    if (n.length == 1) n="0"+n;
		    n="%"+n;
		    return unescape(n);
		}


/**
 *
 *	Miscellanous utility functions
 *
 */

		// Set opacity
		this.setOpacity = function (obj, level)
		{
			if (getBrowser() == "MSIE")
			{
				obj.style.filters.alpha.opacity = 100;	
			} else {
				obj.style.MozOpacity = level / 100;	
			}
			
		}
 
		// Confirm
		this.ask = function (s) {
			
			return confirm (s);
			
		}	
		
		// Toggle an element display none <-> block
		this.toggle = function (element, display) 
		{
			if (!isObject(element)) throw ("Toggled invalid element");
			
			/* Are we using the display toggle */
			if (!isUndefined(display)) {
				if (display == true) {

					element.style.display = 'block';
				
					// If the class name is just hide then reset it in DOM
					if (element.className == "hide") {
						element.className = ""
					}					
				} else {
					element.style.display = 'none';
				}
				return;
			}
			
			/* Otherwise determine from the element styles */
			if (element.className == "hide" || element.style.display == 'none') {
				element.style.display = 'block';
				
				// If the class name is just hide then reset it in DOM
				if (element.className == "hide") {
					element.className = ""
				}				
			} else {
				element.style.display = 'none';
			}
					
		}


		// Do includes		
		this.include("/common/"+BASECODE_VERSION+"/js/forms/builder.js");
		this.include("/common/"+BASECODE_VERSION+"/js/util.js");
	}
	
	/**
	 * Encapsulates a request to the BaseCode
	 *
	 * @param String screen
	 * @param String module
	 * @param String package
	 * @param String loader
	 */
	function ByngRequest (screen, module, packageName, loader) 
	{
		if (!screen && !module)
		{
			// If no screen is set then use the current URI
			s = location.pathname.substr(1).split ('/');
			this.loader = s[0];
			this.module = s[1];
			this.screen = s[2];
		} else {
		
			if (!loader)
			{
				// Reverse engineer the loader name
				s = location.pathname.split ('/'+module);
				this.loader = s[0];
			} else {
				this.loader = loader;
			}

			/**
			 * @type String
			 */
			this.screen = screen;
			
			/**
			 * @type String
			 */
			this.module = module;
			
			/**
			 * @type String
			 */
			this.packageName = packageName;
					
		}

		/**
		 * @type Object
		 */
		this.vars = new Object ();
		
		this.uri = "";
		
		/**
		 * @param String a
		 */
		this.setAction = function (a)
		{
			this.action = a;
		}
		
		/**
		 * @type String
		 */
		this.getAction = function ()
		{
			return this.action;
		}
		
		/**
		 * Set the screen
		 * 
		 * @return String
		 */
		this.setScreen = function (screen)	
		{
			this.screen = screen;
		}

		/**
		 * Return the screen
		 * 
		 * @return String
		 */
		this.getScreen = function ()	
		{
			return this.screen;
		}
		
		/**
		 * Get the module name
		 * 
		 * @return String
		 */
		this.getModule = function ()
		{
			return this.module;
		}
		
		/**
		 * Get the loader
		 * 
		 * @return String
		 */
		this.getLoader = function ()
		{
			return this.loader;
		}
		
		/**
		 * Get the package name
		 * 
		 * @return String
		 */
		this.getPackage = function ()
		{
			return this.packageName;
		}
		
		/**
		 * Add a parameter to the request
		 * 
		 * @param String key
		 * @param String value
		 */
		this.addParam = function (key, value)
		{
			this.vars[key] = value;
		}
		
		/**
		 * Return a string of the GET variables
		 * 
		 * @return String
		 */
		this.gets = function ()
		{
			if (this.vars.length < 1) return;
			
			var s = "?";
			
			for (k in this.vars) {
				s += k + '=' + this.vars[k]  + '&';
			}
			
			return s;
		}
		
		/**
		 * Set the URI manually
		 * 
		 * @param String uri
		 */
		this.setUri = function ( uri )
		{
			uri = uri.toString();
			var q = uri.indexOf ('?');
			if (q > 0)
			{
				query = window.location.search.substring(1);
				vars = query.split("&");
				for (var i=0;i<vars.length;i++) {
					var pair = vars[i].split("=");
					if (!isUndefined(pair[1])) this.addParam (pair[0],pair[1]);
				}
				uri = uri.substring(0,q);
			}

			this.uri = uri;
			
		}
		
		/**
		 * 
		 * Return the ByngRequest as a string
		 * 
		 * @return String
		 */
		this.toString = function (noQuery)
		{

			var uri = "";
			if (this.uri == "") {
				uri = "/" + new Array (this.getLoader(),this.getPackage(),this.getModule(),this.getScreen()).join("/");
			} else {
				uri = this.uri;
			}

			if (this.uri.substring(0,4) != "http")
			{
				uri = uri.replace("//","/");
			}

			if (noQuery == true) return uri;
			
			uri += this.gets ()
							
			return uri;
		}
				
	}
