/**
 * $Id: discovery-auth-ui.js 31751 2010-03-31 17:31:32Z alucas $
 * 
 * Project:		DNI User Authentication: UI Javascript Component
 * Version:		2.0.0
 * Author:		alucas
 * 
 */


if (typeof(discovery) == "undefined") var discovery = {};
if (typeof(discovery.authentication) == "undefined") discovery.authentication = {};
if (typeof(discovery.authentication.ui) == "undefined") discovery.authentication.ui = {};
if (typeof(discovery.authentication.ui.util) == "undefined") discovery.authentication.ui.util = {};
if (typeof(discovery.authentication.ui.lib) == "undefined") discovery.authentication.ui.lib = {};
function augment(obj, args, func) { func.apply(obj, args) }






//#FORM CONTROLS FRAMEWORK

discovery.authentication.ui.core = (function() {
	
	
		//EXTERNALS
	
		var $c_ = $TORA("common"), $c_Dom = $c_.dom;
		var utils = {
			getElementsByClassName:		$c_Dom.getElementsByClassName,
			addEventListener:			$c_Dom.addEventListener,
			each:						$c_.object.each			
		}
	
	
		//PRIVATE VARS	
		
		var _typeMap = {};	// contains the HTML class to Control type mappings
		var _regex = {};		//collection of compiled regular expressions
		var _instances = [];
		
		
		//PUBLIC INTERFACE
		
		return {
			
			/**
			 * associates a control type with a HTML class
			 * 
			 * @param {String} className
			 * @param {Function} constructor
			 * @param {Object} config
			 */
			registerType: function(className, constructor, config) {
	
				if ((className)&&(constructor)) {
					_instances[className] = [];
					_typeMap[className] = {
						constructor: constructor,
						config: config
					};
				}
			},
			
			
			/**
			 * @void
			 * processes child elements and initialises any controls 
			 * based on the <code>_typeMap</code> object
			 * 
			 * @param	{HTMLElement}	targetElement
			 */
			processElement: function(targetElement) {

				utils.each(_typeMap, function(index){
					
					var elements = utils.getElementsByClassName.call(
							document, index),
							constructor = this.constructor,
							config = this.config;
					
					if (elements.length > 0)
						utils.each(elements, function(){

							if (this.tagName.toLowerCase() !== "label") {
								var o = { element: this};
								for (var i in config) o[i] = config[i];
								_instances.push(constructor(o));
							}
						});
				})
			},
			
			
			/**
			 * returns a type to object map of all objects instantiated
			 * 
			 * @return	{Object}
			 */
			getInstances: function() {
				return Array.prototype.slice.apply(_instances);
			}
	
		}	
})();



//#FORM CONTROLS

discovery.authentication.ui.lib = {}; //library of control types

