
/****************************************************************************
//	SECTION::Shortcuts
*/

window[Browser.Engine.name] = window[Browser.Engine.name + Browser.Engine.version] = true;

window.ie = window.trident;
window.ie6 = window.trident4;
window.ie7 = window.trident5;

var $E = function(selector, filter){
	return ($(filter) || document).getElement(selector);
};

var $ES = function(selector, filter){
	return ($(filter) || document).getElements(selector);
};


Element.implement({

	getText: function(){
		return this.get('text');
	},

	setText: function(text){
		return this.set('text', text);
	},

	setHTML: function(){
		return this.set('html', arguments);
	},
	
	getHTML: function(){
		return this.get('html');
	},

	getTag: function(){
		return this.get('tag');
	},
	
	highlight: function(start, end){
		if (!end){
			end = this.retrieve('highlight:original', this.getStyle('background-color'));
			end = (end == 'transparent') ? '#fff' : end;
		}
		
		var newDiv = new Element('div');
		newDiv.setStyle('position', 'absolute');
		$(document.body).adopt(newDiv);
		var position = this.getPosition()
		var size = this.getSize();
		newDiv.setStyles({
			left:		position.x,
			top:		position.y,
			width:		size.x,
			height: 	size.y,
			opacity:	.7,
			'background-color' : (start || '#ffff88')});
		
		newDiv.setStyle('z-index', 10000000);  // fixes unseen in IE when this.position=fixed
		
		var tween = new Fx.Tween(newDiv, {transition: 'Circ', duration: 500 });
		tween.start('opacity', .7, 0).chain(function(){
			newDiv.dispose();
			tween.callChain();
			tween = null;
		}.bind(this));
		return this;
	},

	getFormElements: function(){
		return this.getElements('input, textarea, select');
	}

});



Element.implement({

	replaceWith: function(el){
		el = $(el);
		this.parentNode.replaceChild(el, this);
		return el;
	},
	
	removeElements: function(){
		return this.dispose();
	}

});



Number.implement({
	zeroise: function(length) {
		return String(this).zeroise(length);
	}
});

String.implement({
	repeat: function(times) {
		var ret = [];
		for (var i = 0; i < times; i++) ret.push(this);
		return ret.join('');
	},
	zeroise: function(length) {
		return '0'.repeat(length - this.length) + this;
	}

});





Object.toQueryString = Hash.toQueryString;

var XHR = new Class({

	Extends: Request,

	options: {
		update: false
	},

	initialize: function(url, options){
		this.parent(options);
		this.url = url;
	},

	request: function(data){
		return this.send(this.url, data || this.options.data);
	},

	send: function(url, data){
		if (!this.check(arguments.callee, url, data)) return this;
		return this.parent({url: url, data: data});
	},

	success: function(text, xml){
		text = this.processScripts(text);
		if (this.options.update) $(this.options.update).empty().set('html', text);
		this.onSuccess(text, xml);
	},
	
	failure: function(){
		this.fireEvent('failure', this.xhr);
	}

});

var Ajax = XHR;


String.implement({

	endsWith: function(theEnding) {
		if(theEnding.length > this.length) return false;
		return this.substring(this.length - theEnding.length) == theEnding;
	}
});

/*
//	End SECTION::Shortcuts
****************************************************************************/



/****************************************************************************
//	SECTION::AJAX Script Handling
	
	Let's make the execution of scripts delay a little as a general rule:
*/
	
Request.implement({
	
	processScripts: function(text){
	//	dbug.log('lag version processing of scripts!');
		
		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
		
		var scriptText = '';
		var scriptFreeText = text.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
			scriptText += arguments[1] + '\n';
			return '';
		});
		
		if(this.options.evalScripts)
			$exec.delay(this.options.scriptDelay || 50, null, scriptText);
		
		return scriptFreeText;
	}

});
/*
//	End SECTION::AJAX Script Handling
****************************************************************************/



/****************************************************************************
//	SECTION::Ajax eval scripts onFailure
*/
Ajax = Class.refactor(Ajax, { 
    options: { 
        onFailure: function() {
        	hideLoad();
			this.xhr.responseText.stripScripts(true); // eval what we got as if script
		}
    } 
});
/*
//	End SECTION::Ajax eval scripts onFailure
****************************************************************************/




