/**
 * DET JavaScript Library
 * Version: 1.0.1
 * Author: Chris Kasprowicz
 *
 * Requires: 
 * 		- jQuery > 1.2.6
 * 		- YUI Calendar 2.6.0
 *
 * History:
 *		1.0.0 - Initial Release.
 *		1.0.1 - Added DET.tale.ui.Lookup.
 *			  - Added DET.tale.ui.AjaxLookup
 *			  - Added DET.tale.ui.EmailLookup
 */

/**
 * Creates the specified namespace.
 * 
 * @param name - the namespace to create.
 * @return The created namespace object.
 */
function namespace(name)
{
	var ns = name.split(".");
	var p = window;
	for (var i = 0; i < ns.length; i++)
	{
		p = p[ns[i]] = p[ns[i]] || {};
	}
	return p;
}

namespace("DET");
namespace("DET.tale");

/**
 * Extends standard functionality of the JavaScript Array object.
 * Used to find the index of a certain value in an array.
 *
 * @param value - the value to find in the array.
 * @return The index of the object in the array or -1 if the object
 * does not exist in the array.
 */ 
Array.prototype.indexOf = function(value)
{
	var result = -1;
	for (var i = 0; i < this.length; i = i+1)
	{
		if (this[i] == value)
		{
			result = i;
			break;
		}
	}
	return result;
};

/**
 * 
 * @param obj
 * @param index
 * @return
 */
Array.prototype.insert = function(obj, index)
{
	this.splice(index, 0, obj);
};

/**
 * 
 * @param index
 * @return
 */
Array.prototype.remove = function(index)
{
	this.splice(index, 1);
};


/**
 * Extends the standard functionality of the JavaScript String object.
 * Used to find whether a string starts with a certain prefix.
 *
 * @param str - the prefix to check against the string.
 * @return true if the string starts with the prefix, false otherwise.
 */
String.prototype.startsWith = function(str)
{
	return this.substring(0, str.length) == str;
};

/**
 * 
 * @param str
 * @return
 */
String.prototype.startsWithIgnoreCase = function(str)
{
	return this.toLowerCase().startsWith(str.toLowerCase());
};


/**
 * DET Utils Package.
 */
namespace("DET.tale.util");

/**
 * 
 */
