/*-------------------------------------------------------------------------------------------------------------------------

Gallery - A Javascript-powered photo gallery solution

Current Version 0.2
Complete re-write of structure - now Gallery not Slideshow - slidshow functionality will be moved into a component
Copyright (c) 2008 Paul Wilson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.



******************************* GALLERY OBJECT SYNTAX ****************************************

{
name[string]  
description[string] -  optional
image[string] - optional
thumb[string] - optional
data[object|array_of_objects]
}

************************************* GALLERY DATA OBJECT SYNTAX*********************************

{
uri[string] - the path to the image
title[string] - optional
caption[string] - optional
thumb[string] - path to the thumbnail - optional
}
*/

var Gallery = {}
/*-------------------------------------------Gallery.Core---------------------------------------------------------------------------

contains the core functionality only - loads in gallery data and provides simple API for moving between gallerires - has no direct interaction with users, this is done with components


*/



Gallery.Core = new Class({
/*------------------------- class properties*-------------------------------------------------------------*/
	container : '', // holds reference to slideshow container
	galleries : [], // holds gallery objects (see above)
	imagesToLoad : [], // used for prelaoder
	loadedImages : [], // used by preloader
	currentlyLoadingGallery : '', // used by preloader
	currentGallery : '', // id of gallery currently showing
	currentImage : '', // reference to image tag currently showing
	oldImage : '', // reference to previous image tag
	currentIndex : '', // id of currently displayed image
	transitions : [], // array of avialable transition effects loaded in at start
	delayIndex : '', // used internally
	holdingSlide : '', // the image in slideshow container initially
	resize: '', //holds resizing logic
	
	Implements:Events,
	
	
	options : {
/*------------------------------------options--------------------------------------------*/
		transition : 'fade', // default transition between images 
		url: false,
		datatype: 'json',
		firstGallery: 0,
		transitionDuration: 1000,
		resize: 'scaleAndCrop', // determines what to do if picture is bigger than container - can be 'scaleAndCrop', 'crop' or 'scale'
		debug : false, // if set to true will show alert boxes from time to time detailing internal state off object
		// EVENTS
		onGalleryDataLoad : $empty,  // fired when Gallery data is loaded in
		onImageLoadBegin : $empty, // fired when image preloading begins
		onImageLoaded : $empty, // fired when each image has loaded
		onImageLoadComplete : $empty, //fired when preloading is complete
		onGalleryChange: $empty, //fired when galleries are switched
		onTransitionBegin: $empty, //fired before picture changes
		onTransitionComplete: $empty // fired when picture has changed
	},
	
	
	/* *Public Method CONSTRUCTOR
	
	Arguments - 
	containerId [string] - the id of the container element
	options [object] -  optional settings - see above
	**/
	initialize : function(containerId, options){
		this.setOptions(options);
		this.container = $(containerId);
		this.container.setStyles({
			'position' : 'relative',
			'overflow' : 'hidden'
		});
		
		//load in transition effects and holding slide
		for(prop in Gallery.Transitions){
			this.transitions.push( prop );
		}
		// load in resize functions
		for(prop in Gallery.Resize){
			Gallery.Resize[prop] = Gallery.Resize[prop].bind(this);
		}
		this.holdingSlide = this.container.getElement('img');
		this.currentImage = this.holdingSlide;
		// load gallery data, if present
		if(this.options.url) this.loadGallery(this.options.url, this.options.datatype);
		this.addEvent('galleryDataLoad', this.goToGallery.bind(this, [this.options.firstGallery, false] ) );
	},
	
	/*-------------------------UTILITY METHODS-----------------------------------*/
	
	/* private method DEBUG
	
	used to provide debugging functionality whilst testing
	
	*/
	
	debug: function(msg){
		if(this.options.debug){alert(msg);}
	},
	
	/* private method getTransition 
	
	checks gallery data object and options object to determine which transition effect to use - returns random transition if required
	
	*/
	
	getTransition: function(){
		var tran = this.options.transition;
		if(tran == 'random'){
			tran = this.transitions.getRandom();
		}else if($type(tran) == 'array'){
			tran = tran.getRandom();
		}
		return tran;
	},
	
	/* private methods whenImageLoads, whenImageLoadsPlay, whenImageLoadingCompletePlay, whenImageLoadingCompletePlay
	
	Event handlers used by preloadGallery method
	ADD STREAMING HERE
	*/

	
	whenImageLoads: function(counter, index){
		this.galleries[this.currentlyLoadingGallery].data[index].tag = this.loadedImages[index];
		this.fireEvent('imageLoaded');
	},
	
	whenImageLoadingComplete: function(){
		this.galleries[this.currentlyLoadingGallery]['loaded'] = true;
		this.currentlyLoadingGallery = '';
		this.loadedImages = '';
		this.imagesToLoad.empty();
		this.fireEvent('imageLoadingComplete');
	},
	 
	whenImageLoadsPlay:function(counter, index){
		this.whenImageLoads(counter, index);
	/*	if( this.options.stream && (  this.loadedImages.length / (this.imagesToLoad.length/100)) >=  this.options.buffer){
			this.currentGallery = this.currentlyLoadingGallery;
			this.goTo('first');
		}*/
	},
	
	whenImageLoadingCompletePlay: function(){
		this.currentGallery = this.currentlyLoadingGallery;
		this.whenImageLoadingComplete();
		this.goTo('first');
		
	},
	
	/* private method preloadGallery
	
	preloads selected gallery and streams depending on settings 
	
	*/
	
	preloadGallery: function(index, autoplay){
		while(this.currentlyLoadingGallery != ''){
			this.preloadGallery.delay(1000);
			return;
		}
		this.imagesToLoad.empty();
		this.galleries[index].data.each(function(i){this.imagesToLoad.push(i.uri);}, this);
		this.currentlyLoadingGallery = index;
		if(autoplay == true){
			this.currentGallery = index;
			this.goToHoldingSlide();
			this.loadedImages = new Asset.images(this.imagesToLoad, {onProgress:this.whenImageLoadsPlay.bind(this), onComplete:this.whenImageLoadingCompletePlay.bind(this) });
		}else{
			this.loadedImages = new Asset.images(this.imagesToLoad, {onProgress:this.whenImageLoads.bind(this), onComplete:this.whenImageLoadingComplete.bind(this) });
		}
	},
	/*
		parseJSON
			used when loading in gallery data via JSON
	*/
	
	parseJSON: function(obj){
		obj.each(function(o){this.addGallery(o);}, this);
		this.fireEvent('galleryDataLoad');
	},
	
	/*
		parseXML
			used when loading in gallery data via XML
	*/
	
	parseXML: function(txt, xml){
		var obj;
		var data;
		var name, desc, img, thumb;
		var uri, thumb, title, caption, size;
		var galleries = xml.getElements('gallery');
		galleries.each(function(g){
			obj = {};
			obj['data'] = [];
			obj['name'] = g.getElement('name').get('text');
			try{
				desc = g.getElement('description').get('text');
			}catch(e){
				desc = false;
			}
			try{
				img = g.getElement('image').get('text');
			}catch(e){
				img = false;
			}
			try{
				thumb = g.getElement('thumb').get('text');
			}catch(e){
				thumb = false;
			}
			if(desc) obj['desciption'] = desc;
			if(img) obj['image'] = img;
			if(thumb) obj['thumb'] = thumb;
			g.getElements('data').each(function(d){
				data = {};
				data['uri'] = d.getElement('uri').get('text');
				try{
					thumb = d.getElement('thumb').get('text');
				}catch(e){
					thumb = false;
				}
				try{
					title = d.getElement('title').get('text');
				}catch(e){
					title = false;
				}
				try{
					caption = d.getElement('caption').get('text');
				}catch(e){
					caption = false;
				}
				try{
					size = d.getElement('size').get('text');
				}catch(e){
					size = false;
				}
				if(thumb) data['thumb'] = thumb;
				if(title) data['title'] = title;
				if(caption) data['caption'] = caption;
				if(size) data['size'] = size;
				obj['data'].push(data);
			});
			this.addGallery(obj);
		}, this);
		this.fireEvent('galleryDataLoad');
	},
	
	/* private method afterTransition
	
	cleans up DOM after switching images
	
	*/
	
	afterTransition: function(index, newImg, oldImg){
		this.currentIndex = index;
		oldImg.setOpacity(1);
                oldImg.dispose();
		this.currentImage = newImg;
		this.currentImage.setOpacity(1);
		this.fireEvent('transitionComplete');
               this.working = false; 
	},
	
	/***************************LOW LEVEL METHODS *******************************************************************/

     /*
	     goToPic(index:int):void
		 does the grunt work of moving to the next picture
		 Called By: goToAndStop(), GoToAndPlay()
	*/
	
	goToPic: function(index){
               this.working = true; 
		if(index > (this.galleries[this.currentGallery].data.length - 1) ) index = 0;
		var newImg = this.galleries[this.currentGallery].data[index].tag;
		newImg.setStyles({'position':'absolute'});
		newImg = Gallery.Resize[this.options.resize](newImg);
		var tran = this.getTransition();
		if(!Gallery.Transitions[tran]) this.debug('transition "' + tran + '" does not exist');
		Gallery.Transitions[tran].attempt([this.currentImage, newImg, this.options.transitionDuration], this);
		this.afterTransition.delay(this.options.transitionDuration, this, [index, newImg, this.currentImage] );
	},
	
	/*
		goToHoldingSlide():void
			same as above but returns to holding slide (the original image in slideshow div)
			Called By: preloadGallery()
	*/
	
	goToHoldingSlide: function(){
		if(this.holdingSlide == this.currentImage) return;
		var newImg = this.holdingSlide
		Gallery.Transitions['none'].attempt([this.currentImage, newImg, this.options.transitionDuration], this);
		this.afterTransition(0, newImg, this.currentImage );
		this.container.getElements('img').each(function(el){
			if(el != this.holdingSlide) el.dispose();
		}, this);
	},
	
	
		
/****************************************************** THE API******************************************************************/
	
	// playback methods
	
	/*
		goTo(position:string|integer):void
			Moves slideshow to given picture and stops - used for user-controller galleries
			Arguments:
				position: can be 'first', 'last', 'next' or 'previous' or an integer representing the gallery position of the pic you want
	*/
	goTo: function(position){
                if(this.working) return;
		var index;
		$clear(this.delayIndex);
		switch(position){
			case 'first' : index = 0; break;
			case 'last' : index = this.galleries[this.currentGallery].data.length - 1; break;
			case 'next' : index = this.currentIndex + 1; break;
			case 'previous' : index = this.currentIndex - 1; break;
			default : index = position; break;
		}
		this.goToPic(index);
	},
		
	// gallery control methods
	
	/*
		goToGallery(gallName:string|integer):void
			switches to given gallery - will fire the preloader if necessary
			Arguments:
				gallName: either the name of the gallery as a string, or an integer representing it's position in the galleries object
	*/
	
	goToGallery: function(gallName, autoplay){
		var found = false;
		if( $type(gallName) == 'number' ){
			this.currentGallery = gallName;
			found = true;
		}else{
			this.galleries.each(function(gal, index){
				if(gal.name == gallName){
					found = true;
					this.currentGallery = index;
				}
			}, this);
		}
		if(found == false){
			this.debug("Gallery '" + gallName + "' does not exist");
			return;
		}
		this.fireEvent('galleryChange');
		this.goToHoldingSlide();
		if(this.galleries[this.currentGallery].loaded && autoplay == true){this.goTo('first');}else{
			this.preloadGallery(this.currentGallery, autoplay);
		}
	},
	
	/*
		addGallery(obj:object)
			used to add a gallery via javascript
			Arguments:
				obj: a gallery object - see top of page for definintion
	*/
	
	addGallery: function(obj){this.galleries.push(obj);},
	
	/*
		loadGallery(url:string):void
			loads in gallery data from the given url (must be same domain) - curently only accepts JSON
			Arguments:
				url: the url of the json file / processing script
				type: the datatype used, either xml, or json - if not given options.datatype will be used
	*/
		
	loadGallery: function(url, type){
		if(arguments.length == 1) type = this.options.datatype;
		if(type == 'json'){
			new Request.JSON({ url:url, onComplete:this.parseJSON.bind(this)}).get();
		}else{
			new Request({url:url, method:'get', onComplete:this.parseXML.bind(this)}).send();
		}
			
	}
});	

