/** 
 * @projectDescription This jQuery Plugin provides a way 
 * to fill dropdowns dynamically
 * 
 * supported browser: IE6, IE7, (FF2,) FF3, Opera, Safari3
 * requires: jQuery >= 1.2.6 (tested with jQuery 1.2.6, 1.3.0, 1.3.1, 1.3.2)
 * 	optionally requires http://lab.namics.com/frontend/tools/js/debug/jquery.log/jquery.log.js
 * 	if settings.debug is set to true -> for $.log.debug(),$.log.error(), ... 
 * 
 * version 0.6: rewrite of getHelperObject: getHelperObject not compatible with 1.3.0 -> $('string') throws an error
 * version 0.7: added feature to get and show a selected option
 * 
 * @author namics (ernscht@namics.com)
 * @version 0.7
 */


;(function($) {
	
	/**
	 * @name selectTree
	 * @description this is the plugin main part and the name of it
	 * extends default settings with input, prepares private variables and methods
	 * @param {object} options
	 * @return {$object} same jQuery collection as in input
	 * @example $('#myselect').selectTree({url: 'js/drop/drop.%key%.js',rootkey: 'first'});
	 */
	$.fn.selectTree = function(options) {
		
		// extend default settings
		var settings = $.extend({
			url: '',										// {string} url [required] with %key% variable
			rootkey:'',										// {string} rootkey [required]
			
			chooseText:'- - - - - - - - - - - - - - - -', 	// {string} chooseText option text for first option of new select element
			loading: '<span>loading...</span>',				// {string} loading Loading Element
			errorMessage: 'error.',							// {string} error message if json request generates error
			markupSeparator: '<br />',						// {string} markupSeparator Seperator between dynamic Select Elements
			optionSeparator: ':',							// {string} seperator for different values in option - all infos has to be in the value
			cache: true,									// {boolean} cache enable/disable cache
			
			container: '',									// {string} selector of container to inject the dynamics select elements (default: the parent object)
			elementPrefix: 'dyn',							// {string} prefix for name of all dynamic select elements
			dynClass: 'dynselecttree',						// {string} add this class to all generated DOM-elements
			debug: false 									// {boolean} debug mode
			
		}, options);
		

		// cache (cached json objects)
		var cache = {};
		
		
		/**
		 * @name getHelperObject()
		 * @description gets the helper object
		 * @param {String} objstring
		 * @return {$Object} $obj the helper object or nothing
		 */
		function getHelperObject(objstring) {
			
			var $obj;
			
			if (objstring) {
				// new for 1.3.0 - error if only text
				try {
					$obj = $(objstring);
				}
				// wrap in span if only text
				catch (e) {
					$obj = $('<span />').text(objstring);
				}
				
				// add dynamics class
				$obj.addClass(settings.dynClass);
			}
			
			return $obj;
		}
		
		// helper Objects
		var $markupSeparator = getHelperObject(settings.markupSeparator);
		var $loader = getHelperObject(settings.loading);
		
		
		/**
		 * @name injectNewSelect()
		 * @description main function - prepares and injects a new select element inside the $container
		 * @param {String} name url with %key% placeholder
		 * @param {String} key node requested (the value for the placeholder)
		 * @param {Object} $container dynamic selects are injected into this container
		 * @param {Object} $result form element (hidden field) to write down the choosen values
		 * @param {Array} arrSelected array of selected values (index[0] should be the value of the actual Hierarchy)
		 */
		function injectNewSelect (name, key, $container, $result, arrSelected) {
			
			var url = settings.url.replace(/%key%/, key);
			var isCachedResult = (settings.cache && cache[settings.elementPrefix + key]) ? true : false;
			var isFirst = (settings.rootkey === key) ? true : false;
			var $thisSeparator = $markupSeparator ? $markupSeparator.clone() : undefined;
			
			var hasLoader = ($loader && !isCachedResult);
			var hasSeparator = ($thisSeparator && !isFirst);
			var isSeparatorInserted = false;
			
			
			// loader
			if (hasLoader) {
				
				$loader.bind("ajaxError", function(){
					$(this).text(settings.errorMessage);
					
					if (settings.debug) {
						$.log.error('json error');
					}
				});
				
				if (hasSeparator) {
					$container.append($thisSeparator);
					isSeparatorInserted = true;
				}
				// append loader
				$container.append($loader);	
				
			}
			
			// create new select
			var $newSelect = $('<select/>').attr({
				'name': settings.elementPrefix + name + key,
				'className': settings.dynClass
			});
			
			
			
			// fills in the options and shows the new select
			var fillSelectAndShow = function(json) {
				
				if (settings.debug) {
					$.log.time('fillSelectAndShow');
				}
				var options = [];
				
				// first option choose
				if (settings.chooseText) {
					options.push('<option value="">');
					options.push(settings.chooseText);
					options.push('</option>');
				}
				
				// iterate
				/*if (settings.debug) {
					$.log.debug('*arrSelected: ['+ arrSelected.length+'] nächster: '+arrSelected[0] );
				}*/
				
				for (var i = 0; i < json.length; i++) {
					if (json[i].key!==undefined && json[i].value!==undefined) {
						//if (settings.debug) {
						//	$.log.debug( '[json result]' + json[i].value + ' : ' + json[i].hasChildren+ ' : ' + json[i].key);
						//}
						options.push('<option value="');
						options.push(json[i].value);
						options.push(settings.optionSeparator);
						options.push(json[i].key);
						options.push(settings.optionSeparator);
						options.push(json[i].hasChildren);
						options.push('"');
						
						// selected?
						if (arrSelected.length && json[i].value===arrSelected[0]) {
							options.push(' selected="selected"');
							
						}
				
						options.push('>');
						options.push(json[i].value);
						options.push('</option>');
					}
				}
				$newSelect.html(options.join(''));
				
				

				
				// remove loader
				if (hasLoader) {
					$loader.remove();
				}
				
				if (hasSeparator && !isSeparatorInserted) {
					$container.append($thisSeparator);
					isSeparatorInserted = true;
				}
				
				// append new select
				$container.append($newSelect);
				
				// trigger change callback and remove triggered value
				if (arrSelected.length) {
					$newSelect.trigger('select.triggerChangeCallback');	
					arrSelected.shift();
				} 
				
				
				
				if (settings.debug) {
					$.log.time('fillSelectAndShow');
				}
				
			};

			// select change callback
			var changeCallback = function() {
				
				var $this = $(this);
				var arrVal = $this.val().split(settings.optionSeparator);
				if (settings.debug) {
					$.log.debug('callback: ' + $this.attr('name') + ' = value: ' + $this.val());
				}
				
				// remove next selects and markup separators
				$this.nextAll('.'+settings.dynClass).remove();
				
				
				// write down values from dynamic select elements to hidden field
				var values = [];
				
				$('select.'+settings.dynClass, $container).each(function(i) {
					var splitted = this.value.split(settings.optionSeparator);
					
					//if (settings.debug) {
					//	$.log.debug('splitted (' + i + ') ' + splitted[0]);
					//}
					
					if (splitted[0]!=='') {
						if (i >= 1) {
							values.push(settings.optionSeparator);
						}
						values.push(splitted[0]);
					}
				});
				$result.val(values.join(''));
				if (settings.debug) {
					$.log.debug('value of hidden field: ' + $result.val());
				}
				
				// do new select only if the choosen node hasChildren
				if (arrVal[2]==='true') {
					injectNewSelect (name, arrVal[1], $container, $result, arrSelected);
				}
			};
			
			// json
			var getJson = function() {

				$.getJSON(url, {}, function(json){
					
					if (settings.debug) {
						$.log.debug( '[new json request]');
					}
					
					// cache the result
					if (settings.cache) {
						cache[settings.elementPrefix + key] = json;
					}
					
					// process
					fillSelectAndShow(json);
						
				});
				
			};
			
			
			
			
			// bind change events to new select element
			// special behaviour for IE and Opera browsers
			// todo: $.browser is deprecated since 1.3.0 -> try to find another support check
			if ($.browser.msie || $.browser.opera) {
				
				$newSelect
					// bind custom event: sets the metadata (actvalue)	
					.bind('select.setData', function(){
						var $this = $(this);
						$this.data('actvalue',$this.val());
					})
					// Activate changehandler
					.bind('select.triggerChangeCallback', function(){
						if (settings.debug) {
							$.log.info('callback select.triggerChangeCallback');
						}
						$(this).blur();
					})
					.bind('focus', function() {
						$(this).trigger('select.setData');
					})
					.bind('blur click', function() {
						var $this = $(this);
						if ($this.data('actvalue') === $this.val()) {
							return false;
						}
						if (settings.debug) {
							$.log.info('callback blur, click');
						}
						$this.trigger('select.setData');
						changeCallback.apply($this);
					})
					.bind('keyup', function(e) {
						var $this = $(this);
						if (e.keyCode != 13 || ($this.data('actvalue') === $this.val()) ) {
							return false;
						}
						if (settings.debug) {
							$.log.info('callback keyup');
						}
						$this.trigger('select.setData');
						changeCallback.apply($this);
					});
			}
			
			// all other browsers (Firefox, Netscape, Safari, ...)
			else {
				$newSelect
					.bind('change', changeCallback)
					// Activate changehandler
					.bind('select.triggerChangeCallback', function(){
						if (settings.debug) {
							$.log.info('callback select.triggerChangeCallback');
						}
						$(this).change();
					});
			}
			
			
			
			
			// get content
			if (isCachedResult) {
				// cached json content
				fillSelectAndShow(cache[settings.elementPrefix + key]);
			}
			else {				
				// request json content
				if (settings.debug) {
					setTimeout(function(){
						getJson();
					}, 1000);
				}
				else {
					getJson();
				}
			}
			
		}
		
		
		// keep chaining
		return this.each( function(i) {
			
			// no go - if 
			// - not a select element or 
			// - doesn't have a name attribute or
			// - settings.url or settings.rootkey are falsy
			
			if (this.nodeName.toLowerCase() !== "select" || !this.getAttribute('name') || !settings.url || !settings.rootkey) {
				return;
			}

			// cache the jQuery Object and the container to inject the select boxes
			var $self = $(this);
			var	$container = (settings.container && $(settings.container).length===1) ? $(settings.container) : $self.parent();
			var keyName = $self.attr('name');
			var arrSelected = $self.val() ? $self.val().split(settings.optionSeparator) : [];
			
			
			
			// create hidden input instead of select
			$self.remove();
			var inputvis = (settings.debug) ? 'text' : 'hidden';
			var $newInput = $('<input type="'+inputvis+'" />').attr({
				name: keyName,
				id: keyName
			}).appendTo($container);
			
			
			
			if (settings.debug) {
				$newInput.attr({
					disabled: 'disabled',
					size:'40'
				}).css({
					'background-color': 'transparent'
				}).after('<br /><br />');
				$.log.info('[plugin init] element ('+i + '): '+ $self.attr('id')+':'+$self.attr('name')+'='+keyName);
			}
			
			// create dynamic select and inject into DOM
			injectNewSelect(keyName + i, settings.rootkey, $container, $newInput, arrSelected);
		
		});
	};

})(jQuery);
