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

	return null;
}

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

	if(o && o.src) {
		if(!o.xRollState) o.xRollState = "plain";

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

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; }

function bpElement(o) {
	var el = idOrObjectToObject(o);
	if(el && el.nodeName && "undefined" == typeof el.webObject) {
		el.webObject = null;
		
		if(!el.addEventListener && el.attachEvent) {
			el.addEventListener = function( e, listener, c ) {
				return this.attachEvent("on" + e, function() {
					var myEvent = window.event;
					myEvent.target = myEvent.srcElement;
					myEvent.eventPhase = 3;
					/* next two not strictly accurate */
					myEvent.bubbles = true;
					myEvent.cancelable = true;
					myEvent.stopPropagation = function() { this.cancelBubble = true; }
					myEvent.preventDefault = function() { this.returnValue = false; }
					myEvent.timeStamp = Date.parse(new Date());	/* ms since epoch */
					myEvent.relatedTarget = myEvent.fromElement;

					/* IE7 fails if event.button is assigned so instead tell
					 * handler function how to interpret event.button */
					myEvent.ieButton = true;
					return listener(myEvent); 
				});
			}
		}

		if(!el.removeEventListener && el.detachEvent) el.removeEventListener = function( e, l, c ) { return this.detachEvent("on" + e, l); }
	}

	return el;
}

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

function bpWebObject( o ) {
	bpObject.prototype.constructor.call(this);
	this.element = bpElement(o);
	
	if(this.element) {
		this.element.webObject = this;
	}
}

bpWebObject.byId = function(id) {
	var o = bpElement(id);
	if(o && o.webObject) return o.webObject;
	return null;
}

bpWebForm.prototype = new bpWebObject();
bpWebForm.prototype.constructor = bpWebForm;

function bpWebForm( o ) {
	bpWebObject.prototype.constructor.call(this, o);

	if(this.element) {
		var myThis = this;
		this.element.addEventListener("submit", function(e) {myThis.emit("submitting");}, false);
		this.element.addEventListener("reset", function(e) {myThis.emit("reset");}, false);
	}
}

bpWebFormElement.prototype = new bpWebObject();
bpWebFormElement.prototype.constructor = bpWebFormElement;

function bpWebFormElement( o ) {
	bpWebObject.prototype.constructor.call(this, o);
	
	if(this.element) {
		var myThis = this;
		this.element.addEventListener("mouseover", function(event) {myThis.emit("mouseover");}, false);
		this.element.addEventListener("mouseout", function(event) {myThis.emit("mouseout");}, false);
	}
}

bpWebFormElement.prototype.form = function() {
	if(this.element && this.element.form && this.element.form.webObject) return this.element.form.webObject;
	return null;
}

bpWebAbstractLineEdit.prototype = new bpWebFormElement();
bpWebAbstractLineEdit.prototype.constructor = bpWebAbstractLineEdit;

function bpWebAbstractLineEdit( o ) {
	bpWebFormElement.prototype.constructor.call(this, o);

	if(this.element) {
		var myThis = this;
		this.element.addEventListener("keypress", function(e) { myThis.onKeyPressEvent(e); }, false);
		this.element.addEventListener("mousedown", function(e) { myThis.onMouseDownEvent(e); }, false); 
		this.element.addEventListener("blur", function(e) { myThis.emit("editingFinished"); }, false); 
	}
}

bpWebAbstractLineEdit.prototype.isEmpty = function() { return !this.element || 0 == this.value.length; }
bpWebAbstractLineEdit.prototype.text = function() { if(!this.element) return null; return this.element.value; }
bpWebAbstractLineEdit.prototype.setText = function( t ) { if(this.element && "string" == (typeof t)) { if(this.text() != t) { this.element.value = t; this.emit("textChanged", this.text()); } return true; } return false; }

bpWebAbstractLineEdit.prototype.onKeyPressEvent = function( e ) {
	if(13 == (e.charCode ? e.charCode : (e.keyCode ? e.keyCode : (e.which ? e.which : 0)))) {
		this.emit("returnPressed");
		this.emit("editingFinished");
		if(this.enterSubmits()) this.element.form.submit();
	}
}

bpWebAbstractLineEdit.prototype.onMouseDownEvent = function( e ) {
}

bpWebLineEdit.prototype = new bpWebAbstractLineEdit();
bpWebLineEdit.prototype.constructor = bpWebLineEdit;

function bpWebLineEdit( o ) {
	bpWebAbstractLineEdit.prototype.constructor.call(this, o);

	this._placeholderText = "";
	this._usePlaceholderText = false;

	if(this.element) {
		var myThis = this;
		this.element.addEventListener("keypress", function(e) { return myThis.onKeyPressEvent(e); }, false);
		this.element.addEventListener("focus",  function(e) { return myThis.onFocusEvent(e); }, false);
		this.element.addEventListener("blur",  function(e) { return myThis.onBlurEvent(e); }, false);
		this.form().connect("submitting", this, this.onSubmitEvent);
	}

	/* ensure placeholder is refreshed */
	this.setText(this.text());
}

bpWebLineEdit.prototype.setPlaceholderText = function( t ) {
	if("string" != (typeof t)) return false;
	this._placeholderText = t;
	this.updatePlaceholder();
	return true;
}

bpWebLineEdit.prototype.placeholderText = function() { return this._placeholderText; }

bpWebLineEdit.prototype.checkPlaceholder = function() {
	if(0 == this.text().length) this._usePlaceholderText = true;
	else this._usePlaceholderText = false;
}

bpWebLineEdit.prototype.updatePlaceholder = function () {
	if(this.element) {
		if(this._usePlaceholderText) {
			this.element.value = this.placeholderText();
			this.element.style.color = "rgb(128, 128, 128)";
		}
		else
			this.element.style.color = "";
	}
}

/* the value property will return the placeholder text; this tells you
	whether the line edit is really empty */
bpWebLineEdit.prototype.isEmpty = function() { return (this._usePlaceholderText || (this.element && 0 == this.element.value.length)); }

/* the value property will return the placeholder text if the user has
	emptied the line edit; use this method to retrieve the true value */
bpWebLineEdit.prototype.text = function() { if(!this.element || this.isEmpty()) return ""; return this.element.value; }

/* use this to set value of line edit and ensure placeholder is honoured */
bpWebLineEdit.prototype.setText = function( t ) {
	if(bpWebAbstractLineEdit.prototype.setText.call(this, t)) {
		this._usePlaceholderText = false;
		this.checkPlaceholder();
		this.updatePlaceholder();
		return true;
	}

	this.checkPlaceholder();
	this.updatePlaceholder();
	return false;
}

bpWebLineEdit.prototype.onFocusEvent = function( e ) {
	/* when user focuses line edit remove the placeholder text if it's
		active */
	if(this._usePlaceholderText && this.element) this.element.value = "";
	this._usePlaceholderText = false;
	this.updatePlaceholder();
}

bpWebLineEdit.prototype.onBlurEvent = function( e ) {
	this.checkPlaceholder();
	this.updatePlaceholder();
}

bpWebLineEdit.prototype.onSubmitEvent = function( e ) {
	/* ensure placeholder text is not sent (unless user has typed it!) */
	if(this._usePlaceholderText) this.element.value = "";
}
