$.fn.extend({
	// get the id for the first matched element
	id: function() { return $(this).attr('id'); },

	// custom ajax function
	ettload: function(type, url, params, success, failure, timeout) {
		var self = this;
		var callback = success;
		$.ajax({
			url: url,
			data: params,
			type: 'GET',
			timeout: timeout || 15000,
			success: function(data, status) {
				if (status == "success" || status == "notmodified")
					self.html(data);
				if (callback) callback(data, status);
			},
			error: failure
		});
		return this;
	},

	// show loading indicator in one or more elements
	loading: function() {
		return this.html('<div class="loading">&nbsp;<\/div>');
	},

	// show a small loading indicator
	loading_inline: function() {
		return this.html('<img src="images/loading2.gif"/>');
	},

	// prevent focus rectangle in most browsers (not IE)
	preventFocusRect: function() {
		return this.mousedown(function() { return false; });
	}
});



// controls used in both the full and mobile versions
$.cc = {};

// cctimeframe -- by chris
// date selector supporting a date range.
// It is based heavily on Timeframe, which was written for Prototype by Stephen Celis
// (http://stephencelis.com/projects/timeframe)
$.widget('cc.timeframe', {
	_init: function() {
		var self = this;
		this.calendars = [];
		this.range = {
			start: this.options.initialRange.start,
			end: this.options.initialRange.end
		};
		if (this.range.start) this.range.start = this.range.start.neutral();
		if (this.range.end) this.range.end = this.range.end.neutral();

		// this makes the first calendar show that of the current month
		(this.date = new Date().neutral()).setDate(1);
		if (this.options.earliest instanceof Date)
			this.options.earliest = this.options.earliest.neutral();
		if (this.options.latest instanceof Date)
			this.options.latest = this.options.latest.neutral();

		var $cont = $('<div class="timeframe_container"/>').appendTo(this.element);

		$.each(range(0,this.options.months-1), function() {
			self.createCalendar().appendTo($cont);
		});

		this.populate().register().rangeChanged().refreshRange();
	},

	createCalendar: function() {
		var self = this;
		// build html table for the calendar and return it
		var table = '<table class="calendar"><caption><div>';
		if (this.options.allowChangeMonth) {
			table += '<span class="prevmonth">'+this.options.prevMonthBtn+'<\/span><span class="nextmonth">'+this.options.nextMonthBtn+'<\/span>';
		}
		table += '<span class="monthname"><\/span><\/div><\/caption>';
		table += '<thead><tr>';
		$.each(range(0,Date.dayNames.length-1), function() {
			var weekday = Date.dayNames[(this + self.options.weekOffset) % 7];
			table += '<th>'+weekday.substring(0,1)+'<\/th>';
		});
		table += '<\/tr><\/thead><tbody>';
		$.each(range(1,6), function() {
			table += '<tr>';
			$.each(range(1,Date.dayNames.length), function() {
				table += '<td><\/td>';
			});
			table += '<\/tr>';
		});
		table += '<\/tbody><\/table>';
		var $cal = $(table);
		this.calendars.push($cal);
		return $cal;
	},

	populate: function() {
		var self = this, month = this.date.neutral();
		month.setDate(1);

		function unselectable(dt) {
			return	(self.options.earliest && dt < self.options.earliest) ||
					(self.options.latest && dt > self.options.latest);
		}

		$.each(this.calendars, function() {
			var $cal = this;

			$cal.find('caption .monthname').text(month.strftime('%B, %Y'));

			var d = new Date(month);
			var offset = (d.getDay() - self.options.weekOffset) % 7;
			// ensure we always show at least 3 days of the previous month (per calendar)
			if (offset <= 2) offset += 7;
			var inactive = offset > 0 ? 'pre beyond' : false;
			d.setDate(d.getDate() - offset);
			if (d.getDate() > 1 && !inactive) {
				d.setDate(d.getDate() - 7);
				if (d.getDate() > 1) inactive = 'pre beyond';
			}

			$cal.find('td').each(function() {
				$td = $(this);
				$td	.data('date',new Date(d))
					.text(d.getDate())
					.attr('class', (inactive || 'active') + (unselectable(d) ? ' unselectable' : ' selectable'));
				if (d.toString() === new Date().neutral().toString())
					$td.addClass('today');
				$td.data('base_class', $td.attr('class'));

				d.setDate(d.getDate() + 1);
				if (d.getDate() == 1) inactive = inactive ? false : 'post beyond';
			});

			month.setMonth(month.getMonth()+1);
		});

		return this;
	},

	register: function() {
		$(document)
			.mouseup(this.documentMouseUp.setContext(this));
		this.element.find('caption .prevmonth')
			.click(this.prevMonthClick.setContext(this));
		this.element.find('caption .nextmonth')
			.click(this.nextMonthClick.setContext(this));
		this.element
			.mousedown(this.mouseDown.setContext(this))
			.mouseover(this.mouseOver.setContext(this));

		// disable text selection
		if ($.browser.msie) {
			this.element[0].onselectstart = function(event) {
				//if (!/input|textarea/i.test(event.target.tagName))
					return false;
			}
		} else if ($.browser.opera) {
			$(document).mousemove(function(event) {
				if (event.target.tagName=='td' && $(event.target).parents('.calendar').length > 0)
					window.getSelection().removeAllRanges();
			});
		} else {
			this.element.mousedown = function(event) {
				console.log(event);
				if (!/input|textarea/i.test(event.target.tagName))
					return false;
			}
		}

		return this;
	},


	// event handlers
	documentMouseUp: function(event) {
		//console.log('documentMouseUp()');
		if (!this.dragging) return;
		if (!this.stuck) this.dragging = false;
		this.mousedown = false;
		this.refreshRange();
	},

	mouseDown: function(event) {
		if (event.button > 1) return;
		var $td;
		if (event.target.tagName.toLowerCase()=='td' && ($td = $(event.target)).hasClass('selectable')) {
			this.handleDateClick($td);
		}

		return false; // prevents text selection in firefox/safari
	},

	mouseOver: function(event) {
		//console.log('mouseOver()');
		if (!this.dragging) return;
		var $td;
		if (event.target.tagName.toLowerCase()=='td' && ($td = $(event.target)).hasClass('selectable')) {
			this.extendRange($td.data('date'));
		}
	},

	handleDateClick: function($td) {
		//console.log('handleDateClick(%o)', $td);
		this.mousedown = this.dragging = true;
		if (this.stuck) {
			this.stuck = false;
			this.extendRange($td.data('date'));
			return;
		} else if (this.options.maxRange != 1) {
			// user single clicked date. second click sets end range if sticky selection enabled
			if (this.options.allowStickySelect) {
				this.stuck = true;
				setTimeout(function() {
						if (this.mousedown)
							this.stuck = false;
					}.setContext(this), 200);
			} else {
				this.stuck = false;
			}
		}
		var date = $td.data('date');
		if (this.range.start && this.range.start.getTime() == date.getTime()) {
			this.startdrag = this.range.end;
		} else if (this.range.end && this.range.end.getTime() == date.getTime()) {
			this.startdrag = this.range.start;
		} else if ($td.hasClass('selectable')) {
			this.startdrag = this.range.start = this.range.end = date;
			this.rangeChanged();
		}
		this.refreshRange();
	},

	prevMonthClick: function(event) {
		this.date.setMonth(this.date.getMonth()-1);
		this.populate().refreshRange();
		event.stopPropagation();
	},

	nextMonthClick: function(event) {
		this.date.setMonth(this.date.getMonth()+1);
		this.populate().refreshRange();
		event.stopPropagation();
	},


	extendRange: function(date) {
		//console.log('extendRange(%s)', date.strftime('%Y-%m-%d'));
		if (date > this.startdrag) {
			start = this.startdrag;
			end = date;
		} else if (date < this.startdrag) {
			start = date;
			end = this.startdrag;
		} else {
			start = end = date;
		}
		// validate range
		if (this.options.maxRange) {
			var range = this.options.maxRange - 1;
			var days = parseInt((end - start) / 86400000);
			if (days > range) {
				if (start == this.startdrag) {
					end = new Date(this.startdrag);
					end.setDate(end.getDate() + range);
				} else {
					start = new Date(this.startdrag);
					start.setDate(start.getDate() - range);
				}
			}
		}
		this.range.start = start;
		this.range.end = end;

		this.rangeChanged().refreshRange();
	},

	refreshRange: function() {
		//if (this.range.start && this.range.end) console.log('refreshRange() :: this.range.start = %s, this.range.end = %s', this.range.start.strftime('%Y-%m-%d'), this.range.end.strftime('%Y-%m-%d'));
		var self = this;
		this.element.find('td').each(function() {
			var $td = $(this), date = $td.data('date'), newClass = $td.data('base_class');
			if (self.range.start && self.range.end && date >= self.range.start && date <= self.range.end) {
				var tmpClass = (/today/.test(newClass) ? 'today_' :
								/beyond/.test(newClass) ? 'beyond_' :
								null);
				var state = this.stuck || this.mousedown ? 'stuck' : 'selected';
				if (tmpClass) newClass += ' '+tmpClass+state;
				newClass += ' '+state;
				var rangeClass = '';
				if (self.range.start.getTime() == date.getTime()) rangeClass += 'start';
				if (self.range.end.getTime() == date.getTime()) rangeClass += 'end';
				if (rangeClass.length > 0) newClass += ' '+rangeClass+'range';
			}
			$td.attr('class', newClass);
		});

		return this;
	},

	clear: function() {
		//console.log('clear()');
		this.range.start = this.range.end = null;
		this.startdrag = null;
		this.rangeChanged().refreshRange();
		return this;
	},

	rangeChanged: function() {
		if (this.options.valueInput) {
			if (this.range.start && this.range.end) {
				$(this.options.valueInput).val(this.range.start.strftime(this.options.valueFormat)+'-'+this.range.end.strftime(this.options.valueFormat));
			} else {
				$(this.options.valueInput).val('');
			}
		}

		if (this.options.displayElement) {
			var $elem = $(this.options.displayElement);
			var str = '';
			if (this.range.start && this.range.end)
				str = this.range.start.strftime(this.options.displayFormat)+' - '+this.range.end.strftime(this.options.displayFormat);

			if (/input/i.test($elem[0].tagName))
				$elem.val(str);
			else
				$elem.text(str);
		}

		$(this.element).trigger('change');

		return this;
	}
});

