if(!top["console"]) {
	top["console"] = {
		log: function(){},
		debug: function(){},
		info: function(){},
		warn: function(){},
		error: function(){},
		assert: function(){},
		dir: function(){},
		dirxml: function(){},
		trace: function(){},
		group: function(){},
		groupEnd: function(){},
		profile: function(){},
		profileEnd: function(){},
		time: function(){},
		timeEnd: function(){},
		count: function(){}
	};
}
// Correction for Internet Explorer document#getElementById implementation.
if(/msie/i.test(navigator.userAgent)) {
	document.nativeGetElementById = document.getElementById;
	document.getElementById = function(id) {
		var element = document.nativeGetElementById(id);
		if(element) {
			//make sure that it is a valid match on id
			if(element.attributes["id"].value == id) {
				return element;
			}
			else {
				//otherwise find the correct element
				for(var index = 1; index < document.all[id].length; index++) {
					if(document.all[id][index].attributes["id"].value == id) {
						return document.all[id][index];
					}
				}
			}
		}
		return null;
	};
}

// "Window" is read-only for Internet Explorer.
Zindow = {
	getDimensions: function() {
		var size = {width: 0, height: 0};

		if(self.innerHeight) { // all except Explorer
			size.width = self.innerWidth;
			size.height = self.innerHeight;
		}
		else if(document.documentElement &&
			document.documentElement.clientHeight) { // Explorer 6 (strict mode)
			size.width = document.documentElement.clientWidth;
			size.height = document.documentElement.clientHeight;
		}
		else if(document.body) { // other Explorer versions
			size.width = document.body.clientWidth;
			size.height = document.body.clientHeight;
		}

		return size;
	}
};

Page = {
	getDimensions: function() {
		var size = {width: 0, height: 0};

		var scrolling = {x: 0, y: 0};
		if(window.innerHeight && window.scrollMaxY) {
			scrolling.x = document.body.scrollWidth;
			scrolling.y = window.innerHeight + window.scrollMaxY;
		}
		else if(document.body.scrollHeight > document.body.offsetHeight) { // all but Explorer for Mac
			scrolling.x = document.body.scrollWidth;
			scrolling.y = document.body.scrollHeight;
		}
		else { // Explorer for Mac, Explorer 6 (strict mode), Mozilla and Safari.
			scrolling.x = document.body.offsetWidth;
			scrolling.y = document.body.offsetHeight;
		}

		var windowDimensions = Zindow.getDimensions();

		// for pages smaller than the viewport
		size.height = Math.max(windowDimensions.height, scrolling.y);
		size.width = Math.max(windowDimensions.width, scrolling.x);

		return size;
	}
};

/** Additional class methods & properties for Date class. */
Object.extend(Date, {
	Day: {
		/** @var ? Duration of a day in milliseconds. */
		duration: 86.4e6/*1000 * 60 * 60 * 24*/
	},

	Month: {
		/** @function ? Returns the count of days in the month of the given date (28, 29, 30 or 31). */
		getDaysCount: function(date) {
			var reference = new Date(date.getTime());
			while(reference.getMonth() == date.getMonth()) {
				reference.setTime(reference.getTime() + Date.Day.duration);
			}
			return new Date(reference.getTime() - Date.Day.duration).getDate();
		}
	},

	Year: {
		/** @function ? Returns the count of days in the year of the given date (useful to detect leap years). */
		getDaysCount: function(date) {
			var first = new Date(date.getFullYear(), 0, 1);
			var reference = new Date(first);
			while(reference.getFullYear() == date.getFullYear()) {
				reference.setTime(reference.getTime() + Date.Day.duration);
			}
			return (reference.getTime() - first.getTime()) / Date.Day.duration;
		}
	},

	/** @function ? Returns the date of today at noon. */
	today: function() {
		var today = new Date();
		today.setHours(12);
		today.setMinutes(0);
		today.setSeconds(0);
		today.setMilliseconds(0);
		return today;
	},

	/** @function ? Checks if the given date is today. */
	isToday: function(date) {
		var today = Date.today();
		return (date.getDate() == today.getDate())
			&& (date.getMonth() == today.getMonth())
			&& (date.getFullYear() == today.getFullYear());
	}
});