DET.tale.util.BrowserDetails = function()
{      
	var self = this,
			userAgent = navigator.userAgent;
	
	/**
	 * Retrieves the Browser name from the userAgent.
	 */
	this.getBrowser = function(str)
	{
		var _b = [];
		_b[0] = "Chrome";
		_b[1] = "Safari";
		_b[2] = "Opera";
		_b[3] = "Firefox";
		_b[4] = "Internet Explorer";
		
		var result = "Unknown";
		for (var i = 0; i < _b.length; i++)
		{
			if (str.indexOf(_b[i]) != -1)
			{
				result = _b[i];
				break;
			}
		}
		return result;
	};
	
	/**
	 * Retrieves the Browser Version from the userAgent.
	 */
	this.getBrowserVersion = function(str, browser)
	{   
		var result = "Unknown";
		var substr;
		var b = (browser) ? browser : this.getBrowser(str);
		if (b == "Chrome")
		{
			substr = str.substring(str.indexOf("Chrome"));
			result = substr.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
			substr.indexOf(" ") : substr.length));
		}
		else if (b == "Safari")
		{
			substr = str.substring(str.indexOf("Version"));
			result = substr.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
					substr.indexOf(" ") : substr.length));
		}
		else if (b == "Opera")
		{
		}
		else if (b == "Firefox")
		{
			substr = str.substring(str.indexOf("Firefox"));
			result = str.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
					substr.indexOf(" ") : substr.length));
		}
		return result;
	};
	
	/**
	 * Retrieve the OS from the userAgent.
	 */
	this.getOS = function(str)
	{
		var _os = [];
		_os["Windows NT 6.1"] = "Windows 7";
		_os["Windows NT 6.0"] = "Windows Vista";
		_os["Windows NT 5.1"] = "Windows XP";
		_os["Windows NT 5.0"] = "Windows 2000";
		// TODO Add others here.
		var _str = $.trim(str);
		
		var result = (_os[_str] != "undefined") ? _os[_str] : "Unknown";
		return result;
	};
			
	/**
	 * Retrieve the security level from the userAgent.
	 */
	this.getSecurity = function(str)
	{
		var _s = [];
		_s["N"] = "No Security";
		_s["U"] = "Strong Security";
		_s["I"] = "Weak Security"; 
		var _str = $.trim(str);
		var result = (_s[_str] != "undefined") ? _s[_str] : "Unknown";
		return result;
	};

	if (userAgent.indexOf("Opera") != -1) 
	{
		this.browser = "Opera";
		this.browserVersion = userAgent.substring(userAgent.indexOf("/") + 1, userAgent.indexOf(" "));
		var lb = userAgent.indexOf(" (");
		var rb = userAgent.indexOf(") ");
		var os = jQuery.trim(userAgent.substring(lb + 2, rb));
		var osT = os.split("; ");
		
		this.platform = "Unknown";
		this.operatingSystem = $.trim(this.getOS(osT[0]));
		this.security = this.getSecurity(osT[1]);
		this.language = osT[2];
		 
		var suffix = userAgent.substring(rb + 2);
		this.engine = suffix.substring(0, suffix.indexOf("/"));
		this.engineVersion = suffix.substring(suffix.indexOf("/") + 1);
	} 
	else if (userAgent.indexOf("MSIE") != -1) 
	{
		this.browser = "Internet Explorer";
		var lb = userAgent.indexOf(" (");
		var rb = userAgent.indexOf(")");
		var os = jQuery.trim(userAgent.substring(lb + 2, rb));
		var osT = os.split("; ");
		
		this.browserVersion = osT[1].replace("MSIE ", "");
		this.operatingSystem = $.trim(this.getOS(osT[2]));
		
		if (osT.length > 5)
		{
			var eng = osT[5].split("/");
			this.engine = eng[0];
			this.engineVersion = eng[1];
		}
	} 
	else 
	{
		if (!userAgent)
		{
			userAgent = navigator.userAgent;
		}
		
		var lb = userAgent.indexOf(" (");
		var rb = userAgent.indexOf(") ");
		var os = $.trim(userAgent.substring(lb + 2, rb));
		var osT = os.split("; ");
		
		this.platform = osT[0];
		this.security = this.getSecurity(osT[1]);
		this.operatingSystem = $.trim(this.getOS(osT[2]));
		this.language = osT[3]; // TODO Add interpretation
		
		var suffix = userAgent.substring(rb + 2);
		var index = suffix.indexOf(" ");
		var eng = suffix.substring(0, index).split("/");
		var bro = suffix.substring(index + 1);
		
		this.engine = eng[0];
		this.engineVersion = eng[1];
		
		this.browser = this.getBrowser(bro);
		this.browserVersion = this.getBrowserVersion(bro, this.browser);
	}
};

/**
 * 
 */
DET.tale.util.Dimension = DET.tale.util.Size = function(x, y)
{
	this.x = x;
	this.y = y;
	this.width = x;
	this.height = y;
};

/**
 * Map object allows storing a value against a key.
 */
DET.tale.util.Map = function()
{
  this.keys = new Array();
  this.values = new Array();
};

DET.tale.util.Map.prototype.containsKey = function(key)
{
  return this.keys.indexOf(key) != -1;
};

DET.tale.util.Map.prototype.get = function(key)
{
  return this.values[this.keys.indexOf(key)];
};

DET.tale.util.Map.prototype.indexOf = function(key)
{
  return this.keys.indexOf(key);
};

DET.tale.util.Map.prototype.put = function(key, value)
{
  this.keys.push(key);
  this.values.push(value);
};

DET.tale.util.Map.prototype.remove = function(key)
{
  var index = this.indexOf(key);
  var result = this.values[index];
  this.keys.splice(index, 1);
  this.values.splice(index, 1);
  return result;
};

DET.tale.util.Map.prototype.size = function()
{
  return this.keys.length;
};


