/**
 *	@author Andrew Dodson
 *	@since 25th may 2007
 */

jQuery.fn.isObject = function ( s ){
	return ( s !== null && typeof( s ) === "object" ? true:false );
};

jQuery.fn.isEmpty = function(s){
	var x;
	if(this.isObject(s)){
		for(x in s){if(x!=='prototype'){return false;}}
		return true;
	}
	return ( s === null || s === undefined || s === 0 || s === '' || s === false ? true:false );
};


/**
 * Get information about the selected text.
 * @param the scope/window object
 & @return selected element
 */
jQuery.fn.selectedText = function(win){
	var obj = null,
		text = null,
		sel = null;

	win = win || window;

	// Get parent element to determine the formatting applied to the selected text
	if(win.getSelection){
		obj	= win.getSelection().anchorNode;
		text= win.getSelection().toString();
		sel	= win.getSelection();

		console.log(sel);

		// Mozilla seems to be selecting the wrong Node, the one that comes before the selected node.
		// I'm not sure if there's a configuration to solve this,
		if(!sel.isCollapsed&&$.browser.mozilla){
			// If we've selected an element, (note: only works on Anchors, only checked bold and spans)
			// we can use the anchorOffset to find the childNode that has been selected
			if(sel.focusNode.nodeName !== '#text'){
				// Is selection spanning more than one node, then select the parent
				if((sel.focusOffset - sel.anchorOffset)>1){
					console.log("Selected spanning more than one",obj = sel.anchorNode);
				}else if ( sel.anchorNode.childNodes[sel.anchorOffset].nodeName !== '#text' ){
					console.log("Selected non-text",obj = sel.anchorNode.childNodes[sel.anchorOffset]);
				}else{
					console.log("Selected whole element",obj = sel.anchorNode);
				}
			}
			// if we have selected text which does not touch the boundaries of an element
			// the anchorNode and the anchorFocus will be identical
			else if( sel.anchorNode.data === sel.focusNode.data ){
				console.log("Selected non bounding text",obj = sel.anchorNode.parentNode);
			}
			// This is the first element, the element defined by anchorNode is non-text.
			// Therefore it is the anchorNode that we want
			else if( sel.anchorOffset === 0 && !sel.anchorNode.data ){
				console.log("Selected whole element at start of paragraph (whereby selected element has not text e.g. &lt;script&gt;",obj = sel.anchorNode);
			}
			// If the element is the first child of another (no text appears before it)
			else if( ( typeof sel.anchorNode.data !== 'undefined' ) && ( sel.anchorOffset === 0 ) && ( sel.anchorOffset < sel.anchorNode.data.length ) ){
				console.log("Selected whole element at start of paragraph",obj = sel.anchorNode.parentNode);
			}
			// If we select text preceeding an element. Then the focusNode becomes that element
			// The difference between selecting the preceeding word is that the anchorOffset is less that the anchorNode.length
			// Thus
			else if( typeof sel.anchorNode.data !== 'undefined' && sel.anchorOffset < sel.anchorNode.data.length ){
				console.log("Selected preceeding element text",obj = sel.anchorNode.parentNode);
			}
			// Selected text which fills an element, i.e. ,.. <b>some text</b> ...
			// The focusNode becomes the suceeding node
			// The previous element length and the anchorOffset will be identical
			// And the focus Offset is greater than zero
			// So basically we are at the end of the preceeding element and have selected 0 of the current.
			else if( typeof sel.anchorNode.data !== 'undefined' && sel.anchorOffset === sel.anchorNode.data.length && sel.focusOffset === 0 ){
				console.log("Selected whole element text", obj = (sel.anchorNode.nextSibling || sel.focusNode.previousSibling));
			}
			// if the suceeding text, i.e. it bounds an element on the left
			// the anchorNode will be the preceeding element
			// the focusNode will belong to the selected text
			else if( sel.focusOffset > 0 ){
				console.log("Selected suceeding element text", obj = sel.focusNode.parentNode);
			}
		}
		else if(sel.isCollapsed){
			obj = obj.parentNode;
		}
		
	}
	else if(win.document.selection){
		sel = win.document.selection.createRange();
		obj = sel;

		if(sel.parentElement){
			obj = sel.parentElement();
		}else {
			obj = sel.item(0);
		}
		text = sel.text || sel;
	
		if(text.toString){
			text = text.toString();
		}
	}
	else {
		throw 'Error';
	}
	// webkit
	if(obj.nodeName==='#text'){
		obj = obj.parentNode;
	}

	// if the selected object has no tagName then return false.
	if(typeof obj.tagName === 'undefined'){
		return false;
	}
	return {'obj':obj,'text':text};
};

/**
 * ClearForm Control
 */
jQuery.fn.clearForm = function(){
	// TODO for all the table.multiple children, remove all but one element
	
	// Loop through all the elements and clear values
	this.find(":input, iframe").each(function(){
		var type = this.type, tag = this.tagName.toLowerCase();
		if(tag == 'form'){
			$(this).clearForm();
		}
		else if(tag == 'iframe' ){
			$( 'body[contenteditable]', this.contentWindow.document || this.contentDocument )[0].innerHTML= '';
		}
		else if(type == 'text' || type == 'password' || tag == 'textarea'){
			this.value = '';
		}
		else if(type == 'checkbox' || type == 'radio'){
			this.checked = false;
		}
		else if(tag == 'select'){
			this.selectedIndex = -1;
		}
	});
	return this;
};


/**
 * Create EDITOR on the currently selected TEXTAREA
 */