/* Examples:
 *     var range = [Date.today().shift(-1), Date.today().shift(+1)];
 *     Date.Range.decorate(range);
 *     range.includes(Date.today()) // -> true
 *     range.includes(Date.today().shift(+2)) // -> false
 *
 *     var range1 = Date.Range.decorate([Date.today().shift(-5), Date.today().shift(+1)]);
 *     var range2 = Date.Range.decorate([Date.today().shift(-1), Date.today().shift(+5)]);
 *     range1.intersects(range2) // -> true;
 *     var range3 = Date.Range.decorate([Date.today().shift(-2), Date.today().shift(-1)]);
 *     range3.isWithin(range1) // -> true;
 */
Date.Range = {
	decorate: function(array) {
		if(!array.__decoration_Date_Range) {
			// Sort the dates in the correct order, in case of...
			array.sortBy(function(date){ return date.toISO() });

			return Object.extend(array, {
				__decoration_Date_Range: true,
	
				includes: function(date) {
					return (this[0] <= date) && (date <= this[1]);
				},
	
				isWithin: function(range, scale) {
					scale = scale || Date.Day;
					// :toDo: make %scale effective
					return (range[0] <= this[0]) && (this[1] <= range[1]);
				},
	
				intersects: function(range, scale) {
					scale = scale || Date.Day;
					// :toDo: make %scale effective
					return (this[0] <= range[1]) && (this[1] >= range[0]);
				},
	
				getIntersection: function(range) {
					var intersection = null;
					if(this.intersects(range)) {
						intersection = Date.Range.decorate([((this[0] > range[0]) ? this[0] : range[0]),
						                                    ((this[1] < range[1]) ? this[1] : range[1])]);
					}
	
					return intersection;
				},
	
				shift: function(offset, step) {
					return Date.Range.decorate([this[0].shift(offset, step), this[1].shift(offset, step)]);
				},

				_each: function(iterator) {
					// Do not provide the date instance itself, clone it.
					var date = Date.parse(this[0].toISO());

					while(date <= this[1]) {
						iterator(date);
						date = date.shift(1);
					}
				}//,
				// ...
			});
		}
	}
};

/** Additional class methods & properties for Date class. */
Object.extend(Date.prototype, {
	/**
	 * @param offset Offset to apply.
	 * @param {optional} increment: Date.Day (default), Date.Month, Date.Year.
	*/
	shift: function(offset, step) {
		step = step || Date.Day;
		var result = new Date(this.getTime());

		// Temporarily position the time at noon to avoid daylight savings exceptions.
		var actualHours = result.getHours();
		result.setHours(12);

		if(offset) {
			var norm = Math.abs(offset);
			var sign = offset / norm;
			offset = Math.round(norm);

			switch(step) {
				case Date.Day:
					result.setTime(result.getTime() + sign * offset * Date.Day.duration);
					break;
				case Date.Month:
					var yearOffset = Math.floor(offset / 12);
					var monthOffset = offset % 12;
					result = new Date(this.getFullYear() + sign * yearOffset,
									this.getMonth() + sign * monthOffset,
									1);
					var daysCount = Date.Month.getDaysCount(result);
					if(daysCount < this.getDate()) {
						result.setDate(daysCount);
					}
					else {
						result.setDate(this.getDate());
					}
					break;
				case Date.Year:
					result = new Date(this.getFullYear() + sign * offset,
									this.getMonth(),
									1);
					var daysCount = Date.Month.getDaysCount(result);
					if(daysCount < this.getDate()) {
						result.setDate(daysCount);
					}
					else {
						result.setDate(this.getDate());
					}
					break;
			}
		}

		// Restore the initial hours.
		result.setHours(actualHours);

		return result;
	},

	isWithin: function(range) {
		return (range[0] <= this) && (this <= range[1]);  
	},

	// Returns an ISO-8601 representation of the date object.
	toISO: function() {
		return this.getFullYear()
			+ "-" + (this.getMonth() + 1).toPaddedString(2)
			+ "-" + this.getDate().toPaddedString(2);
	}
});

