
/**
 * Load a global namespace static controller
 *
 * The Byng controller provides singleton object for reference
 * to the page components through a single handler
 * 
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var Byng = {

	/**
	 * Set the application to run
	 * 
	 * @param Object
	 */
	setApp 	: function ( fn ) { Byng.app = fn },
	
	/**
	 * Add a global FX instance by global handle
	 * 
	 * @param Object Fx
	 * @param String handle
	 */
	addFx  	: function ( fx, handle ) { Byng.fx[handle] = fx; },
	
	/**
	 * Get a global FX instance
	 * 
	 * @param String handle
	 */
	getFx  	: function ( handle ) { return Byng.fx[handle] },	
	
	/**
	 * Set a help controller
	 * 
	 * @param Object
	 */
	setHelp : function ( fn ) { Byng.help = fn },			
	
	/**
	 * Set the UI controller
	 * 
	 * @param Object
	 */
	setUi  	: function ( fn ) { Byng.ui = fn },	
	
	/**
	 * Set the input handler
	 * 
	 * @param Object
	 */
	setInput: function ( fn ) { Byng.input = fn },

	/**
	 * Set the transit handler
	 * 
	 * @param Object
	 */
	setTransit: function ( fn ) { Byng.transit = fn },
	
	/**
	 * Holds the application object with data processing methods
	 *
	 * @see ByngApp
	 * @param ByngApp
	 */
	app 	:  	null,
	
	/**
	 * Holds the FX libraries
	 * 
	 * @param Object
	 */
	fx 		: 	{},
	
	/**
	 * Holds the help handler
	 * 
	 * @param ByngHelp
	 */
	help 	: 	null,
	
	/**
	 * Holds the UI handler
	 * 
	 * @param ByngUI
	 */
	ui 		: 	(ByngUI ? ByngUI : null),

	/**
	 * Holds the input handler
	 * 
	 * @param ByngInput
	 */
	input	:	null,
	
	/**
	 * Holds the transit handler
	 * 
	 * @param ByngTransit
	 */
	transit  : 	null
};

/**
 * Handles commom application events and provides
 * a base class for applications implementing ByngApp
 * 
 * ByngApp is responsible for:
 *
 *			- Client side application layer
 *			- Sending and receiving requests
 *			- Configuration of the UI
 *			- Error handling
 *			- Authentication handling
 *
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngApp = new Class(
{
	
	/**
	 * Return a reference to the HtmlGet handler
	 *
	 */ 
	htmlget : null,

	/**
	 * Return a reference to the HtmlGet handler
	 *
	 */ 
	xmlpost : null,

	/**
	 * Get the popup
	 *
	 */
	getPopup : function ()
	{
		return Byng.ui.dom.getInputPopup();
	},
	
	/**
	 * Handle an error in the application
	 *
	 * @param String
	 */
	error : function ( exception )
	{
		return false;
	},
	
	startLoader : function ()
	{
		return Byng.ui.dom.showLoader( $('loading-icon'), Byng.app.icons.loader );
	},
	
	/**
	 * Add an event to the application
	 * 
	 * Method here for abstraction as the window element
	 * can carry the event stack
	 * 
	 * Special event codes:
	 * 		"popup"   - call Function(popup) on showing HTML in a popup (chains "content")
	 * 		"wait"    - call the event when waiting on a request [todo]
	 *		"error"   - call on uncaught exception [todo]
	 * 
	 * @param String e 
	 * @param Function fn 
	 * @param Function bind
	 * @param Mixed args
	 * @return DomElement 
	 */
	addEvent : function (e,fn,bind,args)
	{
		return window.addEvent(e,fn,bind,args);
	},
	
	/**
	 * Wrapper method to fire an event from the stack
	 * 
	 * @param String e
	 * @return DomElement
	 */
	fireEvent : function (e)
	{
		return window.fireEvent(e);
	}

});

/**
 * Represents a form input handler
 * 
 * @para Integer
 */
var BYNG_INPUT_FORM   = 1;

/**
 * Represents a Find-as-you-type input handler
 * 
 * @param Integer
 */
var BYNG_INPUT_FAYT = 2;

/**
 * Represents a criteria input handler
 * 
 * @type Integer
 */
var BYNG_INPUT_CRITERIA = 3;

