/*
 * Global utility functions
 */
function debug( msg ) {
	if ( $('#debug') ) {
		$('#debug').append( '<div>' + msg + '<'+'/div>' );
	}
}


/*
 * Side search box
 */
// List of params (<name>=<value>), with a leading semicolon (for better or worse)
function list_search_box_params( except ) {
	var myParams = '';
	$('select.searchbox').each( function () {
		if ( !except || except.length == 0 || -1 == $.inArray( $(this).attr('name'), except ) ) {
			myParams += ';' + $(this).attr('name') + '=' + $(this).val();
		}
	} );

	return myParams;
}

function get_json_sync( url, callbackFunction ) {
	$.ajax({
		type: 'GET',
		url: url,
		dataType: 'json',
		success: callbackFunction,
		data: null,
		async: false
	});
}

function update_search_box_selects() {
	// changedName is the dropdown that triggered this onchange event.
	var changedName = $(this).attr('name');

	// Validate all the previously-selected dropdowns based on the change, clearing any selections that are no longer valid.
	var allParams = list_search_box_params();
	var invalids = [];
	get_json_sync( '/dd/validate_selections_json?changed=' + changedName + allParams + ';callback=?', 
		function ( data, textStatus ) {
			$('select.searchbox').each( function () {
				if ( -1 == $.inArray( $(this).attr('name'), data ) ) {
					invalids.push( $(this).attr('name') );
				}
			} );
		}
	);

	var reloadElements = '';
	switch ( changedName ) {
	case 'gateway_dep':
		// For gateway change, clear everything.
		$('#no_hotel').val('');
		if ( -1 == $.inArray( 'no_hotel', invalids ) ) {
			invalids.push( 'no_hotel' );
		}

		reloadElements = '#dest_dep, #no_hotel';
		break;
	case 'dest_dep':
		// For destination change, clear hotel and duration.
		if ( -1 == $.inArray( 'no_hotel', invalids ) ) {
			invalids.push( 'no_hotel' );
		}

		reloadElements = '#no_hotel';
		break;
	default:
		reloadElements = '#no_hotel';
		break;
	}

	// Fire off requests to repopulate each of the other dropdowns.
	$(reloadElements).each( function () {
		// reqName is the dropdown for the current request.
		var reqName = $(this).attr('name');
		if ( reqName != changedName && reqName != 'gateway_dep' ) {
			var otherParams = list_search_box_params( $.merge( [ reqName ], invalids ) );
			var url = '/dd/byname_json?name=' + reqName + otherParams + ';callback=?';

			$.getJSON( url, 
				function ( data, textStatus ) {
					populate_select( reqName, data );
				}
			);
		}
	} );
}

function initialize_select( name, sel ) {
	$(sel).empty();

	switch ( name ) {
	case 'gateway_dep':
	case 'deals_gateway':
		$(sel).append( '<option value="">- Select a gateway -<' + '/option>' );
		break;
	case 'no_hotel':
	case 'dest_dep':
	case 'duration':
		break;
	default:
	}
}

function default_selection( name, sel ) {
	switch ( name ) {
	case 'gateway_dep':
	case 'deals_gateway':
	case 'dest_dep':
	case 'no_hotel':
		break;
	case 'duration':
		$(sel).val('7');
		break;
	default:
		break;
	}
}

function trimmed_name( name, max ) {
	return name.length > max ? name.substr( 0, max - 3 ) + '...' : name;
}

function populate_select( name, data, initial ) {
	var sel = 'select[name=' + name + ']';
	var $sel = $(sel);
	var old_val = initial ? initial : $sel.val();
	initialize_select( name, sel );

	var lastGroup = null;
	var groups = [];
	if ( name == 'dest_dep' || name == 'no_hotel' ) { // || name == 'duration'
		var allIDs = [];

		for ( ent in data ) {
			allIDs.push( data[ent].id );

			if ( name == 'dest_dep' && data[ent].country_id ) {
				if ( data[ent].country_id != lastGroup ) {
					groups.push( { group_id: data[ent].country_id, members: [] } );

					lastGroup = data[ent].country_id;
				}

				groups[groups.length - 1].members.push( data[ent].id );
			}
		}

		$sel.append( '<option value="'
			+ ( name == 'dest_dep' ? ''
				: ( name == 'no_hotel' ? 'g_all' : allIDs.join(',') ) )
			+ '">- '
			+ ( name == 'dest_dep' ? 'Select a destination'
				: ( name == 'no_hotel' ? 'All Hotels' : 'All' ) )
			+ ' -</option>' ); 
	}

	var validSelection = false;
	lastGroup = null;
	for ( ent in data ) {
		var suppressEntry = false;
		if ( name == 'dest_dep' ) {
			if ( data[ent].country_id != lastGroup ) {
				if ( data[ent].country_id ) {
					for ( g in groups ) {
						if ( groups[g].group_id == data[ent].country_id ) {
							var group_val = 'g_' + groups[g].group_id;
							if ( !validSelection && group_val == old_val ) {
								validSelection = true;
								$sel.append( '<option class="country" value="' + group_val + '" selected="selected">' + data[ent].country_name + '<' + '/option>' );
							}
							else {
								$sel.append( '<option class="country" value="' + group_val + '">' + data[ent].country_name + '<' + '/option>' );
							}

							if ( groups[g].members.length == 1 ) {
								//suppressEntry = true;
							}
						}
					}
				}
				lastGroup = data[ent].country_id;
			}
		}

		if ( !suppressEntry && data[ent].id && data[ent].name ) {
			var prepend = name == 'dest_dep' ? '&nbsp; - &nbsp;' : '';
			var selected = '';
			if ( !validSelection && data[ent].id == old_val ) {
				validSelection = true;
				selected = ' selected="selected"';
			}

			$sel.append( '<option value="' + data[ent].id + '"' + selected + '>' + ( name == 'dest_dep' ? prepend + data[ent].name : prepend + trimmed_name( data[ent].name, max_name_len ) ) + '<' + '/option>' );
		}
	}

	if ( !validSelection ) {
		default_selection( name, sel );
	}

	if ( name != 'gateway_dep' && name != 'deals_gateway' && !$('select[name=gateway_dep]').val() ) {
		$sel.attr( 'disabled', 'disabled' );
	}
	else {
		$sel.removeAttr( 'disabled' );
	}

	if ( $sel.val() != old_val ) {
		$sel.change();
	}
}

/* "Star box" control */
function star_width( star_count ) {
	var stars = parseInt( star_count );
	if ( isNaN( stars ) ) {
		stars = 0;
	}
	var sw = $('#star-box').width() * stars / 5;
	return sw;
}
function star_count( star_width ) {
	var sc = Math.ceil( star_width * 5 / $('#star-box').width() );
	return sc;
}
function star_box_mousemove( e ) {
	var offsetX = e.pageX - $(this).offset().left;
	var sw = star_width( star_count( offsetX ) );
	$('#star-box > span').width( sw );
}
function star_box_mouseenter( e ) {
	$('#star-box').bind( 'mousemove', star_box_mousemove );
}
function star_box_mouseleave( e ) {
	$('#star-box').unbind( 'mousemove', star_box_mousemove );

	$('#star-box > span').width( star_width( $('#star').val() ) );
}
function star_box_click( e ) {
	var stars = star_count( e.pageX - $(this).offset().left );

	$('#star').val( stars );
	$('#star-box > span').width( star_width( stars ) );
}
function star_change( e ) {
	$('#star-box > span').width( star_width( $(this).val() ) );
}

function rooms_adults_change( e ) {
	var adults = Math.round( parseFloat( $('#adults').val() ) ),
		children = Math.round( parseFloat( $('#children').val() ) ),
		rooms = Math.round( parseFloat( $('#rooms').val() ) );

	if ( isNaN( rooms ) || rooms < 1 ) {
		alert( 'At least one room is required.' );
		rooms = 1;
	}
	else if ( rooms > 6 ) {
		alert( 'No more than six rooms are allowed.' );
		$('#rooms').val( rooms = 6 );
	}

	if ( isNaN( adults ) || adults < 1 ) {
		alert( 'At least one adult is required.' );
		adults = 1;
	}
	else if ( adults > 6 ) {
		alert( 'No more than six adults are allowed.' );
		$('#adults').val( adults = 6 );
	}

	if ( isNaN( children ) ) {
		children = 0;
	}
	if ( adults + children > 6 ) {
		alert( 'No more than six total passengers are allowed.' );
		$('#adults').val( adults = 6 - children );
	}

	if ( rooms > adults ) {
		alert( 'Cannot have more rooms than adults.' );
		$('#rooms').val( rooms = adults );
	}

	var occupancy = Math.ceil( ( adults ) / rooms );
	switch ( occupancy ) {
	case 1:
		$('#occupancy').val( 'S' ); break;
	case 3:
		$('#occupancy').val( 'T' ); break;
	case 4:
		$('#occupancy').val( 'Q' ); break;
	case 2:
	default:
		$('#occupancy').val( 'D' );
	}
}
function children_change( e ) {
	var children = Math.round( parseFloat( $('#children').val() ) ),
		adults = Math.round( parseFloat( $('#adults').val() ) );

	if ( isNaN( children ) ) {
		$('#children').val( children = 0 );
	}
	else if ( children > 4 ) {
		alert( 'No more than four children are allowed.' );
		$('#children').val( children = 4 );
	}

	if ( isNaN( adults ) ) {
		adults = 0;
	}

	if ( children + adults > 6 ) {
		alert( 'No more than six total passengers are allowed.' );
		$('#children').val( children = 6 - adults );
	}

	if ( children == 0 ) {
		$('#search-box .child-ages').remove();
	}
	else {
		/* While too many boxes, remove the last one. */
		while ( $('#search-box .child-ages select').length > children ) {
			$('#search-box .child-ages select:last').remove();
		}

		/* While not enough boxes, add one to the end. */
		while ( $('#search-box .child-ages select').length < children ) {
			if ( $('#search-box .child-ages select').length == 0 ) {
				var tabindex = parseInt( $('#children').attr('tabindex') ) + 1;
				var html = '<p class="next-row child-ages"><label for="age_1">Age:</label><br/> <select id="age_1" name="age_1" tabindex="' + tabindex + '"><option></option>', n;
				for ( n = 0; n <= 15; ++n ) {
					if ( n == 0 ) {
						html += '<option value="0">&lt; 1</option>';
					}
					else {
						html += '<option>' + n + '</option>';
					}
				}
				html += '</select></p>';
				$('#search-box .children').after( html );
			}
			else {
				var tabindex = parseInt( $('#search-box .child-ages select:last').attr('tabindex') ) + 1;
				var html = '<select id="age_' + ( $('#search-box .child-ages select').length + 1 ) + '" name="age_' + ( $('#search-box .child-ages select').length + 1 ) + '" tabindex="' + tabindex + '"><option></option>', n;
				for ( n = 0; n <= 15; ++n ) {
					if ( n == 0 ) {
						html += '<option value="0">&lt; 1</option>';
					}
					else {
						html += '<option>' + n + '</option>';
					}
				}
				html += '</select>';
				$('#search-box .child-ages select:last').after( html );
			}
		}
	}

	if ( $('#search-box .child-ages label') ) {
		$('#search-box .child-ages label').text( $('#search-box .child-ages input').length == 1 ? 'Child\'s Age:' : 'Children\'s Ages:' );
	}
	if ( e ) {
		setTimeout( function () { $('#search-box .child-ages input[value=""]:first').focus(); }, 10 );
	}
}

