/* requires prototype.js */

function padNumber( num, l ) {
	ret = "" + num;
	while(ret.length < l) ret = "0" + ret;
	return ret;
}

function typeOf( v ) {
	var t = typeof v;

	if(t == "object") {
		if(v) {
			if(v instanceof Array) t = "array";
			/* else t = "object" is correct */
		}
		else
			t = "null";
	}
	else if(t == "number") {
		if(String("" + v).indexOf(".") === -1)
			t = "integer";
		/* else t == "number" means floating-point value */
	}

	return t;
}

function getBasename( path ) {
	var p = path.lastIndexOf(".");
	if(p > -1) return path.substring(0, p);
	return "";
}

function getExtension( path ) {
	var p = path.lastIndexOf(".");
	if(p > -1) return path.substring(p + 1);
	return "";
}

function isValidNumber( n ) {
	/* can't just !isNaN(parseFloat(n)) - see isValidInteger() below */
	switch(typeof n) {
		case "number": return true;
		case "string": return null !== n.match(/^[-+]{0,1}([0-9]*\.){0,1}[0-9]+$/);
	}
	
	return false;
}

function isValidInteger( n ) {
	/* can't just !isNaN(parseInt(n)) because "26B" gets 26 from parseInt() */
	switch(typeof n) {
		case "number": ("" + n).indexOf(".") == -1;
		case "string": return null !== n.match(/^[-+]{0,1}[0-9]+$/);
	}

	return false;
}

function isLeapYear( year ) { return bpDate._isLeapYear(year); }
function maxDaysForMonth( month, year ) { return bpDate._maxDaysForMonth(month, year); }
function isValidDate( day, month, year ) { return new bpDate(day, month, year).isValid(); }

function idOrObjectToObject( idOrObject ) {
	switch(typeof idOrObject) {
		case "object": return idOrObject;
		case "string": return document.getElementById(idOrObject);
	}

	return null;
}

function cancelEvent( e ) {
	if(e.preventDefault) e.preventDefault();
	else e.returnValue = false;
}

function bpCopyElementContent( s, d ) {
	s = idOrObjectToObject(s);
	d = idOrObjectToObject(d);
	if(s && d) d.innerHTML = s.innerHTML;
}

function bpUpdateElementByUrl( idOrObject, url ) {
	idOrObject = idOrObjectToObject(idOrObject);
	var id = $(idOrObject.id);
	new Ajax.Request(url, {method: "get", xTargetContainerId: id, onSuccess: function(r){$(r.request.options.xTargetContainerId).update(r.responseText);}, onFailure: function(r){$(r.request.options.xTargetContainerId).update("<p>Failed to retrieve content.</p>");}, onCreate: function(r){$(r.request.options.xTargetContainerId).update("<p><img src=\"images/library/bpAjaxLoading.gif\" alt=\"\" />&nbsp;Loading...</p>");}});
}

function bpToggleElementVisibility( idOrObject ) {
	var object = idOrObjectToObject(idOrObject);
	object = $(object.id);	/* make sure object has prototype extensions */

	if(typeof object.xbpLibraryToggleState == "undefined") object.xbpLibraryToggleState = "visible";

	if(object.xbpLibraryToggleState == "visible") {
		object.fade({duration: 0.5, transition: Effect.Transitions.sinoidal});
		object.xbpLibraryToggleState = "invisible"
	}
	else {
		object.appear({scaleTo: 2, scaleContent: false, duration: 0.5, transition: Effect.Transitions.sinoidal});
		object.xbpLibraryToggleState = "visible"
	}
}

function bpRollImage( idOrObject ) {
	var o = idOrObjectToObject(idOrObject);

	if(o && o.src) {
		if(!o.xbpLibraryRollState) o.xbpLibraryRollState = "notrolled";

		if(o.xbpLibraryRollState == "rolled") {
			o.xbpLibraryRollState = "notrolled";
			o.src = o.src.replace(/_roll/, "");
		}
		else {
			o.xbpLibraryRollState = "rolled";
			var parts = /(.*)\.(.*)$/.exec(o.src);	/* regex is greedy by default */
			o.src = parts[1] + "_roll" + (parts.length > 2 ? "." + parts[2] : "");;
		}
	}
}

/* flags for bpOpenUrl */
var BPOPENURL_NEWWINDOW = 1;

