/* -------------------------------------------------------------------------------- *\
	M.Tarbit 24/01/08 - Ported to Prototype from the original jQuery version:
	http://famspam.com/facebox
   --------------------------------------------------------------------------------
	document.observe('dom:loaded', function(){
		$$('a[rel=facebox]').each(function(link){
			new Facebox(link);
		});
	});

	<a href="#terms" rel="facebox">Terms</a>
		Loads the #terms div in the box

	<a href="terms.html" rel="facebox">Terms</a>
		Loads the terms.html page in the box

	<a href="terms.png" rel="facebox">Terms</a>
		Loads the terms.png image in the box

   --------------------------------------------------------------------------------
	You can also use it programmatically:
   --------------------------------------------------------------------------------

	new Facebox('<p>Blah <a href="#">blah</a> blah.</p>');
		Loads the html in the box

	new Facebox(function(box){ ...ajax... });
		Shows a loading image in the box and then calls the specified function,
		passing a reference to the box into the function as an argument.

\* -------------------------------------------------------------------------------- */

document.observe('dom:loaded', function(){
	$$('a[rel=facebox]').each(function(link){
		new Facebox(link);
	});
});

// Type detection extracted from jQuery
function isString(v)	{ return typeof v == "string"; }
function isElement(v)	{ return v.nodeType; }
function isLink(v)		{ return isElement(v) && v.tagName == 'A'; }
function isFunction(v)	{ return !!v && typeof v != "string" && !v.nodeName && v.constructor != Array && /function/i.test( v + "" ); }

function Facebox(v) {
	this.init();
	if (isFunction(v))	{ this.withFunc(v); }
	else if (isLink(v))	{ this.withLink(v); }
	else 				{ this.withHTML(v); }
}

Facebox.prototype.withHTML = function(html) {
	this.loading();
	this.reveal(html);
}

Facebox.prototype.withFunc = function(func) {
	this.loading();
    func.bind(this)();
}