/**
 * Creates a new YUI calendar to represent a text input field.
 * 
 * The following files will need to be included for the calendar to function correctly:
 *  
 * &lt;link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.6.0/build/calendar/assets/skins/sam/calendar.css"/&gt; 
 * &lt;script type="text/javascript" src="http://yui.yahooapis.com/2.6.0/build/yahoo-dom-event/yahoo-dom-event.js"&gt;&lt;/script&gt; 
 * &lt;script type="text/javascript" src="http://yui.yahooapis.com/2.6.0/build/calendar/calendar-min.js"&gt;&lt;/script&gt; 
 * 
 * @param inputId the id of the input field that the calendar will input values into.
 * @param divId the id of the div that the calendar will be displayed in. This will
 * allow the calendar to be shown/hidden by showing/hiding the div.
 * @param imgId the id of the calendar img. The calendar div will be toggled between
 * shown/hidden each time the calendar image is clicked.
 * @return void
 */
DET.tale.util.createCalendar = function(inputId, divId, imgId)
{
  var d = DET.tale.util.parseDate(jQuery("#" + inputId).val());

  var calendar = new YAHOO.widget.Calendar("calendar", divId, 
		    { selected: DET.tale.util.formatDate(d, "mm/dd/yyyy") });
  calendar.selectEvent.subscribe(
		    function(type, args, obj)
		    {
		      var newDate = this.toDate(args[0][0]);
		      jQuery("#" + inputId).val(newDate.getDate() + "/" + 
		          (newDate.getMonth() + 1) + "/" + newDate.getFullYear());
		      jQuery("#" + divId).hide();
		    },
		    calendar, true);
		calendar.render();
		// TODO: Add a parameter to allow this to be specified.
		//jQuery("#" + inputId).attr("readonly", "readonly");
  jQuery("#" + imgId).bind("click", function(e) { jQuery("#" + divId).toggle(); } )
      .css("cursor", "pointer");
};

/**
 * Formats a Date object into a String based on a format string.
 * 	- dd represents a 2-digit date
 * 	- mm represents a 2-digit month
 * 	- yyyy represents a 4-digit year
 * TODO: This may need to work for other formats.
 * 
 * @param d the input Date object.
 * @param fmt the date format to be used.
 * @return the formatted String.
 */
DET.tale.util.formatDate = function(d, fmt)
{
  var result = fmt;
  var d1 = new String(d.getDate());
  var d2 = (d1.length < 2) ? ("0" + d1) : d1;
  var m1 = new String(d.getMonth() + 1);
  var m2 = (m1.length < 2) ? ("0" + m1) : m1;
  
  result = result.replace("dd", d2);
  result = result.replace("d", d1);
  result = result.replace("mm", m2);
  result = result.replace("m", m1);
  result = result.replace("yyyy", d.getFullYear());
  result = result.replace("yy", d.getYear());
  return result;
};

/**
 * Obtains the correct bottom position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute bottom position of the element in pixels.
 */
DET.tale.util.getAbsoluteBottom = function(jQ)
{
	var result = DET.tale.util.getAbsoluteTop(jQ) + jQ[0].offsetHeight;
	return result;
};

/**
 * Obtains the correct left position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute left position of the element in pixels.
 */
DET.tale.util.getAbsoluteLeft = function(jQ)
{
	var result = jQ.offset().left;
	var parents = jQ.parent();
	for (var i = 0; i < parents.length; i++)
	{
		var parent = parents.get(i);
		try
		{
			if (parent.css("position") == "relative" || 
					parent.css("position") == "absolute")
			{
				result += parent.offset().left;
			}
		}
		catch (e)
		{
			break;
		}
	}
	return result;
};

/**
 * Obtains the correct right position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute right position of the element in pixels.
 */
DET.tale.util.getAbsoluteRight = function(jQ)
{
	var result = DET.tale.util.getAbsoluteLeft(jQ) + jQ[0].offsetWidth;
	return result;
};

/**
 * Obtains the correct top position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute top position of the element in pixels.
 */
DET.tale.util.getAbsoluteTop = function(jQ)
{
	var result = jQ.offset().top;
	var parents = jQ.parent();
	for (var i = 0; i < parents.length; i++)
	{
		var parent = parents.get(i);
		try
		{
			if (parent.css("position") == "relative" || 
					parent.css("position") == "absolute")
			{
				result += parent.offset().top;
			}
		}
		catch (e)
		{
			break;
		}
	}
	return result;
};

/**
 * Returns the window size (not document size).
 * @return DET.tale.util.Size object.
 */