/**
 * Handles input mechanims into the application
 *
 * ByngInput is responsible for:
 *
 *			- Raising prompts, inputs and models
 *			- Create and handling forms and events
 *			- Sanitising inputs and outputs
 *			- Clientside validation of inputs
 *
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngInput = new Class(
{
	/**
	 * Holds a stack of search handlers
	 * 
	 * @type Array
	 */
	inputHandlers : [],
	
	/**
	 * Holds an instance of the FormBuilder
	 * 
	 * @type FormBuilder
	 */
	builder : null,
	
	/**
	 * Get a form builder instance
	 * 
	 */
	getBuilder : function ()
	{
		if (!this.builder) {
			if (FormBuilder && !isFunction(FormBuilder)) {
				this.builder = FormBuilder;
			} else {
				this.builder = new FormBuilder();	
			}
		}
		return this.builder;
	},

	/**
	 * Register a form to this application
	 *
	 * @param String formName
	 * @return FormBuilder
	 */
	addForm : function (formName, depends)
	{
		if (FormBuilder && !isFunction(FormBuilder)) {
			var clone = FormBuilder;
				clone.setFormName(formName);
			return clone;
		} else {
			return new FormBuilder(formName,depends);	
		}
	},

	/**
	 * Raise a confirm bos
	 *
	 * @param String
	 */
	confirm : function ( s )
	{
		return confirm(s);
	},

	/**
	 * Raise a prompt box
	 *
	 * @param String
	 */
	prompt : function ( q, ans )
	{
		if (!ans) ans = '';
		return prompt(q, ans);
	},

	/**
	 * Create a new modal popup
	 * 
	 * @param Options options
	 */
	popup 	: function ( options, params )
	{
		// if the request object is passed directly then push into an object
		if (!options.request) options = {'request' : options};
		
		if (!options.onComplete) options.onComplete = Byng.ui.dom.showPopup;
		if (!options.target) 	 options.target = Byng.app.getPopup();
		
		// register the popup
		Byng.ui.dom.registerPopup ( options.target );		

		// retreive the HTML for the popup
		return Byng.transit.html( options, params );		
	},
	
	/**
	 * Add an input handler to the stack
	 * 
	 * @param Object handler
	 */
	addInputHandler : function ( handler, domRef, type )
	{
		if (!this.inputHandlers[type]) {
			this.inputHandlers[type] = [];
		}
		/**
		 * @todo Mootools1.2 use a hash map here with domRef
		 */
		this.inputHandlers.push(handler);
		return handler;
	},
	
	/**
	 * Set the focus for an input field
	 * 
	 * @param DomElement
	 */
	onSearchFocus : function ( input )
	{
		// clear the text
		input.value = '';
		/**
		 * @todo Set the active search request here
		 */
	}

});

	
/**
 * Represents a .csv output
 * 
 * @param Integer
 */
var BYNG_TRANSIT_OUTPUT_CSV = "csv";

/**
 * Represents a .svg output
 * 
 * @param Integer
 */
var BYNG_TRANSIT_OUTPUT_SVG = "svg";

/**
 * Represents a .png output
 * 
 * @param Integer
 */
var BYNG_TRANSIT_OUTPUT_PNG = "png";