Date._nativeParseMethod = Date.parse;
// By default, the date representation is parsed as a ISO-8601 string.
Date.parse = function(representation, format) {
	format = (format != undefined) ? format : "ISO";
	var date = null;

	switch(format) {
		case "ISO":
			// :TODO: proceed with full implementation (timezone, ...)
			var capture = representation.match(/^(\d{4})-(\d{2})-(\d{2})((?:T|\s)(\d{1,2})\:(\d{2})(?:\:(\d{2}))?)?$/);
			if(capture) {
				date = new Date(capture[1], capture[2] - 1, capture[3], parseInt(capture[4]) || 12, parseInt(capture[5]) || 0, parseInt(capture[6]) || 0);
			}
			break;
		default:
			date = new Date(Date._nativeParseMethod(representation));
			break;
	}

	if(date && isNaN(date.getTime())) {
		date = null;
	}

	return date;
};


Object.extend(String.prototype, {
	trim: function() {
		return this.replace(/^\s+|\s+$/g, "");
	},

	capitalize: function(lowerCase) {
		lowerCase = (lowerCase !== undefined) ? lowerCase : true;
		return this.charAt(0).toUpperCase() + (lowerCase
					? this.substring(1).toLowerCase()
					: this.substring(1));
	},

	/**
	 * Retrieves the dimensions of the given string if it were printed with the provided styles and/or in the provided
	 * model element. If nothing is provided it relies on the body style.
	 */
	measure: function(options) {
        options = Object.extend({ /* default options*/
        	model: document.body
        }, options);

        // Style properties having an impact while rendering the string.
    	var properties = ["fontFamily", "fontSize", "fontWeight", "fontStyle", "fontVariant", "fontSizeAdjust",
    	                  "fontStretch", "direction", "lineHeight", "letterSpacing", "textAlign", "textDecoration",
    	                  "textIndent", "textShadow", "textTransform", "unicodeBidi", "whiteSpace", "wordSpacing"];
    	var style = {};
    	properties.each(function(property) {
			style[property] = options.model.getStyle(property);
    	});

    	// Mandatory styles.
    	style = Object.extend(style, {
            position: "absolute",
            top: 0,
            left: 0,
            margin: 0,
            border: "0 none",
            padding: 0,
            width: "auto",
            height: "auto"
        });

    	var container = new Element("div");
        container.setStyle(style);
        container.update(this);
        document.body.appendChild(container);
        var dimensions = container.getDimensions();
        container.remove();

        return dimensions;
    },

    /**
     * Truncates a string after the given length (cf. legacy String#truncate method).
     * 
     * If a model element is provided it performs measurements in order to truncate the string so that it would not
     * exceed the given dimension if it were printed in the model element.
     * 
     * Examples:
     * 
     * 		// Would truncate the string to "Tyrannausoru..." with a basic font.
     * 		"Tyrannausorus Rex".truncate(80, {
     * 			model: $("narrowParagraph"),
     * 		})
     * 
     * 		// Would truncate the string to "Tyrann... [...]" with a basic font.
     * 		"Tyrannausorus Rex".truncate(80, {
     * 			model: $("narrowParagraph"),
     * 			truncation: "... [...]"
     * 		})
	 *
     * Note: options could be used to tweak the measurement since they are all passed internally to String#measure.
	 *
     *
     * Legacy String#truncate method usage:
     * 
     * 		// Will truncate the string to "Tyrannausorus...".
     * 		"Tyrannausorus Rex".truncate(16);

     * 		// Will truncate the string to "Tyrannausorus -".
     * 		"Tyrannausorus Rex".truncate(15, " -");
     */
    truncate: String.prototype.truncate.wrap(function(legacy, length, options) {
        // Avoid loops potentially too long.
        var watchdog = 10;

        var result = null;

        if(typeof(options) == "object") {
    		var truncation = options.truncation || "...";

        	var width = this.measure(options).width;
            if(width > length && watchdog) {

                // Speed up with a dichotomic truncation, first.
                var subject = this.substr(0, this.length * length / width - truncation.length) + truncation;

                // Is it still too large?
                if((width = subject.measure(options).width) > length) {
                    // Decremental correction.
                    do {
                		if(!watchdog--) { throw new Error("Too many iterations!"); };
                        subject = this.substr(0, (subject.length - truncation.length) - 1) + truncation;
                    }
                    while((width = subject.measure(options).width) > length);
                }
                else {
                    // Incremental correction.
                    do {
                		if(!watchdog--) { throw new Error("Too many iterations!"); };
                        subject = this.substr(0, (subject.length - truncation.length) + 1) + truncation;
                    }
                    while((width = subject.measure(options).width) > length);
                }

                result = subject;
            }
            else {
                result = this;
            }
        }
        else if(typeof(options) == "string") {
            result = legacy.apply(this, $A(arguments).without(legacy));
        }
        else {
    		throw new Error("Invalid parameter.");
        }

        return result;
    })
});