function bpOpenUrl( url, flags ) {
	if("undefined" == typeof(flags)) flags = 0;
	if(flags & BPOPENURL_NEWWINDOW) window.open(url, "", "");
	else window.location = url;
}

/* string class extensions */
String.prototype.isInt = function () { return null !== this.match(/^[-+]{0,1}[0-9]+$/); }
String.prototype.isNumber = function () { return null !== this.match(/^[-+]{0,1}([0-9]*\.){0,1}[0-9]+$/); }
String.prototype.simplified = function() { return this.replace(/[\s]+/g, " ").trim(); }
String.prototype.tidied = function() { return this.trim().replace(/\r/g, "\n").replace(/\n+/g, "\n").replace(/[ \f\v\t]+/g, " "); }
if(!String.prototype.trimLeft) String.prototype.trimLeft = function() { return this.replace(/^\s*/, ""); }
if(!String.prototype.trimRight) String.prototype.trimRight = function() { return this.replace(/\s*$/, ""); }
if(!String.prototype.trim) String.prototype.trim = function() { return this.trimLeft().trimRight(); }
if(!String.prototype.trimmed) String.prototype.trimmed = String.prototype.trim;

/* on chrome v6 (Athlon64 X2 6000), in text edit with over 30,000 words, can
 * calculate wc on entry of every word-boundary character of a 80wpm typist and
 * write wc to debug log with barely noticeable slowing of input response. FF
 * and Konq can get over 15,000 words */
String.prototype.wordCount = function() { var m = this.match(/\w\b/g); if (m) return m.length; return 0; }

/*
 * \class bpDate
 */
function bpDate( stringDateOrDay, m, y ) {
	if("string" == (typeof stringDateOrDay)) stringDateOrDay = new Date(stringDateOrDay);
	
	if(stringDateOrDay instanceof Date) {
		m = stringDateOrDay.getMonth() + 1;
		y = stringDateOrDay.getFullYear();
		stringDateOrDay = stringDateOrDay.getDate();
	}

	this._day = stringDateOrDay;
	this._month = m;
	this._year = y;
}

bpDate.FORMAT_MYSQL = "%YYYY-%MM-%DD";
bpDate.FORMAT_EU = "%DD/%MM/%YYYY";
bpDate.FORMAT_UK = "%DD/%MM/%YYYY";
bpDate.FORMAT_US = "%MM/%DD/%YYYY";

bpDate._isLeapYear = function( y ) {
	y = parseInt(y);
	return (y > 8) && (((y % 400) == 0) || (((y % 4) == 0) && ((y % 100) != 0)));
}

bpDate._maxDaysForMonth = function( m, y ) {
	m = parseInt(m); if(isNaN(m) || m < 1 || m > 12) return -1;
	y = parseInt(y); if(isNaN(y)) y = 1981;	/* arbitrary non-leap-year */

	switch(m) {
		case 2:
			if(bpDate._isLeapYear(y)) return 29;
			return 28;

		case 4: case 6: case 9: case 11:
			return 30;
	}
	
	return 31;
}

bpDate.prototype.day = function() { return this._day; }
bpDate.prototype.getDate = bpDate.prototype.day;
bpDate.prototype.month = function() { return this._month; }
bpDate.prototype.getMonth = function() { return this.month() - 1; }
bpDate.prototype.year = function() { return this._year; }
bpDate.prototype.getFullYear = bpDate.prototype.year;
bpDate.prototype.setDay = function( d ) { this._day = parseInt(d); }
bpDate.prototype.setMonth = function( m ) { this._month = parseInt(m); }
bpDate.prototype.setYear = function( y ) { y = parseInt(y); if(y < 50) y += 2000; else if(y < 100) y += 1900; this._year = y; }
bpDate.prototype.isLeapYear = function() { return bpDate._isLeapYear(this.year()); }
bpDate.prototype.maxDays = function() { return bpDate._maxDaysForMonth(this.month(), this.year()); }
bpDate.prototype.isValid = function() { var d = this.day(); var y = this.year(); if(isNaN(d) || d < 1 || isNaN(y) || y < 1) return false; return d <= bpDate._maxDaysForMonth(this.month(), y); }

bpDate.prototype.compare = function( other ) {
	if(!other || !other.getFullYear) return null;
	if("Date" == other.constructor.name) other = new bpDate(other);
	return parseInt(this.toString("%YYYY%MM%DD")) - parseInt(other.toString("%YYYY%MM%DD"));
}

