
spackle; // (spackle.js is required.)
transitions; // (transitions.js is required.)

function Carousel(element, spacing, thumbnails_height) {
	this.element = spackle(element);

	this.element_normal_height = this.element.size().height;

	this.spacing = spacing;
	this.thumbnails_height = thumbnails_height;

	var list_request = new HTTPRequest("GET", "carousel/images-list.cgi");
	var that = this;
	list_request.onresponse = function(status, text) {
		that.list_response(status, text);
	};
	list_request.send();

	this.scrolling_begun = false;

	this.scroll_thumbnails_transition = null;

	this.presenting_full_size_image = false;

	this.paused = false;
	this.hidden = false;
}

Carousel.TIMER_GRANULARITY = 20;

Carousel.SCROLL_BY_ONE_THUMBNAIL_DURATION = 1000;
Carousel.DELAY_BETWEEN_SCROLLING_BY_ONE_THUMBNAIL = 2000;
Carousel.PRESENT_FULL_SIZE_IMAGE_DURATION = 500;

Carousel.prototype.list_response = function(status, text) {
	if (status != 200) {
		alert("Carousel list_response: HTTP error " + status);
		return;
	}

	var file_names = text.replace(/\r\n$/, "").split("\r\n");
	if (!file_names.length) {
		alert("No Carousel images listed!");
		return;
	}

	this.items = new Array(file_names.length);
	for (var i = 0; i < this.items.length; ++i) {
		this.items[i] = {
			file_name: file_names[i],

			thumbnail: null,

			full_size_image: null
		};
	}

	this.load_thumbnail(0);

/*
	var debug = "";
	for (var i = 0; i < file_names.length; ++i) {
		if (i > 0)
			debug += "\n";
		debug += '"' + file_names[i] + '"';
	}
	alert(debug);
*/
}

Carousel.prototype.load_thumbnail = function(item_index) {
	var item = this.items[item_index];

	item.thumbnail = spackle(new Image);
	item.thumbnail.src = "carousel/thumbnail.cgi?" +
	                     "file=" + encodeURIComponent(item.file_name) + "&" +
	                     "height=" + this.thumbnails_height;

	var that = this;
	item.thumbnail.onload = function() {
		that.thumbnail_loaded(item_index);
	};

	var ua = navigator.userAgent;
	if (ua.match(/^Mozilla/) && ua.indexOf("MSIE") == -1)
		item.thumbnail.style.cursor = "-moz-zoom-in";
	else if (ua.indexOf("WebKit") != -1)
		item.thumbnail.style.cursor = "-webkit-zoom-in";
	else
		item.thumbnail.style.cursor = "url('/carousel/zoom-in.cur')";

	item.thumbnail.onclick = function() {
		that.thumbnail_clicked(item);
	};
}

Carousel.prototype.thumbnail_loaded = function(item_index) {
	var item = this.items[item_index];

	item.thumbnail.style.position = "absolute";
	item.thumbnail.set_top(this.element.offsetTop + this.spacing);

	// There's no way of knowing what order the thumbnails will be loaded in,
	// so update the positions of all of them here, not just those before.
	var left = this.spacing;
	for (var i = 0; i < this.items.length; ++i) {
		var a_thumbnail = this.items[i].thumbnail;
		if (!a_thumbnail)
			continue;

		a_thumbnail.set_left(left);

		left += a_thumbnail.width + this.spacing;
	}

	if (!this.scrolling_begun &&
	    !this.presenting_full_size_image) {
		var loaded_thumbnails_width = this.spacing,
		    contiguous = true;
		for (var i = 0; i < this.items.length; ++i) {
			var a_thumbnail = this.items[i].thumbnail;
			if (a_thumbnail)
				loaded_thumbnails_width += a_thumbnail.width + this.spacing;
			else
				contiguous = false;
		}

		// Since the thumbnails will soon be scrolled left by the following
		// amount, there needs to be at least one extra image to the right.
		var extra = this.spacing + this.items[0].thumbnail.width;
		if (loaded_thumbnails_width >= (this.element.clientWidth + extra) &&
		    contiguous) {
			this.begin_scrolling();
		}
	}

	if (++item_index < this.items.length)
		this.load_thumbnail(item_index);
}

Carousel.prototype.begin_scrolling = function() {
	if (!this.scrolling_begun)
		this.scrolling_begun = true;
	else {
		alert("Carousel begin_scrolling: scrolling already begun!");
		return;
	}

	this.scroll_by_one_thumbnail();
}

