/***************************************************************************
 * gallery.js
 *     :
 ***************************************************************************/

/***************************************************************************
 * class ModuleGallery
 ***************************************************************************/
function ModuleGallery()
{
// Ensure this function is only called as a constructor.
	if (!(this instanceof ModuleGallery))
	{	return new ModuleGallery();	}
	return this;
}

// Inherit from class Object.
ModuleGallery.prototype = new Object();

(function($)
{
/***************************************************************************
 * Widget: Gallery
 ***************************************************************************/
var Gallery =
{
// Class members:
	'options' :
	{
		'template' : 'gallery',
		'photos'   : {}
	},

// Class methods:
	'_create' : function()
	{
	// Gather information.
		var thisObj = this;
		var $node   = this.element;
		var $photos = $node.find('*[photoId]');

	// Initialize the photo data.
		var photoIds = [];
		$photos.each(function()
		{	photoIds[photoIds.length] = $(this).attr('photoId');	});
		$.getJSON('rpc/gallery.json',
		{
			'action' : 'get_photos',
			'photos' : photoIds
		},
		function(data)
		{
			thisObj.options['photos'] = data;
		});

	// Initialize the lightBox plugin...
		$photos.find('a.gallery_photo').lightBox()
	// ...but handle anchor clicks specially.
		                               .bind('click',{'gallery':this},this.OnClick);

	// Setup event handlers.
		$photos.bind('click',{'gallery':this},this.OnClick);
		$photos.mouseover(this.OnMouseOver);
		$photos.mouseout (this.OnMouseOut);
	},

/***************************************************************************
 * Gallery::OnClick
 ***************************************************************************/
	OnClick : function(eventObj)
	{
	// Gather information.
		var galleryObj = eventObj.data['gallery'];
		var photoId    = $(this).attr('photoId');

	// View the photo immediately, or after an AJAX subrequest.
		if (photoId in galleryObj.options['photos'])
		{	galleryObj.ViewPhoto(photoId);	}
		else
		{
		// Request the photo.
			$.getJSON('rpc/gallery.json',
			{
				'action' : 'get_photos',
				'photos' : [ photoId ]
			},
			function(data)
			{
				galleryObj.options['photos'][photoId] = data[photoId];
				galleryObj.ViewPhoto(photoId);
			});
		}

	// Prevent the default action.
		return false;
	},

/***************************************************************************
 * Gallery::OnMouseOver
 ***************************************************************************/
	OnMouseOver : function(eventObj)
	{
	// Highlight the node.
		$(this).addClass('highlight');
	},

/***************************************************************************
 * Gallery::OnMouseOut
 ***************************************************************************/
	OnMouseOut : function(eventObj)
	{
	// Un-highlight the node.
		$(this).removeClass('highlight');
	},

/***************************************************************************
 * Gallery::ViewPhoto
 ***************************************************************************/
	ViewPhoto : function(photoId)
	{
	// Gather information.
		if (photoId in this.options['photos'] == false)
		{	return false;	}
		var photoData = this.options['photos'][photoId];
		if (photoData == undefined)
		{	return false;	}

	// Open a link associated with the photo, or open the photo's URL.
		if (photoData['link'])
		{
			var target = photoData['linkTarget'];
			if (target == '_blank')
			{
			// Open the link in a new window.
				window.open(photoData['link']);
			}
			else if (target == '_self' ||
					 target == null    ||
					 target == false   ||
					 target == '') // TODO: Is this good? Maybe we can use the
			{                      //       page's default target instead?
			// Open the link in the current window.
				window.location.href = photoData['link'];
			}
			else
			{
			// Open the link in a specified frame.
			// TODO: Implement me.
			}
		}
		else if (photoData['photo']['url'])
		{
		// Pass the click through to the lightBox plugin.
			var $photo = this.element.find('*[photoId='+photoId+'] a.gallery_photo');
			$photo.click();
		}
		else
		{
		// Do nothing.
		// TODO: This should never happen, but can we log it?
		}
	}
};
$.widget('ui.Gallery',Gallery);

/***************************************************************************
 * Widget: GallerySlideshow
 ***************************************************************************/
var GallerySlideshow =
{
// Class members:
	'options' :
	{
		'template'  : 'slideshow',
		'photos'    : {},
		'slideshow' :
		{
			'curIdx' : 0
		},
		'slideshowMode'           : 'sequential',
		'slideshowRate'           : 5000,
		'slideshowTransition'     : 'crossfade',
		'slideshowTransitionRate' : 1000
	},

// Class methods:
	'_create' : function()
	{
	// Gather information.
		var thisObj = this;
		var $node   = this.element;
		var $photos = $node.find('*[photoId]');

	// Initialize the photo data.
		var photoIds = [];
		$photos.each(function()
		{	photoIds[photoIds.length] = $(this).attr('photoId');	});
		$.getJSON('rpc/gallery.json',
		{
			'action' : 'get_photos',
			'photos' : photoIds
		},
		function(data)
		{
			thisObj.options['photos'] = data;
		});
	},

/***************************************************************************
 * GallerySlideshow::Play
 ***************************************************************************/
	Play : function()
	{
	// Discover the photos available to the slideshow.
		this.options['slideshow']['photos'] = this.element.find('.gallery_slideshow_photo');

	// Gather information.
		var slideshowMode = this.options['slideshowMode'];
		var slideshowRate = this.options['slideshowRate'];
		var slideCount    = this.options['slideshow']['photos'].length;

	// Set a starting photo based on the slideshow mode.
		switch (slideshowMode)
		{
		case 'random' :
		// Find a random index to start with.
			this.options['slideshow']['curIdx'] = this.__GetRandomInt(0,slideCount-1);
			break;

		case 'sequential' :
		default           :
			this.options['slideshow']['curIdx'] = 0;
			break;
		}

	// Show the first photo.
		this.SetPhoto(this.options['slideshow']['curIdx']);

	// Set up a transition to the next slideshow photo.
		if (slideCount >= 2)
		{
		// Only start the clock if there are two or more photos to switch between.
			this.element.everyTime(this.options['slideshowRate'],
			                       this.__Callback_Next);
		}
	},

/***************************************************************************
 * GallerySlideshow::__Callback_Next
 ***************************************************************************/
	__Callback_Next : function()
	{
		$(this).GallerySlideshow('Next');
	},

/***************************************************************************
 * GallerySlideshow::Next
 ***************************************************************************/
	Next : function()
	{
	// Gather information.
		var curIdx = this.options['slideshow']['curIdx'];
		var count  = this.options['slideshow']['photos'].length;

	// Is there a "next" photo to display?
		if (count <= 1)
		{	return false;	}

	// Advance to the next photo.
		var mode = this.options['slideshowMode'];
		switch (mode)
		{
		case 'random' :
		// Find a random index, but do not consider the current index.
			var randA = this.__GetRandomInt(0       ,curIdx-1);
			var randB = this.__GetRandomInt(curIdx+1, count-1);
			var which = this.__GetRandomInt(0,1); // (A or B?)
		// Advance to the next photo randomly.
			if (curIdx == 0)
			{	this.SetPhoto(randB);	}
			else if(curIdx == count-1)
			{	this.SetPhoto(randA);	}
			else if (which)
			{	this.SetPhoto(randB);	}
			else
			{	this.SetPhoto(randA);	}
			break;

		case 'sequential' :
		default           :
		// If the current index is invalid, use the "init" index value.
			if ((curIdx <  0) ||
				(curIdx >= (count - 1)))
			{	curIdx  = -1;	}
		// Advance to the next photo in sequence.
			this.SetPhoto(curIdx+1);
			break;
		}
	},

/***************************************************************************
 * GallerySlideshow::__GetRandomInt
 ***************************************************************************/
	__GetRandomInt : function(min,max)
	{
		return Math.floor(Math.random() * (max - min + 1)) + min;
	},

/***************************************************************************
 * GallerySlideshow::SetPhoto
 ***************************************************************************/
	SetPhoto : function(idx)
	{
	// Gather information.
	// TODO: Validation.
		var $node        = this.element;
		var $photos      = this.options['slideshow']['photos'];
		var curIdx       = this.options['slideshow']['curIdx'];
		var newPhotoNode = $photos[idx];
		var newPhotoObj  = $(newPhotoNode);

	// Determine if a cross fade is needed.
		if (curIdx == -1 ||
			curIdx == idx)
		{
		// Bring the photo to the front of the stack.
			newPhotoNode.parentNode.appendChild(newPhotoNode);

		// Initialize the style.
			newPhotoObj.show();
		}
		else
		{
		// Gather (more) information.
		// TODO: Validation.
			var oldPhotoNode = $photos[curIdx];
			var oldPhotoObj  = $(oldPhotoNode);

		// Move the old photo to the front of the stack.
		// Position the new photo directly behind the old photo.
			var parentNode = newPhotoNode.parentNode;
			parentNode.appendChild(oldPhotoNode);
			parentNode.insertBefore(newPhotoNode,oldPhotoNode);

		// Initialize the style.
			newPhotoObj.show();
			oldPhotoObj.show();

		// Transition to the next photo in the slideshow.
			var transition     = this.options['slideshowTransition'    ];
			var transitionRate = this.options['slideshowTransitionRate'];
			switch (transition)
			{
			case 'crossfade' :
			// Fade the old photo out so the new photo is visible.
				oldPhotoObj.fadeOut(transitionRate,function()
				{
				// Move the new photo to the top of the stack.
					parentNode.appendChild(newPhotoNode);
				});
				break;

			case 'immediate' :
			default          :
			// Move the new photo to the top of the stack.
				parentNode.appendChild(newPhotoNode);
				break;
			}
		}

	// Update the current photo.
		this.options['slideshow']['curIdx'] = idx;
		return true;
	}
};
$.widget('ui.GallerySlideshow',GallerySlideshow);

/***************************************************************************
 * Widget: GalleryFilmstrip
 ***************************************************************************/
var GalleryFilmstrip =
{
// Class members:
	'options' :
	{
		'template' : 'filmstrip',
		'photos'   : {}
	},

// Class methods:
	'_create' : function()
	{
	// Gather information.
		var thisObj = this;
		var $node   = this.element;
		var $photos = $node.find('*[photoId]');

	// Initialize the photo data.
		var photoIds = [];
		$photos.each(function()
		{	photoIds[photoIds.length] = $(this).attr('photoId');	});
		$.getJSON('rpc/gallery.json',
		{
			'action' : 'get_photos',
			'photos' : photoIds
		},
		function(data)
		{
			thisObj.options['photos'] = data;
		});

	// Setup event handlers.
		$photos.bind('click',{'gallery':this},this.OnClick);
		$photos.mouseover(this.OnMouseOver);
		$photos.mouseout (this.OnMouseOut);

	// Filmstrip scroll left/right buttons:
		this.element.find('.gallery_filmstrip\\-scroll_left' ).bind('click',{'gallery':this},this.ScrollLeft_OnClick);
		this.element.find('.gallery_filmstrip\\-scroll_right').bind('click',{'gallery':this},this.ScrollRight_OnClick);

	// Switch to the first photo.
	// TODO: Clean this up.
		$node.find('*[photoId]:first-child').click();
	},

/***************************************************************************
 * GalleryFilmstrip::OnClick
 ***************************************************************************/
	OnClick : function(eventObj)
	{
	// Gather information.
		var galleryObj = eventObj.data['gallery'];
		var photoId    = $(this).attr('photoId');

	// View the photo immediately, or after an AJAX subrequest.
		if (photoId in galleryObj.options['photos'])
		{	galleryObj.ViewPhoto(photoId);	}
		else
		{
		// Request the photo.
			$.getJSON('rpc/gallery.json',
			{
				'action' : 'get_photos',
				'photos' : [ photoId ]
			},
			function(data)
			{
				galleryObj.options['photos'][photoId] = data[photoId];
				galleryObj.ViewPhoto(photoId);
			});
		}
	},

/***************************************************************************
 * GalleryFilmstrip::ScrollLeft_OnClick
 ***************************************************************************/
	ScrollLeft_OnClick : function(eventObj)
	{
	// Gather information.
		var galleryObj = eventObj.data['gallery'];
		var $cells     = galleryObj.element.find('.gallery_filmstrip_cells');
		var $cell      = $cells.find('*[photoId].selected');
		var $prevCell  = $cell.prev('*[photoId]');
		$prevCell.click();
	},

/***************************************************************************
 * GalleryFilmstrip::ScrollRight_OnClick
 ***************************************************************************/
	ScrollRight_OnClick : function(eventObj)
	{
	// Gather information.
		var galleryObj = eventObj.data['gallery'];
		var $cells     = galleryObj.element.find('.gallery_filmstrip_cells');
		var $cell      = $cells.find('*[photoId].selected');
		var $nextCell  = $cell.next('*[photoId]');
		$nextCell.click();
	},

/***************************************************************************
 * GalleryFilmstrip::OnMouseOver
 ***************************************************************************/
	OnMouseOver : function(eventObj)
	{
	// Highlight the node.
		$(this).addClass('highlight');
	},

/***************************************************************************
 * GalleryFilmstrip::OnMouseOut
 ***************************************************************************/
	OnMouseOut : function(eventObj)
	{
	// Un-highlight the node.
		$(this).removeClass('highlight');
	},

/***************************************************************************
 * GalleryFilmstrip::ViewPhoto
 ***************************************************************************/
	ViewPhoto : function(photoId)
	{
	// Gather information.
		if (photoId in this.options['photos'] == false)
		{	return false;	}
		var photoData = this.options['photos'][photoId];
		if (photoData == undefined)
		{	return false;	}
		var $cells = this.element.find('.gallery_filmstrip_cells');
		var $cell  = $cells.find('*[photoId="'+photoId+'"]');

	// Cell highlighting.
		$cells.find('*[photoId]').removeClass('selected');
		$cell.addClass('selected');

	// Scroll the cell node fully into view.
		$cell.scrollintoview();

	// Show/hide the scroll buttons.
		if ($cell.prev('*[photoId]').length == 0)
		{	this.element.find('.gallery_filmstrip\\-scroll_left').fadeOut();	}
		else
		{	this.element.find('.gallery_filmstrip\\-scroll_left').fadeIn();		}
		if ($cell.next('*[photoId]').length == 0)
		{	this.element.find('.gallery_filmstrip\\-scroll_right').fadeOut();	}
		else
		{	this.element.find('.gallery_filmstrip\\-scroll_right').fadeIn();	}

	// Set the URL.
		this.element.find('a.gallery_filmstrip_link')
			.attr('href',photoData['link'])
			.attr('target',photoData['linkTarget']);

	// Set the description.
		this.element.find('.gallery_filmstrip_description')
			.html(photoData['description']);

	// Set the photo.
		this.element.find('.gallery_filmstrip_screen_image')
			.attr('src'   ,'') // (awkward scaling while the old URL is still displayed)
			.attr('src'   ,photoData['filmstrip-screen']['url'   ])
			.attr('width' ,photoData['filmstrip-screen']['width' ])
			.attr('height',photoData['filmstrip-screen']['height']);
	}
};
$.widget('ui.GalleryFilmstrip',GalleryFilmstrip);

})(jQuery);