DET.tale.util.getWindowSize = function()
{
	var result, w, h;
	if (window.innerWidth && window.innerHeight)
	{
		w = window.innerWidth;
		h = window.innerHeight;
	}
	else if (document.documentElement)
	{
		w = document.documentElement.offsetWidth;
		h = document.documentElement.offsetHeight;
	}
	result = new DET.tale.util.Size(w, h);
	return result;
};

/**
 * Parses a String into a Date object.
 * This currently assumes a dd/mm/yyyy date format.
 * 
 * TODO: Add an input format string
 * 
 * @param str the String to be parsed.
 * @return the Date object.
 */
DET.tale.util.parseDate = function(str)
{
  var result = new Date();
  result.setDate(parseInt(str.substr(0, 2)));
  result.setMonth(parseInt(str.substr(3,2)) - 1);
  result.setFullYear(parseInt(str.substr(6,4)));
  return result;
};

/**
 * Specifies the option in the select element (identified by id) as selected. This method can be
 * used to apply multiple selections to a select element.
 *  
 * @param id - the ID of the select element.
 * @param value - the value of the option to be selected.
 */
DET.tale.util.selectOption = function(id, value)
{
	var options = jQuery("#" + id).attr("options");
	for (var i = 0; i < options.length; i++)
	{
		if (options[i].value == value)
		{
			options[i].selected = true;
		}
	}
};

/**
 * UI Package
 */
namespace("DET.tale.ui");

/**
 * Creates a new div element with the class 'popup' and adds it to the dom. If the option url and
 * postdata parameters are included, the div is populated from the url using jQuery's load function.
 */
DET.tale.ui.createPopup = function(id, url, postdata)
{
	var $result = jQuery(document.createElement("div"));
	$result.attr("id", id);
	$result.addClass("popup");
	jQuery("body").append($result);
	if (url != null)
	{
		$result.load(url, postdata);
	}
	return $result;
};

/**
 * 
 * @param id
 */