$.extend($.cc.timeframe, {
	defaults: {
		months: 1,
		allowChangeMonth: true,
		weekOffset: 0,
		maxRange: null,
		initialRange: {start:null,end:null},
		earliest: new Date(),
		latest: null,
		allowStickySelect: true,
		valueInput: null,
		valueFormat: '%Y%m%d',
		displayElement: null,
		displayFormat: '%b %d, %Y',
		prevMonthBtn: '&#9668;',
		nextMonthBtn: '&#9658;'
	}
});



/**
 * prompt -- by chris
 * shows faded text in a textbox that prompts the user to enter something.
 * when the textbox gains focus, the prompt is hidden.
 */
$.fn.extend({
	prompt: function(prompt) {
		if (!prompt) return this;
		var promptStyle = { color: '#999' };
		var defaultStyle = { color: '' };
		return this.each(function() {
			$(this)
				.focus(function() {
					if ($(this).val() == prompt)
						$(this).css(defaultStyle).val('');
				})
				.blur(function() {
					if ($(this).val().length == 0)
						$(this).css(promptStyle).val(prompt);
				})
				.blur();
		});
	}
});

/**
 * restrict -- by chris
 * restrict input in a text box
 */
$.fn.extend({
	restrict: function(allowedChars) {
		if (!allowedChars) return this;
		return this.each(function() {
			$(this).keypress(function(e) {
				var k = e.charCode || e.keyCode || e.which;
				console.log('keypress '+k);
				if (e.ctrlKey || e.altKey || e.metaKey) {
					return true;
				} else if ((k >= 32 && k <= 125) || k > 186) {
					switch(k)
					{
						case 37:	// left arrow
						case 39:	// right arrow
							return true;

						// typeable characters
						default:
							return (allowedChars.indexOf(String.fromCharCode(k)) >= 0);
					}

				}
				return true;
			});
		});
	}
});