Object.extend(Array.prototype, {
	contains: function(object) {
		return (this.indexOf(object) > -1);
	},

	append: function(object) {
		if(object instanceof Array) {
			$A(object).each(function(element) {
				this.push(element);
			});
		}
		else {
			this.push(object);
		}

		return this;
	}
});


/** @class ? Simple class enclosing localized strings. */
Localization = {
	strings: {},

	addStrings: function(strings) {
		$H(strings).each(function(iterator) {
			this.addString(iterator.key, iterator.value);
		}, this);
	},

	addString: function(id, string) {
		this.strings[id] = string;
	},

	getString: function(id) {
		return this.strings[id];
	}
};


/** ------------------------------------------------------------------------------------------------
 * Returns the character corresponding to the given entity.
 *
 * @param code      String representation of the entity. Example: "&#248;".
 * @param stupefy   If true the function acts on the opposite: give it the character and it will
 *                  produce the string representation.
 */
function entity(code, stupefy) {
	var element = document.createElement("div");
	element.innerHTML = String(code);

	if(stupefy) {
		return "&#" + element.innerHTML.charCodeAt(0) + ";";
	}

	return element.innerHTML;
}


// Stores the targeted check-box state in its associated hidden control field.
function updateCheckBoxControlField(event) {
	if(this.controlField && this.checkBox) {
		this.controlField.value = this.checkBox.checked;
	}
}


function setLinksNavigable(navigable) {
	navigable = (navigable == undefined) ? true : navigable;
	var index = navigable ? 0 : -1;
	$A(document.getElementsByTagName("a")).each(function(element) {
		element.tabIndex = index;
	});
}



// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mask = new Object();
Mask.create = function(style) {
	style = Object.extend({
		color: "#FFFFFF",
		opacity: "0.33",
		"z-index": 99
	}, style);

	var anchor = $(document.body);
	anchor.insert({top: '<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;'
						+ ' display: none; z-index: ' + style["z-index"] + ';'
//						+ ' background-color: ' + style.color + ';'
//						+ ' filter: alpha(opacity=' + (style.opacity * 100)+ '); -moz-opacity: ' + style.opacity + '; opacity: ' + style.opacity + ';'
						+ ' cursor:' + style.cursor + ';"/>'});
	var mask = $(anchor.firstChild);

	mask.show = mask.show.wrap(function(legacy) {
		legacy.apply(this, $A(arguments).without(legacy));			
		if ($(document.body).isVisible()){
			mask.style.height = $(document.body).getHeight() + "px";
		}
	});

	mask.hide = mask.hide.wrap(function(legacy) {
		legacy.apply(this, $A(arguments).without(legacy));
	});

	return mask;
};