function check_search_box() {
	if ( !$('#gateway_dep').val() || !$('#dest_dep').val() ) {
		alert( 'Please select "Leaving from" and "Going to."' );
		return false;
	}
}

function initialize_search_box() {
	$('#search-box form').submit( check_search_box );
	$('select.searchbox').change( update_search_box_selects );

	$('#star-box').mouseenter( star_box_mouseenter ).mouseleave( star_box_mouseleave ).click( star_box_click );
	$('select#star').change( star_change );
	$('select#star').change();

	$('#children').change( children_change );
	$('#rooms, #adults').change( rooms_adults_change );

	populate_select( 'gateway_dep', gateway_dep_data, gateway_dep_initial );
	populate_select( 'dest_dep', dest_dep_data, dest_dep_initial );
	populate_select( 'no_hotel', hotel_data, hotel_initial );
}

/* 
 * Home page deals
 */
function update_deals( e ) {
	if ( $(this).val() == '' ) {
		location.href = '/';
	}
	else {
		location.href = '/' + $(this).val() + '-' + $('option:selected', this).text().replace( ' ', '_' ) + '-vacation-deals/';
	}
}


/*
 * Shopping AJAX sections: best prices, calendars and listings.
 */

function BestPrices( gateway_sel, duration_sel, params, user_selection_level ) {
	this.params = params;
	this.scrollable_init();
	this.set_selection( gateway_sel, duration_sel );
	this.user_selection_level = user_selection_level;

	window.triplink_selected_dates = undefined;
}
BestPrices.prototype = {
	selected_cell: function () {
		return $('#best-prices .scrollable .items > div > div.selected');
	},
	scrollable_init: function () {
		$('#best-prices .scrollable').height( $('#best-prices .gateways').height() ).scrollable({ prev: '#best-prices .prev', next: '#best-prices .next', keyboard: false, onBeforeSeek: function (e, i) {
			e.stopImmediatePropagation();
			$('#best-prices .prev').toggleClass('disabled', i <= 0);

			var pos = parseInt( $('#best-prices .items > div:last').position().left );
			var width = parseInt( $('#best-prices .items > div:last').outerWidth() );
			var i_pos = parseInt( $('#best-prices .items > div').eq(i).position().left );
			var scrollable_width = parseInt( $('#best-prices .scrollable').width() );

			$('#best-prices .next').toggleClass('disabled', pos + width - i_pos <= scrollable_width );
		} });

		var pos = parseInt( $('#best-prices .items > div:last').position().left );
		var width = parseInt( $('#best-prices .items > div:last').outerWidth() );
		var left = parseInt( $('#best-prices .items').css( 'left' ).replace( 'px', '' ) );
		var scrollable_width = parseInt( $('#best-prices .scrollable').width() );
		if ( isNaN( left ) ) {
			left = 0;
		}

		$('#best-prices .next').toggleClass('disabled', pos + width + left < scrollable_width );
	},
	minimize_width: function () {
		if ( $('#best-prices .items div').length > 0 ) {
			var scrollable_width = parseInt( $('#best-prices .scrollable').width() );
			var last_pos = parseInt( $('#best-prices .items div:last').parent().position().left );
			var last_width = parseInt( $('#best-prices .items div:last').parent().outerWidth() );

			if ( last_pos + last_width < scrollable_width ) {
				var gateways_width = parseInt( $('#best-prices .gateways').width() );

				$('#best-prices .scrollable').width( last_pos + last_width );
				$('#best-prices').width( last_pos + last_width + gateways_width + 1 );
			}
		}
	},
	set_selection: function ( gateway, duration ) {
		this.gateway_sel = gateway;
		this.duration_sel = duration;

		// Duration headings
		$('#best-prices .scrollable h3.selected').removeClass( 'selected' );
		$('#best-prices .scrollable h3.duration-'+duration).addClass( 'selected' );

		// Gateway headings
		$('#best-prices .gateways > div.selected').removeClass( 'selected' );
		$('#best-prices .gateways > div.gateway-'+gateway).addClass( 'selected' );

		// Price cells
		$('#best-prices .scrollable .items > div > div.selected').removeClass( 'selected' );
		$('#best-prices .scrollable .items > div > div.gateway-'+gateway+'.duration-'+duration).addClass( 'selected' );

		this.scroll_to_selection();
	},
	scroll_to_selection: function () {
		if ( $('#best-prices .items div.selected').length > 0 ) {
			var sel_pos = parseInt( $('#best-prices .items div.selected').parent().position().left );
			var sel_width = parseInt( $('#best-prices .items div.selected').parent().outerWidth() );
			var left = parseInt( $('#best-prices .items').css( 'left' ).replace( 'px', '' ) );
			var scrollable_width = parseInt( $('#best-prices .scrollable').width() );
			if ( isNaN( left ) ) {
				left = 0;
			}

			if ( sel_pos + sel_width + left > scrollable_width ) {
				var offset = sel_pos + sel_width + left - scrollable_width;

				/* Scroll forward the desired amount */
				$('#best-prices .scrollable').data( 'scrollable' ).move( Math.ceil( offset / sel_width ) );
			}
		}
	}
};

function BestPricesCell( bp, gateway, duration, price, dates ) {
	this.bp = bp;
	this.gateway = gateway;
	this.duration = duration;

	if ( isNaN( price ) || price < 0 ) {
		price = 0;
	}
	this.getPrice = function () { return price };
	this.getDates = function () { return dates };
	this.setPriceDates = function ( new_price, new_dates ) {
		var old_price = price;
		var old_dates = dates;

		if ( isNaN( new_price ) || new_price < 0 ) {
			new_price = 0;
		}
		price = new_price;
		dates = new_dates;

		if ( new_price != old_price ) {
			this.update_text();
			this.update_cache();
		}
	};

	// click handler--this will refer to the clicked element, not the BestPricesCell object, so private variables (arguments) have to be used instead.
	// var cell = this  could be used if need be.
	this.cellClick = function ( e ) {
		var override_params = { gateway_dep: gateway, duration: duration, exact_dur: duration };
		if ( dates !== undefined && dates.constructor == Array && dates.length > 0 ) {
			override_params.date_dep = dates[0].toString();
		}
		else {
			override_params.date_dep = first_valid_date_dep();
		}
		override_params.month = override_params.date_dep.substr( 0, 6 );

		window.triplink_selected_dates = undefined;
		bp.user_selection_level = 'duration';
		bp.set_selection( gateway, duration );
		shopping_cal_please_wait();
		load_shopping_cal( $.extend( new Params(), bp.params, override_params ) );
		$('#shopping').empty();
	}
}
BestPricesCell.prototype = {
	update_text: function () {
		var round_price = Math.round( this.getPrice() );
		if ( round_price > 0 ) {
			$('#best-prices .scrollable .items div.gateway-'+this.gateway+'.duration-'+this.duration+' p').text( '$' + round_price );
			var cell_dates = this.getDates();
			if ( cell_dates !== undefined && cell_dates.constructor == Array ) {
				$('#best-prices .scrollable .items div.gateway-'+this.gateway+'.duration-'+this.duration+' p').attr( 'title', cell_dates.join(', ') );
			}
		}
		else {
			$('#best-prices .scrollable .items div.gateway-'+this.gateway+'.duration-'+this.duration+' p').text( '' );
			$('#best-prices .scrollable .items div.gateway-'+this.gateway+'.duration-'+this.duration+' p').attr( 'title', '' );
		}
	},
	update_cache: function () {
		var round_price = Math.round( this.getPrice() );
		if ( round_price > 0 ) {
			var cell_dates = this.getDates();
			if ( cell_dates !== undefined && cell_dates.constructor == Array ) {
				// Update cache entry with new price/dates.
				var ajax_data = $.extend( new Params(), this.bp.params, { gateway_dep: this.gateway, exact_dur: this.duration, price: round_price, dates: cell_dates.join(',') } );
				$.ajax({ url: '/bp_update?' + ajax_data.toString(), type: 'GET', success: function ( data, textStatus, jqXHR ) {}, dataType: 'text' });
			}
		}
		else {
			// Delete cache entry?
		}
	}
}