DET.tale.ui.Lookup = function(id)
{		
	var self = this;
	
	self.id = id;
	self.options = [];
	self.cache = [];
	self.displayed = [];
	
	self.oldDisplayed = []; // Testing
	
	self.curText = "";
	self.selIndex = -1;
	self.visible = false;
	
	self.$element = $("#" + id);
	self.$lookup = $(document.createElement("div"));
	self.$lookup.css("position", "absolute");
	self.$lookup.css("zIndex", "99999");
	self.$lookup.addClass("lookup");
	self.$lookup.hide();
	$("body").append(self.$lookup);
	
	/**
	 * Add an option to the list of availables. 
	 * This will be used to retrieve a list of displayable options.
	 * Each option must be unique (any duplicates are ignored).
	 */
	self.addOption = function(str)
	{
		if (self.options.indexOf(str) == -1)
		{
			self.options.push(str);
		}
	};
	
	/**
	 * Adds an option to the cache.
	 * Each option must be unique (any duplicates will be ignored).
	 */
	self.addToCache = function(str)
	{
		if (self.cache.indexOf(str) == -1)
		{
			self.cache.push(str);
		}
	};

	/**
	 * Adds an option to the list of displayed options.
	 * Each option must be unique (any duplicates will be ignored).
	 */
	self.addToDisplayed = function(str)
	{
		if (self.displayed.indexOf(str) == -1)
		{
			self.displayed.push(str);
		}
	};
	
	/**
	 * Creates the option div to display in the lookup.
	 */
	self.createOptions = function()
	{
		// Remove dead options
		for (var i = self.oldDisplayed.length - 1; i >= 0; i--)
		{
			if (self.displayed.indexOf(self.oldDisplayed[i]) == -1)
			{
				$(self.$lookup.find("div")[i]).remove();
				self.oldDisplayed.remove(i);
			}
		}
	
		// Add new options
		for (var i = 0; i < self.displayed.length; i++)
		{
			if (self.oldDisplayed.indexOf(self.displayed[i]) == -1)
			{
				var $div = $(document.createElement("div"));
				$div.html(self.displayed[i]);
				$div.addClass((i % 2 == 0) ? "a" : "b");
				$div.addClass("lookup_option_ll");
				$div.bind("mouseover", function () { self.highlightOption($(this)); });
				$div.bind("mouseout", function () { self.lowlightOption($(this)); });
				$div.bind("click", function(e) {
					e.preventDefault();
					self.selectOption($(this)); 
				});
				
				if (i != 0)
				{
					$(self.$lookup.find("div")[i - 1]).after($div);
				}
				else
				{
					self.$lookup.prepend($div);
				}
				self.oldDisplayed.insert(self.displayed[i], i);
			}
			else
			{
				var $div = $(self.$lookup.find("div")[i]);
				$div.removeClass("a");
				$div.removeClass("b");
				$div.addClass((i % 2 == 0) ? "a" : "b");
			}
		}
		$(document).bind("click", function() { self.hide(); });
	};
	
	self.doTab = function()
	{
	};
	
	/**
	 * Hide the lookup.
	 */
	self.hide = function()
	{
		if (self.visible)
		{	
			if (self.selIndex != -1)
			{
				self.lowlightOption($(self.$lookup.find("div")[self.selIndex]));
			}
			self.$lookup.fadeOut();
			self.visible = false;
		}
		
		self.selIndex = -1;
	};
	
	/**
	 * Displays the option in a highlighted state.
	 */
	self.highlightOption = function($option)
	{
		$option.removeClass("lookup_option_ll");
		$option.addClass("lookup_option_hl");
	};
	
	/**
	 * Displays the option in a non-highlighted state.
	 */
	self.lowlightOption = function($option)
	{
		$option.removeClass("lookup_option_hl");
		$option.addClass("lookup_option_ll");
	};
	
	/**
	 * Retrieve a list of displayable options from those available.
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		
		// Retrieve data.
		for (var i = 0; i < self.options.length; i++)
		{
			if (new String(self.options[i]).startsWithIgnoreCase(self.$element.val()))
			{
				self.cache.push(self.options[i]);
				self.displayed.push(self.options[i]);
			}
		}

		// Create options.
		self.createOptions();
	};
	
	/**
	 * Select the option and populate the field with that value.
	 */
	self.selectOption = function($option)
	{
		var str = $option.html();
		self.selIndex = self.displayed.indexOf(str);
		self.$element.val(str);
		self.hide();
	};
	
	/**
	 * Displays the lookup.
	 */
	self.show = function()
	{
		self.selIndex = -1;
		
		if (!self.visible)
		{
			// Reposition Lookup
			var x = self.$element.offset().left, 
					y = self.$element.offset().top + self.$element.outerHeight() + 2;
			self.$lookup.css("width", self.$element.width() + 2)
					.css("left", x).css("top", y).fadeIn();
			self.visible = true;
		}
	};
	
	/**
	 * Key up event for all non-escape characters.
	 */
	self.doKeyUp = function()
	{
		var str = $.trim(new String(self.$element.val()));
						
		if (str != "" && str.length > 1)
		{							
			self.populateOptions();
			
			// Display the popup.
			if (self.displayed.length > 0)
			{
				self.show();
			}
			else
			{
				self.hide();
			}
		}
		else
		{
			self.hide();
		}
		
		self.curText = str;
	};
	
	/**
	 * Handle the keydown event. This is used to capture all the special characters
	 * before the response is sent.
	 */
	self.keyDown = function(event)
	{
		var key = event.keyCode;
		var curIndex = (self.selIndex != -1) ? self.selIndex : 0;
		
		if (key == 9 || key == 13)
		{
			if (self.displayed.length != 0)
			{
				if (key == 9 && self.selIndex == -1)
				{
					self.hide();
				}
				else
				{
					self.selectOption(jQuery(self.$lookup.find("div")[curIndex]));
				}
			}
		}
		else if (key == 27)
		{
			self.hide();
		}
		else if (key == 38 && curIndex > 0)
		{				
			self.lowlightOption(jQuery(self.$lookup.find("div")[curIndex]));
			self.highlightOption(jQuery(self.$lookup.find("div")[--curIndex]));
			self.selIndex = curIndex;
		}
		else if (key == 40 && self.selIndex < (self.displayed.length - 1))
		{					
			if (self.selIndex != -1)
			{
				self.lowlightOption(jQuery(self.$lookup.find("div")[self.selIndex]));
			}
			self.highlightOption(jQuery(self.$lookup.find("div")[++self.selIndex]));
		}
	};
	
	/**
	 * Control the hiding/displaying of the lookup on key up.
	 */
	self.keyUp = function(event)
	{				
		var key = event.keyCode;
	
		if (key != 9 && key != 13 && key != 27 && key != 38 && key != 40)
		{
			self.doKeyUp();
		}
	};
	
	// Bind events
	self.$element.bind("keydown", self.keyDown);
	self.$element.bind("keyup", self.keyUp);
	
	return self;
};