/****************************************************************************
//	SECTION::Selection Range & Caret Getting and Setting
*/
Element.implement({

	getSelectedRange: function() {
		if (!Browser.Engine.trident) return {start: this.selectionStart, end: this.selectionEnd};
		var pos = {start: 0, end: 0};
		var range = this.getDocument().selection.createRange();
		if (!range || range.parentElement() != this) return pos;
		var dup = range.duplicate();
		if (this.type == 'text') {
			pos.start = 0 - dup.moveStart('character', -100000);
			pos.end = pos.start + range.text.length;
		} else {
			var value = this.value;
			var offset = value.length - value.match(/[\n\r]*$/)[0].length;
			dup.moveToElementText(this);
			dup.setEndPoint('StartToEnd', range);
			pos.end = offset - dup.text.length;
			dup.setEndPoint('StartToStart', range);
			pos.start = offset - dup.text.length;
		}
		return pos;
	},
 
	selectRange: function(start, end) {
		if (Browser.Engine.trident) {
			var diff = this.value.substr(start, end - start).replace(/\r/g, '').length;
			start = this.value.substr(0, start).replace(/\r/g, '').length;
			var range = this.createTextRange();
			range.collapse(true);
			range.moveEnd('character', start + diff);
			range.moveStart('character', start);
			range.select();
		} else {
			this.focus();
			this.setSelectionRange(start, end);
		}
		return this;
	}

});
/*
//	End SECTION::Selection Range & Caret Getting and Setting
****************************************************************************/



/****************************************************************************
//	SECTION::OuterClick Event
*/
(function() {
	var events;
	var check = function(e) {
		var target = $(e.target);
		var parents = target.getParents();
		events.each(function(item) {
			var element = item.element;
			if(element != target  &&  !parents.contains(element))
				item.fn.call(element, e);
		});
	};
	Element.Events.outerClick = {
		onAdd: function(fn) {
			if(!events) {
				document.addEvent('click', check);
				events = [];
			}
			events.push({element: this, fn: fn});
		},
		onRemove: function(fn) {
			events = events.filter(function(item) {
				return item.element != this  ||  item.fn != fn;
			}, this);
			if(!events.length) {
				document.removeEvent('click', check);
				events = null;
			}
		}
	};
})();

/*
//	End SECTION::OuterClick Event
****************************************************************************/

	
/****************************************************************************
//	SECTION::Number Formatting
*/
Number.implement({

	/*
	Property: numberFormat
		Format a number with grouped thousands.

	Arguments:
		decimals, optional - integer, number of decimal percision; default, 2
		dec_point, optional - string, decimal point notation; default, '.'
		thousands_sep, optional - string, grouped thousands notation; default, ','

	Returns:
		a formatted version of number.

	Example:
		>(36432.556).numberFormat()  // returns 36,432.56
		>(36432.556).numberFormat(2, '.', ',')  // returns 36,432.56
	*/

	numberFormat : function(decimals, dec_point, thousands_sep) {
		decimals = Math.abs(decimals) + 1 ? decimals : 2;
		dec_point = dec_point || '.';
		thousands_sep = thousands_sep || ',';
	
		var matches = /(-)?(\d+)(\.\d+)?/.exec((isNaN(this) ? 0 : this) + ''); // returns matches[1] as sign, matches[2] as numbers and matches[3] as decimals
		var remainder = matches[2].length > 3 ? matches[2].length % 3 : 0;
		return (matches[1] ? matches[1] : '') + (remainder ? matches[2].substr(0, remainder) + thousands_sep : '') + matches[2].substr(remainder).replace(/(\d{3})(?=\d)/g, "$1" + thousands_sep) + 
				(decimals ? dec_point + (+matches[3] || 0).toFixed(decimals).substr(2) : '');
	}

/*
//	End SECTION::Number Formatting
****************************************************************************/
});




