function GalleryViewer(id, bgClass, fgClass, previewMetrics, sizeGrades)
{
	this.id = id;
	GalleryViewer.instances[ this.id ] = this;
	this.overlay = null;
	this.fgClass = fgClass;
	this.bgClass = bgClass;
	this.previewMetrics = previewMetrics;
	this.sizeGrades = sizeGrades;
	this.main = document.createElement( "DIV" );
	this.main.id = id;
	
	this.title = document.createElement( "DIV" );
	this.heading = document.createElement( "h1" );
	this.subheading = document.createElement( "h2" );
	this.content = document.createElement( "DIV" );
	this.nav = document.createElement( "DIV" );
	
	setClassName( this.title, "title" );
	setClassName( this.content, "content" );
	setClassName( this.nav, "nav" );
	
	this.main.appendChild( this.title );
	this.main.appendChild( this.content );
	this.main.appendChild( this.nav );
	this.title.appendChild( this.heading );
	this.title.appendChild( this.subheading );
	
	this.actions = new Array();
	
	this.btnDown = this.addButton( "down", "downDis", "actionDown" );
	this.btnUp = this.addButton( "up", "upDis", "actionUp" );
	this.btnPause = this.addButton( "pause", "pauseDis", "actionPause" );
	this.btnPlay = this.addButton( "play", "playDis", "actionPlay" );
	this.btnExit = this.addButton( "exit", "exitDis", "actionExit" );
	this.btnBack = this.addButton( "back", "backDis", "actionBack" );
	
	this.question = null;
	this.galleryData = null;
	this.imageData = null;
	this.currentImageId = null;
	this.metrics = { width: 0, height: 0 };
	this.contentMetrics = { width: 0, height: 0 };
	this.paging = { pages : 0, current : 0, pageHeight : 0 };
	this.slideshowIndex = 0;
	this.slideshowTimeout = null;
	this.elementImage = null;
	this.loading = null;
	this.loadChecker = null;
	
	this.state = "none";
}

GalleryViewer.instances = new Array();

GalleryViewer.onResize = function(width, height, olid)
{
	var gid = new String( olid ).substr( 2 );
	
	if( GalleryViewer.instances[ gid ] ) {
		GalleryViewer.instances[ gid ].doResize( width, height );
	}
}

GalleryViewer.onGalleryLoaded = function(gid, ok, response)
{
	if( ok ) {
		if( GalleryViewer.instances[ gid ] ) {
			GalleryViewer.instances[ gid ].state = "gallery";
			GalleryViewer.instances[ gid ].galleryData = response;
			GalleryViewer.instances[ gid ].heading.innerHTML = response.title;
			GalleryViewer.instances[ gid ].doResize( GalleryViewer.instances[ gid ].overlay.metrics.width,
													 GalleryViewer.instances[ gid ].overlay.metrics.height );
		}
	}
}

GalleryViewer.onZoomImageReady = function(id, ok, response)
{
	if( ok ) {
		if( GalleryViewer.instances[ id ] ) {
			GalleryViewer.instances[ id ].imageData = response;
			GalleryViewer.instances[ id ].state = "galleryZoom";
			GalleryViewer.instances[ id ].subheading.innerHTML = response.title;
			GalleryViewer.instances[ id ].doImage();
		}
	}
}

GalleryViewer.onSlideshowImageReady = function(id, ok, response)
{
	if( ok ) {
		if( GalleryViewer.instances[ id ] ) {
			GalleryViewer.instances[ id ].imageData = response;
			GalleryViewer.instances[ id ].state = "slideshow";
			GalleryViewer.instances[ id ].subheading.innerHTML = response.title;
			GalleryViewer.instances[ id ].doImage();
		}
	}
}

GalleryViewer.onZoomedImageLoaded = function(id, ok, response)
{
	if( ok ) {
		if( GalleryViewer.instances[ id ] ) {
			GalleryViewer.instances[ id ].imageData = response;
			GalleryViewer.instances[ id ].state = "zoom";
			GalleryViewer.instances[ id ].heading.innerHTML = response.title;
			GalleryViewer.instances[ id ].doImage();
		}
	}
}