bpDate.prototype.isBefore = function( other ) { return this.compare(other) < 0; }
bpDate.prototype.isAfter = function( other ) { return this.compare(other) > 0; }
bpDate.prototype.equals = function( other ) { return this.compare(other) === 0; }

bpDate.prototype.format = function( fmt ) { return this.toString(fmt); }
bpDate.prototype.toString = function( fmt ) {
	if("undefined" == (typeof fmt)) fmt = bpDate.FORMAT_EU;
	var d = this.getDate();
	var m = this.getMonth() + 1;
	var y = this.getFullYear();
	fmt = fmt.replace(/%DD/g, (d < 10 ? "0" + d : "" + d));
	fmt = fmt.replace(/%D/g, "" + d);
	fmt = fmt.replace(/%MM/g, (m < 10 ? "0" + m : "" + m));
	fmt = fmt.replace(/%M/g, "" + m);
	fmt = fmt.replace(/%YYYY/g, "" + y);
	fmt = fmt.replace(/%YY/g, ("" + y).substring(3));
	return fmt;
}


/*
 * \class bpTime
 */
function bpTime( hour, minute, second, ms ) {
	this._hour = ("number" == (typeof hour) ? parseInt(hour) : 0);
	this._minute = ("number" == (typeof minute) ? parseInt(minute) : 0);
	this._second = ("number" == (typeof second) ? parseInt(second) : 0);
	this._ms = ("number" == (typeof ms) ? parseInt(ms) : 0);
}

bpTime.FORMAT_HOURMINUTE = "%HH:%II";
bpTime.FORMAT_HOURMINUTESECOND = "%HH:%II:%SS";
bpTime.FORMAT_FULL = "%HH:%II:%SS.%NNN";

bpTime.prototype.hour = function() { return this._hour; }
bpTime.prototype.minute = function() { return this._minute; }
bpTime.prototype.second = function() { return this._second; }
bpTime.prototype.millisecond = function() { return this._ms; }
bpTime.prototype.setHour = function( h ) { this._hour = parseInt(h); }
bpTime.prototype.setMinute = function( m ) { this._minute = parseInt(m); }
bpTime.prototype.setSecond = function( s ) { this._second = parseInt(s); }
bpTime.prototype.setMillisecond = function( ms ) { this._ms = parseInt(ms); }
bpTime.prototype.isValid = function() { var h = this.hour(); var m = this.minute(); var s = this.second(); var ms = this.millisecond(); return !isNaN(h) && h >= 0 && h < 24 && !isNaN(m) && m >= 0 && m < 60 && !isNaN(s) && s >= 0 && s < 60 && !isNaN(ms) && ms >= 0 && ms < 1000; }
bpTime.prototype.compare = function( other ) { if(!other || !other.constructor || "bpTime" != other.constructor.name) return null; return this.toInt() - other.toInt(); }
bpTime.prototype.isBefore = function( other ) { return this.compare(other) < 0; }
bpTime.prototype.isAfter = function( other ) { return this.compare(other) > 0; }
bpTime.prototype.equals = function( other ) { return this.compare(other) === 0; }
bpTime.prototype.addMilliseconds = function(ms) { this.addSeconds(Math.floor((this._ms + ms) / 1000)); this._ms = (this._ms + ms) % 1000; }
bpTime.prototype.addSeconds = function(s) { this.addMinutes(Math.floor((this._second + s) / 60)); this._second = (this._second + s) % 60; }
bpTime.prototype.addMinutes = function(m) { this.addHours(Math.floor((this._minute + m) / 60)); this._minute = (this._minute + m) % 60; }
bpTime.prototype.addHours = function(h) { this._hour = (this._hour + h) % 24; }
bpTime.prototype.toInt = function() { return this.hour() * 360000 + this.minute() * 60000 + this.second() * 1000 + this.millisecond(); }
bpTime.prototype.format = function( fmt ) { return this.toString(fmt); }

bpTime.prototype.toString = function( fmt ) {
	if("undefined" == (typeof fmt)) fmt = bpTime.FORMAT_FULL;
	var h = this.hour(), m = this.minute(), s = this.second(), ms = this.millisecond();
	fmt = fmt.replace(/%HH/g, (h < 10 ? "0" + h : "" + h));
	fmt = fmt.replace(/%H/g, "" + h);
	fmt = fmt.replace(/%II/g, (m < 10 ? "0" + m : "" + m));
	fmt = fmt.replace(/%I/g, "" + m);
	fmt = fmt.replace(/%SS/g, (s < 10 ? "0" + s : "" + s));
	fmt = fmt.replace(/%S/g, "" + s);
	fmt = fmt.replace(/%NNN/g, "" + (ms < 10 ? "00" + ms : (ms < 100 ? "0" + ms : "" + ms)));
	fmt = fmt.replace(/%N/g, "" + ms);
	return fmt;
}


