
/**
 * 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 },
	
	/**
	 * Set the cache
	 * 
	 * @param Object
	 */
	 setCacher : function ( fn ) { Byng.cacher = 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(
{
	/**
	 * Use the Events construct to hold the stack of application events
	 * 
	 */
	Implements : Events,
	
	/**
	 * Return a reference to the HtmlGet handler
	 *
	 */ 
	htmlget : null,

	/**
	 * Return a reference to the HtmlGet handler
	 *
	 */ 
	xmlpost : null,

	/**
	 * Get the popup
	 *
	 * @return DomElement
	 */
	getPopup : function ()
	{
		return Byng.ui.dom.getInputPopup();
	},
	
	/**
	 * Handle an error in the application
	 *
	 * @param String
	 * @return Boolean
	 */
	error : function ( exception )
	{
		return false;
	},
	
	/**
	 * Start loader icon
	 * 
	 * @return DomElement
	 */
	startLoader : function ()
	{
		return Byng.ui.dom.showLoader( $('loading-icon'), Byng.app.icons.loader );
	}

});

/**
 * 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,
	
	/**
	 * Holds a stack of FormBuilders for the page
	 * 
	 * @type Hash
	 */
	stack : null,
	
	/**
	 * Get a form builder instance
	 * 
	 * @return FormBuilder
	 */
	getBuilder : function ()
	{
		if (!this.builder) {
			if (FormBuilder && $type(FormBuilder) == 'object') {
				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, options)
	{
		if (!this.stack) this.stack = $H();		
		var form = this.getForm(formName);		
		if (form) {
			return form;
		} else {
			
			var form = this.getBuilder().clone()
							.setOptions(options)			
							.setFormName(formName);
			this.stack.set(formName, form);
			return form;
		}
	},
	
	/**
	 * Get a form from the stack
	 * 
	 * @param String formName
	 * @return FormBuilder
	 */
	getForm : function (formName)
	{
		return this.stack.get(formName.replace('.','_'));
	},

	/**
	 * 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);
	},
	
	/**
	 * New Find-as-you-type element
	 * 
	 * @param DomElement
	 * @param String gateway
	 * @param String redirect
	 * @return Search
	 */
	fayt : function ( element, gateway, redirect )
	{
		var options = {'input' : element, 'gateway' : gateway, 'redirect' : redirect};
		return new Search (options);
	},
	
	/**
	 * Add an input handler to the stack
	 * 
	 * @param Object handler
	 * @return Object
	 */
	addInputHandler : function ( handler, element, type )
	{
		if (!this.inputHandlers[type]) {
			this.inputHandlers[type] = new Hash();
		}
		
		this.inputHandlers[type].set(element.getProperty('accesskey'), handler);
		return handler;
	},
	
	/**
	 * Get an input handler
	 * 
	 * @param Elememt element
	 * @param Integer type
	 * @return Object
	 */
	getInputHandler : function ( element, type )
	{
		if (!type) {
			if (element.hasClass('fayt')) type = BYNG_INPUT_FAYT;
			else if (element.hasClass('criteria')) type = BYNG_INPUT_CRITERIA; 
		}
		if (!this.inputHandlers[type]) {
			return $empty;
		} else {
			var handle = ($type(element) == 'element' ? element.getProperty('accesskey') : element);
			return this.inputHandlers[type].get(handle);
		}
	},
	
	/**
	 * Set the focus for an input field
	 * 
	 * @param DomElement
	 */
	onSearchFocus : function ( input )
	{
		// clear the text
		input.value = '';
		/**
		 * @todo Set the active search request here
		 */
	},
	
	/**
	 * 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);		
	}	

});

/**
 * Encapsulates a request to the BaseCode
 *
 * @param String screen
 * @param String module
 * @param String package
 * @param String loader
 */
var ByngRequest  = new Class(
{
	/**
	 * Of type Byng request
	 * 
	 */	
	type : 'byngrequest',
		
	/**
	 * Create a new ByngRequest
	 * 
	 * @param String screen
	 * @param String module
	 * @param String packageName
	 * @param String loader
	 * @return ByngRequest
	 */
	initialize : function (screen, module, packageName, loader) 
	{
		
		if ((!screen || !$chk(screen)) && (!module || !$chk(module))) {		
			// If no screen is set then use the current URI
			var 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 Hash
		 */
		this.vars = {};
		
		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 ()
	{
		var vars = new Hash(this.vars);
		if (!$chk(vars.getLength) || vars.getLength() < 1) return '';
		
		var s = "?";
		
		vars.each(function(v,k) {
			s += k + '=' + v  + '&';
		});
		
		return s;
	},
	
	/**
	 * Set the URI manually
	 * 
	 * @param String uri
	 * @return ByngRequest
	 */
	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;
		}
		
		// condition: strip double slashes
		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 Hash
	 */
	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);
	}
			
});

/**
 * Byng 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 )
	{
		if ($type(arg) == 'string') {
			return arg;
		} else {
			if ($type(arg) == 'event') {
				arg.stop();
				arg = arg.target;
			}
			if ($type(arg) == 'element') {
				arg = $(arg);
				return (arg.get('tag') != 'a' ? arg.getParent("a") : 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'));
	}
});

/**
 * Model class
 * 
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 */
var ByngEngine = new Class(
{
	

});