function Params( gateway_dep, dest_dep, no_hotel, adults, children, age_1, age_2, age_3, age_4, star, ai, duration, exact_dur, month, date_dep, rooms, occupancy ) {
	this.gateway_dep = gateway_dep;
	this.dest_dep    = dest_dep;
	this.no_hotel    = no_hotel;
	this.adults      = adults;
	this.children    = children;
	this.age_1       = age_1;
	this.age_2       = age_2;
	this.age_3       = age_3;
	this.age_4       = age_4;
	this.star        = star;
	this.ai          = ai;
	this.duration    = duration;
	this.exact_dur   = exact_dur;
	this.month       = month;
	this.date_dep    = date_dep;
	this.rooms       = rooms;
	this.occupancy   = occupancy;
}
Params.prototype = {
	toString: function () {
		var p, frags = [];
		for ( p in this ) {
			if ( p == 'toString' ) {
				continue;
			}
			frags.push( '' + p + '=' + this[p] );
		}
		return frags.join( ';' );
	}
};


function load_shopping_bp( params ) {
	$('#bp').load( '/bp?' + params.toString() );
}

function load_shopping_cal_listings( params ) {
	shopping_cal_please_wait();
	load_shopping_cal( params );

	shopping_listings_please_wait();
	load_shopping_listings( params );
}

function shopping_cal_please_wait() {
	$('#cal').html( '<div id="select-month"><div class="ghost"><br/><span class="price"><br/></span></div></div><div id="calendars" class="listing"><div><div class="ghost"><div class="cal"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/></div><div class="cal"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/></div></div></div></div>' );
}
function load_shopping_cal( params ) {
	$('#cal').data( 'params', params );
	$('#cal').load( '/cal?' + params.toString() );
}

function shopping_listings_please_wait() {
	$('#shopping').html( '<div class="please-wait"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/><p>Loading...</p></div>' );
}
function load_shopping_listings( params ) {
	if ( $('#shopping').data('load_shopping_listings_xhr') ) {
		$('#shopping').data('load_shopping_listings_xhr').abort();
		shopping_listings_please_wait();
	}

	$('#shopping').data( 'load_shopping_listings_xhr', $.ajax({
		url: '/listings?' + params.toString(),
		dataType: 'html',
		success: function ( data, textStatus, jqXHR ) {
			$('#shopping').html(data);
			$('#shopping').removeData('load_shopping_listings_xhr');
		}
	}) );
}

function lowest_price_from_listing() {
	return $('.listing:first .price .amount .number').text();
}

function ShoppingCalendars( selector, min_lowest, max_lowest, initial_index ) {
	this.global_min_lowest = function ( price ) {
		if ( price && ( min_lowest === undefined || isNaN( min_lowest ) || price < min_lowest ) ) {
			min_lowest = price;
		}

		if ( min_lowest === undefined || isNaN( min_lowest ) ) {
			return 100; // sane default
		}
		else {
			return min_lowest;
		}
	};

	this.global_max_lowest = function ( price ) {
		if ( price && ( max_lowest === undefined || isNaN( max_lowest ) || price > max_lowest ) ) {
			max_lowest = price;
		}

		if ( max_lowest === undefined || isNaN( max_lowest ) ) {
			return 3000; // sane default
		}
		else {
			return max_lowest;
		}
	};

	this.pending_cals = 0;

	this.seekTo = function ( idx ) {
		$(selector).data('scrollable').seekTo( idx );
	}

	$(selector).scrollable({ prev: null, next: null, keyboard: false, onSeek: this.calendars_seek });
	if ( initial_index === undefined || isNaN( initial_index ) ) {
		this.seekTo( 0 );
	}
	else {
		this.seekTo( initial_index );
	}
}
ShoppingCalendars.prototype = {
	price_level: function ( price ) {
		var min = this.global_min_lowest(),
			max = this.global_max_lowest();

		if ( max > min ) {
			var spread = max - min,
				pos = price - min,
				level = Math.floor( pos / spread * 6 ) + 1;

			if ( isNaN( level ) ) {
				level = 1;
			}
			else if ( level > 6 ) {
				level = 6;
			}
			else if ( level < 1 ) {
				level = 1;
			}
			return level;
		}
		else {
			return 1;
		}
	},

	current_day_price: function () {
		var day_price = parseInt( $('#calendars td.current .price').text().replace( '\$', '' ) );
		if ( isNaN( day_price ) ) {
			day_price = 0;
		}

		return day_price;
	},

	update_month_price: function ( month, price ) {
		if ( Math.round( price ) > 0 ) {
			$('#month-' + month + ' .price').text( '$' + Math.round( price ) );
		}
		else {
			$('#month-' + month + ' .price').text( '\u00A0' );
		}
	},

	calendars_seek: function ( e, i ) { 	// Scrollable event handler, 'this' will refer to scrollable, not cals.
		if ( $('#select-month .clickable').length >= i ) {
			$('#select-month .clickable.selected').removeClass( 'selected');
			$('#select-month .clickable').eq(i).addClass( 'selected' );

			var month = $('#select-month .clickable').eq(i).attr('id').replace( /^month-/, '' );
			if ( month && $('#cal-'+month).data('cal') ) {
				$('#cal-'+month).data('cal').request_live_calendar();
				if ( this.getSize() > i + 1 ) {
					month = $('#select-month .clickable').eq(i + 1).attr('id').replace( /^month-/, '' );
					if ( month && $('#cal-'+month).data('cal') ) {
						$('#cal-'+month).data('cal').request_live_calendar();
					}
				}
			}
		}
	}
};

function ShoppingCalendar( params, month ) {
	this.params = params;
	this.month = month;

	// Event handler for cells: receives clicked element as 'this' so 'cal' must be used instead.
	var cal = this;
	this.cellClick = function (e) {
		window.triplink_selected_dates = undefined;
		$('#best-prices').data('bp').user_selection_level = 'date';

		select_month( cal.month, cal.cell_date( this ) );
	};
}
ShoppingCalendar.prototype = {
	cell_date: function ( cell ) {
		var day = $('.date', $(cell)).text();
		if ( day < 10 ) {
			day = '0' + day;
		}
		return '' + this.month + day;
	},

	cell_price: function ( cell ) {
		var cell_price = parseInt( $('.price', $(cell)).text().replace( '\$', '' ) );
		if ( isNaN( cell_price ) ) {
			cell_price = 0;
		}

		return cell_price;
	},

	day_price: function ( date ) {
		var month = date.substr( 0, 6 );

		return this.cell_price( '#calendars #cal-'+month+' td#day-'+date );
	},

	lowest_price_dates: function () {
		var cal = this;
		var lowest = { price: undefined, dates: undefined };
		$('#cal-'+cal.month+' td.day').each( function () {
			var day_price = cal.cell_price( this );

			if ( day_price > 0 && ( lowest.price === undefined || day_price <= lowest.price ) ) {
				var day_date = cal.cell_date( this );
				if ( lowest.price !== undefined && day_price == lowest.price ) {
					lowest.dates.push( day_date );
				}
				else {
					lowest.price = day_price;
					lowest.dates = [ day_date ];
				}
			}
		});

		return lowest;
	},

	request_live_calendar: function () {
		var title_cell = $('#cal-'+this.month+' > thead > tr.title > td');
		if ( $('.live', title_cell).size() == 0 ) {
			var cal = this;

			// Shade the current calendar.
			if ( $('#cal-shade').size() == 0 ) {
				$('#calendars > div').prepend( '<div id="cal-shade"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/></div>' );
				$('#cal-shade').animate({ opacity: 0.7 }, 'fast' );
			}

			// Load /livecal into a hidden div.
			var livecal_overrides = { "month": this.month, "date_dep": null };
			var livecal_params = $.extend( new Params(), this.params, livecal_overrides );
			var livecal_url = '/livecal?' + livecal_params.toString();

			++$('#calendars').data('calendars').pending_cals;

			title_cell.append( '<div class="live">live</div>' );
			$('.live', title_cell).load( livecal_url, function () {
				if ( $('#calendars').data('calendars') && --$('#calendars').data('calendars').pending_cals <= 0 ) {
					$('#calendars').data('calendars').pending_cals = 0;
					$('#cal-shade').fadeOut( 'fast', function () { $(this).remove(); update_months_and_bp() } );
				}
			});
		}
	}
};

function update_calendar_prices( dateprices, price_class, iter ) {
	if ( $('#select-month > div.month').length > 0 && $('#calendars table.cal').length > 0 ) {
		var i, $day, cals = $('#calendars').data('calendars');

		// First pass to get global min & max.
		for ( i = 0; i < dateprices.length; ++i ) {
			$day = $('#day-' + dateprices[i].date);
			// Don't overwrite an override price, but always overwrite *with* an override price.
			if ( price_class == 'override' || ! $day.hasClass( 'override' ) ) {
				if ( Math.round( dateprices[i].price ) > 0 ) {
					cals.global_min_lowest( dateprices[i].price );
					cals.global_max_lowest( dateprices[i].price );
				}
			}
		}

		// Second pass to update calendar using new min & max.
		for ( i = 0; i < dateprices.length; ++i ) {
			$day = $('#day-' + dateprices[i].date);
			// Don't overwrite an override price, but always overwrite *with* an override price.
			if ( price_class == 'override' || ! $day.hasClass( 'override' ) ) {
				$day.removeClass( 'livecal cal override' ).addClass( price_class );
				if ( Math.round( dateprices[i].price ) > 0 ) {
					$('.price', $day).text( '$' + Math.round( dateprices[i].price ) );
					$day.removeClass( 'level1 level2 level3 level4 level5 level6' ).addClass( 'level' + cals.price_level( dateprices[i].price ) );
				}
				else {
					$('.price', $day).empty();
					$day.removeClass( 'level1 level2 level3 level4 level5 level6' );
				}
			}
		}

		if ( cals.pending_cals <= 0 ) {
			update_months_and_bp();
		}
	}
	else {
		if ( iter === undefined ) {
			iter = 0;
		}

		if ( iter < 1000 ) {
			window.setTimeout( function () { update_calendar_prices( dateprices, price_class, iter + 1 ) }, 100 );
		}
	}
}