/* \class bpObject */
function bpObject() { this._signalListeners = new Array(); }

bpObject.prototype.className = function() {
	if(this && this.constructor) return this.constructor.name;
	return "<unknown class>";
}

bpObject.prototype.emit = function( signal ) {
	if(this.signalsBlocked()) return;

	/* fn arguments not real array obj. this turns it into an array */
	var args = Array.prototype.slice.call(arguments);
	args.shift();	/* remove name of signal being emitted */

	if(signal != "emittingSignal") this.emit("emittingSignal", signal, this, args);
	
	for(var i = 0; i <  this._signalListeners.length; i++) {
		if(this._signalListeners[i].signal == signal) this._signalListeners[i].fn.apply(this._signalListeners[i].receiver, args);
	}
}

bpObject.prototype.connect = function( signal, receiver, fn ) {
	if("string" != (typeof signal) || "function" != (typeof fn) || (null != receiver && "object" != (typeof receiver))) {
		bpDebug.logError("...failed");
		return false;
	}

	/* if already connected, just return */
	for(var i = 0; i < this._signalListeners.length; i++)
		if(this._signalListeners[i].signal == signal && this._signalListeners[i].receiver == receiver && this._signalListeners[i].fn == fn) return true;

	this._signalListeners.push({signal: signal, receiver: receiver, fn: fn});
	return true;
}

/* connect to object's signal and have receiver emit a signal with same arguments */
bpObject.prototype.chainSignal = function( signal, receiver, signal ) {
	var fn = function() { var args = new Array(); args.push(signal); args = args.concat(Array.prototype.slice.call(fn.arguments)); bpObject.prototype.emit.apply(receiver, args) };
	return this.connect(signal, receiver, fn);
}

bpObject.prototype.disconnect = function( signal, receiver, fn ) {
	if("string" == (typeof signal) && "function" == (typeof fn) && (null == receiver || "object" == (typeof receiver))) {
		for(var i = 0; i < this._signalListeners.length;) {
			if(this._signalListeners[i].signal == signal && this._signalListeners[i].receiver == receiver && this._signalListeners[i].fn == fn) {
				this._signalListeners.splice(i, 1);
				continue;
			}

			i++;
		}
	}
}

bpObject.prototype.blockSignals = function( block ) {
	if("boolean" == (typeof block)) {
		this._blockSignals = block;
		return true;
	}
	
	return false;
}

bpObject.prototype.signalsBlocked = function() { return this._blockSignals; }

/*
 * \class bpRange
 */
function bpRange( start, end ) {
	this.start = start;
	this.end = end;
}

bpRange.prototype.isValid = function() { return "number" == (typeof this.start) && "number" == (typeof this.end) && this.start >= 0 && this.start <= this.end; };
bpRange.prototype.move = function( dist ) { if("number" == (typeof dist) && this.isValid()) { this.start += dist; this.end += dist; return true; } return false; };
bpRange.prototype.moveStart = function( dist ) { if("number" == (typeof dist) && this.isValid()) { this.start += dist; return true; } return false; };
bpRange.prototype.moveEnd = function( dist ) { if("number" == (typeof dist) && this.isValid()) { this.end += dist; return true; } return false; };
bpRange.prototype.size = function() { if(this.isValid()) return this.end - this.start; return null };
bpRange.prototype.equals = function( other ) { return "undefined" != other.start && "undefined" != other.end && this.start == other.start && this.end == other.end; };

/*
 * \class bpDebug
 */
function bpDebug() {};

bpDebug._logElement = null;