/* --------------------------------------------------------------- */
Object.extend(Form, {
	getFields: function(form) {
		var fields = {};
		Form.getElements(form).each(function(field) {
			if(field.name) {
				fields[field.name] = field;
			}
		});
		return fields;
	},

	/** @funtcion ? Make the form fields text automatically selected when the fields gain focus. */
	setAutoSelection: function(form) {
		form.select("input").each(function(field) {
			if(field.type != "hidden") {
				Event.observe(field, "focus", function(event){this.select();}.bind(field));
			}
		});
	}
});

// Return the current choice in the given radio button's family.
Field.getChoice = function(field) {
	field = $(field);
	var choice = null;
	$$("input[name='" + field.name + "']").each(function(iterator) {
		if(iterator.checked) {
		choice = iterator.value;
		}
	});
	return choice;
};



Element.addMethods({
	// dimensions are the space directions
	adapt: function(element, model, dimensions) {
		dimensions = Object.extend({x: true, y: true}, dimensions);
		element = $(element);

		var display = element.style["display"];
		if(display == "none") {
			// All *Width and *Height properties give 0 on elements with display "none", so we
			// enable the element temporarily.
			var originalVisibility = element.style.visibility;
			var originalPosition = element.style.position;
			var originalDisplay = element.style.display;
			element.style.visibility = "hidden";
			element.style.position = "absolute";
			element.style.display = "block";
		}

		if(dimensions.x) {
			element.style["width"] = model.offsetWidth - (element.offsetWidth - element.clientWidth);
		}
		if(dimensions.y) {
			element.style["height"] = model.offsetHeight - (element.offsetHeight - element.clientHeight);
		}

		if(display == "none") {
			element.style.display = originalDisplay;
			element.style.position = originalPosition;
			element.style.visibility = originalVisibility;
		}
	},

	append: function(element, child) {
		return element.appendChild(child);
	},

	isVisible: function(element) {
		var visible = true;
		var current = element;
		while(current && (current.nodeType == 1) // %current% being null means that the node is detached
			&& (visible &= current.visible())) {
			current = current.up();
		}
		return visible ? true : false;
	},

	// Checks if the given coordinates are within the element boundaries, according to the test context (cf. options).
	surrounds: function(element, x, y, options) {
		options = Object.extend({
			context: window,
			margin: {
				top: 0,
				right: 0,
				bottom: 0,
				left: 0
			}
		}, options);

		var position = [0, 0];
		if(options.context == document) {
			position = element.cumulativeOffset();
		}
		else if(options.context == window) {
			position = element.viewportOffset();
		}

		var dimensions = element.getDimensions(element);
		var bounds = {
			top: position[1] - (options.margin.top || 0),
			left: position[0] - (options.margin.left || 0),
			right: position[0] + dimensions.width + (options.margin.right || 0),
			bottom: position[1] + dimensions.height + (options.margin.bottom || 0)
		};

		var result = ((x >= bounds.left) && (x <= bounds.right)
				&& (y >= bounds.top) && (y <= bounds.bottom));
//console.log(x+","+y+" ? ["+bounds.left+" - "+bounds.right+"] x ["+bounds.top+" - "+bounds.bottom+"] -> " + result);
		return result;
	}
});



/*
	Registry usage example:

		Pirate = Class.create({
			name: null,

			initialize: function(name) {
				Pirate.register(this, name);
				this.name = name;
			}
		});

		Registry.decorate(Pirate);

		new Pirate("Wayne");
		new Pirate("Bill");
		new Pirate("John");

		Pirate.get(); // returns "Wayne" (since it was the first registered, the default one)
		Pirate.get("John"); // returns John
*/
Registry = {
	decorate: function(klass) {
		Object.extend(klass, {
			instances: null,
			instancesByIndex: null,

			register: function(instance, key) {
				if(!klass.instances) {
					klass.instances = {};
					klass.instancesByIndex = [];
				}
				key = (key !== undefined) && (key !== null) ? key : "default"; // allow the usage of "0"
				klass.instances[key] = instance;
				klass.instancesByIndex.push(instance);
				// Memorize the default key (first registration)
				if(klass["Registry.default"] === undefined) { // think it could be "0"
					klass["Registry.default"] = key;
				}
			},

			// If %key% is not provided it returns the first registered instance.
			get: function(key) {
				var instance = null;
				if(klass.instances) {
					instance = klass.instances[(key !== undefined) && (key !== null) ? key : klass["Registry.default"]];
				}
				return instance || null;
			},

			getIndex: function(instance) {
				return klass.instancesByIndex.indexOf(instance);
			},

			getCount: function(instance) {
				return klass.instancesByIndex ? klass.instancesByIndex.length : 0;
			},

			each: function() {
				var map = $H(klass.instances);
				return map.each.apply(map, arguments);
			}
		});
	}
};