function refresh_calendar_date( params ) {
	var url = '/listings?refresh_calendar_date=1;' + params.toString();
	$('#shopping').append( '<div id="refresh_calendar_date_' + params.date_dep + '" style="display: none"></div>' );
	$('#refresh_calendar_date_' + params.date_dep).load( url );
}

function select_nearest_available_date( target, available, iter ) {
	if ( $('#calendars').length > 0 && $('#calendars').data('calendars') ) {
		var nearest, i, a, target_date, distance, acomp,
			date_re = /(\d\d\d\d)(\d\d)(\d\d)/,
			t = date_re.exec( target );

		if ( t ) {
			t = new Date( t[1], t[2] - 1, t[3] );

			for ( i = 0; i < available.length; ++i ) {
				a = date_re.exec( available[i] );
				if ( a ) {
					a = new Date( a[1], a[2] - 1, a[3] );

					distance = Math.abs( a.getTime() - t.getTime() );
					if ( distance > 80000000 ) {	// Gotta be a day away.
						if ( !nearest || nearest.distance > distance ) {
							nearest = { distance: distance, date: available[i] };
						}
					}
				}
			}

			if ( nearest ) {
				if ( $('#best-prices').data('bp').user_selection_level == 'date' ) {
					select_month( nearest.date.substr( 0, 6 ), nearest.date, { selected_nearest: 1 } );
				}
				else {
					select_month( nearest.date.substr( 0, 6 ) );
				}
			}
			else {
				alert( 'Unable to determine nearest date to ' + t.toString() + ' from ' + available.join( ', ' ) + '.' ); 
			}
		}
	}
	else {
		if ( iter === undefined ) {
			iter = 0;
		}

		if ( iter < 1000 ) {
			window.setTimeout( function () { select_nearest_available_date( target, available, iter + 1 ) }, 100 );
		}
	}
}

function date_dep_from_date( date ) {
	if ( date.constructor == Date ) {
		var month = date.getMonth() + 1;
		var day = date.getDate();
		if ( month < 10 ) {
			month = '0' + month;
		}
		if ( day < 10 ) {
			day = '0' + day;
		}
		return '' + date.getFullYear() + month + day;
	}
}

function first_valid_date_dep( month ) {
	var date, today = new Date();
	if ( month === undefined ) {
		date = today;
	}
	else {
		var mcomp = /(\d\d\d\d)(\d\d)/.exec( month );
		date = new Date( mcomp[1], mcomp[2] - 1, 1 );
	}

	// Minimum date is two days from today.
	if ( ( date.getTime() - today.getTime() ) / 86400000 < 3 ) {
		date.setTime( today.getTime() + 3 * 86400000 );
	}

	return date_dep_from_date( date );
}

function select_month( month, date, listings_params ) {
	if ( !date ) {
		var m_lowest = $('#cal-'+month).data('cal').lowest_price_dates();
		if ( m_lowest.dates === undefined ) {
			date = first_valid_date_dep( month );
		}
		else {
			date = m_lowest.dates[0];
		}
	}

	var params = $('#cal').data('params');
	params.month = month;
	params.date_dep = date;

	$('#calendars').data('calendars').seekTo( $('#select-month .month').index($('#select-month #month-'+month)) );

	select_date( params.date_dep, $.extend( new Params(), params, listings_params ) );
}

function select_date( date, listings_params ) {
	if ( !window.triplink_selected_dates || -1 == $.inArray( date, window.triplink_selected_dates ) ) {
		if ( window.triplink_selected_dates ) {
			window.triplink_selected_dates.push( date );
		}
		else {
			window.triplink_selected_dates = [ date ];
		}

		$('table.cal .clickable.current').removeClass( 'current');
		$('table.cal #day-'+date).addClass( 'current' );

		shopping_listings_please_wait();
		load_shopping_listings( listings_params );
	}
}

function update_months_and_bp() {
	var bp = $('#best-prices').data('bp'),
		cals = $('#calendars').data('calendars'),
		months_lowest = { price: undefined, dates: undefined },
		nothing_selected = true;

	$('#select-month > div.month').each( function () {
		var m = $(this).attr('id').replace( /^month-/, '' );
		var m_lowest = $('#cal-'+m).data('cal').lowest_price_dates();
		cals.update_month_price( m, m_lowest.price );

		if ( m_lowest.price !== undefined && m_lowest.dates !== undefined ) {
			if ( bp.user_selection_level == 'month' ) {
				// Select the first date in m_lowest.
				var params = $('#cal').data('params');
				if ( $(this).hasClass( 'selected' ) && cals.current_day_price() != m_lowest.price ) {
					params.date_dep = m_lowest.dates[0];

					select_date( params.date_dep, params );
					nothing_selected = false;
				}
			}

			if ( months_lowest.price === undefined || m_lowest.price <= months_lowest.price ) {
				if ( months_lowest.price !== undefined && m_lowest.price == months_lowest.price ) {
					$.merge( months_lowest.dates, m_lowest.dates );
				}
				else {
					months_lowest.price = m_lowest.price;
					months_lowest.dates = m_lowest.dates;
				}
			}
		}
	});

	if ( months_lowest.price !== undefined && months_lowest.dates !== undefined ) {
		var sel_cell = bp.selected_cell();

		months_lowest.dates.sort();
		if ( sel_cell && $(sel_cell).data('bp_cell') ) {
			$(sel_cell).data('bp_cell').setPriceDates( months_lowest.price, months_lowest.dates );
		}

		if ( bp.user_selection_level == 'duration' || !bp.user_selection_level  ) {
			// Select the first month and date in months_lowest.
			if ( cals.current_day_price() != months_lowest.price ) {
				var date_dep = months_lowest.dates[0], month = date_dep.substr( 0, 6 );

				select_month( month, date_dep );
				nothing_selected = false;
			}
		}
	}

	if ( nothing_selected && $('#shopping').is(':empty') ) {
		if ( bp.user_selection_level == 'month' ) {
			var i = $('#select-month > div.month').index($('.selected'));
			if ( i < 0 ) {
				i = 0;
			}
			select_month( $('#select-month > div.month').eq(i).attr('id').replace( /^month-/, '' ) );
		}
		else if ( bp.user_selection_level == 'date' && bp.params.date_dep ) {
			select_month( bp.params.date_dep.substr( 0, 6 ), bp.params.date_dep );
		}
		else if ( months_lowest.dates !== undefined ) {
			var date_dep = months_lowest.dates[0], month = date_dep.substr( 0, 6 );

			select_month( month, date_dep );
		}
		else {
			select_month( $('#select-month > div.month').eq(0).attr('id').replace( /^month-/, '' ) );
		}
	}
}

/*
 * Listings overlay, with a modified "apple" effect.
 */
function overlay_init_wrap( api, id, width, height ) {
	var wrap = api.getOverlay().find(".contentWrap");

	wrap.width( width );
	wrap.height( height );
	wrap.empty().append('<iframe id="' + id + '" src="' + api.getTrigger().attr("href") + '" width="100%" height="100%" frameborder="0"/>');
}
function listings_overlay_init() {
	var domain_re = new RegExp('://(.*?)/'),
		domain = this.getTrigger().attr("href").match(domain_re)[1],
		width;

	if ( brochure_width[domain] ) {
		width = brochure_width[domain] + 20;
	}
	else {
		width = Math.floor( $(window).width() * 0.85 );
	}
	overlay_init_wrap( this, 'listings-overlay-iframe', width, Math.floor( $(window).height() * 0.85 ) );
}
function insurance_overlay_init() {
	overlay_init_wrap( this, 'insurance-overlay-iframe', 790, Math.floor( $(window).height() * 0.85 ) );

	if ( this.getTrigger().attr("href").match( /declined\.html$/ ) ) {
		var o = this.getOverlay();
		o.find('.close').css({width: o.width() + 40, height: o.height() + 40, backgroundPosition: 'top right', backgroundRepeat: 'no-repeat'});
	}
	else {
		this.getOverlay().find('.close').css({width: '74px', height: '38px', backgroundPosition: 'top right', backgroundRepeat: 'no-repeat'});
	}
}
// Functions for "newton" effect, based on flowplayer's "apple" effect.
// Uses a div instead of an image.
function getPosition(el) {
	var p = el.offset();
	return {
		top: p.top + el.height() / 2, 
		left: p.left + el.width() / 2
	};
}
function newton_overlay_load(pos, onLoad) {
	var overlay = this.getOverlay();
	var	conf = this.getConf(),
		trigger = this.getTrigger(),
		self = this,
		oWidth = overlay.outerWidth({margin:true}) - 2,
		oHeight = overlay.outerHeight({margin:true}) - 2,
		img = overlay.data("img"),
		position = conf.fixed ? 'fixed' : 'absolute',
		w = $(window);

	// growing image is replaced by a div.
	if (!img) { 
		img = $('<div class="overlay-newton"></div>');
		img.css({display:'none'}).width(oWidth).height(oHeight);
		$('body').append(img); 
		overlay.data("img", img);
	}

	// initial top & left
	var itop = conf.start.top || Math.round(w.height() / 2), 
		ileft = conf.start.left || Math.round(w.width() / 2);

	// override with trigger position
	if (trigger) {
		var p = getPosition(trigger);
		itop = p.top;
		ileft = p.left;
	} 

	// put overlay into final position
	if (conf.fixed) {
		itop -= w.scrollTop();
		ileft -= w.scrollLeft();
	} else {
		pos.top += w.scrollTop();
		pos.left += w.scrollLeft();				
	}

	// initialize background image and make it visible
	img.css({
		position: 'absolute',
		top: itop, 
		left: ileft,
		width: 0,
		height: 0,
		zIndex: conf.zIndex
	}).show();

	pos.position = position;
	overlay.css(pos);

	// begin growing
	img.animate({
			top: overlay.css("top"), 
			left: overlay.css("left"), 
			width: oWidth, height: oHeight
		},
		conf.speed,
		function () {
			// set close button and content over the image
			overlay.css("zIndex", conf.zIndex + 1).fadeIn(conf.fadeInSpeed, function () { 
				if (self.isOpened() && !$(this).index(overlay)) {	
					img.hide();
					onLoad.call(); 
				} else {
					overlay.hide();	
				} 
			});
		}
	).css("position", position);
}
function newton_overlay_close(onClose) {
	// variables
	var overlay = this.getOverlay().hide(), 
		 conf = this.getConf(),
		 trigger = this.getTrigger(),
		 img = overlay.data("img"),

		 css = { 
		 	top: conf.start.top, 
		 	left: conf.start.left, 
		 	width: 'hide',
			height: 'hide'
		 };
	img.show();

	// trigger position
	if (trigger) { $.extend(css, getPosition(trigger)); }

	// change from fixed to absolute position
	if (conf.fixed) {
		img.css({position: 'absolute'})
			.animate({ top: "+=" + $(window).scrollTop(), left: "+=" + $(window).scrollLeft()}, 0);
	}

	// shrink image		
	img.animate(css, conf.closeSpeed, onClose);
}
$.tools.overlay.addEffect( 'newton', newton_overlay_load, newton_overlay_close );
$.extend( $.tools.overlay.conf, {
	effect: 'newton',
	top: 'center',
	mask: { color: null, loadSpeed: 200, opacity: 0.8 }
});

