/*
 * Inspired by jNice (by Sean Mooney - sean@whitespace-creative.com), this is a custom form plugin for Novus. 
 * This plugin takes all form elements and dynamically replaces them with custom "fancy" elements.
 */

/** Curry a function with arguments. Returns a zero-arg function that calls the provided function with the provided arguments. */
function curry(fn, argsArray) {
	return function() {
		if (arguments.length == 0) {
			// Simplest case: all args curried
			return fn.apply(null, argsArray);
		}
		else {
			// Partial application. Add remaining args.
			var args = [];
			for (var i = 0; i < argsArray.length; i++) {
				args[args.length] = argsArray[i];
			}
			for (var i = 0; i < arguments.length; i++) {
				args[args.length] = arguments[i];
			}
			return fn.apply(null, args);
		}
	}
}

(function($){

	$.fn.txForms = function() {
		return this.each(function(){
			$('input[type="radio"]', this).each(txTypeRadio);
			$('input[type="checkbox"]', this).each(txTypeCheckbox);
			$('input[type="text"]', this).each(txTypeText);
			$('input[type="password"]', this).each(txTypePassword);
			$('textarea', this).each(txTextarea);
			$('select', this).each(txSelect);
			//$('input:radio, input:checkbox', this).each(txTabIndex);
		});
	} // End Plugin.
	
	// Function to detect whether our radio button or checkbox has been activated, either by keypress on the 'space' bar, or by click on the <span> element.
	function elmIsActive(e, isActive){
		
		// We want to replicate normal form functionality here, so if a user presses the 'space' bar, having tabbed to a custom radio button or checkbox, we need to cater for it.
		var keyPressed = e.keyCode;
		// Unicode keycode for space == 32.
		if (keyPressed) {
			if (keyPressed == 32) {
				isActive = true;	
			}
		}
		// The element has been made active from elsewhere, in this case, a click on the <span> element.
		else {
			isActive = true;
		}
		
		return isActive;
		
	}
	
	function hideLoader() {
	
		$('body').css('visibility', 'visible');
		
	}
	
	// Function to actually check radio buttons.
	function chkRadioBtn(e, elm, btn, btnName) {
		
		// if elm does not already have the class "radio-checked", remove it from the other <span> elements and add it to this one. Then check the underlying radio button.
		if (!(elm.hasClass('radio-checked'))) {
			// Only uncheck radio buttons in the same group, that have the same "name" attribute.
			$('input:radio').each(function() {
				var thisBtn = $(this);
				if (thisBtn.attr('name') == btnName) {
					thisBtn.next('span').removeClass('radio-checked');
				}
			});
			// Add our "radio-checked" class to elm.
			elm.addClass('radio-checked');
			// Check our radio button.
			btn.attr('checked', true);
			
			// Call the radio button onclick event
			var onclick = btn.attr('onclick');
			if (typeof(onclick) != 'function') {
				// if it's a string or otherwise not a function, convert to function
				onclick = (function(e) { eval(onclick); });
			}
			onclick.call(btn.get(0), e);
		}
		
	}
	
	// Function to actually check checkboxes.
	function chkCheckbox(elm, btn) {
		
		// if elm already has the class "checkbox-checked", remove it and uncheck the underlying checkbox.
		if (elm.hasClass('checkbox-checked')) {	
			elm.removeClass('checkbox-checked');
			btn.attr('checked', false);
		}
		// Otherwise apply the class and check the underlying checkbox.
		else {
			elm.addClass('checkbox-checked');
			btn.attr('checked', true);
		}
		
	}
	
	// Function to check radio buttons upon <label> click.
	function chkRadioLbl(e) {
		
		var lbl = $(this);
		var fr = lbl.attr('for');
		
		var btn;
		$('input:radio').each(function() {
			if ($(this).attr('id') == fr) {
				 btn = $(this);
			}
		});
		var btnName = btn.attr('name');
		var elm = btn.next('span');
		
		chkRadioBtn(e, elm, btn, btnName);
		
		return false;
		
	}
	
	// Function to check radio buttons upon <span> click.
	function chkRadioSpn(e) {
		
		// Variable to keep track of whether our custom element has been pressed or clicked.
		var active = false;
	
		var isActive = elmIsActive(e, active);
		
		// If our custom element has been pressed or clicked execute the following code.
		if (isActive) {
			var btn = $(this).prev('input');
			var btnName = btn.attr('name');
			var elm = $(this);
			
			chkRadioBtn(e, elm, btn, btnName);
		}
		
	}
	
	// Function to check checkboxes upon <label> click.
	function chkCheckboxLbl() {
		
		var lbl = $(this);
		var fr = lbl.attr('for');
		var btn;
		$('input:checkbox').each(function() {
			if ($(this).attr('id') == fr) {
				btn = $(this);	
			}
		});
		var elm = btn.next('span');
		
		chkCheckbox(elm, btn);
		
		return false;
		
	}
	
	// Function to check checkboxes upon <span> click.
	function chkCheckboxSpn(e) {
		
		// Variable to keep track of whether our custom element has been pressed or clicked.
		var active = false;
	
		var isActive = elmIsActive(e, active);
		
		// If our custom element has been pressed or clicked execute the following code.
		if (isActive) {
			var btn = $(this).prev('input');
			var btnName = btn.attr('name');
			var elm = $(this);
			
			chkCheckbox(elm, btn);
		}
		
	}
	
	// Function to replace radio buttons with custom elements.
	var txTypeRadio = function() {
		
		var radioBtn = $(this);
		
		// Get the associated label and bind the chkElm function to it.
		var label = radioBtn.next('label');
		label.click(chkRadioLbl);
		
		// Create our custom span and give it a tabindex.
		var span = '<span class="type-radio"></span>';
		
		// Insert our custom <span> equivalent after the radio button.
		radioBtn.after(span);
		span = radioBtn.next('span');
		
		// Bind our ChkElm function to our custom elements. We need to cater for click and keydown events.
		span.click(chkRadioSpn);
		
		span.keydown(chkRadioSpn);
		
		// On page load, if a radio button is already checked, then apply the class "checked" to its associated span.
		if (radioBtn.is(':checked')) {
			span.addClass('radio-checked');	
		}
		// Hide the system radio button.
		radioBtn.hide();
		
	}
	
	// Function to replace checkboxes with custom elements.
	var txTypeCheckbox = function() {
		
		var checkbox = $(this);
		
		// Get the associated label and bind the chkElm function to it.
		var label = checkbox.next('label');
		label.click(chkCheckboxLbl);
		
		// Create our custom span and give it a tabindex.
		var span = '<span class="type-checkbox"></span>';
		
		// Insert our custom <span> equivalent after the checkbox.
		checkbox.after(span);
		span = checkbox.next('span');
		
		// Bind our ChkElm function to our custom elements. We need to cater for click and keydown events.
		span.click(chkCheckboxSpn);
		span.keydown(chkCheckboxSpn);
		
		// On page load, if a checkbox is already checked, then apply the class "checked" to its associated span.
		if (checkbox.is(':checked')) {
			span.addClass('checkbox-checked');	
		}
		// Hide the system radio button.
		checkbox.hide();
		
	}
	
	// Function to replace text fields with custom elements.
	var txTypeText = function() {
		
		var txtField = $(this);
		
		// Create our custom div and then wrap it around our text field.
		var div = '<div class="type-text"></div>';
		txtField.wrap(div);
		div = txtField.parent();
		
		// We need to prepend a span element before our text field. this allows us to have variable widths on the fields by have the left edge of the custom element represented by the span.
		div.prepend('<span />');
		var span = div.children('span');
		
		// Check to see if the text field is read only.
		if (txtField.attr('readonly')) {
			div.removeClass('type-text')
			div.addClass('type-text-readonly');
		}
		
		// Set the width of our wrapper div to be the same as the text field, taking into account the width of the prepended span element.
		// Disabled for Tx.
		//div.width(txtField.width() + span.width());
		
		// Debugging: --- alert((txtField.width() + span.width()) + ' ' + div.width())
		
	}
	
	// Function to replace password fields with custom elements.
	var txTypePassword = function() {
		
		var txtField = $(this);
		
		// Create our custom div and then wrap it around our text field.
		var div = '<div class="type-text"></div>';
		txtField.wrap(div);
		div = txtField.parent();
		
		// We need to prepend a span element before our text field. this allows us to have variable widths on the fields by have the left edge of the custom element represented by the span.
		div.prepend('<span />');
		var span = div.children('span');
		
		// Check to see if the text field is read only.
		if (txtField.attr('readonly')) {
			div.removeClass('type-text')
			div.addClass('type-text-readonly');
		}
		
		// Set the width of our wrapper div to be the same as the text field, taking into account the width of the prepended span element.
		// Disabled for Tx.
		//div.width(txtField.width() + span.width());
		
		// Debugging: --- alert((txtField.width() + span.width()) + ' ' + div.width())
		
	}
	
	// Function to replace textareas with custom elements.
	var txTextarea = function() {
		
		var txtArea = $(this);
		
		//alert(txtArea.height());
		
		// Create our custom div and then wrap it around our text field.
		var div = '<div class="textarea"></div>';
		txtArea.wrap(div);
		var ctr = '<div class="ctr-textarea"></div>';
		txtArea.wrap(ctr);
		div = txtArea.parents('.textarea');
		ctr = txtArea.parent('.ctr-textarea');
		
		// We need to prepend a span element before our text field. this allows us to have variable widths on the fields by have the left edge of the custom element represented by the span.
		div.prepend('<span />');
		var span = div.children('span');
		
		// Set the width of our wrapper div to be the same as the text field, taking into account the width of the prepended span element.
		div.width(txtArea.width() + span.width());
		ctr.width(txtArea.width());
		
		//ctr.jScrollPane({scrollbarWidth:12});
		
		//txtArea.keyup(function() {
			//alert($(this).height());					   
		//});
		
		// Debugging: --- alert((txtField.width() + span.width()) + ' ' + div.width())
		
	}
	
	// Function to open and close select boxes.
	function toggleDrpdwn(viewport, drpdwn, pos) {
		
		if (viewport.css('visibility') == 'hidden') {
			viewport.css('visibility', 'visible');
			drpdwn.animate({
				top: 0
			}, {
				duration: 500,
				easing: 'easeOutCirc',
				queue: false
			});
		}
		else {
			drpdwn.animate({
				top: pos
			}, {
				duration: 0,
				complete: function() {
					viewport.css('visibility', 'hidden');		
				},
				queue: false
			});
		}
		
	}
	
	// Function to truncate dynamic text in fixed width select boxes.
	function truncateTxt(ctr, txtElm, txt) {
		
		ctr.prepend('<div class="ruler" />')
		var ruler = ctr.find('.ruler');
		
		var rulerProperties = {
			'fontSize': txtElm.css('font-size'),
			'fontWeight': txtElm.css('font-weight'),
			'textTransform': txtElm.css('text-transform')
		};
		
		ruler.css({fontSize: rulerProperties.fontSize, fontWeight: rulerProperties.fontWeight, textTransform: rulerProperties.textTransform})
		
		var trimmed = "";
		var num = 0;
		
		while ((ruler.width() < txtElm.width())) {
			trimmed = txt.substring(0, num);
			ruler.text(trimmed + '...');
			num++;
			if (num >= txt.length) {
				// No truncation required.
				ruler.remove();
				txtElm.text(txt);
				return txt;
			}
		}
		// We need to truncate.
		ruler.remove();
		var truncate = trimmed.substring(0, trimmed.length - 1) + '...';
		return truncate;
		
	}
	
	// Function to replace select boxes with custom elements.
	var txSelect = function() {
		
		var selectBox = $(this);
		
		if (selectBox.is(':visible')) {
		
			var opts = selectBox.children('option');
			var div = '<div class="select-box"></div>';
			
			selectBox.after(div);
			div = selectBox.next('div');
			div.append('<div class="ctr-txt"><span class="left-bg" /><div class="txt" /></div><div class="viewport"><div class="ctr-dropdown"><div class="dropdown"><ul /></div></div></div>');
			
			var ctrTxt = div.find('.ctr-txt');
			var leftBg = div.find('.left-bg');
			var txt = div.find('.txt');
			var viewport = div.find('.viewport');
			var drpdwn = div.find('.ctr-dropdown');
			var dd = div.find('.dropdown');
			var ul = div.find('ul');
			
			
			// Infer heights and widths for various elements from CSS.
			ctrTxt.width(div.width())
				.height(div.height());
			viewport.width(div.width())
				.css('top', div.height() / 2);
			drpdwn.width(viewport.width())
				.height(viewport.height())
				.css('top', '-' + viewport.height() + 'px');
			dd.width(viewport.width());
				
			
			var pos = drpdwn.position().top;
			
			// Assign text from first option to txt.
			txt.text(opts.eq(0).text());
			
			// Obtain the options and append them as list items to our ul. 
			opts.each(function() {
				var opt = $(this);
				ul.append('<li><span id="' + opt.attr('value') + '">' + opt.text() + '</span></li>');
			});
			
			var li = ul.find('li');
			var lastLi = li.size() - 1;
			
			li.eq(lastLi).addClass('last');
			
			txt.click(curry(toggleDrpdwn, [viewport, drpdwn, pos]));
		
			if (selectBox.attr('disabled') != true) {
				ul.find('span').click(function(e) {					 
					
					var span = $(this);
					
					opts.each(function() {
									   
						var opt = $(this);
						
						if (span.attr('id') == opt.attr('value')) {
							ul.find('span').each(function() {
								$(this).removeClass('selected');							
							});
							
							var t = truncateTxt(div, txt, span.text());
							
							txt.text(t);
							//Cufon.replace(txt, { fontFamily: 'Arial' });
							span.addClass('selected');
							opt.attr('selected', 'selected');
							
							toggleDrpdwn(viewport, drpdwn, pos);

							// Call the selectBox change event
							var onchange = selectBox.attr('onchange');
							if (typeof(onchange) != 'function') {
								// if it's a string or otherwise not a function, convert to function
								onchange = (function(e) { eval(onchange); });
							}
							onchange.call(selectBox.get(0), e);
						}
						
					});
					
				})
				.hover(
					function() {
						$(this).addClass('hover');
					},
					function() {
						$(this).removeClass('hover');			
					}
				);
				
			}
			
			// On page load, if an option is already selected, then apply the class "selected" to its associated list item.
			opts.each(function(i) {
							   
				var opt = $(this);
				if (opt.is(':selected')) {
					ul.find('span').eq(i).addClass('selected');
					
					var t = truncateTxt(div, txt, opts.eq(i).text());
					
					txt.text(t);
					//Cufon.replace('.txt', { fontFamily: 'Arial' });
				}
				
			});
			
			ul.jScrollPane({scrollbarWidth:12, dragMinHeight:20});
			
			// Hide the system select box.
			selectBox.hide();
		}
		
	}
	
	// Function to add a tabindex attribute to all our our custom elements
	var txTabIndex = function() {
		
		var elm = $(this);
		
		if (elm.attr('tabindex')) {
		
			var tabIndex = elm.attr('tabindex')
			
			// input type="radio".
			if (elm.is('input:radio')) {
				elm.next('span').attr('tabIndex', tabIndex);
			}
			// input type="checkbox".
			else if (elm.is('input:checkbox')) {
				elm.next('span').attr('tabIndex', tabIndex);	
			}
			// input type="text".
			/*else if (elm.is('input:text')) {
				elm.attr('tabIndex', tabIndex);
			}*/
			// select.
			/*else if (elm.is('select')) {
				elm.next('div').find('.txt').attr('tabIndex', tabIndex);
			}*/
			// textarea.
			/*else if (elm.is('textarea')) {
				elm.attr('tabIndex', tabIndex);
			}*/
			// This case shouldn't happen.
			/*else {
				throw 'input not known'; 
			}*/
			
			elm.removeAttr('tabindex');
			
			//tabIndex++;
			
		}
		
	}
	
	// Apply custom elements to all forms dynamically.
	$(function(){
		$('form').txForms();	
	});
	
	$(window).load(hideLoader);

})(jQuery);

