function ImageViewer()
{
	// this is a singleton implementation :)

	if (this == window)
		return new ImageViewer();

	if (ImageViewer.instance)
		return ImageViewer.instance;

	this.images = {};
	this.thumbs = [];
	this.elements = {};
	this.currentSet = null;
	this.currentImage = -1;
	this.timer = null;
	this.imageViewerActive = false;
	this.initialWidth = 230;
	this.initialHeight = 230;
	this.minWidth = 460;
	this.minHeight = 100;
	this.page = {};

	this.defaultOptions = {
		slideshow: false,
		transitionspeed: 5,
		thumbview: true
	};
	this.options = {};

	this.init();

	return (ImageViewer.instance = this);
}

Object.extend(ImageViewer.prototype,
{
	init: function()
	{	
		this.elements.imageOverlay = document.createElement('div');
		this.elements.imageOverlay.id = 'imageOverlay';

		var viewer = HTMLBuilder().built(
			{
				n:'div',
				a:{id:'imageViewer'},
				c:[
					{
						n:'div',
						a:{id:'outerImageContainer'},
						c:[
							{
								n:'div',
								a:{id:'imageContainer'},
								c:[
									{
										n:'img',
										a:{id:'imageViewerImage'}
									},
									{
										n:'div',
										a:{id:'loadingContainer'},
										c:[
											{
												n:'img',
												a:{id:'loadingImage',src:ImgURL+'/images/imageviewer/loading.gif'}
											}
										]
									}
								]
							},
							{
								n:'div',
								a:{id:'imageDataContainer'},
								c:[
									{
										n:'img',
										a:{id:'closeImage',src:ImgURL+'/images/imageviewer/closelabel.gif'}
									},
									{
										n:'div',
										a:{id:'imageDetails'},
										c:[
											{
												n:'span',
												a:{id:'numberDisplay'}
											},
											{
												n:'span',
												a:{id:'imageCaption'}
											}
										]
									},
									{
										n:'div',
										a:{id:'thumbContainer'},
										c:[
											{
												n:'div',
												a:{id:'thumbPrev',title:'Vorige afbeelding',onclick:this.prevImage.bind(this)}
											},
											{
												n:'div',
												a:{id:'thumbNext',title:'Volgende afbeelding',onclick:this.nextImage.bind(this)}
											},
											{
												n:'div',
												a:{id:'thumbView'}
											}
										]
									}
								]
							}
						]
					}
				]
			}
		);

		Object.extend(this.elements, HTMLBuilder().getIdList());

		// default hiding of elements
		this.hide('imageOverlay','imageViewer','imageViewerImage','imageDataContainer');

		document.body.appendChild(this.elements.imageOverlay);
		document.body.appendChild(viewer);

		addEvent(this.elements.imageOverlay, 'click', this.stop.bind(this));
		addEvent(this.elements.imageViewer, 'click', this.stop.bind(this));
		addEvent(window, 'resize', this.setImageViewerPosition.bind(this));
		addEvent(window, 'unload', this.cleanUp.bind(this));
	},
	cleanUp: function()
	{
		this.images = null;
		this.thumbs = null;
		this.elements = null;
	},
	start: function(set, num, options, e)
	{
		if (!this.imageViewerActive)
		{
			this.imageViewerActive = true;
			this.currentSet = set;

			// hide all flash object
			this.toggleFlash('hidden');

			this.show('loadingContainer');
			this.setImageViewerSize(this.initialWidth, this.initialHeight);
			this.setImageViewerPosition();

			this.options = options || {};
			Object.extend(this.options, this.defaultOptions);
			if (!num) num = 0;

			if (set == 'single')
			{
				this.hide('numberDisplay','thumbContainer');
			}
			else
			{
				this.show('numberDisplay');
				if (this.options.thumbview && this.images[set].length > 1)
				{
					this.generateThumbView(set, num);
				}
				else
				{
					this.options.thumbview = false;
					this.hide('thumbContainer');
				}
			}

			this.changeImage(num);
		}

		if (e)
			e.preventDefault();
	},
	stop: function(e)
	{
		var target = e.target || e.srcElement;

		if (target.id && target.id in Set('imageOverlay', 'imageViewer', 'loadingContainer', 'loadingImage', 'closeImage'))
		{
			this.imageViewerActive = false;
			this.currentImage = -1;
			if (this.timer)
			{
				clearTimeout(this.timer);
				this.timer = null;
			}
			this.hide('imageOverlay','imageViewer','imageViewerImage','imageDataContainer');

			// show all flash object
			this.toggleFlash('');
		}

		if (e)
			e.stopPropagation();
	},
	show: function()
	{
		var i = arguments.length;
		while (i--)
			this.elements[arguments[i]].style.display = 'block';
	},
	hide: function()
	{
		var i = arguments.length;
		while (i--)
			this.elements[arguments[i]].style.display = 'none';
	},
	toggleFlash: function(visibility)
	{
		['object','embed'].forEach(
			function(tag)
			{
				Array.forEach(
					document.getElementsByTagName(tag),
					function(el)
					{
						el.style.visibility = visibility;
					}
				)
			}
		);
	},
	setImageViewerPosition: function()
	{
		if (this.imageViewerActive)
		{
			this.hide('imageOverlay','imageViewer');

			this.page = getPageDimensions();

			this.elements.imageOverlay.style.width = this.page.scrollWidth + 'px';
			this.elements.imageOverlay.style.height = this.page.scrollHeight + 'px';

			this.elements.imageViewer.style.left = this.page.offsetX + 'px';
			this.elements.imageViewer.style.top = Math.round(this.page.offsetY + (this.page.innerHeight / 10)) + 'px';

			this.show('imageOverlay','imageViewer');
		}
	},
	setImageViewerSize: function(width, height)
	{
		// allow for padding (10px on both sides)
		width += 20;
		height += 20;

		this.elements.outerImageContainer.style.width = width + 'px';
		this.elements.imageContainer.style.height = (height - 20) + 'px';
		this.elements.loadingContainer.style.width = width + 'px';
		this.elements.loadingContainer.style.height = height + 'px';
		this.elements.loadingImage.style.marginTop = (Math.round(height / 2) - 16) + 'px';
	},
	changeImage: function(num)
	{
		if (num != this.currentImage)
		{
			if (this.timer)
			{
				clearTimeout(this.timer);
				this.timer = null;
			}

			this.show('loadingContainer');

			var image = this.images[this.currentSet][num], preloadImage = new Image();
			preloadImage.onload = this.showImage.bind(this, preloadImage, num);
			preloadImage.src = image.src;
		}
	},
	showImage: function(preloadImage, num)
	{
		var originalWidth = preloadImage.width, originalHeight = preloadImage.height;
		preloadImage.onload = function(){};

		this.elements.imageViewerImage.src = preloadImage.src;
		this.elements.imageViewerImage.originalWidth = originalWidth;
		this.elements.imageViewerImage.originalHeight = originalHeight;

		this.setImageSize(originalWidth, originalHeight, 'smallSize');

		this.elements.imageCaption.innerHTML = this.images[this.currentSet][num]['caption'] || '&nbsp;';
		if (this.currentSet != 'single')
			this.elements.numberDisplay.innerHTML = '(' + (num + 1) + ' / ' + this.images[this.currentSet].length + ') ';

		this.hide('loadingContainer');
		this.show('imageViewerImage','imageDataContainer');

		if (this.options.thumbview)
		{
			if (this.currentImage > -1)
				this.thumbs[this.currentImage].className = '';

			this.elements.thumbView.scrollLeft = this.thumbs[num].offsetLeft - ((Math.max(this.elements.imageViewerImage.width, this.minWidth) - this.thumbs[num].width) / 2);

			this.thumbs[num].className = 'current';
		}

		this.currentImage = num;

		if (this.options.slideshow)
			this.timer = setTimeout(this.nextImage.bind(this), this.options.transitionspeed * 1000);
	},
	setImageSize: function(width, height, size)
	{
		var resized = size == 'fullSize';

		if (!resized)
		{
			var hRatio = this.page.innerWidth / width;
			var vRatio = this.page.innerHeight / height;
			vRatio *= this.options.thumbview ? 0.7 : 0.8;

			if (hRatio < 1 || vRatio < 1)
			{
				width = Math.floor(width * Math.min(hRatio, vRatio));
				height = Math.floor(height * Math.min(hRatio, vRatio));
				resized = true;
			}
		}

		this.setImageViewerSize(Math.max(width, this.minWidth), Math.max(height, this.minHeight));

		this.elements.imageViewerImage.width = width;
		this.elements.imageViewerImage.height = height;

		if (resized)
		{
			this.elements.imageViewerImage.className = size;
			this.elements.imageViewerImage.onclick = this.toggleImageSize.bind(this);
		}
		else
		{
			this.elements.imageViewerImage.className = '';
			this.elements.imageViewerImage.onclick = function(){};
		}
	},
	toggleImageSize: function()
	{
		this.setImageSize(
			this.elements.imageViewerImage.originalWidth,
			this.elements.imageViewerImage.originalHeight,
			this.elements.imageViewerImage.className == 'smallSize' ? 'fullSize' : 'smallSize'
		);
	},
	nextImage: function()
	{
		var num = this.currentImage + 1;
		if (!this.images[this.currentSet][num])
			num = 0;

		this.show('loadingContainer');
		this.changeImage(num);		
	},
	prevImage: function()
	{
		var num = this.currentImage - 1;
		if (!this.images[this.currentSet][num])
			num = this.thumbs.length - 1;

		this.show('loadingContainer');
		this.changeImage(num);		
	},
	addImage: function(set)
	{
		var i = 1, image, num, l;

		if (!set) set = 'single';
		if (!this.images[set])
			this.images[set] = [];

		while ((image = arguments[i++]))
		{
			// check for duplicate
			for (num = 0, l = this.images[set].length; num < l; num++)
			{
				if (this.images[set][num].src == image.src)
					break;
			}

			if (num == l)
			{
				this.images[set][num] = {
					src: image.src,
					thumbsrc: image.thumbsrc,
					caption: image.caption
				};
			}

			if (image.anchor)
				addEvent(image.anchor, 'click', this.start.bind(this, set, num, false));
		}
	},
	generateThumbView: function(set, num)
	{
		var i = 0, image, img;
		this.elements.thumbView.innerHTML = '';
		this.thumbs = [];
		while ((image = this.images[set][i]))
		{
			img = document.createElement('img');
			img.src = image.thumbsrc;
			if (i == num)
				img.className = 'current';
			img.onclick = this.changeImage.bind(this, i);

			this.thumbs[i] = img;

			this.elements.thumbView.appendChild(img);

			i++;
		}

		this.show('thumbContainer');
	}
});