augment(discovery.authentication.ui.lib,
		[discovery.authentication.ui.lib,
		 discovery.authentication],
		function(module, namespace){


	//EXTERNALS
	
	var $c_ = $TORA("common"), $c_Dom = $c_.dom;
	var $a_FB;
	var utils = {
		getElementsByClassName:		$c_Dom.getElementsByClassName,
		addEventListener:			$c_Dom.addEventListener,
		each:						$c_.object.each			
	}
	
	var user;

	var AUTH_REQ = $TORA.Requirement("authentication.fb");
	var FB_REQ = function(_user, code) {
		if (!$a_FB) $a_FB = $TORA("authentication").fb;
		if (!_user) var _user = (!user)
				? (user = $TORA("USER")) : user;
		AUTH_REQ(function() {
			$a_FB.whenAPIAvailable(user, code);
		});
	};
	
	
	//STUBS
	
	var _isTrue = function() { return true }
	var _isFalse = function() { return false }
	var _isNull = function() { return null }

	
	//PRIVATE METHODS
	
	utils.attachHandlers = function(elem, events) {
		
		utils.each(events, function(index) {
			utils.addEventListener.call(elem, index, this);
		});
	}
	

	//PICTURE PICKER FACTORY #

	/**
	 * initialises a <code>PicturePicker</code> instance
	 * for a corresponding form control
	 * 
	 * @param {Object} config
	 */
	module.PicturePicker = function(config) {
		
		
			//PRIVATE VARS
			
			var _DEFAULT_SETTINGS = {	//default settings applied to PicturePicker instances
				'HOVER_CLASS': "hover", 'SELECTED_CLASS': "selected",
				'KEY_IMAGE_ID': "handle", 'REMOVE_BTN_CLASS': "remove-avatar-button",
				'HIDE_AVATAR_CLASS': "removed", 'CURRENT_AVATAR_CLASS': "avatar"
			}
			
			var _getRegex = function (picker){	//collection of compiled regular expressions
				
				return {
					'IMAGE_ID_EXTRACT': { //extracts KEY_IMAGE_ID value
						match:		RegExp('(^|.*? )' + picker.settings["KEY_IMAGE_ID"]
											+ '-(.*?)($| .*)'),
						index:		2
					},
					'HOVER_REMOVE': {	//removes HOVER_CLASS class
						match:		RegExp('(^| )' + picker.settings["HOVER_CLASS"]
											+ '($| )', "g"),
						replace:	"$1$2"
					},
					'SELECT_REMOVE': {	//removes SELECTED_CLASS class
						match:		RegExp('(^| )' + picker.settings["SELECTED_CLASS"]
						      				+ '($| )', "g"),
						replace:	"$1$2"
					},
					'HIDE_AVATAR_REMOVE': {	//removes SELECTED_CLASS class
						match:		RegExp('(^| )' + picker.settings["HIDE_AVATAR_CLASS"]
						      				+ '($| )', "g"),
						replace:	"$1$2"
					}
				}
			};
				
			var _hoverInactive = {}; //keeps track of hover state
			
			
			//CONSTRUCTOR
			
			/**
			 * @constructor
			 * 
			 * Initialises a <code>PicturePicker</code> form control
			 */
			function PicturePicker(config) {
				
				var input = config.element;

				this.settings = _DEFAULT_SETTINGS;
				this.settings._REGEX = _getRegex(this);
				
				this.selectedImage = { id: null, element: null };
				
						//CUSTOM METHODS

						/**
						 * returns the input associated with this
						 * <code>PicturePicker</code> instance
						 * 
						 * @return	{HTMLElement}
						 */
						this.getInput = function() { return input };
				
						/**
						 * returns the container associated with
						 * this <code>PicturePicker</code> instance
						 * 
						 * @return	{HTMLElement}
						 */
						this.getContainer = function() {
							
							var container = this.getInput().parentNode.parentNode;
							this.getContainer = function() { return container };
							return container;
						};
						
						/**
						 * returns the avatar form icon associated
						 * this this <code>PicturePicker</code>
						 * instance
						 * 
						 * @return	{HTMLElement}
						 */
						this.getCurrentAvatar = function() {
							
							var avatar = utils.getElementsByClassName.call(
									this.getContainer(),
									this.settings['CURRENT_AVATAR_CLASS']);
							
							if (avatar.length > 0) avatar = avatar[0];
							else avatar = null
							
							this.getCurrentAvatar = function() {
								return avatar;
							};
							return avatar;
						};
						
						/**
						 * @void
						 * toggles the remove status of this
						 * <code>PicturePicker</code> instance
						 */
						this.toggleRemove = function() {
							
							var removed = false;
							this.toggleRemove = function() {
							
								if (!removed) {
									removed = true;
									setRemove(this);
								} else {
									removed = false;
									unsetRemove(this);
								}
							};
							this.toggleRemove();
						};
						
						/**
						 * returns an image's identifier for submission
						 * 
						 * @param	{HTMLElement}	image
						 * @return	{String}
						 */
						this.getImageId = function(image) {
							
							var returnValue = null;
							
							if (!image.imageId) {
								var regex = this.settings._REGEX['IMAGE_ID_EXTRACT'];
								returnValue = getImageId(image, regex);
								
							} else {
								returnValue = image.imageId;
							}
							
							return returnValue;
						};
						
						//END CUSTOM METHODS
						
				initRemoveLink(this);
				var images = [];
				
				utils.each(this.getInput().parentNode
						.getElementsByTagName("img"), function(){
					if (this.tagName)
						images.push(this);
				});
				
				this.images = images;
				this.processImages();			
			};
			
			
			//PROTOTYPED METHODS
			
			
			/**
			 * @void
			 * overwrites the settings for this <code>PicturePicker</code>
			 * instance
			 * 
			 * @param	{String}	name
			 * @param	{Object}	value
			 */
			PicturePicker.prototype.changeSetting = function(name, value) {
				this.settings[name] = value;
				this.settings._REGEX = _getRegex();
			};
			
			
			/**
			 * @void
			 * processes all images associated with this
			 * <code>PicturePicker</code> instance
			 * 
			 * @param	{PicturePicker}	picker
			 */			
			PicturePicker.prototype.processImages = function() {
				
				var self = this;
				utils.each(this.images, function(){
					
					var image = this;
					_hoverInactive[self.getImageId(image)] = true;
					
					utils.attachHandlers(image, {
						'mouseOver': 	function() { onImageOver(self, image) },
						'mouseOut':		function() { onImageOut(self, image) },
						'click':		function() { onImageClick(self, image) }
					});
				});
			};			
		
			
			//PRIVATE METHODS
			
			
			function getImageId(image, regex) {
				
				var val = image.className.match(regex.match);
				if (val) return val[regex.index];
				else return null;
			};
			
			function initRemoveLink(picker) {
				
				var input = utils.getElementsByClassName
						.call(picker.getContainer(),
								"removeAvatar");
				
				if (input.length > 0) {
					
					input = input[0];
					picker.getRemoveInput = function() {
						return input;
					}
				
					var link = utils.getElementsByClassName.call(
						picker.getContainer(),
						picker.settings["REMOVE_BTN_CLASS"]);
					
					if (link.length > 0) {
						link = link[0];
						utils.addEventListener.call(link, "click", function(){
							picker.toggleRemove();
						});
					}
				}
			};
			
					//EVENT LISTENERS
			
					function setRemove(picker) {
						
						picker.getRemoveInput().value = "1";
						
						var avatar = picker.getCurrentAvatar();
						avatar.className = avatar.className
								+ " " + picker.settings["HIDE_AVATAR_CLASS"];
					};
					
					function unsetRemove(picker) {
						
						picker.getRemoveInput().value = "0";
						
						var avatar = picker.getCurrentAvatar();
						var regex = picker.settings._REGEX["HIDE_AVATAR_REMOVE"];
						avatar.className = avatar.className
								.replace( regex.match, regex.replace);
					};
			
					function onImageOver(picker, image) {
						
						var imageId = picker.getImageId(image);
						if ((imageId != null)&&(_hoverInactive[imageId])) {
							
							image.className = image.className
									+ " " + picker.settings["HOVER_CLASS"];
							
							_hoverInactive[imageId] = false;
						}
					};
					
					function onImageOut(picker, image) {
						
						var imageId = picker.getImageId(image);
						_hoverInactive[imageId] = true;
						
						var regex = picker.settings._REGEX["HOVER_REMOVE"];
						image.className = image.className
								.replace( regex.match, regex.replace);
					};
					
					function onImageClick(picker, image) {
						
						var regex = picker.settings._REGEX["SELECT_REMOVE"];
						
						var imageId = picker.getImageId(image);
						var selectedImage = picker.selectedImage;
						var selectedImageElement = selectedImage.element;
						
						//check image is not already selected
						if (selectedImage.id != imageId) {
							
							//set the value of the hidden input
							picker.getInput().value = imageId;
						
							if (selectedImage.id != null) {
								
								//deselect the previously selected image
								selectedImageElement.className = selectedImageElement
										.className.replace( regex.match, regex.replace);
							}
							
							//update selectedImage
							selectedImage.element = image;
							selectedImage.id = imageId;
							
							image.className = image.className
									+ " " + picker.settings["SELECTED_CLASS"];
						}
					};
			
			//CLOSE FACTORY
			return (module.PicturePicker = function(config) {
				return new PicturePicker(config);
			})(config);
	};
	//END PICTURE PICKER FACTORY #
	
	
	
	
	//NEWSLETTER SELECT FACTORY #

	/**
	 * initialises a <code>NewsletterSelect</code> instance
	 * for a corresponding form control
	 * 
	 * @param {Object} config
	 */
	module.NewsletterSelect = function(config) {
			
			
			//PRIVATE VARS
			
			var _DEFAULT_SETTINGS = {	//default settings applied to PicturePicker instances
				'DISABLED_CLASS': "disabled", 'KEY_CHECKBOX_ID': "checkbox",
				'ENABLED_VALUE_SUFFIX': "n"
			}
			
			var _getRegex = function (nSelect){	//collection of compiled regular expressions
				
				return {
					'CHECKBOX_ID_EXTRACT': { //extracts KEY_CHECKBOX_ID value
						match: RegExp('(^|.*? )' + nSelect.settings["KEY_CHECKBOX_ID"] + '-(.*?)($| .*)'),
						index: 2
					},
					'DISABLED_REMOVE': {	//removes HOVER_CLASS class
						match:		RegExp('(^| )' + nSelect.settings["DISABLED_CLASS"] + '($| )', "g"),
						replace:	"$1$2"
					},
					'HAS_NEWSLETTER': {
						match: RegExp('(^.*)' + nSelect.settings["ENABLED_VALUE_SUFFIX"] + "$"),
						index: 1
					}
				}
			};			
			
			//CONSTRUCTOR
				
			/**
			 * @constructor
			 * 
			 * initialises a corresponding <code>NewsletterSelect</code>
			 * form control
			 */
			function NewsletterSelect(config) {

				var select = config.element;

				this.settings = _DEFAULT_SETTINGS;
				this.settings._REGEX = _getRegex(this);
				
				this.getElement = function() { return select };
				this.getCheckbox = function() { };
				this.hasNewsletter = function () { return true }
						
				this.refresh();
				
				if (this.getCheckbox() != null)
					this.attachListeners();
				else
					return null;
			};
			
			
			//PROTOTYPED METHODS

			
			/**
			 * attaches event listeners to this <code>NewsletterSelect</code>'s
			 * associated inputs
			 */
			NewsletterSelect.prototype.attachListeners = function() {
				var self = this;
				var listener = createListener(self);
				utils.attachHandlers(this.getElement(), {
					'change':	listener
				});
				listener();
			};
	
	
			/**
			 * overwrites the settings for this <code>NewsletterSelect</code>
			 * instance
			 * 
			 * @param	{String}	name
			 * @param	{Object}	value
			 */
			NewsletterSelect.prototype.changeSetting = function(name, value) {
				this.settings[name] = value;
				this.refresh();
			};
			
			
			/**
			 * causes this <code>NewsletterSelect</code> instance to
			 * update after a change to a vital property
			 */
			NewsletterSelect.prototype.refresh = function() {
				var regex = _getRegex(this);
				
				this.settings._REGEX = regex;
				var checkbox = getCheckbox(
						this.getElement(), regex['CHECKBOX_ID_EXTRACT']);
				
				if (!checkbox != null)
					this.getCheckbox = function() { return checkbox };
			};
		
			
			
			//PRIVATE METHODS
			
			function createListener(nSelect) {
				
				return function() {
					if (hasNewsletter(nSelect)) {
						
						if(!nSelect.hasNewsletter()){
							
							var checkboxParent = nSelect.getCheckbox().parentNode;
							var regex = nSelect.settings._REGEX['DISABLED_REMOVE'];
							checkboxParent.className = checkboxParent.className
									.replace( regex.match, regex.replace );
							
							nSelect.hasNewsletter = function() { return true }
						}
					} else {
						if (nSelect.hasNewsletter()) {
							
							var checkboxParent = nSelect.getCheckbox().parentNode;
							checkboxParent.className = checkboxParent.className
									+ " " + nSelect.settings['DISABLED_CLASS'];
							
							nSelect.hasNewsletter = function() { return false }
						}
					}
				};
			};
			
			function getCheckbox(select, regex) {
				
				var val = select.className.match(regex.match);
				if (val) return document.getElementById(val[regex.index]);
				else return null;
			};
			
			function hasNewsletter(nSelect) {
				var regex = nSelect.settings._REGEX['HAS_NEWSLETTER'];
				return !(!nSelect.getElement().value.match(regex.match));
			};
			

					
			
			//CLOSE FACTORY
			return (module.NewsletterSelect = function(config) {
				return new NewsletterSelect(config);
			})(config);
	};
	
	//END NEWSLETTER SELECT FACTORY #
	
	
	
	//FB CONNECT BUTTON FACTORY #

	module.FBLoginButton = function(config) {
		config.type = "login";
		return module.FBBaseButton(config);		
	};

	module.FBConnectButton = function(config) {
		config.type = "connect";
		return module.FBBaseButton(config);		
	};
	
	module.FBDisconnectButton = function(config) {
		config.type = "disconnect";
		return module.FBBaseButton(config);		
	};

	/**
	 * initialises an <code>FBConnectButton</code> instance
	 * for a corresponding form control
	 * 
	 * @param {config} config
	 */
	module.FBBaseButton = function(config) {
			
			//CONSTRUCTOR
				
			/**
			 * @constructor
			 * 
			 * initialises a corresponding <code>FBConnectButton</code>
			 * form control
			 */
			function FBBaseButton(config) {
				
				this._$userContext = config.userContext || $TORA("USER");

				var element;
				if (!(this._$type = config.type)
						||!(element = (this._$element = config.element)))
					throw "[FBBaseButton] Invalid config";
				
				this._$containers = this._$findContainers(
						(config.parentCount || 1));

				this._$initStates();				
				utils.attachHandlers(element, {
						'click': this._$getOnClick()});
			};

			//PUBLIC METHODS
			
			FBBaseButton.prototype.updateState = function(state) {

				if (state == this.CONNECTED_STATE)
					this._$updateContainerClass(this._$connectRegex);
				else if (state == this.DISCONNECTED_STATE)
					this._$updateContainerClass(this._$disconnectRegex);
							
			};

			FBBaseButton.prototype.getUserContext = function() {

				return (this._$userContext || user);
			};
			
			
			//PROTECTED METHODS

			FBBaseButton.prototype._$findContainers = function(depth) {

				if (!depth) var depth = 1;
				var curNode = this._$element,
						containers = [];

				for (var i = 0; i < depth; i++)
					containers.push((curNode = curNode.parentNode));

				return containers;
			};
			
			FBBaseButton.prototype._$initStates = function(activeClass) {

				this.CONNECTED_STATE = 1;
				this.DISCONNECTED_STATE = 2;
				this._$connectRegex = {
					match: RegExp('('+ activeClass +'|)'),
					replace: activeClass
				};
				this._$disconnectRegex = {
					match: RegExp('(^| )'+ activeClass +'( |$)', 'g'),
					replace: "$1$2"
				};
			};

			FBBaseButton.prototype._$updateContainerClass = function(config) {
				
				var containers = this._$containers,
						match = config.match,
						rpelace = config.replace;
				if (containers)
					utils.each(containers, function(){
						this.className = this.className
								.replace(match, replace);
					});
			};

			FBBaseButton.prototype._$getOnClick = function() {
				
				var self = this;				
				return function() {

					FB_REQ(self._$user, function() {
					
						switch(self._$type) {
							case "connect":
								$a_FB.connectUserWithSite(self.getUserContext(), function() {
									self.updateState(self.CONNECT_STATE);
								});
								break;
							case "disconnect":
								$a_FB.disconnectUserWithSite(self.getUserContext(), function() {
									self.updateState(self.DISCONNECT_STATE);
								});
								break;
							default:
								$a_FB.logUserIn(self.getUserContext());
						}
					});
				};
			};

			
			//CLOSE FACTORY
			return (module.FBBaseButton = function(config) {
				return new FBBaseButton(config);
			})(config);
	};
	//END FB CONNECT BUTTON FACTORY #
	
});
	