GalleryViewer.prototype.addButton = function(className, classDisabled, action)
{
	var el = document.createElement( "A" );
	var alink = "javascript:GalleryViewer.instances['" + this.id + "'].onAction('" + action + "')";
	
	setClassName( el, className );

	this.setLink( el, alink );
	
	this.nav.appendChild( el );
	this.actions[ action ] = {
		button : el,
		actionName : action,
		enabledClassName : className,
		disabledClassName : classDisabled,
		js : alink
	};
	
	return el;
}

GalleryViewer.prototype.showImage = function(queryUrl)
{
	this.imageQuery = queryUrl;
}

GalleryViewer.prototype.showPicture = function(imageQueryUrl)
{
	var mtx = getDocMetrics();
	
	if( (mtx.width < 400) || (mtx.height < 400) )
		return true;
	
	this.overlay = new Overlay( "ol" + this.id, -50, -50, this.bgClass, this.fgClass, GalleryViewer.onResize );
	this.overlay.create();
	this.overlay.addChild( this.main );
	this.refreshActions();
	
	this.zoomImageQuery = imageQueryUrl;

	this.startLoadingZoomedImage();
	
	return false;
}

GalleryViewer.prototype.startLoadingZoomedImage = function()
{
	var mtx = getDocMetrics();

	var sizing = this.findRightSize( mtx.width - 100, mtx.height - (this.nav.offsetHeight + this.title.offsetHeight + 100));
	var url = new String( this.zoomImageQuery ).replace( /%w/, sizing.width ).replace( /%h/, sizing.height );

	this.question = new Question( this.id, url, null, GalleryViewer.onZoomedImageLoaded );
	this.question.ask();
}

GalleryViewer.prototype.showGallery = function(galleryQueryUrl, imageQueryUrl)
{
	var mtx = getDocMetrics();
	
	if( (mtx.width < 400) || (mtx.height < 400) )
		return true;

	this.galleryQuery = galleryQueryUrl;
	this.galleryImageQuery = imageQueryUrl;
	
	this.overlay = new Overlay( "ol" + this.id, -50, -50, this.bgClass, this.fgClass, GalleryViewer.onResize );
	this.overlay.create();
	this.overlay.addChild( this.main );
	this.refreshActions();
	
	this.question = new Question( this.id, this.galleryQuery, null, GalleryViewer.onGalleryLoaded );
	this.question.ask();
	
	return false;
}

GalleryViewer.prototype.findRightSize = function(width, height)
{
	var rw, rh;
	
	for( var j = this.sizeGrades.length - 1; j >= 0; j -- ) {
		if( width < this.sizeGrades[ j ].a ) {
			rw = this.sizeGrades[ j ].b;
		}

		if( height < this.sizeGrades[ j ].a ) {
			rh = this.sizeGrades[ j ].b;
		}
	}
	
	return { width: rw, height: rh };
}

GalleryViewer.prototype.onGalleryImageClick = function(imageId)
{
	if( (this.contentMetrics.width <= 400) || (this.contentMetrics.height <= 400) ) {
		return true;
	}	
	
	this.currentImageId = imageId;
	this.startLoadingGalleryImage( false );

	return false;
}

GalleryViewer.prototype.startLoadingGalleryImage = function(slideshow)
{
	var sizing = this.findRightSize( this.contentMetrics.width, this.contentMetrics.height );
	var url = new String( this.galleryImageQuery ).replace( /%i/, this.currentImageId ).replace( /%w/, sizing.width ).replace( /%h/, sizing.height );

	this.question = new Question( this.id, url, null, (slideshow) ? GalleryViewer.onSlideshowImageReady : GalleryViewer.onZoomImageReady );
	this.question.ask();
}