/****************************************************************************
//	SECTION::Element Effect Shortcuts
*/
Element.implement({
	appear: function() {
		var currentOpacity = this.getStyle('opacity');
		var currentDisplay = this.getStyle('display');
		var currentVisibility= this.getStyle('visibility');
		dbug.nog('appear', currentOpacity, currentDisplay, currentVisibility);
		if(currentOpacity == 1  && 
		   currentDisplay != 'none'  &&
		   currentVisibility == 'visible')
			return; // is already appeared!
		
		if(currentDisplay == 'none'  ||  currentVisibility == 'hidden') {
			this.setStyle('opacity', 0);
			currentOpacity = 0;
			this.setStyle('display', $pick(this.originalDisplay, this.tagName.toLowerCase()=='span' ? 'inline' : 'block'));
			this.setStyle('visibility', 'visible');
		}
		
		// Now we're ready to show:
		this.set('tween', { duration: 200 });
		this.tween('opacity', currentOpacity, 1);
	},
	
	
	fade: function() {
		if(currentOpacity == 0  || 
		   currentDisplay == 'none'  ||
		   currentVisibility == 'hidden')
			return; // is already invisible!
		
		var currentOpacity = this.getStyle('opacity');
		var currentDisplay = this.getStyle('display');
		var currentVisibility= this.getStyle('visibility');
		dbug.nog('fade', currentOpacity, currentDisplay, currentVisibility);
		
		if(currentDisplay != 'none')
			this.originalDisplay = currentDisplay; // save to preserve on re-appear!
		
		// Now we're ready to fade:
		this.set('tween', { duration: 200 });
		this.tween('opacity', currentOpacity, 0);
	},
	
	
	evaporate: function() {
		this.fade();
		this.store('slide:options', {duration: 1000 });
		this.slide();
		this.getParent().dispose.delay(1000, this.getParent());  // slide makes a wrapper
	}

});
/*
//	End SECTION::Element Effect Shortcuts
****************************************************************************/




/****************************************************************************
//	SECTION::Element Spatial Computation Shortcuts
*/
Element.implement({
	getNonContentHeight: function() {
		return 0 +
			parseInt(this.getStyle('border-top-width')) +
			parseInt(this.getStyle('border-bottom-width')) +
			parseInt(this.getStyle('margin-top')) +
			parseInt(this.getStyle('margin-bottom')) +
			parseInt(this.getStyle('padding-top')) +
			parseInt(this.getStyle('padding-bottom'));
	}
	
});
/*
//	End SECTION::Element Spatial Computation Shortcuts
****************************************************************************/





/****************************************************************************
//	SECTION::Drag that can handle % in addition to px in position styling
*/