/**
 * Ajax Lookup Class
 */
DET.tale.ui.AjaxLookup = function(id, url)
{
	var self = new DET.tale.ui.Lookup(id);
	
	self.url = url;
	
	/**
	 * Retrieve a list of displayable options from those available.
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		
		// Retrieve Data					
		var data = { searchText: self.$element.val() };
		$.getJSON(self.url, data,
				function(data)
				{						
					for (var i = 0; i < data.length; i++)
					{
						//self.cache.push(data[i].value);
						self.addToCache(data[i].value);
						//self.displayed.push(data[i].value);
						self.addToDisplayed(data[i].value);
					}
					
					// Create options.
					self.createOptions(self.displayed[i]);
					
					// Force a display call.
					
					self.show();
				});
	};

	/**
	 * Overriden to allow displaying/hiding to be handled by asynchronous callback.
	 * @override
	 */
	self.doKeyUp = function()
	{				
		var str = new String(self.$element.val());
					
		if (jQuery.trim(str) != "")
		{
			self.populateOptions();
		}
		else
		{
			self.hide();
		}
		
		self.curText = str;
	};
	return self;
};

/**
 * EmailLookup Class
 */
DET.tale.ui.EmailLookup = function(id)
{
	var self = new DET.tale.ui.Lookup(id);
	
	/**
	 * Retrieve a list of displayable options from those available.
	 * @override
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		
		// Retrieve data.
		for (var i = 0; i < self.options.length; i++)
		{
			if (new String(self.options[i]).startsWithIgnoreCase(self.getCurrentEmail()))
			{
				self.cache.push(self.options[i]);
				self.displayed.push(self.options[i]);
			}
		}

		// Create options.
		self.createOptions();
	};
	
	/**
	 * Select the option and populate the field with that value.
	 * @override
	 */
	self.selectOption = function($option)
	{
		var str = $option.attr("innerHTML");
		//self.selIndex = self.displayed.indexOf(str);
		self.setCurrentEmail(str);	
		self.hide();
	};
	
	/**
	 * Retrieves the current email (based on the cursor position).
	 */
	self.getCurrentEmail = function()
	{
		var value = self.$element.val();
		var caret = DET.tale.form.doGetCaretPosition(self.$element[0]);
		var start = value.substring(0, caret).lastIndexOf(";") + 1;
		var end = value.indexOf(";", caret);
		end = (end != -1) ? end : value.length;
		return jQuery.trim(value.substring(start, end));
	};
	
	/**
	 * Sets the current email (based on the cursor position).
	 */
	self.setCurrentEmail = function(email)
	{
		var value = self.$element.val();
		var caret = DET.tale.form.doGetCaretPosition(self.$element[0]);
		var start = value.substring(0, caret).lastIndexOf(";") + 1;
		var end = value.indexOf(";", caret);
		end = (end != -1) ? end : value.length;
		self.$element.val(value.substring(0, start) + email + "; " + 
				value.substring(end, value.length));
	};
	
	return self;
};

/**
 * Uses elements of both the AjaxLookup and EmailLookups
 */