/* Show/hide other room options inside listings */
function listings_show_others_click( e ) {
	$(e.currentTarget).siblings('.others').slideToggle( function () {
		if ( $(this).is(':visible') ) {
			$('span', $(this).siblings('.show-others') ).text( 'Hide Other Room Options' );
			$('img', $(this).siblings('.show-others') ).attr( 'src', '/img/listing-others-up-arrow.png' );
		}
		else {
			$('span', $(this).siblings('.show-others') ).text( 'Show Other Room Options' );
			$('img', $(this).siblings('.show-others') ).attr( 'src', '/img/listing-others-down-arrow.png' );
		}
	});
}

/*
 * Verify and booking form
 */
function load_verify_pricing( params, package_id ) {
	$('#flights').append( '<div class="please-wait"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/><p>Loading...</p></div>' );
	$('#vacation-details #pricing').empty().append( '<div class="please-wait"><img src="/img/ajax-loader.gif" height="32" width="32" alt="spinner"/><p>Loading...</p></div>' );

	// Load pricing and flights based on session.
	$('#flights').load( '/verify-passengers?' + params.toString() + ';package_id=' + package_id, function () {
		occupancy_submit_success();
	});
}
function room_occupancy_submit(e) {
	window.location.replace( $(this).attr('action') + '?' + $(this).serialize() );
	return e.preventDefault();
}
function room_occupancy_init() {
	$('#occ_children').unbind( 'change', occupancy_children_change ).bind( 'change', occupancy_children_change );
	$('#occ_adults, #occ_rooms').unbind( 'change', occupancy_rooms_adults_change ).bind( 'change', occupancy_rooms_adults_change );
	$('#room-occupancy').unbind( 'submit', room_occupancy_submit ).bind( 'submit', room_occupancy_submit );
	occupancy_children_change();
	occupancy_rooms_adults_change();
}

function occupancy_children_change( e ) {
	var adults = Math.round( parseFloat( $('#occ_adults').val() ) ),
		children = Math.round( parseFloat( $('#occ_children').val() ) );

	if ( isNaN( children ) ) {
		$('#occ_children').val( children = 0 );
	}
	else if ( children > 4 ) {
		alert( 'No more than four children are allowed.' );
		$('#occ_children').val( children = 4 );
	}

	if ( isNaN( adults ) ) {
		adults = 0;
	}

	if ( children + adults > 6 ) {
		alert( 'No more than six total passengers are allowed.' );
		$('#occ_children').val( children = 6 - adults );
	}

	if ( children == 0 ) {
		$('#room-occupancy .child-ages').remove();
	}
	else {
		/* While too many boxes, remove the last one. */
		while ( $('#room-occupancy .child-ages select').length > children ) {
			$('#room-occupancy .child-ages select:last').remove();
		}

		/* While not enough boxes, add one to the end. */
		while ( $('#room-occupancy .child-ages select').length < children ) {
			if ( $('#room-occupancy .child-ages select').length == 0 ) {
				var tabindex = parseInt( $('#occ_children').attr('tabindex') ) + 1;
				var html = '<p class="child-ages"><label for="occ_age_1">' + ( children == 1 ? "Child\'s Age:" : "Children's Ages:" ) + '</label> <select id="occ_age_1" name="age_1" tabindex="' + tabindex + '"><option></option>', n;
				for ( n = 0; n <= 15; ++n ) {
					if ( n == 0 ) {
						html += '<option value="0">&lt; 1</option>';
					}
					else {
						html += '<option>' + n + '</option>';
					}
				}
				html += '</select></p>';
				$('#room-occupancy #occupancy-counts').after( html );
			}
			else {
				var tabindex = parseInt( $('#room-occupancy .child-ages select:last').attr('tabindex') ) + 1;
				var html = ' <select id="occ_age_' + ( $('#room-occupancy .child-ages select').length + 1 ) + '" name="age_' + ( $('#room-occupancy .child-ages select').length + 1 ) + '" tabindex="' + tabindex + '"><option></option>', n;
				for ( n = 0; n <= 15; ++n ) {
					if ( n == 0 ) {
						html += '<option value="0">&lt; 1</option>';
					}
					else {
						html += '<option>' + n + '</option>';
					}
				}
				html += '</select>';
				$('#room-occupancy .child-ages select:last').after( html );
			}
		}
	}
}

function occupancy_rooms_adults_change( e ) {
	var adults = Math.round( parseFloat( $('#occ_adults').val() ) ),
		children = Math.round( parseFloat( $('#occ_children').val() ) ),
		rooms = Math.round( parseFloat( $('#occ_rooms').val() ) );

	if ( isNaN( rooms ) || rooms < 1 ) {
		alert( 'At least one room is required.' );
		rooms = 1;
	}
	else if ( rooms > 6 ) {
		alert( 'No more than six rooms are allowed.' );
		$('#occ_rooms').val( rooms = 6 );
	}

	if ( isNaN( adults ) || adults < 1 ) {
		alert( 'At least one adult is required.' );
		adults = 1;
	}
	else if ( adults > 6 ) {
		alert( 'No more than six adults are allowed.' );
		$('#occ_adults').val( adults = 6 );
	}

	if ( isNaN( children ) ) {
		children = 0;
	}
	if ( adults + children > 6 ) {
		alert( 'No more than six total passengers are allowed.' );
		$('#occ_adults').val( adults = 6 - children );
	}

	if ( rooms > adults ) {
		alert( 'Cannot have more rooms than adults.' );
		$('#occ_rooms').val( rooms = adults );
	}

	var occupancy = Math.ceil( ( adults ) / rooms );
	switch ( occupancy ) {
	case 1:
		$('#occ_occupancy').val( 'S' ); break;
	case 3:
		$('#occ_occupancy').val( 'T' ); break;
	case 4:
		$('#occ_occupancy').val( 'Q' ); break;
	case 2:
	default:
		$('#occ_occupancy').val( 'D' );
	}
}

function occupancy_submit_error( pane ) {
	$('#room-occupancy .please-wait').remove();
	$('#vacation-details .please-wait').remove();
}
function occupancy_submit_success( pane ) {
	$('#room-occupancy .please-wait').remove();
	$('#vacation-details').replaceWith( $('#text-content').attr({ id: 'vacation-details' }) );
	$('#vacation-details').show();
	$('a#change-occupancy').overlay({ closeOnClick: false, onBeforeLoad: room_occupancy_init });

	if ( window.outbound_itins ) {
		init_outbounds();
	}
}