Gallery.Core.implement(new Options);

/*--------------------GALLERY.RESIZE------------------------------------------------*/

/*


Placeholder for photo resizing logic

*/

Gallery.Resize = {

	none: function(img){img.setStyles({'top':0,'left':0});
		return img;
	},

	crop: function(img){
		var containerSize = this.container.getSize();
		var left, top;
		if(img.width > containerSize.x){
			left = (img.width - containerSize.x) / 2;
			left = left - (left * 2);
		}else{
			left = (containerSize.x - img.width) / 2;
		}
		if(img.height > containerSize.y){
			top = (img.height - containerSize.y) / 2;
			top = top - (top * 2);
		}else{
			top = (containerSize.y - img.height) / 2;
		}
		img.store('left', left);
		img.store('top', top);
		img.setStyles({'top':top,'left':left});
		return img;
	},
	
	scaleByX: function(img, x){
		var ratio = img.height / img.width;
		if(x < img.width) img.width = x;
		if(ratio < 1) img.height = img.width * ratio;
		return img;
	},
	
	scaleByY: function(img, y){
		var ratio = img.width / img.height;
		if(y < img.height) img.height = y;
		if(ratio < 1) img.width = img.height * ratio;
		return img;
	},
	
	scale: function(img){
		var containerSize = this.container.getSize();
		if(img.width > img.height){
			img = Gallery.Resize.scaleByX(img, containerSize.x);
		}else{
			img = Gallery.Resize.scaleByY(img, containerSize.y);
		}
		return Gallery.Resize.crop(img);
	},
	
	scaleAndCrop: function(img){
		var containerSize = this.container.getSize();
		if(img.width > img.height){
			img = Gallery.Resize.scaleByY(img, containerSize.y);
		}else{
			img = Gallery.Resize.scaleByX(img, containerSize.x);
		}
		return Gallery.Resize.crop(img);
	},
	
	resizeContainer: function(img){
		var containerSize = this.container.getSize();
		Gallery.Resize.crop(img);
		var dimensions = {};
		dimensions.width = img.width;
		dimensions.height = img.height;
		dimensions.top = img.getStyle('top');
		dimensions.left = img.getStyle('left');
		this.container.setStyles(dimensions);
		return Gallery.Resize.none(img);
	}
}

/*-------------------------------------Gallery.Transistions-----------------------------------------------------

Contains all transition logic.

By default newImg has position property set to 'absolute',.

'top' and 'left' properties are set by Gallery.Resize.  If you need to change these the original values can be be found by newImg.retrieve('left') and newImg.retrieve('top')


*/

Gallery.Transitions = {
	
	none:function(oldImg, newImg, dur){
		newImg.setOpacity(1);
		newImg.inject(this.container, 'bottom');
		oldImg.setStyle('visibility', 'hidden');
	},
	
	fade: function(oldImg, newImg, dur){
		newImg.setOpacity(1);
                newImg.setStyle('visibility', 'visible');
		newImg.inject(this.container, 'top');
		oldImg.set('tween', {duration:dur, transition:'linear'} );
		oldImg.tween('opacity', 0);
	}
}




	