bpDebug._ensureLogElement = function() {
	if(bpDebug._logElement && bpDebug._logElement.nodeName) return;

	var b = document.getElementsByTagName("body");
	if(b.length != 1) return;
	b = b[0];
	bpDebug._logElement = document.createElement("DIV");
	bpDebug._logElement.id = "bpDebug_log";
	bpDebug._logElement.className = "bpDebug_log";
	bpDebug._logElement.style.margin = "1em";
	bpDebug._logElement.style.padding = "1em";
	bpDebug._logElement.style.fontFamily = "monospace";
	bpDebug._logElement.style.backgroundColor = "rgb(255, 255, 230)";
	bpDebug._logElement.style.borderRadius = "0.5em";
	bpDebug._logElement.style.borderStyle = "solid";
	bpDebug._logElement.style.borderColor = "rgb(128, 128, 128)";
	bpDebug._logElement.style.borderSize = "2px";
	bpDebug._logElement.style.height = "10em";
	bpDebug._logElement.style.overflow = "auto";
	b.insertBefore(this._logElement, b.firstChild);
}

bpDebug._createLogEntry = function( msg ) {
	var c = document.createElement("SPAN");
	c.style.color = "black";
	c.appendChild(document.createTextNode(msg));
	return c;
}

bpDebug.logMessage = function( msg ) {
	bpDebug._ensureLogElement();
	if(bpDebug._logElement == null) return;
	var c = bpDebug._createLogEntry(msg);
	bpDebug._logElement.appendChild(c);
	bpDebug._logElement.appendChild(document.createElement("BR"));
	bpDebug._logElement.scrollTop = bpDebug._logElement.scrollHeight - bpDebug._logElement.offsetHeight
}

bpDebug.logError = function( msg ) {
	bpDebug._ensureLogElement();
	if(bpDebug._logElement == null) return;
	var c = bpDebug._createLogEntry(msg);
	c.style.color = "red";
	bpDebug._logElement.appendChild(c);
	bpDebug._logElement.appendChild(document.createElement("BR"));
	bpDebug._logElement.scrollTop = bpDebug._logElement.scrollHeight - bpDebug._logElement.offsetHeight
}

bpDebug.clearLog = function() {
	bpDebug._ensureLogElement();
	if(bpDebug._logElement == null) return;
	bpDebug._logElement.innerHTML = "";
	return;
}

bpDebug.hideLog = function() {
	bpDebug._ensureLogElement();
	if(bpDebug._logElement == null) return;
	bpDebug._logElement.style.display = "none";
}

bpDebug.showLog = function() {
	bpDebug._ensureLogElement();
	if(bpDebug._logElement == null) return;
	bpDebug._logElement.style.display = "";
}

/*
 * \class bpTimer
 */
bpTimer.prototype = new bpObject();
bpTimer.prototype.constructor = bpTimer;

function bpTimer( interval ) {
	bpObject.prototype.constructor.call(this);

	this._interval = ((typeof interval) == "undefined" ? 1000 : interval);
	this._timer = null;
	this._isActive = false;
	this._repeatCount = 0;

	var myThis = this;
	this._emitTimout = function() { myThis.emit("timeout"); myThis.stop(); if(myThis._repeatCount > 0) { myThis._repeatCount--; myThis.start();} }
}

/* A blocking pause. The fallback code is extraordinarily inefficient and will
 * eat up your CPU cycles. Do not use it except for debugging if you REALLY need
 * to pause a script to see what's going on. Never write code like this, even if
 * you're really desperate: it's no way to make a process wait. */
bpTimer.pause = function( ms ) {
	if(window.showModalDialog) {
		/* can't take credit for this - see posting on June 8th, 2007, 10:10 am
		   at http://www.ozzu.com/programming-forum/javascript-sleep-function-t66049.html */
		var s = "window.setTimeout( function() { window.close(); }, " + ms + ");";
		window.showModalDialog("javascript:document.writeln(\"<script>" + s + "</script>\")");
	}
	else {
		var end = Date.parse(new Date()) + ms, d = null;
		do{ d = Date.parse(new Date()); } while(d < end);
	}
} 

bpTimer.prototype.start = function() {
	if(this.isActive()) this.stop();
	var myThis = this;
	this._timer = window.setTimeout(this._emitTimout, this._interval);
	this._isActive = true;
}

bpTimer.prototype.repeatCount = function() { return this._repeatCount; };
bpTimer.prototype.setRepeatCount = function( repeatCount ) { if("number" == (typeof repeatCount)) this._repeatCount = repeatCount; };
bpTimer.prototype.stop = function() { if(this._timer != null) window.clearTimeout(this._timer); this._isActive = false; }
bpTimer.prototype.isActive = function() { return this._isActive; }
bpTimer.prototype.interval = function() { return this._interval; }
bpTimer.prototype.setInterval = function( interval ) { this._interval = interval; }