function BookingForm( form, cc_types, birthdate_sample, birthdate_datejs_format ) {
	var bf = this, $form = $(form);

	bf.cc_types = cc_types;
	bf.birthdate_sample = birthdate_sample ? birthdate_sample : 'DD / MM / YYYY';
	bf.birthdate_datejs_format = birthdate_datejs_format ? birthdate_datejs_format : 'dd / MM / yyyy';

	bf.handlers = {
		birthdate_focus: function ( e ) {
			var pos = bf.get_position( this );
			var max_pos = $(this).val().replace( /[^0-9]/g, '' ).length;
			max_pos = max_pos + ( max_pos >= 2 ? 3 : 0 ) + ( max_pos >= 4 ? 3 : 0 );

			if ( pos > max_pos ) {
				bf.set_position( this, max_pos );
			}
		},
		birthdate_blur: function ( e ) {
			var val = $(this).val().replace( /[^0-9]/g, '' );
			if ( val.length > 8 ) {
				val = val.slice( 0, 8 );
			}
			val = val.replace( /^([0-9]{4})/, '$1 / ' ).replace( /^([0-9]{2})/, '$1 / ' );

			if ( val.length < 14 ) {
				val += bf.birthdate_sample.slice( val.length );
			}
			$(this).val( val );
			$(this).change();
		},
		birthdate_keypress: function ( e ) {
			if ( e.which == 46 ) {
				return e.preventDefault();
			}
			else if ( e.shiftKey || e.metaKey || e.ctrlKey || e.altKey || e.which == 13 || e.which == 0 || e.which == 9 || ( e.which >= 33 && e.which <= 40 ) ) {
				return true;
			}
			else if ( e.which == 8 ) {
				return e.preventDefault();
			}
			else if ( e.which >= 48 && e.which <= 57 || e.which >= 96 && e.which <= 105 ) {
				return e.preventDefault();
			}
			else {
				return e.preventDefault();
			}
		},
		birthdate_keyup: function ( e ) {
			if ( e.which == 46 ) {
				$(this).val( bf.birthdate_sample );

				bf.set_position( this, 0 );

				return e.preventDefault();
			}
			else if ( e.shiftKey || e.metaKey || e.ctrlKey || e.altKey || e.which == 13 || e.which == 0 || e.which == 9 || ( e.which >= 33 && e.which <= 40 ) ) {
				return true;
			}
			else if ( e.which == 8 ) {
				var pos = bf.get_position( this );

				if ( pos > 2 && pos <= 5 ) {
					pos = 2;
				}
				else if ( pos > 7 && pos <= 10 ) {
					pos = 7;
				}

				$(this).val( ( pos == 0 ? '' : $(this).val().slice( 0, pos - 1 ) ) + bf.birthdate_sample.charAt( pos - 1 ) + $(this).val().slice( pos ) );

				bf.set_position( this, pos - 1);

				return e.preventDefault();
			}
			else if ( e.which >= 48 && e.which <= 57 || e.which >= 96 && e.which <= 105 ) {
				var pos = bf.get_position( this );

				if ( pos > $(this).attr('maxlength') - 1 ) {
					return e.preventDefault();
				}

				if ( pos >= 2 && pos < 5 ) {
					pos = 5;
				}
				else if ( pos >= 7 && pos < 10 ) {
					pos = 10;
				}
				$(this).val( $(this).val().slice( 0, pos ) + ( e.which < 96 ? e.which - 48 : e.which - 96 ) + $(this).val().slice( pos + 1 ) );

				if ( pos + 1 >= 2 && pos + 1 < 5 ) {
					pos = 4;
				}
				else if ( pos + 1 >= 7 && pos + 1 < 10 ) {
					pos = 9;
				}
				bf.set_position( this, pos + 1);

				return e.preventDefault();
			}
			else {
				return e.preventDefault();
			}
		},
		credit_card_change: function ( e ) {
			var type = bf.cc_types.type_from_number( $(this).val() );

			bf.cc_types.update_card_icon( type );

			if ( type && type instanceof CreditCardType ) {
				$('#cc_type1', $form).val( type.id );
				if ( $('#cc_security_code1', $form).val() || $('#cc_security_code1', $form).hasClass('error') ) {
					$form.validate().element( '#cc_security_code1' );
				}
			}
			else {
				$('#cc_type1', $form).val( '' );
			}
		},
		address_change: function ( e ) {
			if ( $('#ship_same:checked', $form).val() ) {
				// Same--update ship_ counterpart with new value.
				var $target = $('#ship_'+$(this).attr('id'), $form);
				$target.val( $(this).val() );

				if ( $target.val() || $target.hasClass('error') ) {
					$form.validate().element('#ship_'+$(this).attr('id'));
				}
			}
		},
		ship_same_change: function ( e ) {
			var validate = $form.validate();

			if ( $('#ship_same:checked', $form).val() ) {
				// Checked--populate with same values and disable.
				$('#ship_address', $form).val(		$('#address', $form).val() 	);
				$('#ship_city', $form).val(   		$('#city', $form).val() 		);
				$('#ship_state', $form).val(		$('#state', $form).val() 		);
				$('#ship_postal_code', $form).val(	$('#postal_code', $form).val() );

				$('#ship_address, #ship_city, #ship_state, #ship_postal_code', $form).attr( 'disabled', 'disabled' );
			}
			else {
				// Not checked--enable.
				$('#ship_address, #ship_city, #ship_state, #ship_postal_code', $form).removeAttr( 'disabled' );
			}

			if ( $('#ship_address', $form).val() || $('#ship_address', $form).hasClass('error') ) {
				validate.element('#ship_address');
			}
			if ( $('#ship_city', $form).val() || $('#ship_city', $form).hasClass('error') ) {
				validate.element('#ship_city');
			}
			if ( $('#ship_state', $form).val() || $('#ship_state', $form).hasClass('error') ) {
				validate.element('#ship_state');
			}
			if ( $('#ship_postal_code', $form).val() || $('#ship_postal_code', $form).hasClass('error') ) {
				validate.element('#ship_postal_code');
			}
		},
	};

	// Set up custom handlers, outside of validation routines
	$('input.birthdate', $form)
		.keypress( bf.handlers.birthdate_keypress ).keydown( bf.handlers.birthdate_keypress ).keyup( bf.handlers.birthdate_keyup )
		.focus( bf.handlers.birthdate_focus ).blur( bf.handlers.birthdate_blur );
	$('#number1', $form).keyup( bf.handlers.credit_card_change ).change( bf.handlers.credit_card_change );
	$('#ship_same', $form).change( bf.handlers.ship_same_change );
	$('#address, #city, #state, #postal_code', $form).change( bf.handlers.address_change );

	$.validator.addMethod("birthdate_required", function(value, element) {
		var ret = value && value != bf.birthdate_sample;
		return ret;
	}, 'This field is required.');
	$.validator.addMethod("birthdate_format", function(value, element) {
		var ret;
		if (value == bf.birthdate_sample) {
			ret = true;
		} else {
			ret = Date.parseExact(value, bf.birthdate_datejs_format);
		}
		return ret;
	}, 'Please enter a valid date in the form ' + bf.birthdate_sample);
	$.validator.addMethod("birthdate_reasonable", function(value, element) {
		var ret, bd;
		if (value == bf.birthdate_sample) {
			ret = true;
		} else {
			bd = Date.parseExact(value, bf.birthdate_datejs_format);
			if ( !bd ) {
				ret = true;
			}
			else {
				ret = bd.between( Date.today().addYears(-130), Date.today() );
			}
		}
		return ret;
	}, 'Please enter a reasonable birth date.');
	$.validator.addClassRules("birthdate", { birthdate_required: true, birthdate_format: true, birthdate_reasonable: true });

	$.validator.addMethod("first_last", function(value, element, param) {
		var ret, other_name_length = bf.letters_length( $(param).val() );
		switch ( $('#tour_op').val() ) {
		case 'NOL':
		case 'VAT':
			ret = bf.letters_length( value ) <= 28 - other_name_length;
			break;
		case 'VAC':
			ret = bf.letters_length( value ) <= 24 - other_name_length;
			break;
		default:
			ret = bf.letters_length( value ) <= 20;
		}
		return ret;
	}, 'First name or last name are too long.');
	$.validator.addMethod("sec_code", function(value, element) {
		var ret;
		switch ( $('#cc_type1').val() ) {
		case 'MC':
		case 'VI':
			ret = value.length == 3;
			break;
		case 'AX':
			ret = value.length == 4;
			break;
		}
		return ret;
	}, 'Security code must be 3 digits for Visa or MasterCard and 4 digits for American Express.');
	$.validator.addMethod("not_expired", function ( value, element ) {
		if ( $('#cc_ex_m1').val() && $('#cc_ex_y1').val() ) {
			var exp_month = $('#cc_ex_m1').val(), exp_year = $('#cc_ex_y1').val(), days_in_exp_month = 32 - new Date( exp_year, exp_month - 1, 32 ).getDate();
			return new Date( exp_year, exp_month - 1, days_in_exp_month ) >= new Date();
		}
		else {
			return true;
		}
	}, 'Please use a card that has not expired.' );
	$.validator.addMethod("credit_card_type", function ( value, element ) {
		var type = bf.cc_types.type_from_number( value );

		bf.cc_types.update_card_icon( type );

		if ( !value || value.length < 2 ) {
			return true;
		}
		else if ( type && type instanceof CreditCardType ) {
			if ( bf.cc_types.type_is_valid( type ) ) {
				return true;
			}
		}

		return false;
	}, 'That type of card is not accepted.  We only accept ' + bf.cc_types.valid_type_names() + '.');
	$.validator.addClassRules("required_cc_number", { required: true, credit_card_type: true, creditcard: true });

	$.validator.addMethod("postal_or_zip", function ( value, element, param ) {
		if ( !value ) {
			return true;
		}

		if ( !$(param).length ) {
			return true;
		}

		return /^[ABCEGHJKLMNPRSTVXYabceghjklmnprstvxy][_\W]*\d[_\W]*[A-Za-z][_\W]*[- ]?[_\W]*\d[_\W]*[A-Za-z][_\W]*\d[_\W]*$/.test( value ) || /^\d{5}(-\d{4})?$/.test( value );
	}, 'Please enter a valid postal or zip code.' );

	bf.rules = {
		cc_security_code1: { required: true,                   sec_code: true },
		postal_code:       { required: true,                   postal_or_zip: true },
		ship_address:      { required: "#ship_same:unchecked" },
		ship_city:         { required: "#ship_same:unchecked" },
		ship_state:        { required: "#ship_same:unchecked" },
		ship_postal_code:  { required: "#ship_same:unchecked", postal_or_zip: "#ship_same:unchecked" }
	};
	$('[id^=first_name]').each( function () {
		var id = $(this).attr('id');
		var suff = id.match(/([0-9]$)/)[1];
		bf.rules[id] = { first_last: '#last_name'+suff };
	});
	$('[id^=last_name]').each( function () {
		var id = $(this).attr('id');
		var suff = id.match(/([0-9]$)/)[1];
		bf.rules[id] = { first_last: '#first_name'+suff };
	});
	$form.validate({
		rules: bf.rules,
		groups: {
			expiry: "cc_ex_m1 cc_ex_y1"
		},
		messages: {
			agree: "You must agree to the Terms & Conditions."
		},
		errorPlacement: function (error, element) {
			if ( element.attr('id') == 'agree' ) {
				element.closest('p').attr( 'title', error.text() );
			}
			else {
				element.attr( 'title', error.text() );
			}
		},
		highlight: function (element, errorClass) {
			if ( $(element).attr('id') == 'agree' ) {
				$(element).closest('p').addClass( errorClass );
			}
			else {
				$(element).addClass( errorClass );
			}
		},
		unhighlight: function (element, errorClass) {
			if ( $(element).attr('id') == 'agree' ) {
				$(element).closest('p').removeClass( errorClass );
				$(element).closest('p').removeAttr( 'title' );
			}
			else {
				$(element).removeClass( errorClass );
				$(element).removeAttr( 'title' );
			}
		},
		ignoreTitle: true
	});

	// Manage initial states
	$('input.birthdate', $form).each( function() {
		if ( !$(this).val() ) {
			$(this).val( bf.birthdate_sample );
		}
	});
	$('#number1', $form).change();
	$('#ship_same', $form).change();
}
BookingForm.prototype = {
	letters_length: function ( string ) {
		return string.replace( /[^A-Za-z]+/g, '' ).length;
	},
	get_position: function (ctrl) {
		var elem = ctrl.jquery ? ctrl[0] : ctrl;

		var position = 0;
		// IE Support
		if ( document.selection ) {
			elem.focus();
			var Sel = document.selection.createRange();

			Sel.moveStart('character', -elem.value.length);

			position = Sel.text.length;
		}
		// Firefox support
		else if (elem.selectionStart || elem.selectionStart == '0') {
			position = elem.selectionStart;
		}

		return position;
	},
	set_position: function (ctrl, pos) {
		var elem = ctrl.jquery ? ctrl[0] : ctrl;

		if(elem.setSelectionRange) {
			elem.focus();
			elem.setSelectionRange(pos,pos);
		}
		else if (elem.createTextRange) {
			var range = elem.createTextRange();
			range.collapse(true);
			range.moveEnd('character', pos);
			range.moveStart('character', pos);
			range.select();
		}
	}
};