Carousel.prototype.scroll_by_one_thumbnail = function() {
	var disappearing_thumbnail = this.items[0].thumbnail;

	var disappearing_thumbnail_left = this.spacing,
	    fully_offscreen = -disappearing_thumbnail.width;

	var that = this;
	this.scroll_thumbnails_transition = new transitions.BasicTransition(
		disappearing_thumbnail_left, fully_offscreen, -1,
		function(left) {
			for (var i = 0; i < that.items.length; ++i) {
				var a_thumbnail = that.items[i].thumbnail;
				if (!a_thumbnail)
					continue;

				a_thumbnail.set_left(left);

				var document_visible_width = spackle.document_visible_width();

				// Clip.
				if (left + a_thumbnail.width > document_visible_width) {
					var thumbnail_visible_width = document_visible_width - left;
					a_thumbnail.style.clip = "rect(" +
					                         /* top:    */ "0px, " +
					                         /* right:  */ thumbnail_visible_width + "px, " +
					                         /* bottom: */ a_thumbnail.height + "px, " +
					                         /* left:   */ "0px" +
					                         ")";
				} else
					a_thumbnail.style.clip = "auto";

				left += a_thumbnail.width + that.spacing;

				// No point scrolling further images that are off-screen.
				if (left >= document_visible_width)
					break;
			}
		},
		Carousel.SCROLL_BY_ONE_THUMBNAIL_DURATION,
		/* onfinish: */ function() {
			that.wrap_items_around();

			that.scroll_thumbnails_transition = null;
	
			setTimeout(function() {
				that.scroll_by_one_thumbnail();
			}, Carousel.DELAY_BETWEEN_SCROLLING_BY_ONE_THUMBNAIL);
		}
	);

	// scroll_thumbnails_transition is null during delay before next
	// scroll_by_one_thumbnail(), so present_full_size_image(), pause(), etc.,
	// might not have been able to suspend it.
	if (this.presenting_full_size_image ||
	    this.paused ||
	    this.hidden)
		this.scroll_thumbnails_transition.suspend();
}

Carousel.prototype.wrap_items_around = function() {
	var first_item = this.items.shift();
	this.items.push(first_item);

	var left = this.spacing;
	for (var i = 0; i < this.items.length; ++i) {
		var thumbnail = this.items[i].thumbnail;
		if (!(thumbnail != undefined)) {
			alert("Invariant (thumbnail != undefined) failure.");
			break;
		}

		// Since about to scroll thumbnails left by the following amount,
		// there needs to be at least one extra image to the right.
		var extra = this.spacing + this.items[0].thumbnail.width;
		if (left < (spackle.document_visible_width() + extra)) {
			thumbnail.set_left(left);

			if (!thumbnail.parentNode)
				this.element.appendChild(thumbnail);

			// Clip.
			var document_visible_width = spackle.document_visible_width();
			if (left + thumbnail.width > document_visible_width) {
				var thumbnail_visible_width = document_visible_width - left;
				thumbnail.style.clip = "rect(" +
				                       /* top:    */ "0px, " +
				                       /* right:  */ thumbnail_visible_width + "px, " +
				                       /* bottom: */ thumbnail.height + "px, " +
				                       /* left:   */ "0px" +
				                         ")";
			} else
				thumbnail.style.clip = "auto";
		}
		else
			if (thumbnail.parentNode)
				this.element.removeChild(thumbnail);

		left += thumbnail.width + this.spacing;
	}
}


Carousel.prototype.thumbnail_clicked = function(item) {
	if (this.presenting_full_size_image)
		return;

	this.present_full_size_image(item);
}