GalleryViewer.prototype.startSlideshow = function()
{
	if( this.galleryData == null ) return;
	if( this.galleryData.items.length <= this.slideshowIndex ) return;
	
	this.currentImageId = this.galleryData.items[ this.slideshowIndex ].id;
	this.startLoadingGalleryImage( true );
}

GalleryViewer.prototype.doResize = function(width, height)
{
	if( (width <= 0) || (height <= 0) )
		return;
		
/*	if( (width <= 400) || (height <= 400) ) {
		window.resizeTo( 400, 400 );
		return;
	} */

	this.metrics.width = width;
	this.metrics.height = height;
		
	var galleryWidth = this.metrics.width;
	var galleryHeight = (this.metrics.height - (this.title.offsetHeight + this.nav.offsetHeight));
	
	this.contentMetrics.width = galleryWidth;
	this.contentMetrics.height = galleryHeight;
	
	if( this.state == "none" ) {
	} else if( this.state == "zoom" ) {
		this.startLoadingZoomedImage( true );
	} else if( this.state == "gallery" ) {
		this.doGallery();
	} else if( this.state == "galleryZoom" ) {
		this.startLoadingGalleryImage( false );
	} else if( this.state == "slideshow" ) {
		this.startLoadingGalleryImage( true );
	}
}

GalleryViewer.prototype.doGallery = function()
{
	if( (this.contentMetrics.width <= 0) || (this.contentMetrics.height <= 0) )
		return;
		
	var rows, cols;
	
	cols = Math.floor( this.contentMetrics.width / this.previewMetrics.width );
	rows = Math.floor( this.contentMetrics.height / this.previewMetrics.height );
	
	var restX, restY;
	
	restX = (Math.floor( (this.contentMetrics.width % this.previewMetrics.width) / 2 ) - 1);
	restY = (Math.floor( (this.contentMetrics.height % this.previewMetrics.height) / 2 ) - 1);
	
	if( restX <= 0 )
		restX = 0;
	if( restY <= 0 )
		restY = 0;
	
	this.content.style.margin = restY + "px " + restX + "px " + restY + "px " + restX + "px";
	this.content.innerHTML = "&nbsp;";
	this.content.style.width = (cols * 162) + "px";
	this.content.style.height = (rows * 162) + "px";
	this.content.scrollTop = 0;
	
	var totalRows = 0;
	
	for( var j = 0; j < this.galleryData.items.length; j ++ ) {
		var item = this.galleryData.items[ j ];
		var next = document.createElement( "DIV" );
		var padX, padY;
		
		padX = Math.floor( (this.previewMetrics.innerWidth - item.width) / 2 );
		padY = Math.floor( (this.previewMetrics.innerHeight - item.height) / 2 );
		
		if( padX <= 0 )
			padX = 0;
		if( padY <= 0 )
			padY = 0;

		setClassName( next, "previewBox" );
		next.innerHTML = "<A target=\"_blank\" href=\"" + item.link + "\" onclick=\"return GalleryViewer.instances['" + this.id + 
						 "'].onGalleryImageClick(" + item.id + ");\" " +
						 "onmouseover=\"GalleryViewer.instances['" + this.id + "'].onOverPreview(" + j + ");\"" +
						 "onmouseout=\"GalleryViewer.instances['" + this.id + "'].onOutPreview()\">" +
						 "<img class=\"preview\" src=\"" + item.url + "\" width=\"" + 
						 item.width + "\" height=\"" + item.height + "\" /></A>";
		next.style.paddingTop = padY + "px";
		next.style.paddingBottom = padY + "px";
		next.style.paddingLeft = padX + "px";
		next.style.paddingRight = padX + "px";
		next.style.width = (item.width + (this.previewMetrics.horzBorderWidth*2)) + "px";
		next.style.height = (item.height + (this.previewMetrics.vertBorderWidth*2))+ "px";
		
		this.content.appendChild( next );
		

		if( (((j + 1) % cols) == 0) ) {
			next = document.createElement( "DIV" );
			next.style.display = "block";
			next.style.clear = "both";
			next.style.height = "1px";
			next.innerHTML = "&nbsp;";
			this.content.appendChild( next );
			totalRows ++;
		}
	}
	
	if( (this.galleryData.items.length % 5) != 0 ) {
		totalRows ++;
	}
	
	if( (totalRows % rows) != 0 ) {
		var bottomPadding = document.createElement( "DIV" );
		bottomPadding.style.display = "block";
		bottomPadding.style.clear = "both";
		bottomPadding.style.height = ((totalRows % rows) * this.previewMetrics.height) + "px";
		bottomPadding.innerHTML = "&nbsp;";
		this.content.appendChild( bottomPadding );
	}
	
	this.paging = {
		pages : ((totalRows / rows) + ((totalRows % rows) > 0 ? 1 : 0)),
		current : 0,
		pageHeight : (this.previewMetrics.height * rows) 
	};

	this.imageLoaderCatch();
	this.refreshActions();
}