Facebox.prototype.withLink = function(link) {
	// support for rel="facebox[.inline_popup]" syntax, to add a class
	var className = link.rel.match(/facebox\[\.(\w+)\]/);
	if (className) className = className[1];

	var imageTypes = ['jpg','jpeg','gif','png'];
	imageTypes = new RegExp('\.' + imageTypes.join('|') + '$', 'i');

	if (link.href.match(/#/)) {
		var clickHandler = this.divHandler;
	} else if (link.href.match(imageTypes)) {
		var clickHandler = this.imgHandler;
	} else {
		var clickHandler = this.urlHandler;
	}

	var self = this;
	link.observe('click', function(e){
		e.stop();
		self.loading();
		clickHandler(self, this.href, className);
	});
}

Facebox.prototype.divHandler = function(self, href, className) {
	var url = window.location.href.split('#')[0];
	var id = href.replace(url+'#','');
	var id = id.replace('#','');
	var node = $(id).cloneNode(true).show();
	node.id = null; // Try to prevent duplication of IDs
	self.reveal(node, className);
}

Facebox.prototype.imgHandler = function(self, href, className) {
	var image = new Image();
	image.onload = function() {
		var html = '<div class="image" style="width:' + this.width + 'px;"><img src="' + this.src + '" /></div>';
		self.reveal(html, className);
	}
	image.src = href;
}

Facebox.prototype.urlHandler = function(self, href, className) {
	new Ajax.Request(href, {
		method: 'get',
		onSuccess: function(transport){
			self.reveal(transport.responseText, className);
		}
	});
}

Facebox.prototype.conf = { defaultWidth:400, viewportPadding:20 };
Facebox.prototype.refs = {}
Facebox.prototype.html = '\
	<div id="overlay" style="display:none;">\
		<div class="basicBox gradBox">\
			<div class="bTop"><div class="bTopL"></div><div class="bTopR"></div></div>\
			<div class="bContent"><div class="bPadding"><div class="overlayExtraPadding">\
				<div class="loadingIcon"></div>\
				<div class="overlayHead"><a href="#" class="closeBox">Close</a><h2 class="blue flush"></h2></div>\
				<div class="overlayBody"></div>\
			</div></div></div>\
			<div class="bBtm"><div class="bBtmL"></div><div class="bBtmR"></div></div>\
		</div>\
	</div>\
	<div id="overlayBackground" style="display:none;"></div>\
	<iframe id="overlayShim" src="/javascript/blank.html" scroll="no" frameborder="no" style="display:none;"></iframe>\
';

Facebox.prototype.init = function() {
	if (this.refs.inited) return true;

	// Insert the overlay HTML into the page once and store references
	// to it's component elements as class variables (effectively).
	$(document.body).insert({bottom: this.html});

	this.refs.container = $('overlay');
	this.refs.background = $('overlayBackground');
	this.refs.iframeShim = $('overlayShim');

	if (!this.refs.container || !this.refs.background || !this.refs.iframeShim) return;

	this.refs.closeLink = this.refs.container.down('a.closeBox');
	this.refs.headingArea = this.refs.container.down('.overlayHead h2');
	this.refs.contentArea = this.refs.container.down('.overlayBody');
	this.refs.loadingIcon = this.refs.container.down('.loadingIcon');

	// Attach event handlers for closing the overlay when the close link or the
	// background is clicked. Also reposition the overlay when the window is resized.
	this.refs.closeLink.observe('click', this.close.bind(this));
	this.refs.background.observe('click', this.close.bind(this));
	Event.observe(window, 'resize', this.resizeAndPosition.bindAsEventListener(this));

	// Only do this once per page load.
	this.refs.inited = true;
}

Facebox.prototype.loading = function() {
	this.loadingIndicator(true);
	this.show();
	this.resizeAndPosition();
}

Facebox.prototype.loadingIndicator = function(toggle) {
	this.refs.contentArea.setStyle({display: (toggle ? 'none' : '') });
	this.refs.headingArea.setStyle({display: (toggle ? 'none' : '') });
	this.refs.closeLink.setStyle({display: (toggle ? 'none' : '') });
	// this.refs.contentArea.setStyle({visibility: (toggle ? 'hidden' : 'visible') });
	// this.refs.headingArea.setStyle({visibility: (toggle ? 'hidden' : 'visible') });
	// this.refs.closeLink.setStyle({visibility: (toggle ? 'hidden' : 'visible') });
	this.refs.loadingIcon.setStyle({display: (toggle ? '' : 'none') });
}

Facebox.prototype.show = function() {
	this.refs.container.show();
	this.refs.background.show();
	this.refs.iframeShim.show();
}

Facebox.prototype.hide = function() {
	this.refs.container.hide();
	this.refs.background.hide();
	this.refs.iframeShim.hide();
}

Facebox.prototype.resizeAndPosition = function() {
	this.resize();
	this.position();
}

Facebox.prototype.resize = function() {
	this.resizeWidth();
	this.resizeHeight();
}

Facebox.prototype.resizeWidth = function() {
	this.refs.container.setStyle({width: this.conf.defaultWidth + 'px' });

	var w1 = this.refs.container.getWidth();
	var w2 = this.refs.contentArea.getWidth() - 30; // Content area is now padded
	var paddingWidth = (w1-w2);

	var el = this.refs.contentArea.down();
	if (el) {
		var w3 = el.getWidth();
		if (w3) {
			var w = (w3+paddingWidth) + 'px';
			this.refs.container.setStyle({width:w});
		}
	}
}

Facebox.prototype.resizeHeight = function() {
	this.refs.contentArea.setStyle({height:'auto'});

	var h1 = document.viewport.getHeight();
	var h2 = this.refs.container.getHeight();
	var w2 = this.refs.container.getWidth();

	// Adjust for content potentially being too tall to fit on screen
	var availableHeight = h1 - (this.conf.viewportPadding*2);
	if (h2 > availableHeight) {
		// Widen the overlay to allow some space for the scrollbar
		this.refs.container.setStyle({width: (w2 + 18) + 'px'});
		// The 65 here is a "magic number", roughly the right amount
		// to allow for vertical box padding and the overlayHeader div
		this.refs.contentArea.setStyle({height: (availableHeight - 65) +'px'});
	}
}

Facebox.prototype.position = function() {
	var h1 = document.viewport.getHeight();
	var w1 = document.viewport.getWidth();

	var h2 = this.refs.container.getHeight();
	var w2 = this.refs.container.getWidth();

	var h3 = document.documentElement.scrollHeight;

	// Don't need to adjust for scroll offsets now we've switched to position:fixed
	// var scroll = [0,0];
	// Switched back to position:absolute to avoid triggering IE6 & FF bugs
	var scroll = document.viewport.getScrollOffsets();
	var x = scroll[0] + (w1/2) - (w2/2);
	var y = scroll[1] + (h1/2) - (h2/2);

	//prevent the window from sitting right at the top of the screen
	if (y<20) y = this.conf.viewportPadding;

	this.refs.container.setStyle({top:y+'px', left:x+'px'});
	this.refs.background.setStyle({height:h3+'px'});
	this.refs.iframeShim.setStyle({height:h3+'px'});
}

Facebox.prototype.reveal = function(content, className) {
	// In an ajax request, the overlay might be closed prematurely,
	// in which case we should just discard the content returned.
	if (!this.refs.container.visible()) return;

	// If the content was a node in the document, we need to remember 
	// where it came from so we can put it back again when we're done
	if (isElement(content)) {
		this.refs.contentWasElement = true;
		this.refs.contentWasAfter = content.previous();
		if (!this.refs.contentWasAfter) {
			// In IE if this is a dynamically generated element and not
			// currently part of the document, it will have a parent, but 
			// its nodeName will be '#document-fragment'. If this is the 
			// case, just ignore it.
			var parent = content.up();
			if (parent && parent.nodeName != '#document-fragment') {
				this.refs.contentWasInside = parent;
			}
		}
		if (!content.visible()) content.show();
	} else {
		this.refs.contentWasElement = false;
	}

	// Clear the content area
	this.refs.contentArea.update();

	// Insert the content (preserving any radio values in IE)
	var values = this.getRadioValues(content);
	this.refs.contentArea.insert(content);
	this.setRadioValues(content, values);

	// Extract the heading from the inserted content
	this.refs.headingArea.update(this.extractHeading());

	this.loadingIndicator(false);
	this.resizeAndPosition();
}

Facebox.prototype.extractHeading = function(content, className) {
	var headingText = '';
	var descendants = this.refs.contentArea.descendants();
	for (var i=0; i<descendants.length; i++) {
		if (descendants[i].tagName.match(/h[1-6]/i)) {
			headingText = descendants[i].innerHTML;
			descendants[i].hide();
			break;
		}
	}
	return headingText;
}

Facebox.prototype.close = function(e) {
	this.hide();
	this.reinstateContent();

	this.refs.headingArea.update();
	this.refs.contentArea.update();

	if (e) e.stop();
}

Facebox.prototype.reinstateContent = function() {
	if (!this.refs.contentWasElement) return;

	var content = this.refs.contentArea.down();
	if (!content) return;

	var values = this.getRadioValues(content);
	if (this.refs.contentWasAfter) {
		this.refs.contentWasAfter.insert({ after:content.hide() });
	} else if (this.refs.contentWasInside) {
		this.refs.contentWasInside.insert(content.hide());
	}
	this.setRadioValues(content, values);
}


// When moving a node around by inserting it into a new position in the DOM
// IE doesn't properly carry over radio button selections. This is necessary when
// temporarily moving an optional part of a main form out into overlay.
// So, we store the radio selections for IE re-instate them after the node is moved.

Facebox.prototype.getRadioValues = function(content) {
	if (!this.refs.contentWasElement) return;
	var checks = content.select('input[type=radio], input[type=checkbox]');
	var values = {};
	for (var i=0; i<checks.length; i++) {
		if (checks[i].checked) values[checks[i].name] = checks[i].value;
	}
	return values;
}
Facebox.prototype.setRadioValues = function(content, values) {
	if (!this.refs.contentWasElement) return;
	var checks = content.select('input[type=radio], input[type=checkbox]');
	for (var i=0; i<checks.length; i++) {
		var key = checks[i].name;
		var val = checks[i].value;
		if (values[key] == val) checks[i].checked = true;
	}
}