function CreditCardType( id, name, prefixes, icon ) {
	this.id = id;
	this.name = name;
	this.prefixes = prefixes;
	this.icon = icon;
}
CreditCardType.prototype = {
	matches_prefix: function ( prefix ) {
		return -1 != $.inArray( prefix, this.prefixes );
	}
};
function CreditCardTypes( valid_type_ids, type_icons ) {
	var types = this;

	types.all_types = [
		new CreditCardType( 'DI', 'Diner\'s Club',    [ '30', '36', '38' ], type_icons.DI ),
		new CreditCardType( 'VI', 'VISA',             [ '40', '41', '42', '43', '44', '45', '45', '46', '47', '48',  ], type_icons.VI ),
		new CreditCardType( 'MC', 'MasterCard',       [ '51', '52', '53', '54', '55' ], type_icons.MC ),
		new CreditCardType( 'AX', 'American Express', [ '34', '37' ], type_icons.AX ),
		new CreditCardType( 'DC', 'Discover',         [ '60' ], type_icons.DC ),
	];
	types.valid_type_ids = valid_type_ids;
}
CreditCardTypes.prototype = {
	type_from_prefix: function ( prefix ) {
		var i;
		for ( i in this.all_types ) {
			if ( this.all_types[i] instanceof CreditCardType && this.all_types[i].matches_prefix( prefix ) ) {
				return this.all_types[i];
			}
		}
		return undefined;
	},
	type_from_number: function ( number ) {
		var digits_only = number.replace( /\D+/g, '' );
		if ( digits_only.length >= 2 ) {
			var prefix = digits_only.substr( 0, 2 );
			return this.type_from_prefix( prefix );
		}
		return undefined;
	},
	type_id_is_valid: function ( type_id ) {
		return -1 != $.inArray( type_id, this.valid_type_ids );
	},
	type_is_valid: function ( type ) {
		return type && type instanceof CreditCardType && this.type_id_is_valid( type.id );
	},
	valid_type_names: function () {
		var msg, i, valids = [];
		for ( i = 0; i < this.all_types.length; ++i ) {
			if ( this.type_is_valid( this.all_types[i] ) ) {
				valids.push( this.all_types[i] );
			}
		}
		if ( valids.length > 0 ) {
			msg = valids[0].name;
			for ( i = 1; i < valids.length - 1; ++i ) {
				msg += ', ' + valids[i].name;
			}
			if ( valids.length - 1 == i ) {
				msg += ' and ' + valids[i].name;
			}
		}
		else {
			msg = 'cash'; // :-D
		}
		return msg;
	},
	update_card_icon: function ( type ) {
		if ( this.type_is_valid( type ) ) {
			// Display just the given type.
			$('#card-icons').html( '<img src="' + type.icon + '" width="44" height="27" alt="' + type.name + '"/> ' );
		}
		else {
			var i;
			// List all valid types.
			$('#card-icons').empty();
			for ( i = 0; i < this.all_types.length; ++i ) {
				if ( this.type_is_valid( this.all_types[i] ) ) {
					$('#card-icons').append( '<img src="' + this.all_types[i].icon + '" width="44" height="27" alt="' + this.all_types[i].name + '"/> ' );
				}
			}
		}
	}
};

/*
 * Flight selection
 */
var outboundIndexRE = /outbound-([0-9]+)/;
var inboundIndexRE = /inbound-([0-9]+)/;

function flight_date_time( d, t ) {
	var dcomp = /(\d\d\d\d)(\d\d)(\d\d)/.exec( d );
	var tcomp = /(\d?\d)(\d\d)/.exec( t );

	if ( dcomp && tcomp ) {
		return new Date( dcomp[1], dcomp[2] - 1, dcomp[3], tcomp[1], tcomp[2] );
	}
	else {
		return null;
	}
}

function duration_string( minutes, compact ) {
	// Unfortunately, it seems flight durations sometimes come out negative,
	// which messes up the floor function, so we need to use the absolute value
	// and reincorporate the sign later.
	var negative = false;
	if ( minutes < 0 ) {
		negative = true;
		minutes = -minutes;
	}

	var d = Math.floor( minutes / 1440 );
	var h = Math.floor( minutes % 1440 / 60 );
	var m = minutes % 60;

	var str = '';
	if ( compact ) {
		if ( d > 0 ) {
			str = ( negative ? '-' : '' ) + d + 'd' + h + ':' + ( m < 10 ? '0' : '' ) + m; 
		}
		else if ( h > 0 ) {
			str = ( negative ? '-' : '' ) + h + ':' + ( m < 10 ? '0' : '' ) + m; 
		}
		else {
			str = ( negative ? '-' : '' ) + m + ' ' + ( m == 1 ? 'minute' : 'minutes' );
		}
	}
	else {
		if ( d > 0 ) {
			str += d + ' ' + ( d == 1 ? 'day' : 'days' );
		}
		if ( h > 0 ) {
			if ( str.length > 0 ) {
				str += ', ';
			}
			str += h + ' ' + ( h == 1 ? 'hr' : 'hrs' );
		}
		if ( m > 0 ) {
			if ( str.length > 0 ) {
				str += ', ';
				str += m + ' ' + ( m == 1 ? 'min' : 'mins' );
			}
			else {
				str += m + ' ' + ( m == 1 ? 'minute' : 'minutes' );
			}
		}

		if ( str == '' ) {
			str = '0';
		}
		else if ( negative ) {
			str = '-' + str;
		}
	}

	return str;
}

function format_time( dt ) {
	if ( dt ) {
		var h = dt.getHours();
		var m = dt.getMinutes();

		var ap = h < 12 ? 'am' : 'pm';
		if ( h > 12 ) {
			h -= 12;
		}
		else if ( h == 0 ) {
			h = 12;
		}

		return h + ':' + ( m < 10 ? '0' : '' ) + m + ap;
	}
	else {
		return '-invalid-';
	}
}

function add_itinerary( idx, itin, inout ) {
	// Add a template div to the add container, and retain a reference to it.
	var $container = $('#' + inout + ' .add');
	$container.empty();
	var $tmpl = $container.append( '<div id="' + inout + '-' + idx + '" class="itinerary"><'+'/div>' ).children( 'div' );

	for ( var i in itin.flights ) {
		var fl = itin.flights[i];

		var flight_dep = flight_date_time( fl.DATE_DEP, fl.TIME_DEP );
		var flight_arr = flight_date_time( fl.DATE_ARR, fl.TIME_ARR );

		var flight_time = duration_string( fl.DURATION_MINUTES );

		var next_day = '';
		if ( +fl.DATE_DEP < +fl.DATE_ARR ) {
			if ( fl.ORIG_DATE_ARR ) {
				next_day = ' (next day, corrected)';
			}
			else {
				next_day = ' (next day)';
			}
		}

		$tmpl.append( '<div class="code">' + fl.AIRLINE + ' ' + fl.FLIGHT_NO + '<'+'/div>'
			+ '<div>' + fl.AIRPORT_DEP_NAME + ' (' + fl.AIRPORT_DEP + ') Dep: ' + format_time( flight_dep ) + '<'+'/div>'
			+ '<div>' + fl.AIRPORT_ARR_NAME + ' (' + fl.AIRPORT_ARR + ') Arr: ' + format_time( flight_arr ) + next_day + '<'+'/div>'
		);

		if ( fl.VIA_ARP ) {
			$tmpl.append( '<div>Via ' + fl.VIA_ARP_NAME + ' (' + fl.VIA_ARP + ')<'+'/div>' );
		}

		$tmpl.append( '<div>Duration: ' + flight_time + '<'+'/div>' );
	}

	var total_time = duration_string( itin.total_duration_minutes );
	$tmpl.prepend( '<div class="total">Total time: ' + total_time + '<'+'/div>' );
	$tmpl.addClass( 'clickable' );

	$('#' + inout + ' .scrollable .items').append($tmpl.clone());

	var $itinDiv = $('#' + inout + ' .scrollable .items' ).children('div.itinerary:last');

	$itinDiv.bind( 'click', inout == 'outbound' ? select_outbound_itinerary : select_inbound_itinerary );
}