GalleryViewer.prototype.doImage = function()
{
	var restX, restY;
	
	restX = Math.floor((this.contentMetrics.width - (this.imageData.width + (this.previewMetrics.horzBorderWidth*2))) / 2);
	restY = Math.floor((this.contentMetrics.height - (this.imageData.height + (this.previewMetrics.vertBorderWidth*2))) / 2);
	
	if( restX <= 0 )
		restX = 0;
	if( restY <= 0 )
		restX = 1;
		
	var shownWidth = this.imageData.width;
	var shownHeight = this.imageData.height;
	
	if( (shownWidth > this.contentMetrics.width) || (shownHeight > this.contentMetrics.height) ) {
		if( shownWidth > shownHeight ) {
			shownWidth = this.contentMetrics.width;
			shownHeight = Math.floor(this.contentMetrics.height * (this.imageData.height / this.imageData.width));
			
			if( shownHeight > this.contentMetrics.height ) {
				shownHeight = this.contentMetrics.height;
				shownWidth = Math.floor(this.contentMetrics.width * (this.imageData.width / this.imageData.height));
			}
		} else {
			shownHeight = this.contentMetrics.height;
			shownWidth = Math.floor(this.contentMetrics.width * (this.imageData.width / this.imageData.height));
			
			if( shownWidth > this.contentMetrics.width ) {
				shownWidth = this.contentMetrics.width;
				shownHeight = Math.floor(this.contentMetrics.height * (this.imageData.height / this.imageData.width));
			}
		}
	}
	
	this.content.innerHTML = "&nbsp;";
	this.content.style.margin = restY + "px " + restX + "px " + restY + "px " + restX + "px";
	this.content.innerHTML = "<img class=\"zoom\" src=\"" + this.imageData.url + "\" width=\"" + shownWidth + 
							 "\" height=\"" + shownHeight + "\" />";
	this.content.style.width = (shownWidth + (this.previewMetrics.horzBorderWidth*2)) + "px";
	this.content.style.height = (shownHeight + (this.previewMetrics.vertBorderWidth*2)) + "px";
	this.content.scrollTop = 0;

	this.imageLoaderCatch();
	this.refreshActions();
}

GalleryViewer.prototype.onOverPreview = function(index)
{
	if( this.galleryData.items.length > index ) {
		this.subheading.innerHTML = this.galleryData.items[ index ].title;
	}
}

GalleryViewer.prototype.onOutPreview = function()
{
	this.subheading.innerHTML = "&nbsp;";
}