/**
 * Handles transit mechanims between the application layer and
 * the user interface, inputs and feedback 
 *
 * ByngTransit is responsible for:
 *
 *			- Transport layer
 * 			- Encoding and decoding requests
 * 			- Hydration of XML to objects
 * 			- Outputting of views
 *
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngTransit = new Class(
{
	/**
	 * Initialize the transit class
	 * 
	 * @param Options options
	 */
	initialize : function ( options )
	{
		// read configuration options	
		this.setOptions( options );
	},
	
	/**
	 * Set the request for the default transit
	 * 
	 * @param ByngRequest
	 */
	setRequest : function ( request )
	{
		this.options.request = request;
	},
	
	/**
	  Send a request to the server
	 * 
	 * @param Options options
	 */
	post : function ( options, params )
	{
		if (!options) options = {};
		
		// condition: request object not supplied
		if (!options.request) options.request = this.options.request;
		if (!options.onComplete) options.onComplete = Byng.ui.readResponse; 
		 
		// new Byng XmlPost object
		Byng.app.xmlpost = new XmlPost ();
 		Byng.app.xmlpost.fromRequest( options.request );
 		
 		// condition: form supplied in options
 		if (options.form) {
 			// hydrate the parameters with the form data
 			Byng.input.getBuilder().hydrate(Byng.app.xmlpost, options.form);
 		}
 		
 		// condition: parameters given
		if (params) {
			// add the params
			$each(params, function(v,k) {
				this.addParam(k,v);
			}.bind(Byng.app.xmlpost));
		}
		
		// send the POST		
 		Byng.app.xmlpost.fetch( options.onComplete );
 		return Byng.app.xmlpost;
	},
	
	/**
	 * Retreive HTML from the server
	 * 
	 * @param Options options
	 */
	html : function ( options, params )
	{
		if (!options) options = {};
		
		// if the request object is passed directly then push into an object
		if (!options.request) options.request = this.options.request;

		// condition: parameters given
		if (params) {
			// add the params
			$each(params, function(v,k) {
				this.addParam(k,v);
			}.bind(options.request));
		}
		
		Byng.app.htmlget = new HtmlGet();
		Byng.app.htmlget.set( options.target, options.wrapper );
		Byng.app.htmlget.fromRequest ( options.request );
		Byng.app.htmlget.update ( options.onComplete );
		return Byng.app.htmlget;
	},
	
	/**
	 * Output a view
	 * 
	 * @param Integer flag
	 * @param Object options
	 */
	output : function ( flag, options )
	{
		var params = {};
		switch (flag) {
			case BYNG_TRANSIT_OUTPUT_CSV:
				var params = {'data'     : this.toJson(options.rows),
							  'headings' : this.toJson(options.headings) }
			case BYNG_TRANSIT_OUTPUT_SVG:
			case BYNG_TRANSIT_OUTPUT_PNG:
				var params = {'data'   : options.data,
							  'source' : BYNG_TRANSIT_OUTPUT_SVG}
			break;
		}
		
		// new request to the export module
		var request = new ByngRequest('default','exporter','ui','ajax');
			request.addParam('output', flag);
			request.setAction('output');

		// set the repquest
		Byng.transit.post({
				'request'    : request,
				'onComplete' : this.download.bind(this)
				}, params);
	},
		
	/**
	 * Present files for download
	 * 
	 * @param DomElement xml ByngXml structured response
	 * @return void
	 */
	download : function ( xml )
	{
		var rspn = new ByngXml (xml);

		// condition: Internet explorer
		if (window.ie == true) {
			// display message for download
			Byng.ui.readResponse(xml);
		} else {
			// present the download in an iframe
			var iframe = new Element('iframe');
				iframe.setAttribute('src', rspn.getParam('asset'));
				iframe.setStyles({'width' : '1px', 'height' : '1px', 'border' : '1px'});
				iframe.injectAfter($(ELEM_FEEDBACK)); 
		}
	},
	
	/**
	 * To JSON
	 * 
	 * @param String string
	 */
	toJson : function (string)
	{
		var j = new ByngJson();
		return j.encode(string);
	},
	
	/**
	 * From JSON
	 * 
	 * @param Object data
	 */
	fromJson : function (data)
	{
		return new ByngJson(data);
	}
	
});

ByngTransit.implement( new Options );


/**
 * Encapsulates a request to the BaseCode
 *
 * @param String screen
 * @param String module
 * @param String package
 * @param String loader
 */
var ByngRequest  = new Class({
	
	initialize : function (screen, module, packageName, loader) 
	{
		
		if ((!screen || isUndefined(screen)) && (!module || isUndefined(module))) {		
			// If no screen is set then use the current URI
			s = location.pathname.substr(1).split ('/');
			this.loader      = s[0];
			this.packageName = s[1];
			this.module      = s[2];
			this.screen      = s[3];
		} 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
	 */
	setAction : function (a)
	{
		this.action = a;
		return this;
	},
	
	/**
	 * @type String
	 */
	getAction : function ()
	{
		return this.action;
	},
	
	/**
	 * Set the screen
	 * 
	 * @return String
	 */
	setScreen : function (screen)	
	{
		this.screen = screen;
		return this;		
	},

	/**
	 * Return the screen
	 * 
	 * @return String
	 */
	getScreen : function ()	
	{
		return this.screen;
	},
	
	/**
	 * Get the module name
	 * 
	 * @return String
	 */
	getModule : function ()
	{
		return this.module;
	},

	/**
	 * Set the module string
	 * 
	 * @param String
	 */
	setModule : function (module)
	{
		this.module = module;
		return this;
	},	
	
	/**
	 * Get the loader
	 * 
	 * @return String
	 */
	getLoader : function ()
	{
		return this.loader;
	},
	
	/**
	 * Set the loader string
	 * 
	 * @param String
	 */
	setLoader : function (loader)
	{
		this.loader = loader;
		return this;
	},
		
	/**
	 * Get the package name
	 * 
	 * @return String
	 */
	getPackage : function ()
	{
		return this.packageName;
	},
	
	/**
	 * Add a parameter to the request
	 * 
	 * @param Mixed key
	 * @param String value
	 */
	addParam : function (key, value)
	{
		if ($type(key) == 'string') { 
			this.vars[key] = value;
		} else if ($type(key) == 'element') {
			// pick the parameters from the element id attribute
			$each(Byng.ui.fromIdTag(key.getProperty('id')), function(value,key){
				this.vars[key] = value;
			}.bind(this));
		} else {
			throw "Invalid parameter supplied";
		}
		return this;		
	},
	
	/**
	 * Return a string of the GET variables
	 * 
	 * @return String
	 */
	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
	 */
	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 this;		
	},
	
	/**
	 * Return the ByngRequest as a string
	 * 
	 * @param String noQuery
	 * @return String
	 */	
	composeAsString : 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;
	},
	
	/**
	 * Utility method to convert request to String
	 * 
	 * @todo Fix in IE (protected method)
	 * 
	 * @return String
	 */
	toString : function (noQuery)
	{
		return this.composeAsString(noQuery);
	},
	
	/**
	 * Pass the ByngRequest to the hash
	 * 
	 * @param Object params Supplementary parameters for hash
	 * @return String
	 */
	toHash : function ( params )
	{
		if (!params) params = {};
		
		// condition: parameters in the request object 
		if (this.vars.length > 0) {
			// merge in request parameters
			$each(this.vars,function(v,k){
				if (!this[k]) this[k] = v;
			}.bind(params));
		}
		
		var s = [];
		// form the key-value pairs
		$each(params,function(v,k){
			this.push(k+'='+v);
		}.bind(s));
		s = s.join('&');
			
		// condition: we have something to store
		if (s.length > 0) {			
			window.location.hash = s;
			return s;
		} else {
			return null;
		}
	},
	
	/**
	 * Assign variables from the #hash
	 * 
	 * @param exclude Exclude parameters from hash
	 * @return Object
	 */
	fromHash : function ( exclude )
	{		
		var skip = false;
		window.location.hash
		.substring(1)
		.split('&')
		.each(function(pair) {
			var pair = pair.split('=');
			if (!pair[0]) return;
			if (exclude) {
				$each(exclude,function(value,key) {
					// parameter exists and has a value
					if (key == pair[0] && value) skip = true;
				});
			}
			if (!skip) this.addParam(pair[0],pair[1]);	
		}.bind(this));
		return this.vars;
	},
	
	/**
	 * Returns whether there is a hash for this ByngRequest
	 * 
	 * @return Boolean
	 */
	hasHash : function ()
	{
		return (window.location.hash.length > 0);
	}
			
});