if (typeof($.tablesorter) != "undefined") {
	/* add tablesorter parsers and set defaults */
	$.tablesorter.addParser({
		id: "timespan",
		is: function(s) { return (/^[0-9]{1,5}\:[0-9]{2}$/.test(s)); },
		format: function(s) { return jQuery.trim(s.replace(/\:/,'')); },
		type: "numeric"
	});

	$.tablesorter.defaults.textExtraction = function(node) {
		var sv = node.attributes.getNamedItem('sortval');
		return sv ? sv.value : node.innerHTML;
	}
}


$.widget('cc.timeofday', {
	_init: function() {
		var $input = $(this.element);

		var values = [], t = 0;
		while (t < 1440) {
			values.push(t);
			t += this.options.increment;
		}

		var selects = [null,null];
		curvals = $input.val().split('-', 2);
		for(var f = 0; f < curvals.length; f++) {
			var curval = curvals[f];
			var foundcurval = false;
			// build select list
			var select = '<select class="timeofday">';
			for (var i=0;i<values.length;i++) {
				var value = this.options.formatValue(values[i]);
				var time = this.options.formatTime(values[i]);
				var sel = '';
				if (value == curval) {
					sel = ' selected="selected"';
					foundcurval = true;
				}
				select += '<option value="'+value+'"'+sel+'>'+time+'<\/option>';
			}
			// if the current value wasn't found in the list, add it to the end
			if (!foundcurval) select += '<option value="'+curval+'" selected="selected">'+curval+'<\/option>';
			select += '<\/select>';
			var $select = $(select);
			$input.hide().before($select);
			selects[f] = $select;
		}

		//add the second select box if it doesn't exist yet.
		if(curvals.length < 2) {
			selects[1] = selects[0].clone();
			selects[0].after(selects[1]);
		}

		//hide the second select box if only 1 value is being used
		if(this.options.controller != null
				&& ($.inArray($(this.options.controller).val(), this.options.timespanif) >= 0
				   || $(this.options.controller).val() == this.options.timespanif)) {
			selects[1].show();
			$(selects).each(function() {
				$(this).css("width","50%");
			});
		} else {
			selects[0].css("width", "100%");
		    selects[1].hide();
        }

        //update the value of the hidden text box whenever the values are changed.
        $(selects).each(function(){$(this).change(change);});
        change();

        function change() {
            if(selects[1].is(":visible"))
                $input.val(selects[0].val() + "-" + selects[1].val());
            else
                $input.val(selects[0].val());
        }
    }
});

