/**
 * 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
 * 
 * @requires MooTools-1.2.1
 * @requires BaseCode-0.2.4
 */
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 		: 	null,
	
	/**
	 * Holds the input handler
	 * 
	 * @param ByngInput
	 */
	input	:	null,
	
	/**
	 * Holds the transit handler
	 * 
	 * @param ByngTransit
	 */
	transit  : 	null,
	
	/**
	 * Holds the library store
	 * 
	 * @type Hash
	 */
	library : new Hash(),
	
	/**
	 * Holds the debug mode
	 * 
	 * @param Boolean 
	 */
	debug : false,
	
	/**
	 * Abstracted registration a library in private namespace
	 * 
	 * @param String ns
	 * @param Class fn
	 */
	register : function (ns, fn)
	{
		if (Byng.debug) Byng.log("Byng.register: "+ns);		
		if ($type(fn.Extends) == 'string') 		fn.Extends = Byng.source(fn.Extends, true);
		if ($type(fn.Implements) == 'string')	fn.Implements = Byng.source(fn.Implements, true);
		if ($type(fn.Implements) == 'array') {
			var tmp = [];
			// replace any Byng namespace strings with the function source
			fn.Implements.each(function(_fn){
				tmp.push ( ($type(_fn) == 'string' ? Byng.source(_fn, true) : _fn) );
			});
			fn.Implements = tmp;
		}
				
		Byng.library.set(ns, new Class(fn));
	},
	
	/**
	 * Initiate a library object from our private namespace
	 * 
	 * @param String ns
	 */
	init : function (ns, args0, args1, args2)
	{
		if ( Byng.library.has(ns) ) {
			if (Byng.debug) Byng.log("Byng.init: "+ns);
			var klass = Byng.library.get(ns);
			return new klass(args0, args1, args2);
		} else {
			throw "Unknown library path: {ns}".substitute({'ns' : ns});
		}
	},
	
	/**
	 * Get the class properties
	 * 
	 * @param String ns Namespace import
	 * @param Boolean silent Silently ignore missing
	 */
	source : function (ns, silent)
	{
		if ($type(ns) == 'array') {
			return ns.each(function(_ns) { _ns = Byng.source(_ns); });	
		} else {
			if (Byng.library.get(ns) == null && !$chk(silent) ) {
				throw "Unable to load library: "+ns;
			}
			return Byng.library.get(ns);
		}
	},
	
	/**
	 * Log something
	 * 
	 * @param Mixed e
	 */
	log : function (e)
	{
		if (console) {
			if (['object'].contains($type(e))) console.debug(e);
			else console.log(e);
		}	
	}
};

/**
 * 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
 */
Byng.register('byng.input',
{
	/**
	 * 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) {
			this.builder = Byng.init("byng.forms.builder");	
		}
		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.stack.get(formName.replace('.','_'));		
		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)
	{
		if (!this.stack) this.stack = $H();
		var form = this.stack.get(formName.replace('.','_'));
		if ( form ) return form;
		else return Byng.input.addForm(formName.replace('.','_'),true);
	},

	/**
	 * 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, options )
	{
		options = options || {};
		options = $merge(options, {'input' : element, 'gateway' : gateway, 'redirect' : redirect});
		return Byng.init('byng.ajax.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 ($type(element) != 'element') type = BYNG_INPUT_FAYT;
			else 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};
		
		// missing required options, use defaults
		if (!options.onSuccess) 	options.onSuccess = Byng.ui.dom.showPopup;
		if (!options.target) 	 	options.target = Byng.app.getPopup();
		
		// add the popup events to the popup dom element
		if (options.onPopup)		options.target.addEvent('popup', options.onPopup)
		if (options.onCancel)		options.target.addEvent('cancel', options.onCancel);
		if (options.onSubmit)		options.target.addEvent('submit', options.onSubmit);
		
		// 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 ( encoded )
	{
		if (encoded == true) {
			var action = Byng.ui.makeIdTag(['module', 'package', 'action'],[this.module, this.packageName, this.action], '_');
			return action;
		} else {
			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;
	},
	
	/**
	 * Set the package parameter
	 * 
	 * @param {Object} packageName
	 */
	setPackage : function (packageName)
	{
		this.packageName = packageName;
		return this;
	},
	
	/**
	 * 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);
	},
	
	/**
	 * Clone this request
	 * 
	 * @return 
	 */
	clone : function (screen)
	{
		var request = new ByngRequest()
						.setScreen((screen ? screen : this.getScreen())).setModule(this.getModule())
						.setPackage(this.getPackage()).setLoader(this.getLoader())
		request.vars = this.vars;
		return request;
	}
			
});

/**
 * 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
	 * @param String property
	 */
	normalise : function ( arg, property )
	{
		if (!$chk(arg)) {
			return null;
		} else if (['string','number'].contains($type(arg))) {
			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) : null);
				if ($chk(id)) {
					return id[property];	
				} else {
					return (arg.get('tag') != 'a' ? arg.getParent("a") : arg).getAttribute('accesskey');
				}
			}
		}
	},
	
	/**
	 * Get the ByngRequest for this view
	 * 
	 * @param String
	 * @returns ByngRequest
	 */
	getRequest : function (screen)
	{
		// condition: we have the getProperty method
		if (this.getProperty) {
			$each({
				// retrieve all the properties from the script URI
				'loader'	: this.getProperty('loader'),
				'package'	: this.getProperty('package'),
				'module'	: this.getProperty('module')
			}, function (v,k) {
				if (v) {
					// override the meta data if contained within the script
					this.meta.gateway[k] = v;
				}
			}.bind(this));
		}
		
		// form the request object from the class meta parameters
		return new ByngRequest(	(screen ? screen : (this.meta.gateway['screen'] ? this.meta.gateway['screen'] : 'default')),   
								(this.meta.gateway['module'] 	? this.meta.gateway['module'] 	: null),
								(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(
{
	

});