DET.tale.ui.TagLookup = function(id, url)
{
	var self = new DET.tale.ui.AjaxLookup(id, url);
	
	self.caret = 0;
	
	/**
	 * Retrieve a list of displayable options from those available.
	 * @override
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed = [];
		
		// Retrieve Data					
		$.getJSON(self.url, { searchText: self.getCurrentTag() }, function(data) {
			for (var i = 0; i < data.length; i++)
			{
				self.addToCache(data[i].value);
				self.addToDisplayed(data[i].value);
			}
			
			// Create options.
			self.createOptions();
			
			// Force a display call.
			if (self.displayed.length > 0)
			{
				self.show();
			}
			else
			{
				self.hide();
			}
		});
	};
	
	/**
	 * Select the option and populate the field with that value.
	 * @override
	 */
	self.selectOption = function($option)
	{
		var str = $option.html();
		self.setCurrentTag(str);	
		self.hide();
	};
	
	/**
	 *
	 */
	self.getCurrentTag = function()
	{
		var value = self.$element.val(),
				//caret = DET.tale.form.doGetCaretPosition(self.$element[0]),
				start = value.substring(0, self.caret).lastIndexOf(" ") + 1,
				end = value.indexOf(" ", self.caret);
		end = (end == -1) ? value.length : end;		
		return $.trim(value.substring(start, end));
	};
	
	/**
	 *
	 */
	self.setCurrentTag = function(tag)
	{	
		var value = self.$element.val(),
				//caret = DET.tale.form.doGetCaretPosition(self.$element[0]),
				start = value.substring(0, self.caret).lastIndexOf(" ") + 1,
				end = value.indexOf(" ", self.caret);
		end = (end != -1) ? end : value.length;
		self.$element.val(value.substring(0, start) + tag + " " + 
				value.substring(end, value.length));
	};
	
	/**
	 * Overriden to allow displaying/hiding to be handled by asynchronous callback.
	 * @override
	 */
	self.doKeyUp = function()
	{
		self.caret = DET.tale.form.doGetCaretPosition(self.$element[0]);		
		var str = self.getCurrentTag();
					
		if ($.trim(str) != "")
		{
			self.populateOptions();
		}
		else
		{
			self.hide();
		}
	};
	
	return self;
};

/**
 * Form Package.
 */
namespace("DET.tale.form");
 
DET.tale.form.Option = function(text, value)
{
	var self = this;
	self.value = value;
	self.text = text;
};

/**
 * Limits a textarea to a specified maxlength. 
 * 
 * This function should be specified in the 'onkeypress' event.
 */
DET.tale.form.checkMaxLength = function(textarea, maxlength)
{
	return (textarea.value.length <= maxlength);
};

/**
 * Disables the default behaviour of submitting a form when the enter key is 
 * pressed in a text input.
 */
DET.tale.form.disableSubmitOnEnter = function()
{
	$(document).bind("keypress", DET.tale.form.doDisableSubmitOnEnterKeyPress);
};

/**
 * The keypress event for disableSubmitOnEnter.
 */
DET.tale.form.doDisableSubmitOnEnterKeyPress = function(evt)
{
	var result = true;
	var e = (evt) ? evt : ((event) ? event : null);
	var node = (e.target) ? e.target : ((e.srcElement) ? e.srcElement : null);
	if ((e.keyCode == 13) && (node.type == "text"))  
	{
		result = false;
	}
	return result; 
};

/**
 * Get the caret position within an input control.
 */
DET.tale.form.doGetCaretPosition = function(ctrl) 
{
	var result = 0;
	// IE Support
	if (document.selection) 
	{
		ctrl.focus();
		var sel = document.selection.createRange();
		var selLength = sel.text.length;
		sel.moveStart('character', -ctrl.value.length);
		result = sel.text.length - selLength;
	}
	// Firefox support
	else if (ctrl.selectionStart || ctrl.selectionStart == '0')
	{
		result = ctrl.selectionStart;
	}
	return result;
};


/**
 * Validation Package.
 */
namespace("DET.tale.validation");

/**
 * Validates an email String against a regular expression.
 * 
 * @param email The email String to be validated.
 * @return true if the String contains a valid email address, false otherwise.
 */
DET.tale.validation.validateEmail = function(email)
{
	var emailRegEx = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	var result = email.match(emailRegEx);
	return result;
};

/**
 * Validates a String of emails separated by the ';' character.
 */
DET.tale.validation.validateEmails = function(emails)
{
	//var emailsRegEx = /^(([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+;)+$/;
	var emailsRegEx = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*([,;]\s*\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*)*$/
	var result = emails.match(emailsRegEx);
	return result;
};

/**
 * Validates a String to be not empty.
 * 
 * @param str The String to validate.
 * @return true if the String is not empty, false otherwise.
 */
DET.tale.validation.validateNotEmpty = function(str)
{
	var result = $.trim(str).length != 0;
	return result;
};