GalleryViewer.prototype.onAction = function(action)
{
	switch( action ) {
		case	"actionUp"		:
			if( this.state == "gallery" ) {
				if( (this.paging.pages > 0) && (this.paging.current > 0) ) {
					this.content.scrollTop = (this.paging.pageHeight * (this.paging.current - 1));
					this.paging.current --;
				}
			} else if( this.state == "galleryZoom" ) {
				var ns = this.getGalleryImageNeighboars( this.currentImageId );
				if( ns.left != null )
					this.onGalleryImageClick( ns.left.id );
			}
			break;
			
		case	"actionDown"	:
			if( this.state == "gallery" ) {
				if( (this.paging.pages > 0) && (this.paging.current < (this.paging.pages - 1)) ) {
					this.content.scrollTop = (this.paging.pageHeight * (this.paging.current + 1));
					this.paging.current ++;
				}
			} else if( this.state == "galleryZoom" ) {
				var ns = this.getGalleryImageNeighboars( this.currentImageId );
				if( ns.right != null )
					this.onGalleryImageClick( ns.right.id );
			}
			break;
			
		case	"actionPlay"	:
			if( this.state == "gallery" ) {
				this.startSlideshow();
			}
			break;

		case	"actionBack"	:
			if( this.state == "galleryZoom" ) {
				this.imageData = null;
				this.currentImageId = null;
				this.state = "gallery";
				this.doGallery();
			} else if( this.state == "slideshow" ) {
				if( this.slideshowTimeout != null )
					clearTimeout( this.slideshowTimeout );
				this.imageData = null;
				this.currentImageId = null;
				this.state = "gallery";
				this.doGallery();
			}
			break;
			
		case	"actionExit"	:
			this.overlay.destroy();
			this.overlay = null;
			this.content.innerHTML = "&nbsp;";
			this.heading.innerHTML = "&nbsp;"
			this.subheading.innerHTML = "&nbsp;"
			this.galleryData = null;
			this.imageData = null;
			this.state = "none";
			break;
	}

	this.refreshActions();
}

GalleryViewer.prototype.setLink = function(l, h)
{
	if( l.href !== undefined )
		l.href = h;
	else
		l.setAttribute( "href", h );
}

GalleryViewer.prototype.refreshActions = function()
{
	for( actionName in this.actions ) {
		var state = this.actionState( actionName );
		var el = this.actions[ actionName ].button;
		
		if( (el == null) || (el === undefined) ) continue;
		
		if( state.enabled ) {
			setClassName( el, this.actions[ actionName ].enabledClassName );
			this.setLink( el, this.actions[ actionName ].js );
		} else {
			setClassName( el, this.actions[ actionName ].disabledClassName );
			this.setLink( el, "javascript:void(0)" );
		}
		
		if( state.visible )
			el.style.display = "";
		else
			el.style.display = "none";
	}
}

GalleryViewer.prototype.getGalleryImageNeighboars = function(id)
{
	var result = {
		left : null,
		right : null
	};
	
	if( this.galleryData != null ) {
		for( var j = 0; j < this.galleryData.items.length; j ++ ) {
			if( this.galleryData.items[ j ].id == id ) {
				if( j > 0 ) {
					result.left = this.galleryData.items[ j - 1 ];
				}
				
				if( (j + 1) < this.galleryData.items.length ) {
					result.right = this.galleryData.items[ j + 1 ];
				}
			}
		}
	}
	
	return result;
}