Drag.Move.implement({

	start: function(event){
		// We're overriding bits in Drag.start, but that won't work directly since it
		// needs to impact Drag.Move.start (which calls the parent Drag.start()),
		// so we take all all of Drag.Move's start() and unroll the parent function:
		
		// jpl move beforeStart event up here (it may alter the spatial properties of
		// the dragging object in a way that impacts the container constraints!)
		this.fireEvent('beforeStart', [this.element, event]);  // jpl: pass the event
		
	if(true) {
		if (this.container){
			var el = this.element, cont = this.container, ccoo = cont.getCoordinates(el.offsetParent), cps = {}, ems = {};

			['top', 'right', 'bottom', 'left'].each(function(pad){
				cps[pad] = cont.getStyle('padding-' + pad).toInt();
				ems[pad] = el.getStyle('margin-' + pad).toInt();
			}, this);

			var width = el.offsetWidth + ems.left + ems.right, height = el.offsetHeight + ems.top + ems.bottom;
			var x = [ccoo.left + cps.left, ccoo.right - cps.right - width];
			var y = [ccoo.top + cps.top, ccoo.bottom - cps.bottom - height];

			this.options.limit = {x: x, y: y};
		}
		
		//===========================================
		// Parent function starts here:
		
		if (this.options.preventDefault) event.preventDefault();
		this.mouse.start = event.page;
		// jpl added
		this.modifierUnits = {x: 'px', y: 'px'};  // px by default, we'll see what's actually used
		
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			
	// jpl change to:
			this.modifierUnits[z] = this.getStyleUnit(this.element.getStyle(this.options.modifiers[z]));
			this.value.now[z] = this.getElementModifierStyleAsPixels(this.options.modifiers[z]);
	///		dbug.log('this.modifierUnits[z]=' + this.modifierUnits[z] + ', this.value.now[z]=' + this.value.now[z]);
	// Was:
	//		if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
	//		else this.value.now[z] = this.element[this.options.modifiers[z]];
			if (this.options.invert) this.value.now[z] *= -1;
			this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}
	} // jpl move this to check
	
		if ($type(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
	
		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
		this.document.addEvent(this.selection, this.bound.eventStop);
	},
	
	resetLimits: function(event) {
		if (this.container){
			var el = this.element, cont = this.container, ccoo = cont.getCoordinates(el.offsetParent), cps = {}, ems = {};

			['top', 'right', 'bottom', 'left'].each(function(pad){
				cps[pad] = cont.getStyle('padding-' + pad).toInt();
				ems[pad] = el.getStyle('margin-' + pad).toInt();
			}, this);

			var width = el.offsetWidth + ems.left + ems.right, height = el.offsetHeight + ems.top + ems.bottom;
			var x = [ccoo.left + cps.left, ccoo.right - cps.right - width];
			var y = [ccoo.top + cps.top, ccoo.bottom - cps.bottom - height];

			this.options.limit = {x: x, y: y};
			dbug.log('resetLimits to ')
			dbug.log(this.options.limit);
		}
		
		// jpl added
		this.modifierUnits = {x: 'px', y: 'px'};  // px by default, we'll see what's actually used
		
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			
			this.modifierUnits[z] = this.getStyleUnit(this.element.getStyle(this.options.modifiers[z]));
			this.value.now[z] = this.getElementModifierStyleAsPixels(this.options.modifiers[z]);
			if (this.options.invert) this.value.now[z] *= -1;
		//	this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}
		
		// reset this.value.now:
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
			else this.value.now[z] = this.element[this.options.modifiers[z]];
			if (this.options.invert) this.value.now[z] *= -1;
			this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}
	},

	check: function(event){
		if (this.options.preventDefault) event.preventDefault();
		
		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
		if (distance > this.options.snap){
			this.cancel();
			this.document.addEvents({
				mousemove: this.bound.drag,
				mouseup: this.bound.stop
			});
			
			// jpl: pass the event to onStart handler:
			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
		}
	},

	
	stop: function(event){
		this.checkDroppables();
		this.fireEvent('drop', [this.element, this.overed]);
		this.overed = null;
	
	
	// jpl We're overriding one thing in the parent function:	
	//	return this.parent(event);
		
		this.document.removeEvent(this.selection, this.bound.eventStop);
		this.document.removeEvent('mousemove', this.bound.drag);
		this.document.removeEvent('mouseup', this.bound.stop);
		if (event) this.fireEvent('complete', [this.element, event]);  // jpl: pass the event
	},
	
	// jpl:  handles either % or px units
	getElementModifierStyleAsPixels: function(modifier) {
		var theStyle = this.element.getStyle(modifier) + '';
		if(theStyle.substring(theStyle.length-1) == '%') {
			// a percentage of what?
			var APCoordinates = this.element.getOffsetParent().getCoordinates();
			if(['height', 'top'].contains(modifier)) 			// the parent height:
				return (theStyle.toInt() * APCoordinates.height/100).round();
			
			else if(['width', 'left'].contains(modifier))		// the parent width:
				return (theStyle.toInt() * APCoordinates.width/100).round();
			
		}
		else
			return theStyle.toInt();
	},
	
	
	// jpl:  handles either % or px units
	getElementStyleFromPixelsToUnit: function(modifier, pixelValue, unit) {
		if(unit == '%') {
			// a percentage of what?
			var APCoordinates = this.element.getOffsetParent().getCoordinates();
			if(['height', 'top'].contains(modifier))			// the parent height:
				return (pixelValue * 100/APCoordinates.height).toFixed(2) + '%';
			
			else if(['width', 'left'].contains(modifier))		// the parent width:
				return (pixelValue * 100/APCoordinates.width).toFixed(2) + '%';
		}
		else // cheap but highly legit assumption
			return pixelValue + 'px';
	},
	
	// jpl:  extract the unit from a given style value:
	getStyleUnit: function(theStyle) {
		if(theStyle.substring(theStyle.length-1) == '%')
			return '%';
		if(theStyle.substring(theStyle.length-2) == 'px')
			return 'px';
		if(theStyle.substring(theStyle.length-2) == 'em')
			return 'em';
		if(theStyle.substring(theStyle.length-2) == 'ex')
			return 'ex';
		if(theStyle.substring(theStyle.length-2) == 'pt')
			return 'pt';
		
		return 'px'; // reasonable default
		
	}
	
});