jQuery.fn.editor = function(){
	/**
	 * Action a popup modal box
	 */
	var win;
	function upload(callback){
		if(win&&!win.closed){
			console.log("Focus:"+win.closed);
			win.focus();
			return;
		}
		var h = 320;
		var w = 350;
		var left = (screen.width/2)-(w/2);
		var top = (screen.height/2)-(h/2);
		win = window.open(KSCRIPT+'uploadpopup.htm','files','status=no,toolbar=no,locationbar=no,location=no,menubar=no,titlebar=no,resizable=1,width='+w+',height='+h+',top='+top+',left='+left);
		win.focus();
		return false;
	}

	/**
	 * Add Tools
	 */
	var toolbox = [
		' ',
		{cmd:'bold', desc:'Make the Selected text bold', label:'<b>B</b>'},
		{cmd:'italic', desc:'Make the Selected text Italic', label:'<i>I</i>'},
		{cmd:'underline', desc:'Underline the selected text', label:'<u>U</u>'},
		{cmd:'strikethrough',desc:'Strike through the selected text', label:'<strike>S</strike>'},
		' ',
		{cmd:'createlink', desc:'Create Link', label:'<u>URL</u>',prompt:'URL of a Link?', promptValue:'http://'},
//		{cmd:'insertimage', desc:'Insert image', label:'<u>IMG</u>',prompt:'URL of an image?'}, // &#9660;
		{cmd:'insertimage', desc:'Insert file or image', label:'<u>File</u>',callback:upload}, // &#9660;
		' ',
		{cmd:'insertorderedlist', desc:'Insert Numbered list', label:'<b>1</b> List'},
		{cmd:'insertunorderedlist', desc:'Insert Bullet list', label:'&#9679; List'},		
		' ',
		{cmd:'justifyleft', desc:'Align Left', label:'<b>|</b>&#9668;'},
		{cmd:'outdent', desc:'Outdent', label:'&#9668;'},
		{cmd:'justifycenter', desc:'Align center', label:'&#9679;'},
		{cmd:'indent', desc:'Indent', label:'&#9658;'},
		{cmd:'justifyright', desc:'Align right', label:'&#9658;<b>|</b>'},
		{cmd:'justifyfull', desc:'Jusitfy', label:'&#9776;'},//8801
		' ',
		{label:' Format', cmd:'formatblock', desc:'Format selected text', cmdvalue:[
			{value:'p', desc:'Normal', label:'Normal'},
			{value:'h1', desc:'Heading 1', label:'<h1>Heading 1</h1>'},
			{value:'h2', desc:'Heading 2', label:'<h2>Heading 2</h2>'},
			{value:'h3', desc:'Heading 3', label:'<h3>Heading 3</h3>'},
			{value:'h4', desc:'Heading 4', label:'<h4>Heading 4</h4>'},
			{value:'pre', desc:'Formatted', label:'<pre>Formatted</pre>'},
			{value:'address', desc:'Address', label:'<address>Address</address>'},
			{value:'blockquote', desc:'Blockquote', label:'<blockquote>Blockquote</blockquote>'},
			{value:'div', desc:'Normal Div', label:'Normal (Div)'}
//			{value:'acronym', desc:'Acronym', cmd:'inserthtml', prompt:'What is the ', label:'<acronym>Acronym</acronym>'}
		]},
		' ', 
		{cmd:'inserthtml', desc:'Insert Special Character or Symbol', label:' Symbol', cmdvalue : [
			// CURRENCY
			'$','&pound;','&euro;','&yen;',
			// COPY
			'&copy;', '&trade;', '&reg;', 
			// LATIN LOWERCASE
			'&aacute;', '&acirc;', '&aelig;', '&agrave;','&aring;', '&atilde;', '&auml;', 
			'&eacute;', '&ecirc;', '&egrave;', '&eth;', '&euml;', 
			'&iacute;', '&icirc;', '&iexcl;', '&igrave;', '&iuml;', 
			'&ntilde;', 
			'&oacute;', '&ocirc;', '&oelig;', '&ograve;', '&otilde;', '&ouml;', 
			'&szlig;',
			'&uacute;', '&ucirc;', '&ugrave;','&uuml;', 
			'&yacute;', '&yuml;', 
			// MATHS
			'&frac12;', '&frac14;', '&frac34;', '&times;', '&divide;', '&lt;', '&gt;', '&deg;', 
			'&circ;', '&plusmn;', '&sum;', '&radic;', '&infin;', '&ne;', '&le;', '&ge;','&asymp;',
			// Punctuation
			'&hellip;', '&iquest;'
		]},
		' ',
		{label:' Font', cmd:'fontname', desc:'Apply font to selected text', cmdvalue:[
			{value:'Times', label:'<span style="font-family:times">Times</span>'},
			{value:'Helvetica', label:'<span style="font-family:Helvetica">Helvetica</span>'},
			{value:'Arial', label:'<span style="font-family:Arial">Arial</span>'},
			{value:'Tahoma', label:'<span style="font-family:Tahoma">Tahoma</span>'},
			{value:'Courier', label:'<span style="font-family:Courier">Courier</span>'},
			{value:'Western', label:'<span style="font-family:Western">Western</span>'},
			{value:'serif', label:'<span style="font-family:serif">Serif</span>'},
			{value:'sans-serif', label:'<span style="font-family:sans-serif">sans-serif</span>'},
			{value:'fantasy', label:'<span style="font-family:fantasy">fantasy</span>'},
			{value:'monospace', label:'<span style="font-family:monospace">monospace</span>'},
			{value:'Verdana', label:'<span style="font-family:Verdana">Verdana</span>'}
		]},
		{cmd:'removeformat', desc:'Remove Formatting', label:'<b style="color:red;">X</b>'}
	];
	

this.each(function(){

	// Unbind the click event
	// We dont want this script running twice for the same textarea
	// This is only useful if the textarea  click event was set, i.e. see how this current lambda function was triggered
	// $(this).unbind('click');

	var $textarea = $(this).hide();

	// If the iframe is not present, then we need to create it.
	// Set this as the controller for deciding whether to just set events

	if($textarea.siblings('iframe').length){
		/**
		 * Delete iframe and toolbar if already exists.
		 */
		$textarea.siblings('iframe').remove();
		$textarea.siblings('div.toolbar').remove();
		/**/
	}

	var $iframe = $("<iframe class='editor' tabIndex=-1 src='"+KSCRIPT+"_blank.htm' frameborder=0>").insertAfter($textarea);
	// ADD toolbar.
	var $toolbar = $('<div class="toolbar">').insertBefore($textarea);

	$iframe.attr('style', $textarea.attr('style'));

	// just in case the textarea is hidden
	$iframe.show();

	// ADD BUTTONS
	// Add controls above the contentEditable(DIV)
	$("<button>").html('<code>Source</code>').css({width:'50px'}).click(function(){
	
		var $editor = $( 'body', $iframe[0].contentWindow.document || $iframe[0].contentDocument );

		// Toggle display DIV vs TEXTAREA
		// Copy the content from the div to the textarea
		// Show the Textarea
		if($textarea.css('display')=='none'){
			// DIV => TEXT
			// Copy the div content to the text and show text
			$textarea.val($editor.html().replace(/<(h|b)r>/g, '<$1r/>')).show();
			// Hide the DIV

			$iframe.addClass('removeeditor');
			
			// change the text on the source button and disable the tools
			$(this).html("<b>Editor</b>").siblings(".tool").attr("disabled", true);
		}
		else {
			// TEXT => DIV
			// Copy the text content to the div and show
			$editor[0].innerHTML = $textarea.val().replace(/<(h|b)r>/g, '<$1r/>');

			$iframe.removeClass('removeeditor');

			// Hide the TEXT
			// Show DIV
			$textarea.hide();

			// Change Text and enable tools
			$(this).html("<code>Source</code>").siblings(".tool").removeAttr("disabled");
		}
		return false;
	}).appendTo($toolbar);
			

	/**
	 * Insert cmd
	 * 
	 */
	var insert = function(cmd,value,tool){
		
		
		/**
		 * Get the selected Text if there is any. 
		 * This function returns {obj:element,text:string}
		 */
		var sel = $().selectedText($iframe[0].contentWindow);

		/**
		 * If this is the createLink or insert image
		 * Get the href|src value of the selected element
		 * set this as the value
		 */
		if( (cmd==='insertimage'||cmd==='createlink') && ( typeof tool.prompt !== 'undefined' )){
			if(sel.obj.tagName==='A'){
				tool.promptValue = sel.obj.href;
			}
			if(sel.obj.tagName==='IMG'){
				tool.promptValue = sel.obj.src;
			}
		}

		/**
		 * User prompted to put in a value
		 */
		if(tool&&tool.prompt){
			/**
			 * Is text selected?
			 * Is selected text a URL? Then prepopulate the content of the prompt
			 */
			value = prompt(tool.prompt, (cmd==='createlink'&&sel.text.match('^https?://')?sel.text:(tool.promptValue || '')));
			if(value===null){
				return false;
			}
		}
		
		//If the command is for a Link there must be text
		//Otherwise we need to insertHTML instead
		if(cmd==='createlink'&&(!sel.text.length)){
			// Get the text
			if(!(sel.text = prompt("Text"))){
				sel.text = value;
			}
				
			if(sel.text===null){
				return false;
			}
			// We are going to insert the element manually
			// Using pasteHTML IE and execCommand insertHTML if the browser supports it.
			value = "<a href='"+value+"'>"+sel.text+"</a>";
			cmd = 'inserthtml';
		}

		/**
		 * IE requires format block tags (e.g. h1, p, pre) to be wrapped with <> syntax
		 */
		if(cmd=='formatblock'&&$.browser.msie){
			value = '<'+value+'>';
		}

		
		// Send the action to the frame
		// update the text area with this new value

		try{
			console.log([$iframe[0].contentWindow, cmd, value]);
			$iframe[0].contentWindow.inBetween(cmd, false, value);
		}
		catch(err){
			//IE WAY to insert at the current point
			if($iframe[0].contentWindow.document.selection){
				var s = $iframe[0].contentWindow.document.selection.createRange();
				if(s.pasteHTML){
					s.pasteHTML(value);
					return false;
				}
			}
			alert("Could not execute "+cmd);
		}
		return false;
	};

	var onselectchange = function(){
		// Make sure the focus is on the window
		if($.browser.msie){
			// $('#iframe_'+$(this).parents("div.toolbar")[0].id.match('[0-9]+')[0])
			var win = $iframe[0].contentWindow;
			var doc = win.document;
			doc.focus();
			cursorPos = doc.body.createTextRange();
			cursorPos.moveToPoint( win.posx, win.posy);
			cursorPos.select();
		}
		/**
		 * Add insert event
		 */
		try{
			insert(toolbox[$(this).attr('xtool')].cmd, this.options[this.selectedIndex].value);
		}
		catch(err){
			console.log("Uncaught error applying insert",err);	
		}
	};
	var onbuttonclick = function(){
		// make sure this is not going to insert outside the contentEditable iframe
		if($.browser.msie){
			try{
			//$('#iframe_'+$(this).parents("div.toolbar")[0].id.match('[0-9]+')[0])
			var win = $iframe[0].contentWindow;
			win.focus();
			var doc = win.document;
			// have we lost cursor positions?
			// excpetions occur selecting images
			var s = doc.selection.createRange().duplicate().getBoundingClientRect();
			if(!(s.left>=0&&s.top>=0)){
				// restore the cursor position
				doc.body.createTextRange().moveToPoint( win.posx, win.posy).select();
			}}
			catch(err){}
		}
		
		/**
		 * Which tool is this
		 */
		var tool = toolbox[$(this).attr('xtool')];
		
		if( $(this).hasClass('selected') ){
			$(this).removeClass('selected');
		}else{
			$(this).addClass('selected');
		}

		
		/**
		 * If Callback
		 */
		$currentiframe = $iframe[0].contentWindow;
		$currentiframe.focus();
		if(tool.callback){
			// because the callback does not take the same scope we assign this to the window object
			tool.callback();
			return false;
		}

		/**
		 * Trigger edit command
		 */
		try{
			insert(tool.cmd, null,tool);
		}
		catch(e){
			console.log("Uncaught error applying insert",e);	
		}
		return false;
	};
	/**
	 * Loop through toolbox object and create buttons for each
	 */
	for(var x in toolbox){if(toolbox.hasOwnProperty(x)){
		
		/**
		 * Seperator?
		 * If the current tool is a string then insert as just a string
		 */
		if(typeof toolbox[x] == 'string'){
			if(toolbox[x].match('[a-z]', 'ig')){
				toolbox[x] = "<span class='tool'>"+toolbox[x].replace(' ', '&nbsp;')+"</span>";
			}
			$toolbar.append(toolbox[x]);
			continue;
		}
		
		
		/**
		 * Create a drop down list (currently a select->option).
		 * Add values from the tool to the select
		 * Add change event to the list
		 * @todo Change this to a drop down div with buttons and attach events to each of the option. 
				Two reasons: 
				1. Style (selects dont allow us flexibility to style option labels).
				2. Only trigger onchange event. We might want to select the same item twice, the change event wont get fired the second time...  i.e. the user has to navigate away. Using other triggers get fired when selecting anywhere, even the select scrollbar. Deselecting the option after use would be a workaround.
		 * 
		 */
		if(toolbox[x].cmdvalue){
			/**
			 * Create select in memory
			 * Attach event to insert into the current iframe
			 */
			var $select = $("<select>").attr({'class':'tool', 'xtool':x, 'cmd':toolbox[x].cmd, 'title':toolbox[x].desc}).change(onselectchange);

			/** 
			 * Create the select OPTIONs
			 */
			for(var y in toolbox[x].cmdvalue){if(toolbox[x].cmdvalue.hasOwnProperty(y)){
				if(typeof toolbox[x].cmdvalue[y] == 'string'){
					toolbox[x].cmdvalue[y] = {value:toolbox[x].cmdvalue[y],label:toolbox[x].cmdvalue[y]};
				}
				$select.append('<option value="'+toolbox[x].cmdvalue[y].value+'">'+toolbox[x].cmdvalue[y].label+'</option>');
			}}

			/**
			 * Wrap the select in a div. 
			 * Append the DIV into the toolbar
			 */
			$("<span class='tool'>"+toolbox[x].label.replace(' ', '&nbsp;')+"</span>").append($select).appendTo($toolbar);

			continue;
		}

		/**
		 * Create a button 
		 * This is the last option for what a tool can be. 
		 */
		$("<button>").attr({'class':'tool', 'xtool':x, 'cmd':toolbox[x].cmd, 'title':toolbox[x].desc}).html(toolbox[x].label).click(onbuttonclick).appendTo($toolbar);
	}}
});}