GalleryViewer.prototype.actionState = function(action)
{
	var stateDesc = {
		enabled : false,
		visible : false
	};

	switch( action ) {
		case	"actionUp"		:
			if( this.state == "gallery" ) {
				stateDesc.visible = true;
				
				if( this.paging.pages > 0 ) {
					if( this.paging.current > 0 )
						stateDesc.enabled = true;
				}
			}
			
			if (this.state == "galleryZoom") {
				stateDesc.visible = true;
				
				if( this.currentImageId != null ) {
					var ns = this.getGalleryImageNeighboars( this.currentImageId );
					if( ns.left != null ) {
						stateDesc.enabled = true;
					}
				}
			}
			
			break;

		case	"actionDown"	:
			if( this.state == "gallery" ) {
				stateDesc.visible = true;
				
				if( this.paging.pages > 0 ) {
					if( this.paging.current < (this.paging.pages - 1) )
						stateDesc.enabled = true;
				}
			}
			
			if( this.state == "galleryZoom" ) {
				stateDesc.visible = true;

				if( this.currentImageId != null ) {
					var ns = this.getGalleryImageNeighboars( this.currentImageId );
					if( ns.right != null ) {
						stateDesc.enabled = true;
					}
				}
			}

			break;

		case	"actionPause"	:
			if( this.state == "slideshow" ) {
				stateDesc.enabled = true;
				stateDesc.visible = true;
			}
			break;

		case	"actionPlay"	:
			if( this.state == "gallery" ) {
				stateDesc.enabled = true;
				stateDesc.visible = true;
			}
			break;

		case	"actionBack"	:
			if( (this.state == "galleryZoom") || (this.state == "slideshow") ) {
				stateDesc.enabled = true;
				stateDesc.visible = true;
			}
			break;
			
		case	"actionExit"	:
			if( this.state != "none" ) {
				stateDesc.enabled = true;
				stateDesc.visible = true;
			}
			break;
	}
	
	return stateDesc;
}

GalleryViewer.prototype.imageLoaderCatch = function()
{
	var images = this.content.getElementsByTagName( "IMG" );

	this.loading = new Array();
	
	for( var j = 0; j < images.length; j ++ ) {
		var xy = { x : 0, y : 0 };
		var el = images[ j ];
		
		while( 1 ) {
			xy.x += el.offsetLeft;
			xy.y += el.offsetTop;
			el = el.offsetParent;			
			if( (el == this.overlay.content) || (el == null) ) break;
		}
		
		var screen = document.createElement( "DIV" );
		setClassName( screen, "loading" );
		screen.style.position = "absolute";
		screen.style.display = "block";
		screen.style.left = xy.x + "px";
		screen.style.top = xy.y + "px";
		screen.style.width = (images[ j ].width + (this.previewMetrics.horzBorderWidth * 2)) + "px";
		screen.style.height = (images[ j ].height + (this.previewMetrics.vertBorderWidth * 2)) + "px";
		this.content.appendChild( screen );
		this.loading[ images[ j ].src ] = screen;
	}
	
	var gv = this;
	
	this.loadChecker = setInterval( function(e) {
		gv.imageLoaderCheck();
	}, 100 );
}

GalleryViewer.prototype.imageLoaderCheck = function()
{
	if( this.loading == null ) {
		clearInterval( this.loadChecker );
		this.loadChecker = null;
	}

	var images = this.content.getElementsByTagName( "IMG" );
	var isReady = true;
	
	for( var j = 0; j < images.length; j ++ ) {
		var img = images[ j ];

	    if (!img.complete) {
	    	isReady = false;
	        continue;
	    }
	    if( img.loaded !== undefined ) {
			if( img.loaded != true ) {
		    	isReady = false;
			    continue;
			}
	    }
	    
	    if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0) {
	    	isReady = false;
	        continue;
	    }
	    	    
	    var s = this.loading[ img.src ];

	    if( (s != null) && (s !== undefined) && (s != 0) ) {
	    	this.content.removeChild( s );
	    	this.loading[ img.src ] = 0;
	    }
	}
	
	if( isReady ) {
		if( this.loading != null ) {
			this.loading.length = 0;
			this.loading = null;
			this.onAllImagesLoaded();
		}
		clearInterval( this.loadChecker );
		this.loadChecker = null;
	}
}

GalleryViewer.prototype.orderNextSlide = function()
{
	if( (this.slideshowIndex + 1) < this.galleryData.items.length ) {
		var gv = this;
		this.slideshowTimeout = setTimeout( function(e) {
			gv.slideshowTimeout = null;
			gv.slideshowIndex ++;
			gv.startSlideshow();
		}, 4000
		);
	}
}

GalleryViewer.prototype.onAllImagesLoaded = function()
{

	if( this.state == "slideshow" ) {
		this.orderNextSlide();
	}
}