/**
 * ByngView interface class
 * 
 * Abstract class for implementing methods used by view/interface handlers
 * 
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngView = new Class(
{
	/**
	 * Normalise an argument
	 * 
	 * @param Mixed arg
	 */
	normalise : function ( arg, property )
	{
		if ($type(arg) == 'string') {
			return arg;
		} else {
			if ($type(arg) == 'event') {
				arg.stop();
				arg = arg.target;
			}
			if ($type(arg) == 'element') {
				arg = $(arg);
				var id = (arg.getProperty('id') ? Byng.ui.fromIdTag(arg.getProperty('id')) : null);
				if ($chk(id)) {
					return id[property];
				} else {
					return (arg.getTag() != 'a' ? arg.getParent() : arg).getAttribute('accesskey');
				}
			}
		}
	},
	
	/**
	 * Get the ByngRequest for this view
	 * 
	 * @param String
	 * @returns ByngRequest
	 */
	getRequest : function (screen)
	{
		return new ByngRequest((screen ? screen : 'default'),   this.meta.gateway['module'],
								(this.meta.gateway['package'] ? this.meta.gateway['package'] : 'site'),
								(this.meta.gateway['loader']  ? this.meta.gateway['loader']  : 'ajax'));
	},
	
	/**
	 * Get the script DomElement
	 * 
	 * @return Element
	 */
	getScript : function ()
	{
		if (!this._script) this._script = this._getScript(this.meta.file);
		return this._script;
	},
	
	/**
	 * Get a script by src attribute
	 * 
	 * @param String src Match the source attribute 
	 * @return DomElement
	 */
	_getScript : function ( src )
	{
		var script = $$('script').filter(function(script){			
			return (script.getProperty('src') && script.getProperty('src').contains(src));
		});
		
		if (script.length == 0) { 
			return {'retrieve' : $empty}; 
		} else { 
			script = script.getLast();
			if (script.getProperty('_parameterised') == null) {
				if (script.getProperty('src').contains("?") == false) {
					return script.setProperty('_parameterised', true);	
				} else {
					script.setProperty('_parameterised', true)
						  .getProperty('src')
						  .split('?')
						  .pop()
				 		  .split('&')
				 		  .each(function(pair) {
					 			pair = pair.split('=');
								this.setProperty(pair[0],pair[1]);
						   }.bind(script));
				}
			}
			return script;
		} 
	},
	
	/**
	 * Get a property from the script element
	 * 
	 * @param String key
	 * @return String
	 */
	getProperty : function ( key )
	{
		return this.getScript().getProperty(key);
	}	
});

/**
 * Model class
 * 
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngEngine = new Class(
{
	

});