/**
 * Builds form validation and editors on each of the selected items.
 */
$.fn.form = function(){

	/**
	 * Expands textarea as one types
	 */
	var expandText = function(){
		var el = this;
		if(el.tagName!=="TEXTAREA"){return;}

		// has the scroll height changed?, we do this because we can successfully change the height 
		var prvLen = el.preValueLength;
		el.preValueLength = el.value.length;

		if(el.scrollHeight===el.prvScrollHeight&&el.prvOffsetHeight===el.offsetHeight&&el.value.length>=prvLen){
			return;
		}
		while(el.rows>1 && el.scrollHeight<el.offsetHeight){
			el.rows--;
		}
		var h=0;
		while(el.scrollHeight > el.offsetHeight && h!==el.offsetHeight && (h=el.offsetHeight) ){
			el.rows++;
		}

		el.rows++;

		el.prvScrollHeight = el.scrollHeight;
		el.prvOffsetHeight = el.offsetHeight;
	};

	/**
	 * Load default Form CSS
	 */
	var control = {
		NOTNULL : { regexp : [".+"],
					errmsg : "Cannot be empty" },
		UNIQUE	: { errmsg : "Is not unique",
					onblur : function(el){
						var patn,
							attr={t:el.value},
							p=/([a-z]+):([^\;]+)/ig,
							ktools = $(el).attr('ktools');
						// check that this does not already exist
						// if the item contains the attribute "ktools" then use the parameters to build the query.
						// ktools="type:type;parent:;", etc..
						if(ktools){
							while((patn = p.exec(ktools))){
								attr[patn[1]]=patn[2];
							}
						}
						$.getJSON("/ktools/list",attr,function(d){
							form_tick(el,$().isEmpty(d.items),"Already exists");
						});/**/
					}} ,
		MATCH	: { errmsg : "Do not match" },
		EMAIL	: { regexp : ["[a-zA-Z0-9\\.-]+", "@", "[a-zA-Z0-9-]+", "[\\.]", "[a-zA-Z0-9\\.-]{1,10}"],
					errmsg : "Invalid Email Address - e.g. name@domain.com" },
		INTEGER : { regexp : ["-?[0-9]+"],
					errmsg : "Use only numbers" },
		PASSWORD: { regexp : ["[a-zA-Z0-9-]{6,20}"],
					errmsg : "Six or more alphanumerical characters long (e.g. pa55word)" },
		STRING	: { regexp : ["[a-zA-Z0-9\\'\\. -]+"],
					errmsg : "Invalid string" },
		POSTCODE: { regexp : ["[a-zA-Z]{1,2}","[0-9]{1,2}","[a-zA-Z]? ?","[0-9]","[a-zA-Z]{2}"],
					errmsg : "Invalid postcode - correct example \"CM3 2JW\"" },
		DATE	: { regexp : ["[0-3]?","[0-9]","\/[0|1]?","[0-9]","\/([1|2][0|9])?","[0-9]{2}"],
					errmsg : "Invalid Date",
					onfocus: function(){
						var $calendar = $('<div class="calendar"></div>').insertAfter(this)
							,days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
							,month= ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
							,$input = $(this);
						// trigger close calendar when clicked outside
						$(this).blur(function(){$calendar.fadeOut('fast');});
						
						var d = new Date();	
						if($(this).val().match(/^[0-9]{4}\-[0-1]?[0-9]/)){
							var a = $(this).val().split("-").slice(0,3);
							a[1]--;
							for(var x in a){
								d['set'+['Year','Month','Date'][x]](a[x]);
							}
						}
						// build the calendar
						createcalendar(d);
						
						// print the headline
						function createcalendar(d){
							var s='<table><caption></caption><thead><tr>';
							for(var x in days){
							   s += "<th>"+days[x]+"<\/th>";
							}
							s+='</tr></thead><tbody>';
							var dom = d.getDate();// user selected dom(day of month)?
							d.setDate(1);
							// pad the table
							var dow = d.getDay();// first dom falls on a...
							if(dow>0){
								s += "<tr><td colspan='"+dow+"'>&nbsp;<\/td>";
							}
							// get the last day of the month
							d.setMonth(d.getMonth()+1);
							d.setDate(0);
							
							for(var j=1;j<=d.getDate();j++){
							   s += ((dow+j-1)%7===0?'<tr>':'') 
									   + "<td"+(dom===j?' class="selected"':'')
									   +  "><a href='javascript:void(0);'>"+j+"<\/a><\/td>" 
									   + ((dow+j)%7===0?'</tr>':'');
							}
							// pad the table
							if(d.getDay()<6){
							   s += "<td colspan='"+(6-d.getDay())+"'>&nbsp;<\/td><\/tr>";
							}
							s+='<\/tbody><\/table>';
							
							// create the calendar and add events
							$calendar.empty().append('<a href="javascript:void(0);" class="close">close</a>').find('a').click(function(){
								$calendar.fadeOut('fast');
							});
							$(s).prependTo($calendar).find('td a').click(function(){
							   $input.val(d.getUTCFullYear()+'-'+(d.getMonth()+1)+'-'+this.innerHTML);
								$calendar.fadeOut('fast');
							}).end().find('caption').append('<a href="" class="prev">'+ month[d.getUTCMonth()==0?11:(d.getUTCMonth()-1)] +'</a> <a href="" class="current">'+ month[d.getUTCMonth()] + ' ' + d.getUTCFullYear() +'</a> <a href="" class="next">'+ month[(d.getUTCMonth()+1)%12] +'</a>').find('a').click(function(){
								if(this.className==='current'){
								   $input.val(d.getUTCFullYear()+'-'+(d.getMonth()+1));
								   return false;
								}
								$calendar.fadeIn('fast');
								d.setDate(1);
								d.setUTCMonth((d.getUTCMonth()-{'next':-1,'prev':1}[this.className]));
								createcalendar(d);
								return false;
							});
						};
				}},
		TEXT	: { onfocus : expandText,
					onkeyup : expandText},
		TIME	: { regexp : ["[0-2]", "[0-9]", ":", "[0-6]", "[0-9]"], 
					errmsg : "Invalid time" },
		EDITOR	: {
					onbuild : function(){
						$(this).editor();	
					}},
		CONFIRMATION : { errmsg	: "Incorrect",
						regexp	: ["[A-Za-z0-9]{6}"],
						onbuild : function(el){
							$(el).siblings("a").click( function(){
								$(this).siblings("img").attr({src:"/ktools/captcha?"+Math.random()});
							});
						},
						onblur	: function (el){
							$.getJSON("/ktools/capt",{s:el.value},function(d){
								form_tick(el, d.bool, "Incorrect, Check again");
							});
						}
					},
		URL		: { regexp : "((ht|f)tp(s?):\\/\\/)?[a-z0-9]([a-z0-9\\.-]+)\\.([a-z]{1,4})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*",
					errmsg : "Invalid URL" },
		PHONE	: { regexp : ["[0-9ext.()- ]{10,}"],
					errmsg : "Invalid Telephone" },
		TAG		: { onbuild	: function(){
						// Get the autocomplete script
						$.ajaxSetup({cache:true});
						var input=this;
						$.getScript(KSCRIPT+'jquery.autocomplete.js',function(){
							$.getJSON("/ktools/list",{type:'tag'}, function(data){
								// add to a div underneath to the item we are searching
								// loop through items
								var s=[],x;
								for(x in data.items){
									s.push(data.items[x].title);
								}
								// dont define it immediatly, because theauto loader sometimes doesn't work
								$(input).focus(function(){
									$(this).unbind('focus').autocomplete(s,{multiple:true,multipleSeparator: " "});
								});
							});
						});
						$.ajaxSetup({cache:false});
					}},
		'NULL'	: null,
		FILE	: {
					onbuild: function ()
						{
							
						}
					},
		PIC		: {
					onbuild: function (){
							// A picture consists of a frame and an upload element
							// The form must let photos be added/deleted/replaced
							// If the browser can handle it: Upload the picture in the background.
								// using GEARS/FLASH or JAVA
							// This means loading the image into an IFRAME... first replacing the inline form element with another form type.
						}
					}
	};


	/**
	 * Hide the CAPTCHA in the form if it exists, and we have the cookie
	 */
	if(document.cookie.match(/_sid=/)){
		this.find(":input[name=captcha]").parents('tr').filter(':first').remove();
	}

	
	/**
	 * Set the spinner function, because forms can take a while to load
	 */
	this.submit(function (){
		var o = this;
		var a = [0,0];
		var oo = o;
		if (oo.offsetParent)
		{
			a = [o.offsetLeft, o.offsetTop];
	
			while ( ( oo = oo.offsetParent ) )
			{
				a[0] += oo.offsetLeft;
				a[1] += oo.offsetTop;
			}
		}
	
	//	$().modalOpen('<div>Please wait...</div>');
	
	//	$(o).append('<div class="dither" style="width:' + o.offsetWidth + 'px;height:' + o.offsetHeight + 'px;left:' + a[0] + 'px;top:'+ a[1] +'px;"><div>Please Wait..</div></div>');
	
		var c = document.createElement('div');
		c.className = "dither";
		c.style.left = a[0] + "px";
		c.style.top = a[1] + "px";
		c.style.width = o.offsetWidth + "px";
		c.style.height = o.offsetHeight + "px";
		c.innerHTML = "<div>Please Wait..</div>";
		o.parentNode.appendChild(c);
	
	/**	$('<div class="dither"><div>Please Wait...</div></div>').css({
			left:a[0] + "px",
			top:a[1] + "px",
			width:o.offsetWidth + "px",
			height:o.offsetHeight + "px"
		}).appendTo(o);
	*/
	
		/**
		 * Create a obfusicated variable to validate the request as coming from a browser.
		 * Eliminating the need for CAPTCHA
		 */
	
		try{
		$(this).append("<input type='hidden' name='jsturing' value='"+document.cookie.match(/_sid=([^;]+)/)[1].substr(Math.random()*20, 10+Math.random()*10 )+"'/>");
		} catch(e){
			return false;
		}


		if( $(this).filter('.xhr').length ){
			var that = this;
			try{
				$.post(this.action.replace(/#.*/,'')+(this.action.match('\\?')?'&':'?')+'format=json', 
					$(this).serialize(), 
					function(e){
						console.log(e);
						$('.dither').hide();
						// is this trying to update an existing item?
						var ref = (function(){var m = that.action.match(/ref=([0-9]+)/);return (m!==null?m[1]:null);})();
						
						if(e.error){
	
							if(typeof e.errors === 'string'){
								return $('#notice span').html(e.errors).parent().hide().slideDown('slow');
							}
								
							$('#notice span').html('Whoops there were errors').parent().hide().slideDown('slow');
							for(var x in e.errors){if(e.errors.hasOwnProperty(x)){
								if(x==='captcha'){
									$(":input[name=captcha]",form).parents('tr').filter(':first').fadeIn();
								}
								form_tick($(':input[name='+x+']', that).get(0), false, e.errors[x]);
							}}
						
							return;
						}
						// if the given refID and the return ID are different or the refID is not present 
						// then @action must be updated 
						else if(ref===null||ref!=e.id){
							if(ref===null){
								// add ref to the URL
								that.action = that.action+(that.action.match(/\?/)?'&':'?')+'ref='+parseInt(e.id);
							}
							else{
								that.action = that.action.replace(/ref=[0-9]+/,'ref='+parseInt(e.id) );
							}
						}
	
						if(e.response==='update' && $(that).parents('#main-content').length === 1 ){
							var s="<div class='depth_1'>";
							$(":input", that).each(function(){
								if(this.type!=='hidden'&&this.type!=='submit'){
									s += "<div class='"+this.name+" depth_2'>"+this.value+"</div>";
								}
							});
							$('#main-content div.list:first').append(s+"</div>");
							// hide notice the post was successful
							$('#notice span').html('Success').parent().slideUp('fast');
	
							that.reset();
						}
						else{
							if(e.execute&&e.execute.length>0){
								if( typeof window[e.execute] === 'undefined'){
									console.log( "could not find ", e.execute );
								}
								else{
									try{
										window[e.execute](e);
									}
									catch(err){};
								}
							}
							else{
								// this is the default to hide the form
								$(that).slideUp('fast');
							}
							
							if(e.response&&e.response.length>0){
								$('#notice span').html(e.response).parent().hide().slideDown('slow'); 
							}
							// if these are active we can remove them
						}
						// just in case this is set
						$('.modal-window,.modal-overlay').remove(); 
						
						console.log(e);
					}, 'json' );
				return false;
			}
			catch(err){
			}
		}
	
		return true;
	
	});
	
	
	/**
	 * At this point the form_input array of objects has been created.
	 * Now we want to add links to add table rows and /input's. To existing data.
	 * This can be done by looping through our array form input and identifying elements which need to be expanded.
	 * For instance there are multiple markets. And market data is sotred in market[0][typename1], market[0][typename2], ... market[N][typenameN], 
	 */
	
	
	/****************************************************
	 *
	 * Add Table Events. 
	 *		- Add/Remove rows/columns
	 ****************************************************/
	
	
	/**
	 * Attach "Add row" button to table
	 */
	this.find("table.multiple").each(function(){
		/** Add delete row control */
		$(this).find("tbody tr").append("<td><a class='del_row'>Delete</a></td>");

		var len = 0;
		$(this).find("thead tr").append("<th>Delete</th>").find("th").each( function (){ len++;} );
	
		/** Add new row control to a "tfoot a"*/
		$(this).append("<tfoot><tr><td colspan='" + len + "'><a>Add " + ( this.className.match(/\bpivot\b/) ? "Row" : "Col" ) + "</a></td></tr></tfoot>").find("tfoot td a").click( function(){
			$(this).closest('table').each(dynTable.addRecord);
		});
	});
	
	
	/****************************************************
	 *
	 * Add Form Events. 
	 * 		 AutoCompletion
	 *
	 * TODO 
	 *		 Add Type verification
	 ****************************************************/
	
	this.each( function(){
		addControls(this);
	});
	
	
	/**
	 * Add Controls
	 */
	function addControls(el){
		/**
		 * Remove the parent TR, table row of this object.
		 */
		$(el).find("a.del_row").click( function(){
			console.log("PARENT", $(this).closest('tr'));
			$(this).closest('tr').each(function(){
				/**
				 * Check that if there are no other rows to simply unset all the values
				 */
				if ( this.parentNode.rows.length > 1 ){
					// In IE we can't hide rows, so lets select the td's instead. 
					// This means we can only run the delete rows once. So check that the elements exist.
					$(this).children().fadeOut('fast',function(){
						// once all the td elements have faded out we can remove the row
						$(this).closest('tr').remove();
					});
				}
				else{
					$( this ).clearForm();
				}
			});
		});
	
		
		// loop through the elements and add controls it they have any
		$(':input',el).each(function(){
			// loop through each of the classes
			var x=null,patt=/[a-z]+/gi;
			patt.lastIndex=0;
			while(( x = patt.exec(this.className) ) ){
				x = x[0].toUpperCase();
				if(control[x]){
					if( control[x].onbuild){
						$(this).each(control[x].onbuild);
					}
					if(control[x].onfocus){
						$(this).focus(control[x].onfocus);
					}
					if(control[x].onclick){
						$(this).click(control[x].onclick);
					}
					if(control[x].onkeyup){
						$(this).keyup(control[x].onkeyup);
					}
				}
			}
		}).change( function(){
			console.log("CHANGE", this.name);
			validateInput(this, true);
		}).keyup( function(){
			console.log("KEYUP", this.name);
			validateInput(this, false);
		});
	}

	
	/****************************************************
	 * Validate input
	 * Is used to provide an error message as the user	/
	 * types into a form field alternatively as the user /
	 * changes part of the text
	 ****************************************************/
	
	function validateInput(el,complete)
	{
		var patt,
			patn,
			bool=true,
			fail,
			onblur=[];
		
		if (jQuery.inArray(el.tagName,['INPUT','SELECT','TEXTAREA']) === -1){
			return console.log("This is not a form element", el, el.tagName);
		}
	
		/**
		 * Determine whether validation is needed 
		 */
	
		if ( el.value === null || el.value === '' ){
			return form_tick(el,(complete&&/notnull/i.exec(el.className)?false:null),control.NOTNULL.errmsg);
		}
		
		/**
		 * Loop through the classes
		 */
		console.log("validateInput",el,complete,el.className);
		var x=null,p=/[a-z]+/gi;
		p.lastIndex=0;
		while((x=p.exec(el.className)))
		{
			x = x[0].toUpperCase();
			if(x==="NOTNULL"||x==="NULL"){
				continue;
			}

			if ( control[x] && control[x].regexp )
			{
				console.log(x, control[x]);
				patt = (complete===true? [control[x].regexp.join('')] : control[x].regexp);
				/**
				 * If it gets here. The pattern has failed to be recognised by this type so flag a fail
				 */
				fail = control[x].errmsg;
				var len=patt.length;
				var regexp = "";
				for(var j=0;j<len;j++)
				{
					regexp += patt[j];
					try{
						patn=new RegExp( "^" + (!complete?regexp.replace(new RegExp("\\{([0-9]+,)?([0-9]+)?\\}$",'gi'),"{0,$2}"):regexp) + "$" );
						bool=patn.test(el.value);
					}catch(err){
						bool=true;
					}
					/**
					 * Does the pattern match part or all of the regular expression?
					 * Then remove any error sign which is present.
					 */
					if(bool&&!complete){
						return form_tick(el,null);
					}
					else if(!bool&&complete){
						return form_tick(el,false,fail);
					}
					console.log(bool,regexp.replace(new RegExp("\\{([0-9]+,)?([0-9]+)?\\}$",'gi'),"{0,$2}"));
				}
			}
			/**
			 * Save to run after all the regular expressions have resulted in true
			 */
			if(complete&&control[x]&&control[x].onblur){
				onblur[x]=true;
			}
		}


		/**
		 * Does this have an on blur event?
		 */
		if(bool){
			for(x in onblur){
				try{
					control[x].onblur(el);
					return true;
				}
				catch(e){}
			}
		}

		if(fail&&!bool){
			return form_tick(el,false,fail);
		}
		if(complete){
			return form_tick(el,true);
		}
	}
	
	
	function form_tick(el, b, m)
	{
		var p = $(el).closest("td").get(0);
	
		console.log( el,b,m );
	
		if(!p){
			return console.log("No parent ", el);
		}
		$(p).find("div.alert_form").remove();
	
		// add or remove the 'alert_input' className
		$(el)[(b===false?'add':'remove')+'Class']('alert_input');

		if(b===null||$(el).length===0){
			return true;
		}

		$(p).append("<div class='alert_form " + (typeof(b)=='string'?b:(b?"ok":"neg")) + "'>" + (b===false?m:"&nbsp;") + "</div>");
	
		if(b===true){
			$(p).find(".alert_form").fadeOut('slow');
		}
	
		return true;
	}
	
	
/****************************************************
 *
 * Dynamic Table Methods
 *
 ****************************************************/

var dynTable = {
	addRecord : function(){
		var m = /depth_([0-9]+)/.exec(this.className);
		level = m[1];
		// determine the depth based upon the number of nested table elements taken to get to this
		console.log("LEVEL:", level);
		dynTable['add'+(this.className.match(/\bpivot\b/)?'Row':'Column')]( this, level );
	},
	
	/**
	 * Add row to the table.
	 * This script copies the last row of the table and appends a new row to the table.
	 * It removes all user inserted values in the copy. And increments the keys
	 */
	addRow : function ( tbl, level ){
		/**
		 * Clone the first row
		 */
		var b = tbl.tBodies[0];
		var n = b.rows[b.rows.length - 1].cloneNode(true);
	
		/**
		 * Remove any previous value/and multiple records.
		 * Reassign dynamic effects.
		 */
		dynTable.clearRowFormValues(n,level);
	
		// Add controls to new nodes created.
		// If we were using JQuery this could be done with $(this).clone(true);
		addControls($(n).appendTo(b));
	},

	/**
	 * Add row to the table.
	 * This script copies the last column of the table and appends a new column to the table.
	 * It removes all user inserted values in the copy. And increments the keys
	 */
	addColumn : function ( tbl, level )
	{
		var tblHead = tbl.tHead;
		var tblBody = tbl.tBodies[0];
		var i = 0;
		var newCell = {};
	
	
		if ( tblHead !== null && ! $().isUndefined( tblHead.rows ) )
		{
			for ( i=0; i<tblHead.rows.length; h++ )
			{
				/** NOT SUPPORTED */
			}
		}
		
	
		if ( tblBody !== null && ! $().isUndefined( tblBody.rows ) )
		{
			for ( i=0; i<tblBody.rows.length; i++)
			{
				/**
				 * copy the content of the previous cell.
				 */
				newCell = tblBody.rows[i].cells[ tblBody.rows[i].cells.length -1 ].cloneNode(true);
				
				dynTable.clearCellFormValues( newCell, level );
	
				addControls(newCell);
				tblBody.rows[i].appendChild(newCell);
			}
		}
	},
	
	
	
	
	/**************************************************
	 *
	 * Table CELL clear out and reassign properties
	 *
	 * Both the above functions need to clear the cells. 
	 * Since they clone the originals completely, inclusing the user values.
	 *
	 **************************************************/
	
	clearRowFormValues : function(row, level){
		$(row).children().each(function(){
			dynTable.clearCellFormValues( this, level );
		});
	},
	
	
	clearCellFormValues : function(cell, level){
		var elem = {};
		var classInt = 0;
		var classArr = [];
		var previousName = '';
		
		for ( var i=0; i < cell.childNodes.length; i++ )
		{
			/**
			 * Initiate elem
			 */
			elem = cell.childNodes[i];
	
			/**
			 * These are the only element types we have controls for.
			 */
			if ( $.inArray( elem.tagName, ['INPUT', 'TEXTAREA', 'SELECT', 'DIV', 'TABLE'] ) === -1 )
			{
				return;
			}
	
				
			/**
			 * Remove previous values/selections from cells
			 */
			if ( $.inArray( elem.tagName, ['INPUT', "TEXTAREA"] ) !== -1 )
			{
				elem.value = "";
			}
			if ( elem.tagName === "SELECT" )
			{
				elem.selectedIndex = -1;
			}
	
			/**
			 * Change the name to reflect incrementing
			 */
			if ( $.inArray( elem.tagName, ['INPUT', 'SELECT', "TEXTAREA"] ) !== -1 )
			{
				previousName = elem.name;
				elem.name = dynTable.incrementFormName( elem.name, level );
			}
			
			/**
			 * If element of the cell is a TABLE. With a dynamic option. then we need to recurse this table
			 */
			if ( elem.tagName === "TABLE" && elem.className === "dynamic" )
			{
				dynTable.clearTableFormValues( elem, level );
			}
		}
	},
	
	clearTableFormValues : function(tbl, level){
		var elem = {};
		
		/**
		 * Update the depth Variable
		 */
		tbl.depth = level + 1;
		
		/**
		 * Keep the table head. Remove all the other duplicates.
		 */
		var body = tbl.tBodies[0];
	
	
		var count = body.rows.length;
	
		/**
		 * Remove all the other rows
		 */
		for( var i = 1; i < count; i++ )
		{
			
			/**
			 * Because this constantly deletes rows. the orders are re-arranged.
			 * so `1` is always the key of the table row that we want to delete. 
			 */
			body.deleteRow( body.rows[1].sectionRowIndex );
		}
	
		dynTable.clearRowFormValues( body.rows[0], level );
		
		/**
		 * Update the button
		 */
		for( var x in tbl.childNodes ){if(tbl.childNodes.hasOwnProperty(x)){
			elem = tbl.childNodes[x];
			if ( elem.tagName === 'INPUT' && elem.type === 'button' ){
				elem.onclick = dynTable.addRecord;
			}
		}}
	},
	
	
	/****************************************************
	 *
	 * Table Remove Records
	 *
	 ****************************************************/
	
	
	
	deleteColumn : function ( p )
	{
		/**
		 * Find the column number
		 */
		var colInt = $(p).parent("td").get(0).cellIndex;
		
		$(p).parent("tbody").find("tr").each( function(el){
			if ( el.cells.length > 2 && colInt >= 1)
			{
				el.deleteCell( colInt );
			}
			else if ( colInt === 1 )
			{
				dynTable.clearCellFormValues( el.cells[1] );
			}
		} );
	},
	
	
	/**************************************************
	 * 
	 * Special function to Forms 
	 *
	 **************************************************/
	
	
	incrementFormName : function (el,lev)
	{
		/**
		 * Break the string apart.
		 */
		var a = el.split(/[\[\]]+/);
		a.pop();
		var pos = (2*lev)-1;
		a[pos] = -(Math.abs(parseInt(a[pos],0))+1);
		
		if( /\[\]$/.test(el) ){
			a.push();
		}
	
		for (var j=1;j<a.length;j++){
			a[j]="["+a[j]+"]";
		}
		
		return a.join('');
	}
};
};

/**
 * Loop through all the forms with the class "kscript_xform"
 * Apply the form methods to the form
 */
$('form.kscript_xform').form();