/*
	Singleton usage example:

		TexasRanger = Class.create({
			name: null,

			initialize: function() {
				this.name = "Walker";
			}
		});

		Singleton.decorate(TexasRanger); // There is only one real Texas ranger...

		TexasRanger.getInstance(); // Here comes Walker!
*/
Singleton = {
	decorate: function(klass) {
		Object.extend(klass, {
			instance: null,

			getInstance: function() {
				if(!klass.instance) {
					klass.instance = new klass();
				}

				return klass.instance;
			}
		});
	}
};


/*
	Observable usage example:

		Observable.decorate(Pirate);
		var blackBeard = new Pirate();

		blackBeard.addObserver({
			notifyAttack: function() {
				console.log("Run!");
			}
		});

		blackBeard .attack();
*/
Observable = {
	decorate: function(klass, prefix) {
		Object.extend(klass.prototype, {
			addObserver: function(observer) {
				this.observers = this.observers || [];
				this.observers.push(observer);
			},

			/**
			 * @function ? Notifies this observers about the given event.
			 * Note: any additional argument is passed while notifying the observers.
			 * @param {object} event Event name.
			 */
			notify: function(event) {
				var method = (prefix || "notify") + event.capitalize(false);
				var parameters = $A(arguments).without(event);
				(this.observers || []).each(function(observer) {
					if(observer[method]) {
						observer[method].apply(observer, parameters);
					}
				});
			}
		});
	}
};

Pattern = {
	/**
	 * @function {RegExp} ? Escapes the given string for it to work in a regular expression.
	 * @param string String to escape.
	 */
	escape: function(string) {
		var expression = string;
		["\\"/* first! */,".", "-", "[", "]", "{", "}", "?", "*", "+", ":"].each(function(character) {
			expression = expression.replace(new RegExp("([^\\\\]|^)\\" + character, "g"), "$1\\" + character);
		});
		return expression;
	}
};



Debug = {
	Marker: {
		create: function(x, y, text) {
			var marker = new Element("div");
			marker.setStyle({
				position: "fixed",
				left: x + "px",
				top: y + "px",
				zIndex: 9999,
				border: "1px solid red",
				width: "1px",
				height: "1px",
				backgroundColor: "white"
			});
			document.body.appendChild(marker);

			if(text) {
				var label = marker.label = new Element("div");
				label.setStyle({
					position: "fixed",
					left: (x + 6) + "px",
					top: (y - 6) + "px",
					zIndex: 9999,
					border: "1px solid black",
					padding: "3px",
					fontSize: "9px",
					backgroundColor: "FFEECC"
				});
				label.update(text);
				document.body.appendChild(label);
			}

			return marker;
		}
	}
};
var SA_ID = null;
var SA_OJ_Base = null;
var SA_Message = null;

/* * * * * THIS SHOULD NOT BE IN THAT FILE! * * * * */
function pingSurfaid(params){
	SA_ID = "aircan;aeroplan";
	SA_OJ_Base = "";
	if(params) {
		$H(params).each(function(iter, index) {
			SA_OJ_Base += ((index > 0) ? "&" : "") + (iter.key + "=" + iter.value);
		});
	}
	SA_Message = SA_OJ_Base + "";
	sa_onclick();
}