$.extend($.cc.timeofday, {
    defaults: {
        //A selection box that controls this needs 2 values or one.
        controller: $(this.element).parent().parent().children(".cmp-op").children("select"),
        //array of values or a single value... Which values of the select box make this widget need two values?
        timespanif: new Array(),
        increment: 15,
        formatTime: function(mins) {
            var h = Math.floor(mins / 60);
            var m = mins - (h * 60);
            return h.toString().lpad(2,'0')+m.toString().lpad(2,'0');
        },
        formatValue: function(mins) {
            return this.formatTime(mins);
        }
    }
});

function updateLongLayoverValue(mainElementId, tsValueElementId, lvrValueElementId) {
    var newVal = $('#' + lvrValueElementId).val()  + '#' + $('#' + tsValueElementId).val();
    $('#' + mainElementId).val(newVal);
}


$.widget('ui.numeric', {
    _init: function() {
        // terminate initialization if numeric already applied to current element
        if($.data(this.element[0], 'numeric')) return;

        // check for onInit callback
        if (this.options.init) {
            this.options.init(this.ui(null));
        }

        var self = this;
        this._setValue( isNaN(this._getValue()) ? this.options.start : this._getValue() );
        this.element.bind('blur.numeric', function(e) {
            self._cleanUp();
        });
        this.element.bind('keydown.numeric', function(e) {
            return self._keydown.call(self, e);
        })
    },

    _constrain: function(value) {
        if(this.options.min != undefined && value < this.options.min) value = this.options.min;
        if(this.options.max != undefined && value > this.options.max) value = this.options.max;
        return value;
    },
    _cleanUp: function() {
        this._setValue(this._constrain(this._getValue()));
    },
    _keydown: function(e) {
        var KEYS = $.keyCode;

        return (e.keyCode == KEYS.TAB || e.keyCode == KEYS.BACKSPACE ||
            e.keyCode == KEYS.LEFT || e.keyCode == KEYS.RIGHT ||
            e.keyCode == KEYS.UP || e.keyCode == KEYS.DOWN || e.keyCode == KEYS.HOME || e.keyCode == KEYS.END ||
            (e.keyCode >= 96 && e.keyCode <= 105) || // add support for numeric keypad 0-9
            (/[0-9:]/).test(String.fromCharCode(e.keyCode))) ? true : false;
    },
    _getValue: function() {
        return this.element.val()
    },
    _setValue: function(newVal) {
        if(isNaN(newVal)) newVal = this.options.start;
        this.element.val(newVal);
    },

    plugins: {},
    ui: function(e) {
        return {
            options: this.options,
            element: this.element,
            value: this._getValue()
        };
    },
});