Carousel.prototype.present_full_size_image = function(item) {
	if (this.scroll_thumbnails_transition)
		this.scroll_thumbnails_transition.suspend();

	this.presenting_full_size_image = true;

	if (!item.full_size_image) {
		item.full_size_image = spackle(new Image);
		item.full_size_image.src = "carousel/images/" + item.file_name;

		item.thumbnail.style.cursor = "wait";

		var that = this;
		item.full_size_image.onload = function() {
			that.present_full_size_image(item);

			item.thumbnail.style.cursor = "";
		};

		return;
	}

	this.dimmer = new transitions.Dimmer(0.5);

	var padding = 20;
	var target_left = padding,
	    target_top  = padding;
	var target_width  = item.full_size_image.width,
	    target_height = item.full_size_image.height;
	var max_width  = spackle.document_visible_width()  - 2*padding,
	    max_height = spackle.document_visible_height() - 2*padding;
	if (target_width  > max_width ||
	    target_height > max_height) {
		var scale = Math.min(max_width  / target_width,
		                     max_height / target_height);
		target_width  *= scale;
		target_height *= scale;
	}

	var thumbnail_position = item.thumbnail.position();
	item.full_size_image.style.position = "absolute";
	item.full_size_image.move_to(thumbnail_position.left, thumbnail_position.top);
	item.full_size_image.resize_to(item.thumbnail.width, item.thumbnail.height);

	document.documentElement.appendChild(item.full_size_image);
	var zoom_transition =
		new transitions.Zoom(item.full_size_image,
		                     target_left, target_top,
		                     target_width, target_height,
		                     Carousel.PRESENT_FULL_SIZE_IMAGE_DURATION);

	this.dimmer.dim(Carousel.PRESENT_FULL_SIZE_IMAGE_DURATION);

	var that = this;
	var dismiss = function() {
		zoom_transition.cancel();

		if (that.presenting_full_size_image) // not already dismissed...
			that.dismiss_full_size_image(item);
	};
	item.full_size_image.onclick = dismiss;

	this.dismiss_keypress_handler_info =
		window.capture_keypress(27 /* Esc */, dismiss);
}
Carousel.prototype.dismiss_full_size_image = function(item) {
	if (this.presenting_full_size_image)
		this.presenting_full_size_image = false;
	else return;

	var thumbnail_position = item.thumbnail.position();
	new transitions.Zoom(item.full_size_image,
	                     thumbnail_position.left, thumbnail_position.top,
	                     item.thumbnail.width, item.thumbnail.height,
	                     Carousel.PRESENT_FULL_SIZE_IMAGE_DURATION,
		/* onfinish: */ function() {
			document.documentElement.removeChild(item.full_size_image);
		});

	var that = this;
	this.dimmer.undim(Carousel.PRESENT_FULL_SIZE_IMAGE_DURATION,
		/* onfinish: */ function() {
			that.dimmer.finalize();
			that.dimmer = null;

			if (that.scroll_thumbnails_transition)
				that.scroll_thumbnails_transition.resume();
			else if (this.scrolling_begun)
				that.scroll_by_one_thumbnail();
		});

	window.release_keypress(this.dismiss_keypress_handler_info);
	this.dismiss_keypress_handler_info = null;
}


// Public control API.

Carousel.prototype.set_pause_unpause_controls = function(pause_element, unpause_element /* or ids */) {
	this.pause_element = spackle(pause_element);
	this.unpause_element = spackle(unpause_element);

	var that = this;
	this.pause_element.onclick = function() { that.pause(); };
	this.unpause_element.onclick = function() { that.unpause(); };

	this.pause_element_normal_display_style = window.getComputedStyle(this.pause_element).display;
	if (this.pause_element_normal_display_style == "none")
		this.pause_element_normal_display_style = "";
	this.unpause_element_normal_display_style = window.getComputedStyle(this.unpause_element).display;
	if (this.unpause_element_normal_display_style == "none")
		this.unpause_element_normal_display_style = "";
}
Carousel.prototype.set_hide_show_controls = function(hide_element, show_element) {
	this.hide_element = hide_element;
	this.show_element = show_element;

	var that = this;
	this.hide_element.onclick = function() { that.hide(); };
	this.show_element.onclick = function() { that.show(); };

	this.hide_element_normal_display_style = window.getComputedStyle(this.hide_element).display;
	if (this.hide_element_normal_display_style == "none")
		this.hide_element_normal_display_style = "";
	this.show_element_normal_display_style = window.getComputedStyle(this.show_element).display;
	if (this.show_element_normal_display_style == "none")
		this.show_element_normal_display_style = "";
}

Carousel.prototype.pause = function() {
	if (!this.paused)
		this.paused = true;
	else return;

	if (this.scroll_thumbnails_transition)
		this.scroll_thumbnails_transition.suspend();

	this.pause_element.style.display = "none";
	this.unpause_element.style.display = this.unpause_element_normal_display_style;
}
Carousel.prototype.unpause = function() {
	if (!this.paused)
		return;

	if (this.scroll_thumbnails_transition)
		this.scroll_thumbnails_transition.resume();
	else if (this.scrolling_begun)
		this.scroll_by_one_thumbnail();

	this.pause_element.style.display = this.pause_element_normal_display_style;
	this.unpause_element.style.display = "none";

	this.paused = false;
}

Carousel.prototype.hide = function() {
	if (!this.hidden)
		this.hidden = true;
	else return;

	this.element.set_height(this.spacing);

	if (this.scroll_thumbnails_transition)
		this.scroll_thumbnails_transition.suspend();

	for (var i = 0; i < this.items.length; ++i) {
		var item = this.items[i];

		if (item.thumbnail)
			item.thumbnail.style.display = "none";
	}

	this.hide_element.style.display = "none";
	this.show_element.style.display = this.show_element_normal_display_style;
}
Carousel.prototype.show = function() {
	if (!this.hidden)
		return;

	this.element.set_height(this.element_normal_height);

	if (!this.paused)
		if (this.scroll_thumbnails_transition)
			this.scroll_thumbnails_transition.resume();

	for (var i = 0; i < this.items.length; ++i) {
		var item = this.items[i];

		if (item.thumbnail)
			item.thumbnail.style.display = "";
	}

	this.hide_element.style.display = this.hide_element_normal_display_style;
	this.show_element.style.display = "none";

	this.hidden = false;
}