function select_outbound_itinerary( e ) {
	// Update selection.
	$('#outbound .itinerary').removeClass( 'selected' );
	$(this).addClass( 'selected' );

	show_inbound_flights( $(this).attr('id') );
}

function select_inbound_itinerary( e ) {
	// Update selection.
	$('#inbound .itinerary').removeClass( 'selected' );
	$(this).addClass( 'selected' );
	
	save_flight_selections();
}

function show_inbound_flights( itin_id ) {
	// Remove and replace scrollable element.
	$('#inbound .scrollable').remove();
	$('#inbound .next, #inbound .prev').addClass( 'disabled' );
	$('#inbound .prev').after( '<div class="scrollable vertical"><div class="items"></div></div>' );

	// Find which one was clicked.
	var idx = outboundIndexRE.exec( itin_id )[1];

	var count = 0;
	for ( var i in itin_options ) {
		if ( itin_options[i].out_idx == idx ) {
			++count;
			add_itinerary( itin_options[i].in_idx, inbound_itins[itin_options[i].in_idx], 'inbound' );
		}
	}

	if ( count > 1 ) {
		$('#inbound .next, #inbound .prev').removeClass( 'disabled' );
	}

	// initialize scrollable  
	$("#inbound .scrollable").scrollable({ 
		vertical:true,  
		mousewheel:true
	});
}

function init_outbounds() {
	var count = 0;
	for ( var i in outbound_itins ) {
		++count;
		add_itinerary( i, outbound_itins[i], 'outbound' );
	}

	if ( count > 1 ) {
		$('#outbound .next, #outbound .prev').removeClass( 'disabled' );
	}

	// initialize scrollable  
	$("#outbound .scrollable").scrollable({ 
		vertical:true,  
		mousewheel:true
	});
}

function save_flight_selections() {
	var $out_sel = $('#outbound .itinerary.selected');
	var $in_sel = $('#inbound .itinerary.selected');

	var out_idx = outboundIndexRE.exec( $out_sel.attr( 'id' ) )[1];
	var in_idx = inboundIndexRE.exec( $in_sel.attr( 'id' ) )[1];

	var value;
	for ( var i in itin_options ) {
		if ( itin_options[i].out_idx == out_idx && itin_options[i].in_idx == in_idx ) {
			// This is our selection.
			value = itin_options[i].opt_value;
			break;
		}
	}

	$('#flight-selection form input[name="flight_selection"]').val( value );
	$('#flight-selection form input[name="flight_selection"]').change();

	show_selected_flights();
}

function show_selected_flights( done_fn ) {
	clear_selected_flights();

	var $out_sel = $('#outbound .itinerary.selected');
	var $in_sel = $('#inbound .itinerary.selected');

	var out_idx = outboundIndexRE.exec( $out_sel.attr( 'id' ) )[1];
	var in_idx = inboundIndexRE.exec( $in_sel.attr( 'id' ) )[1];

	var out_flights = outbound_itins[out_idx].flights;
	var in_flights = inbound_itins[in_idx].flights;

	var $flights_tbody = $('#selected-flights > table > tbody');
	var rows = Math.max( out_flights.length, in_flights.length );
	for ( var i = 0; i < rows; ++i ) {
		var html = '<tr>';

		html += '<td>&nbsp;</td>';

		if ( i < out_flights.length ) {
			html += '<td>' + out_flights[i].AIRLINE + ' ' + out_flights[i].FLIGHT_NO + '</td>';
			html += '<td>' + out_flights[i].AIRPORT_DEP_NAME + ' (' + out_flights[i].AIRPORT_DEP + ')<br/>Dep: ' + out_flights[i].TIME_DEP + '</td>';
			html += '<td>' + out_flights[i].AIRPORT_ARR_NAME + ' (' + out_flights[i].AIRPORT_ARR + ')';
			if ( out_flights[i].VIA_ARP ) {
				html += ' (via ' + out_flights[i].VIA_ARP + ')';
			}
			html += '<br/>Arr: ' + out_flights[i].TIME_ARR + '</td>';
		}
		else {
			html += '<td colspan="3">&nbsp;</td>';
		}

		html += '<td>&nbsp;</td>';
		html += '<td>&nbsp;</td>';

		if ( i < in_flights.length ) {
			html += '<td>' + in_flights[i].AIRLINE + ' ' + in_flights[i].FLIGHT_NO + '</td>';
			html += '<td>' + in_flights[i].AIRPORT_DEP_NAME + ' (' + in_flights[i].AIRPORT_DEP + ')<br/>Dep: ' + in_flights[i].TIME_DEP + '</td>';
			html += '<td>' + in_flights[i].AIRPORT_ARR_NAME + ' (' + in_flights[i].AIRPORT_ARR + ')';
			if ( in_flights[i].VIA_ARP ) {
				html += ' (via ' + in_flights[i].VIA_ARP + ')';
			}
			html += '<br/>Arr: ' + in_flights[i].TIME_ARR + '</td>';
		}
		else {
			html += '<td colspan="3">&nbsp;</td>';
		}

		html += '</tr>';

		$flights_tbody.append( html );
	}

	$('#selected-flights').show();
}

function clear_selected_flights() {
	$('#selected-flights > table > tbody').empty();
	$('#selected-flights').hide();
}

function ChangeInsurancePaymentOverlay( trigger_selector, var_map, cc_selector, addr_selector, error_fields ) {
	var cipo = this;

	cipo.credit_card_html = function() {
		var type = $('#overlay_cc_type1 option:selected').text(), number = cipo.cc_format( $('#overlay_number1').val() ), name = $('#overlay_cc_name1').val();
		return type + ' ' + number + '<br/>' + name;
	};
	cipo.billing_address_html = function() {
		var address = $('#overlay_ship_address').val(), city = $('#overlay_ship_city').val(), state = $('#overlay_ship_state').val(), postal = $('#overlay_ship_postal_code').val();
		return address + '<br/>' + city + ', ' + state + '\u00a0 ' + postal;
	};
	cipo.submit_handler = function (e) {
		var k;

		for ( k in var_map ) {
			$('#'+k).val( $('#'+var_map[k]).val() );
		}
		$(cc_selector).html( cipo.credit_card_html() );
		$(addr_selector).html( cipo.billing_address_html() );

		$(trigger_selector).data('overlay').close();

		return e.preventDefault();
	};
	cipo.cancel_click_handler = function (e) {
		$(trigger_selector).data('overlay').close();
	};
	cipo.init_handler = function () {
		var k, o = this.getOverlay();

		o.find('form').unbind( 'submit', cipo.submit_handler ).bind( 'submit', cipo.submit_handler );
		o.find('#cancel-button').unbind( 'click', cipo.cancel_click_handler ).bind( 'click', cipo.cancel_click_handler );

		if ( error_fields ) {
			var msg = '';
			for ( k in var_map ) {
				if ( $.inArray( k, error_fields ) != -1 ) {
					msg += '\toriginal ' + k + ', overlay ' + var_map[k] + ' -- IN\n';
					$('#'+var_map[k]).addClass( 'error' );
				}
				else {
					msg += '\toriginal ' + k + ', overlay ' + var_map[k] + ' -- NOT IN\n';
					$('#'+var_map[k]).val( $('#'+k).val() );
				}
			}
			alert( 'error_fields:\n' + msg );
			error_fields = undefined;
		}
		else {
			for ( k in var_map ) {
				$('#'+var_map[k]).val( $('#'+k).val() );
			}
		}
	};
	cipo.cc_format = function( number ) {
		// Format different credit card numbers according to how it looks on their sample cards.
		var parts = [ number.substr( 0, 4 ) ];
		switch ( number.length ) {
		case 13:
			parts.push( number.substr( 4, 5 ), number.substr( 9, 4 ) );
			break;
		case 14:
			parts.push( number.substr( 4, 6 ), number.substr( 10, 4 ) );
			break;
		case 15:
			parts.push( number.substr( 4, 6 ), number.substr( 10, 5 ) );
			break;
		case 16:
			parts.push( number.substr( 4, 4 ), number.substr( 8, 4 ), number.substr( 12, 4 ) );
			break;
		}

		return parts.join( ' ' );
	};

	if ( error_fields ) {
		$(trigger_selector).overlay({ closeOnClick: false, onBeforeLoad: cipo.init_handler, load: true });
	}
	else {
		$(trigger_selector).overlay({ closeOnClick: false, onBeforeLoad: cipo.init_handler });
	}
}

function login_form_submit() {
	$.ajax({
		type: "POST",
		url: "/login",
		data: $('#loginform').serializeArray(),
		dataType: "json",
		success: function (data, textStatus, jqXHR) {
			if (data.username != null) {
				$('#login').css('display', 'none');
				$('#logout').css('display', 'block');
				$('#user').text(data.username);
			}
		},
		async: false
	});
	parent.$.fn.colorbox.close();
	return false;
}

function logout_user() {
	$.ajax({
		type: "POST",
		url: "/logout",
		success: function (data) {
			$('#login').css('display', 'block');
			$('#logout').css('display', 'none');
		},
		async: false
	});
}