/*
//	End SECTION::Drag that can handle % in addition to px in position styling
****************************************************************************/




/****************************************************************************
//	SECTION::Fx.Slide Remix
//		Fix a problem of overflow when the height of the slide's element
//		increases.
*

Fx.Slide.implement({
	
	initialize: function(element, options){
		this.addEvent('complete', function(){
			dbug.log('hacked Slide Complete!');
			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
			if (this.open) { // jpl remove hiddenOverflow!
				this.wrapper.setStyle('overflow', '');
				this.wrapper.setStyle('height', 'auto');	
			}
			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
		}, true);
		this.element = this.subject = $(element);
		this.parent(options);
		var wrapper = this.element.retrieve('wrapper');
		this.wrapper = wrapper || new Element('div', {
			styles: $extend(this.element.getStyles('margin', 'position'), {'overflow': 'hidden'})
		}).wraps(this.element);
		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
		this.now = [];
		this.open = true;
	},
	
	start: function(how, mode){
		if (!this.check(arguments.callee, how, mode)) return this;
		this[mode || this.options.mode]();
		this.wrapper.setStyle('overflow', 'hidden'); // jpl restore hiddenOverflow!
		var margin = this.element.getStyle(this.margin).toInt();
		var layout = this.wrapper.getStyle(this.layout).toInt();
		var caseIn = [[margin, layout], [0, this.offset]];
		var caseOut = [[margin, layout], [-this.offset, 0]];
		var start;
		switch (how){
			case 'in': start = caseIn; break;
			case 'out': start = caseOut; break;
			case 'toggle': start = (this.wrapper['offset' + this.layout.capitalize()] == 0) ? caseIn : caseOut;
		}
		return this.parent(start[0], start[1]);
	}

});
/*  This didn't work for some reason... *
Fx.Slide = Class.refactor(Fx.Slide, {
	initialize: function(element, options){
		this.addEvent('complete', function(){
			dbug.log('hacked Slide Complete!');
			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
			if (this.open) { // jpl remove hiddenOverflow!
				this.wrapper.setStyle('overflow', '');
				this.wrapper.setStyle('height', 'auto');	
			}
			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
		}, true);
		this.element = this.subject = $(element);
		this.parent(options);
		var wrapper = this.element.retrieve('wrapper');
		this.wrapper = wrapper || new Element('div', {
			styles: $extend(this.element.getStyles('margin', 'position'), {'overflow': 'hidden'})
		}).wraps(this.element);
		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
		this.now = [];
		this.open = true;
	},
	
	start: function(how, mode){
		if (!this.check(arguments.callee, how, mode)) return this;
		this[mode || this.options.mode]();
		this.wrapper.setStyle('overflow', 'hidden'); // jpl restore hiddenOverflow!
		var margin = this.element.getStyle(this.margin).toInt();
		var layout = this.wrapper.getStyle(this.layout).toInt();
		var caseIn = [[margin, layout], [0, this.offset]];
		var caseOut = [[margin, layout], [-this.offset, 0]];
		var start;
		switch (how){
			case 'in': start = caseIn; break;
			case 'out': start = caseOut; break;
			case 'toggle': start = (this.wrapper['offset' + this.layout.capitalize()] == 0) ? caseIn : caseOut;
		}
		return this.parent(start[0], start[1]);
	}
});  
/*
//	End SECTION::Fx.Slide Remix
****************************************************************************/

