function alz(s, len) {
	var s = s.toString();
	while (s.length < len)
		s = "0" + s;
	return s;
}

var Fed = { 
	availability: function(hotel_id, arrive, depart, rooms, adults, children, callback) {
		arrive = Fed.dateToSQL(arrive);
		if (arrive.length != 10)
			return alert("Fed.availability(): bad arrive date " + arrive);
		depart = Fed.dateToSQL(depart);
		if (depart.length != 10)
			return alert("Fed.availability(): bad depart date " + depart);
		$.get("/favorites_avail",
			{ arrive: arrive,
				depart: depart,
				hotel_id: hotel_id,
				rooms: rooms,
				adults: adults,
				children: children
			},
			function(data) {
				if (data.match(/yes.*/i)) {
					var parts = data.split(";");
					var price = parts[1];
					var sale = parts[2];
					callback(true, hotel_id, price, sale);
				}
				else if (data == "no") {
					callback(false, hotel_id, 0, 0);
				} 
				else {
					alert(data);
				}
			}
		);
	},

	cookie: function(name, val) {
	  if (typeof $.cookie == "undefined")
	    return false;
		if (typeof val == "undefined") {
			return $.cookie(name);
		} else {
			return $.cookie(name, val, { expires: 365, path: "/" });
		}
	},
	
	dateToObject: function(date) {
		// 2009-03-31 tlack: try to use Javascript's built-in date formatting
		// in our favor.. old method below:
		return new Date(Fed.dateToSlashed(date));

		var date = Fed.dateToSlashed(date).split(/\//);
		var o = new Date();
		o.setMonth(date[0]-1);
		o.setDate(date[1]);
		o.setYear(date[2]);
		if (Fed.debug) Fed.debug("dateToObject: " + date);
		return o;
	},

	dateToSQL: function(date) {
		var d = date.replace(/[\/-]/g, "");
		d = d.substr(4,4) + "-" + d.substr(0,2) + "-" + d.substr(2,2);
		return d;
	},

	dateToPlain: function(date) {
		var d = date.replace(/[\/-]/g, "");
		d = d.substr(0,2) + d.substr(2,2) + d.substr(4,4);
		return d;
	},

	dateToSlashed: function(date) {
		var d = date.replace(/[\/-]/g, "");
		d = d.substr(0,2) + "/" + d.substr(2,2) + "/" + d.substr(4,4);
		return d;
	},

	Dates: {
		add: function(date, days) {
			var o = Fed.dateToObject(date);
			o.setDate(o.getDate() + days);
			return Fed.Dates.fromObject(o);
		},
		day: function(d) {
			return Fed.dateToPlain(d).substr(2,2);
		},
		fmt: function(d, fmt) {
			if (Fed.Dates.fmts[fmt])
				fmt = Fed.Dates.fmts[fmt];
			return Fed['dateTo' + fmt](d);
		},
		fmts: { "plain": "Plain", "sql": "SQL", "slashed": "Slashed" },
		fromObject: function(obj, fmt) {
			var s = alz(obj.getMonth()+1, 2) + "/" + alz(obj.getDate(),2) + "/" + obj.getFullYear();
			return Fed.Dates.fmt(s, fmt ? fmt : "slashed");
		},
		gt: function(d1, d2) {
			return Fed.dateToSQL(d1) > Fed.dateToSQL(d2);
		},
		lt: function(d1, d2) {
			return Fed.dateToSQL(d1) < Fed.dateToSQL(d2);
		},
		mo: function(d) {
			return Fed.dateToPlain(d).substr(0,2);
		},
		sqlToSlashed: function(dstr) {
  		var d = dstr.replace(/[\/-]/g, "");
  		d = d.substr(2,2) + "/" + d.substr(4,2) + "/" + d.substr(0,4);
  		return d;
		},
		sqlToSlashedMoYr: function(dstr) {
  		var d = dstr.replace(/[\/-]/g, "");
  		d = d.substr(2,2) + "/" + d.substr(0,4);
  		return d;
		},
		today: function(fmt) {
			var t = new Date();
			return Fed.Dates.fromObject(t);
		},
		valid: function(date) {
			return Fed.Dates.validSlashed(date) || Fed.Dates.validPlain(date);
		},
		validPlain: function(date) {
			return date.match(/^[0-9]{8}$/) && Fed.Dates.mo(date) <= 12 && Fed.Dates.day(date) <= 31;
		},
		validSlashed: function(date) {
			return date.match(/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/) && Fed.Dates.mo(date) <= 12 && Fed.Dates.day(date) <= 31;
		},
		year: function(d) {
			return Fed.dateToPlain(d).substr(4,4);
		}
	},

	debug: function(args) {
		if (Fed.cookie("debug_mode"))
			alert("debug_mode: " + args);
	},
	
	Faves: {
		add: function(property) {
			var cur = Fed.Faves.get();
			if (!Fed.Faves.has(property))
				cur.push(property);
			Fed.Faves.set(cur);
		},
		count: function() {
			return Fed.Faves.get().length;
		},
		get: function() {
			var f = Fed.cookie("faves");
			if (f)
				return $.map(f.split(","), function(item) { return !item ? null : item });
			else
				return [];
		},
		has: function(property) {
			var cur = Fed.Faves.get();
			for (var i in cur)
				if (cur[i] == property)
					return true;
			return false;
		},
		refreshHeader: function() {
			if (Fed.Faves.count()) {
				$("#header-my-favorites").html("My Favorites (" + Fed.Faves.count() + ")");
			} else {
				$("#header-my-favorites").html("My Favorites");
			}
		},
		remove: function(property) {
			var cur = Fed.Faves.get();
			fmap(cur, function(val, idx) {
				if (val == property) 
					delete cur[idx];
			});
			Fed.Faves.set(cur);
		},
		set: function(list) {
			if (!list || list.length == 0)
				Fed.cookie("faves", null);
			else
				Fed.cookie("faves", list.join(","));
		}
	},
	
	filter: function(elems, fn) {
		return Fed.select(elems, fn);
	},

	Fold: {
		toggle: function(id) {
			if ($("#fold-" + id).is(".foldable-open")) {
				$("#fold-" + id + "-contents").hide("slow");
				$("#fold-" + id).addClass("foldable-closed").removeClass("foldable-open");
			} else {
				$("#fold-" + id + "-contents").show("slow");
				$("#fold-" + id).addClass("foldable-open").removeClass("foldable-closed");
			}
		}
	},
	
	formToArray: function(formId) {
	  var els = {};
	  $("#"+formId+" :input").each(function(idx,ele) {

	    if (ele.type && ele.type == "checkbox" && !ele.checked) {
	      els[ele.name] = false;
      } else {
  		  els[ele.name] = $(this).val();
      }
    });	  
    return els;
	},
	
	Html: {
		loading: "<img src=/assets/shared/loading.gif/> Loading..",
		loadingCentered: "<div style=\"padding:1em;text-align:center;\"><img src=/assets/shared/loading.gif/> Loading..</div>",
		loadingImg: "<img src=/assets/shared/loading.gif/>",
		loadingSmallImg: "<img src=\"/assets/shared/loading-small.gif/\" style=\"height:16px;width:16px;\">",
		searchTooShort: "You must enter at least three letters to search for."
	},
	
	keys: function(obj) {	
		var result = [];
		for (var i in obj) result.push(i);
		return result;
	},
	
	map: function(elems, fn) {
		// If a string is passed in for the function, make a function
		// for it (a handy shortcut)
		if ( typeof fn == "string" )
			fn = new Function("a","return " + fn);

		if (elems.constructor == Array) {
			var result = [];
			// Go through the array, translating each of the items to their
			// new value (or values).
			for ( var i = 0, el = elems.length; i < el; i++ ) {
				var val = fn(elems[i],i);

				if ( val !== null && val != undefined ) {
					if ( val.constructor != Array ) val = [val];
					result = result.concat( val );
				}
			}
		} else {
			// assume its an associative array
			var result = {};
			// Go through the array, translating each of the items to their
			// new value (or values).
			for (var i in elems) {
				var val = fn(elems[i], i);
				if ( val !== null && val != undefined ) {
					result[i] = val;
				}
			}
		}
		return result;
	},
	
	notempty: function(list) {
		var res = [];
		// not implemented using Ffilter for speed reasons
		for (var i = 0; i < list.length; i++) {
			if (list[i])
				res.push(list[i]);
		}
		return res;
	},
	
	// return a list of the given properties of the items in list
	pluck: function(list, property) {
		if (typeof property == "undefined") {
			// if called with one arg, return a function that returns
			// this property for a given arg
			// Fpluck('length')[1,2,3] == 3
			prop = list;
			return function(object) {
				return object[property];
			}
		} else {
			// not implemented using Fmap for speed reasons
			var results = []
			for (var item in list) {
				results.push(list[item][property]);
			}
			return results;
		}
	},
	
	Popover: {
		id: "popover",
		hide: function() {
			tb_remove();
			this.elem.empty();
		},
		show: function(title, html, width, height) {
			if (!width)
				width = 400;
			if (!height)
				height = 250;
			if (!tb_show)
				return alert("Thickbox not installed");
			if (this.elem && $(this.elem))
				$(this.elem).empty();
			$('body').append($('<div id="' + Fed.Popover.id + '"></div>').html("<div id=FedPopoverContent>" + html + "</div>"));
			setTimeout(function() {
				var url = "#TB_inline?height=" + height +"&width=" + width + "&inlineId=" + Fed.Popover.id;
				tb_show(title, url, false);
			}, 100);
		},
		update: function(html) {
		  $("#FedPopoverContent").html(html);
		}
	},
	
	repr: function(obj) {
	  return JSON.stringify(obj);
	},
	
	select: function(elems, fn) {
		var i, collect = [];
		if (elems.constructor == Array) {
			// select on an array
			var len = elems.length;
			for (i = 0; i < len; i++) {
				if (fn(elems[i], i))
					collect.push(elems[i]);
			}
		} else {
			// select on an associative array/hash
			for (i in elems) {
				if (fn(elems[i], i))
					collect.push(elems[i]);
			}
		}
	},
	
	trim: function(s) {
		return s.replace(/^\s+|\s+$/g, "");
	},
	
	updateCookies: function() {
		var arrive = $("#arriving").val();
		if (!arrive)
			arrive = $("#arrive").val();
		arrive = Fed.dateToPlain(arrive);
		var depart = $("#departing").val();
		if (!depart)
			depart = $("#depart").val();
		depart = Fed.dateToPlain(depart);
		var rooms = $("#rooms").val();
		var adults = $("#adults").val();
		var children = $("#children").val();
		Fed.cookie("arrive", arrive);
		Fed.cookie("depart", depart);
		Fed.cookie("rooms", rooms);
		Fed.cookie("adults", adults);
		Fed.cookie("children", children);		
	},
	
	values: function(obj) {
		var result = [];
		for (var i in obj) result.push(obj[i]);
		return result;
	}
}

ffilter = Fed.select;
fmap = Fed.map;
fnotempty = Fed.notempty;
fpluck = Fed.pluck;
fselect = Fed.select;

if ($)
	$(document).ready(Fed.Faves.refreshHeader);

if (!this.JSON) {

    JSON = function () {

        function f(n) {    // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function () {

// Eventually, this method will be based on the date.toISOString method.

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };


        var m = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };

        function stringify(value, whitelist) {
            var a,          // The array holding the partial texts.
                i,          // The loop counter.
                k,          // The member key.
                l,          // Length.
                r = /["\\\x00-\x1f\x7f-\x9f]/g,
                v;          // The member value.

            switch (typeof value) {
            case 'string':

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe sequences.

                return r.test(value) ?
                    '"' + value.replace(r, function (a) {
                        var c = m[a];
                        if (c) {
                            return c;
                        }
                        c = a.charCodeAt();
                        return '\\u00' + Math.floor(c / 16).toString(16) +
                                                   (c % 16).toString(16);
                    }) + '"' :
                    '"' + value + '"';

            case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':
                return String(value);

            case 'object':

// Due to a specification blunder in ECMAScript,
// typeof null is 'object', so watch out for that case.

                if (!value) {
                    return 'null';
                }

// If the object has a toJSON method, call it, and stringify the result.

                if (typeof value.toJSON === 'function') {
                    return stringify(value.toJSON());
                }
                a = [];
                if (typeof value.length === 'number' &&
                        !(value.propertyIsEnumerable('length'))) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                    l = value.length;
                    for (i = 0; i < l; i += 1) {
                        a.push(stringify(value[i], whitelist) || 'null');
                    }

// Join all of the elements together and wrap them in brackets.

                    return '[' + a.join(',') + ']';
                }
                if (whitelist) {

// If a whitelist (array of keys) is provided, use it to select the components
// of the object.

                    l = whitelist.length;
                    for (i = 0; i < l; i += 1) {
                        k = whitelist[i];
                        if (typeof k === 'string') {
                            v = stringify(value[k], whitelist);
                            if (v) {
                                a.push(stringify(k) + ':' + v);
                            }
                        }
                    }
                } else {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (typeof k === 'string') {
                            v = stringify(value[k], whitelist);
                            if (v) {
                                a.push(stringify(k) + ':' + v);
                            }
                        }
                    }
                }

// Join all of the member texts together and wrap them in braces.

                return '{' + a.join(',') + '}';
            }
        }

        return {
            stringify: stringify,
            parse: function (text, filter) {
                var j;

                function walk(k, v) {
                    var i, n;
                    if (v && typeof v === 'object') {
                        for (i in v) {
                            if (Object.prototype.hasOwnProperty.apply(v, [i])) {
                                n = walk(i, v[i]);
                                if (n !== undefined) {
                                    v[i] = n;
                                }
                            }
                        }
                    }
                    return filter(k, v);
                }


// Parsing happens in three stages. In the first stage, we run the text against
// regular expressions that look for non-JSON patterns. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we want to reject all
// unexpected forms.

// We split the first stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace all backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.test(text.replace(/\\./g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

// In the optional third stage, we recursively walk the new structure, passing
// each name/value pair to a filter function for possible transformation.

                    return typeof filter === 'function' ? walk('', j) : j;
                }

// If the text is not JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('parseJSON');
            }
        };
    }();
}
