
var PORTFOLIO_HEIGHT = 732;

var PORTFOLIO_OPENED_HEIGHT = 700; // pixels
var PORTFOLIO_CLOSED_HEIGHT = 66; // pixels
var PORTFOLIO_SWING_DIST = 10; // pixels

var PORTFOLIO_ANIMATE_SPEED = 50; // miliseconds
var PORTFOLIO_SWING_SPEED = 20; // miliseconds

var PORTFOLIO_IMAGE_SLIDE_SPEED = 400; // miliseconds
var PORTFOLIO_IMAGE_FADE_SPEED = 400; // miliseconds
var PORTFOLIO_OPDRACHTGEVER_LOGO_FADE_SPEED = 300; // miliseconds

var PORTFOLIO_TEXT_FADE_SPEED = 300; // miliseconds

var PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT = 917;

var PORTFOLIO_EXTRA_PART_SHOW_SPEED = 400; // miliseconds
var PORTFOLIO_MAIN_PART_OPEN_PARTS_LEFT = -575; // pixels

var PORTFOLIO_SLIDESHOW_INIT_TIMEOUT = 1000;
var PORTFOLIO_SLIDESHOW_NEXT_TIMEOUT = 3000;

var PORTFOLIO_OPENED_HEIGHT_TOP_SPACING = 174;

var KEY_LEFT = 37;
var KEY_RIGHT = 39;

var PORTFOLIO_SCROLLER_INTERVAL_TIMEOUT = 20; // miliseconds
var PORTFOLIO_SCROLLER_STEP_PER_INTERVAL = 20; // pixels
var PORTFOLIO_SCROLLER_MAX_MARGIN_LEFT = 0; // pixels

var PORTFOLIO_SCROLLER_SCROLL_AREA_WIDTH = 100; // pixels

var PORTFOLIO_SCROLLER_ITEM_WIDTH = 132 + 16; // width + margin-right, pixels
var PORTFOLIO_SCROLLER_VISIBLE_ITEMS = 4;

var PAGE_SCROLL_SPEED = 100; // miliseconds

var OVERLAY_FADE_SPEED = 100; // miliseconds

var Portfolio = {
	__isOpen: false,
	__isContentLoaded: false,
	__isLoadingImage: false,
	
	__currentOpdrachtgeverID: null,
	__currentProjectID: null,
	
	__type: null,
	
	__commandQueue: null,
	
	__isOpening: false,
	__isClosing: false,
	
	__isExtraPartOpen: false,
	
	__isFirstImageShown: true,
	
	__scroller: null,
	__slideShow: null,
	
	init: function()
	{
		var self = this;
		
		Page.__document.keyup(function(e){
			if ( self.__isOpen )
			{
				switch ( e.keyCode )
				{
					case KEY_LEFT:
						self.triggerPreviousLink();
						break;
					case KEY_RIGHT:
						self.triggerNextLink();
						break;
				}
			}
		});
		
		var scrollHandler = function(){
			self.updateSticky();
		};
		Page.__window.scroll(scrollHandler).resize(scrollHandler);
		
		this.__commandQueue = new CommandQueue();
		
		this.__slideShow = new SlideShow(
			PORTFOLIO_SLIDESHOW_INIT_TIMEOUT,
			PORTFOLIO_SLIDESHOW_NEXT_TIMEOUT,
			function(callback)
			{
				self.triggerNextLink(callback);
			},
			function()
			{
				return !self.__isExtraPartOpen;
			}
		);
		
		this.__scroller = new PortfolioScroller(
			function() // startCallback
			{
				self.__slideShow.stop();
			},
			function() // stopCallback
			{
				self.__slideShow.init();
			}
		);
		
		this.__portfolio = $('#portfolio');
		this.__isOpen = this.__portfolio.hasClass('opened');
		
		Overlay.click(function(){
			if ( self.__isOpen )
			{
				self.toggle();
			}
		});
		
		this.__middleInner = $('.middle .inner', this.__portfolio);
		
		this.__middleInner.height(
			this.__isOpen
				? PORTFOLIO_OPENED_HEIGHT
				: PORTFOLIO_CLOSED_HEIGHT
		);
		
		this.__openCloseAnimator = new OpenCloseSwingAnimator(
			this.__middleInner,
			function()
			{
				return Page.__page.height() - PORTFOLIO_OPENED_HEIGHT_TOP_SPACING;
			},
			PORTFOLIO_CLOSED_HEIGHT,
			PORTFOLIO_SWING_DIST,
			PORTFOLIO_ANIMATE_SPEED,
			PORTFOLIO_SWING_SPEED
		);
		
		$('#portfolio .toggle-link a, #sub-item-projects a')
			.click(function(){
				self.toggle();
				return false;
			});
		
		this.__middleCenter = $('.middle-center', this.__portfolio);
		
		this.updateClasses();
		this.updateSticky();
	},
	
	updateSticky: function()
	{
		var winHeight = Page.__window.height();
		if ( winHeight > PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT )
		{
			PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT = winHeight;
		}
		
		var pageHeight = Page.__page.height();
		if ( pageHeight < PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT )
		{
			PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT = pageHeight;
		}
		
		var scrollTop = Page.__docEl.scrollTop();
		
		if ( this.__isOpen )
		{
			if ( pageHeight < PORTFOLIO_HEIGHT )
			{
				this.__portfolio.css('position', 'absolute');
				this.__portfolio.css('bottom', 'auto');
				this.__portfolio.css('top', PORTFOLIO_HEIGHT + 'px');
			}
			else
			{
				this.__portfolio.css('position', 'absolute');
				this.__portfolio.css('bottom', '0');
				this.__portfolio.css('top', 'auto');
			}
		}
		else if ( winHeight + scrollTop >= PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT )
		{
			this.__portfolio.css('position', 'fixed');
			this.__portfolio.css('bottom', '0');
			this.__portfolio.css('top', 'auto');
		}
		else
		{
			this.__portfolio.css('position', 'absolute');
			this.__portfolio.css('top', PORTFOLIO_STICK_TO_BOTTOM_THRESHOLD_HEIGHT + 'px');
			this.__portfolio.css('bottom', 'auto');
		}
	},
	
	openExtraPart: function()
	{
		this.__isExtraPartOpen = true;
		this.__slideShow.stop();
		this.__parts.animate(
			{left: '0px'},
			PORTFOLIO_EXTRA_PART_SHOW_SPEED,
			'swing'
		);
	},
	
	openMainPart: function()
	{
		this.__isExtraPartOpen = false;
		var self = this;
		this.__parts.animate(
			{left: PORTFOLIO_MAIN_PART_OPEN_PARTS_LEFT + 'px'},
			PORTFOLIO_EXTRA_PART_SHOW_SPEED,
			'swing',
			function()
			{
				self.__slideShow.init();
			}
		);
	},
	
	initContent: function()
	{
		var self = this;

		this.__title = $('#portfolio-title-block');
		
		this.__specs = $('#portfolio-specs-block');
		this.__specsList = $('ul', this.__specs);
		
		this.__opdrachtgever = $('#portfolio-opdrachtgever-filter-block');
		this.__opdrachtgeverSelect = $('#portfolio-opdrachtgever-select');
		this.__urls = $('#portfolio-urls-block');
		
		this.__opdrachtgevers = $('#portfolio-opdrachtgevers');
		this.__opdrachtgeversList = $('ul', this.__opdrachtgevers);
		this.__opdrachtgeversItems = $('li', this.__opdrachtgeversList);
		
		this.__scroller.setElements(this.__opdrachtgevers, this.__opdrachtgeversList);
		
		this.__image = $('#portfolio-project-image');
		this.__textOverImage = $('.text-over-image', this.__image);
		
		this.__parts = $('.parts', this.__image);
		
		this.__mainPart = $('.main-part', this.__parts);
		this.__extraPart = $('.extra-part', this.__parts);
		
		this.__openExtraPartLink = $('.link a', this.__mainPart);
		this.__openMainPartLink = $('.link a', this.__extraPart);
		
		this.__openExtraPartLink.click(function(){
			self.openExtraPart();
			return false;
		});
		this.__openMainPartLink.click(function(){
			self.openMainPart();
			return false;
		});
		
		this.__text = $('.text', this.__extraPart);
		
		this.__nextLink = $('.next a', this.__image);
		this.__nextLink.click(function(){
			self.triggerNextLink();
			return false;
		});
		
		this.__previousLink = $('.previous a', this.__image);
		this.__previousLink.click(function(){
			self.triggerPreviousLink();
			return false;
		});
		
		this.__currentImageNumber = $('.count .current', this.__image);
		this.__totalImageCount = $('.count .total', this.__image);
		
		var imageLoadEvent = function()
		{
			if ( self.hiddenImageLoadHandler ) self.hiddenImageLoadHandler();
		};
		
		this.__firstProjectImage = $('img', this.__image);
		this.__firstProjectImage.load(imageLoadEvent);
		
		this.__secondProjectImage = $(document.createElement('img'));
		this.__secondProjectImage.insertAfter(this.__firstProjectImage);
		this.__secondProjectImage.hide();
		this.__secondProjectImage.load(imageLoadEvent);
		
		this.__opdrachtgeverLogo = $('img', this.__opdrachtgever);
		this.__opdrachtgeverLogo.load(function(){
			self.__opdrachtgeverLogo.fadeIn(PORTFOLIO_OPDRACHTGEVER_LOGO_FADE_SPEED);
		});
		
		this.__titleHeader = $('.header', this.__title);
		
		this.__urlsBody = $('.body', this.__urls);
		
		this.__opdrachtgeverSelect.change(function(){
			var opdrachtgeverID = $(this).val();
			self.open(opdrachtgeverID);
		});
		
		$('#portfolio-opdrachtgevers a').click(function(){
			self.loadOtherAnchor(this);
			return false;
		});
		
		this.__slideShowOptions = $('#portfolio-auto-slide-options');
		this.__slideShowOptions.show();
		this.__slideShowCheckbox = $('input', this.__slideShowOptions);
		
		var slideShowCheckEvent = function(){
			self.__slideShow.setEnabled(this.checked);
			self.__slideShow.start();
		};
		this.__slideShowCheckbox
			.change(slideShowCheckEvent)
			.click(slideShowCheckEvent);
		
		this.__slideShow.setEnabled(this.__slideShowCheckbox.is(':checked'));
		this.__slideShow.init();
		
		sIFRPortfolioHeaders();
	},
	
	updateContent: function(data, callback)
	{
		var self = this;
		
		if ( this.__isFirstImageShown )
		{
			var imageVisible = this.__firstProjectImage;
			var imageHidden = this.__secondProjectImage;
		}
		else
		{
			var imageHidden = this.__firstProjectImage;
			var imageVisible = this.__secondProjectImage;
		}
		if ( imageVisible.attr('src') == data.PROJECT_IMAGE.SRC )
		{
			if ( callback ) callback();
		}
		else
		{
			this.__isFirstImageShown = !this.__isFirstImageShown;
			imageHidden.attr('src', data.PROJECT_IMAGE.SRC);
			
			this.hiddenImageLoadHandler = function()
			{
				var updateImage = function()
				{
					if ( callback ) callback();
				};
				if ( self.__type == 'other' )
				{
					imageVisible.fadeOut(PORTFOLIO_IMAGE_FADE_SPEED);
					imageHidden.fadeIn(PORTFOLIO_IMAGE_FADE_SPEED, updateImage);
				}
				else
				{
					var showDir = self.__type == 'previous' ? 'left' : 'right';
					imageHidden.show('slide', {direction: showDir}, PORTFOLIO_IMAGE_SLIDE_SPEED, updateImage);
					
					var hideDir = self.__type == 'previous' ? 'right' : 'left';
					imageVisible.hide('slide', {direction: hideDir}, PORTFOLIO_IMAGE_SLIDE_SPEED);
				}
			};
		}
		
		this.__nextLink.attr('href', data.NEXT_URL);
		this.__previousLink.attr('href', data.PREVIOUS_URL);
		this.__currentImageNumber.text(data.CURRENT_IMAGE_NUMBER);
		
		if ( this.__currentProjectID != data.CURRENT_PROJECT_ID )
		{
			this.__specs.toggle(data.SPECS.length != 0);
			
			$('li', this.__specsList).remove();
			for ( var i = 0; i < data.SPECS.length; i++ )
			{
				var spec = data.SPECS[i];
				this.__specsList.append('<li><span>' + spec + '</span></li>');
			}
			
			this.__textOverImage.fadeOut(200, function(){
				self.__textOverImage.html(data.TEXT_OVER_IMAGE);
				self.__textOverImage.fadeIn(200);
			})
			
			this.__currentProjectID = data.CURRENT_PROJECT_ID;
		}
		
		if ( this.__currentOpdrachtgeverID != data.CURRENT_OPDRACHTGEVER_ID )
		{
			this.__opdrachtgeversList.animate({marginLeft: data.LIST_MARGIN_LEFT + 'px'}, 'slow');
			
			this.__opdrachtgeversItems.each(function(){
				var id = this.id.substring(5);
				var isActive = id == data.CURRENT_OPDRACHTGEVER_ID;
				$(this).toggleClass('active', isActive).toggleClass('not-active', !isActive);
			});
			
			this.__totalImageCount.text(data.TOTAL_IMAGE_COUNT);
			
			this.__titleHeader.html('<h2>' + data.TITLE + '</h2>');
			
			if ( this.__isExtraPartOpen )
			{
				this.__text.fadeOut(
					PORTFOLIO_TEXT_FADE_SPEED,
					function()
					{
						self.__text.html(data.TEXT);
						self.__text.fadeIn(PORTFOLIO_TEXT_FADE_SPEED);
					}
				);
			}
			else
			{
				this.__text.html(data.TEXT);
			}
			
			this.__openExtraPartLink.parent().toggle(!data.TEXT_EMPTY);
			
			this.__openExtraPartLink.attr('href', data.OPEN_EXTRA_PART_URL);
			this.__openMainPartLink.attr('href', data.OPEN_MAIN_PART_URL);
			
			this.__opdrachtgeverLogo.fadeOut(PORTFOLIO_OPDRACHTGEVER_LOGO_FADE_SPEED, function(){
				var src = data.OPDRACHTGEVER_LOGO ? data.OPDRACHTGEVER_LOGO.SRC : '';
				self.__opdrachtgeverLogo.attr('src', src);
			});
			
			this.__opdrachtgeverSelect.val(data.CURRENT_OPDRACHTGEVER_ID);
			
			this.__urls.toggle(!data.PORTFOLIO_URLS_EMPTY);
			this.__urlsBody.html(data.PORTFOLIO_URLS);
			
			this.__currentOpdrachtgeverID = data.CURRENT_OPDRACHTGEVER_ID;
		}
		
		sIFRPortfolioHeaders();
	},
	
	open: function(opdrachtgeverID)
	{
		if ( this.__isOpening )
		{
			return;
		}
		
		var self = this;
		this.__commandQueue.add(function(){
			self.actualOpen(opdrachtgeverID);
		});
	},
	
	actualOpen: function(opdrachtgeverID)
	{
		this.__type = 'other';
		
		var self = this;
		var loadCommand = function(){
			self.loadImage(
				opdrachtgeverID, 0, 0,
				function()
				{
					self.__isOpening = false;
					self.__commandQueue.process();
				}
			);
		};
		
		if ( this.__isOpen )
		{
			loadCommand();
		}
		else
		{
			this.__isOpening = true;
			this.__isOpen = true;
			this.updateView(loadCommand);
		}
	},
	
	toggle: function()
	{
		if ( this.__isOpening || this.__isClosing )
		{
			return;
		}
		
		var self = this;
		this.__commandQueue.add(function(){
			self.actualToggle();
		});
	},
	
	actualToggle: function()
	{
		var self = this;

		this.__type = 'other';
		
		this.__isOpen = !this.__isOpen;
		if ( this.__isOpen )
		{
			if ( this.__isContentLoaded )
			{
				this.__isOpening = true;
				this.updateView(function(){
					self.__slideShow.init();
					self.__isOpening = false;
					self.__commandQueue.process();
				});
			}
			else
			{
				this.__isOpening = true;
				this.updateView(function(){
					self.loadImage(
						0, 0, 0,
						function()
						{
							self.__isOpening = false;
							self.__commandQueue.process();
						}
					);
				});
			}
		}
		else
		{
			this.__slideShow.stop();
			this.__isClosing = true;
			this.updateView(function(){
				if ( self.__isExtraPartOpen )
				{
					self.openMainPart();
				}
				self.__isClosing = false;
				self.__commandQueue.process();
			});
		}
	},
	
	triggerNextLink: function(callback)
	{
		this.loadAnchor(this.__nextLink.get(0), 'next', callback);
	},
	
	triggerPreviousLink: function(callback)
	{
		this.loadAnchor(this.__previousLink.get(0), 'previous', callback);
	},
	
	loadOtherAnchor: function(anchor, callback)
	{
		this.loadAnchor(anchor, 'other', callback);
	},
	
	loadAnchor: function(anchor, type, callback)
	{
		var self = this;
		this.__commandQueue.add(function(){
			self.actualLoadAnchor(anchor, type, callback);
		});
	},
	
	actualLoadAnchor: function(anchor, type, callback)
	{
		var params = Util.extractQueryStringParams(anchor.href);
		var opdrachtgeverID = params.opdrachtgever;
		var projectID = params.project ? params.project : 0;
		var imageID = params.image ? params.image : 0;
		
		this.__type = type;
		
		var self = this;
		this.loadImage(
			opdrachtgeverID,
			projectID,
			imageID,
			function()
			{
				if ( callback ) callback();
				self.__commandQueue.process();
			}
		);
	},
	
	loadImage: function(opdrachtgeverID, projectID, imageID, callback)
	{
		if ( this.__isContentLoaded )
		{
			this.loadImageParts(opdrachtgeverID, projectID, imageID, callback);
		}
		else
		{
			this.loadImageFull(opdrachtgeverID, projectID, imageID, callback);
		}
		this.updateSticky();
	},
	
	loadImageFull: function(opdrachtgeverID, projectID, imageID, callback)
	{
		this.__isLoadingImage = true;
		
		var self = this;
		var success = function(result)
		{
			self.__middleCenter.html(result);
			
			self.initContent();
			
			self.__isLoadingImage = false;
			self.__isContentLoaded = true;
			
			if ( opdrachtgeverID == 0 )
			{
				var activeLi = $('li.active', self.__opdrachtgeversList).get(0);
				opdrachtgeverID = activeLi.id.substring(5);
			}
			
			self.__currentOpdrachtgeverID = opdrachtgeverID;
			self.__currentProjectID = projectID;
			self.__currentImageID = imageID;
			
			if ( callback ) callback();
		};
		
		PortfolioDataAccessor.loadImageFull(opdrachtgeverID, projectID, imageID, success);
	},
	
	loadImageParts: function(opdrachtgeverID, projectID, imageID, callback)
	{
		this.__isLoadingImage = true;
		this.__slideShow.stop();
		
		var self = this;
		var success = function(result)
		{
			self.__isLoadingImage = false;
			
			self.__slideShow.init();
			
			self.updateContent(result, callback);
		};
		
		PortfolioDataAccessor.loadImageParts(opdrachtgeverID, projectID, imageID, success);
	},
	
	updateClasses: function()
	{
		this.__portfolio.toggleClass('opened', this.__isOpen);
		this.__portfolio.toggleClass('closed', !this.__isOpen);
	},
	
	updateView: function(callback)
	{
		var callbackWrapper = function()
		{
			self.updateSticky();
			if ( callback ) callback();
		};
		
		this.updateClasses();
		
		var self = this;
		if ( this.__isOpen )
		{
			Overlay.show();
			//Page.scrollToTop();
			self.updateOpenCloseState(callbackWrapper);
		}
		else
		{
			Menu.setProjectsItemActive();
			
			this.updateOpenCloseState(function(){
				Overlay.hide(callbackWrapper);
			});
		}
	},
	
	updateOpenCloseState: function(callback)
	{
		this.__openCloseAnimator[this.__isOpen ? 'open' : 'close'](callback);
	}
};


var PortfolioDataAccessor = {
	loadImageFull: function(opdrachtgeverID, projectID, imageID, success)
	{
		$.ajax({
			url: 'index.php',
			data: {
				action: 'get-portfolio-content',
				opdrachtgever: opdrachtgeverID,
				project: projectID,
				image: imageID,
				page: CURRENT_PAGE_ID
			},
			contentType: 'application/x-www-form-urlencoded; charset=iso-8859-1',
			dataType: 'html',
			success: success
		});
	},
	
	loadImageParts: function(opdrachtgeverID, projectID, imageID, success)
	{
		$.ajax({
			url: 'index.php',
			data: {
				action: 'get-portfolio-content-parts',
				opdrachtgever: opdrachtgeverID,
				project: projectID,
				image: imageID,
				page: CURRENT_PAGE_ID
			},
			contentType: 'application/x-www-form-urlencoded; charset=iso-8859-1',
			dataType: 'json',
			success: success
		});
	}
};


function OpenCloseSwingAnimator(
	element,
	openedHeightCallback,
	closedHeight,
	swingDist,
	animateSpeed,
	swingSpeed
) {
	this.__element = element;
	this.__openedHeightCallback = openedHeightCallback;
	this.__closedHeight = closedHeight;
	this.__swingDist = swingDist;
	this.__animateSpeed = animateSpeed;
	this.__swingSpeed = swingSpeed;
}
$.extend(OpenCloseSwingAnimator.prototype, {
	open: function(callback)
	{
		var self = this;
		this.animateToBottomSwingState(
			this.__swingSpeed,
			function()
			{
				if ( callback ) callback();
				self.animateToTopSwingState(
					this.__animateSpeed,
					function()
					{
						self.animateToOpenedState();
					}
				);
			}
		);
	},
	
	close: function(callback)
	{
		var self = this;
		this.animateToTopSwingState(
			this.__swingSpeed,
			function()
			{
				if ( callback ) callback();
				self.animateToBottomSwingState(
					this.__animateSpeed,
					function()
					{
						self.animateToClosedState();
					}
				);
			}
		);
	},
	
	animateToTopSwingState: function(speed, callback)
	{
		this.__element.animate(
			{height: this.__openedHeightCallback() + this.__swingDist},
			speed,
			'swing',
			callback
		);
	},
	
	animateToBottomSwingState: function(speed, callback)
	{
		this.__element.animate(
			{height: this.__closedHeight - this.__swingDist},
			speed,
			'swing',
			callback
		);
	},
	
	animateToOpenedState: function()
	{
		this.__element.animate(
			{height: this.__openedHeightCallback()},
			this.__swingSpeed,
			'swing'
		);
	},
	
	animateToClosedState: function()
	{
		this.__element.animate(
			{height: this.__closedHeight},
			this.__swingSpeed,
			'swing'
		);
	}
});


function PortfolioScroller(startCallback, stopCallback)
{
	this.__startCallback = startCallback;
	this.__stopCallback = stopCallback;
	this.__minMarginLeft = 0;
	this.__scrollSpeed = 0;
	this.__containerElement = null;
	this.__scrollElement = null;
	this.__dir = null;
	this.__intervalHandle = null;
}
PortfolioScroller.DIR_LEFT = 'left';
PortfolioScroller.DIR_RIGHT = 'right';
$.extend(PortfolioScroller.prototype, {
	setElements: function(containerElement, scrollElement)
	{
		var self = this;
		
		this.__containerElement = containerElement;
		this.__scrollElement = scrollElement;
		
		this.__containerElement.mousemove(function(e){
			self.handleMouseMoveEvent(e);
		});
		
		var containerDOMElement = this.__containerElement.get(0);
		this.__containerElement.mouseout(function(e){
			if ( Util.isOwnMouseOutEventOf(containerDOMElement, e) )
			{
				self.stop();
			}
		});
		
		var itemCount = $('li', scrollElement).length;
		var minMarginLeft = -( PORTFOLIO_SCROLLER_ITEM_WIDTH * ( itemCount - PORTFOLIO_SCROLLER_VISIBLE_ITEMS ) );
		this.__minMarginLeft = minMarginLeft;
	},
	
	handleMouseMoveEvent: function(e)
	{
		var posFromLeft = e.pageX - this.__containerElement.offset().left;
		if ( posFromLeft < PORTFOLIO_SCROLLER_SCROLL_AREA_WIDTH )
		{
			this.init(posFromLeft, PortfolioScroller.DIR_LEFT);
		}
		else
		{
			var posFromRight = this.__containerElement.width() - posFromLeft;
			if ( posFromRight < PORTFOLIO_SCROLLER_SCROLL_AREA_WIDTH )
			{
				this.init(posFromRight, PortfolioScroller.DIR_RIGHT);
			}
			else
			{
				this.stop();
			}
		}
	},
	
	init: function(posFromSide, dir)
	{
		this.stop();
		this.__startCallback();
		
		this.__dir = dir;
		var multiplier = 1 - ( ( 1 / PORTFOLIO_SCROLLER_SCROLL_AREA_WIDTH ) * posFromSide );
		this.__scrollSpeed = multiplier * PORTFOLIO_SCROLLER_STEP_PER_INTERVAL;
		
		var self = this;
		this.__intervalHandle = setInterval(
			function()
			{
				self.scroll();
			},
			PORTFOLIO_SCROLLER_INTERVAL_TIMEOUT
		);
	},
	
	scroll: function()
	{
		var marginLeft = parseInt(this.__scrollElement.css('margin-left'));
		marginLeft += this.__dir == PortfolioScroller.DIR_LEFT ? this.__scrollSpeed : -this.__scrollSpeed;
		if ( marginLeft < this.__minMarginLeft )
		{
			marginLeft = this.__minMarginLeft;
			this.stop();
		}
		else if ( marginLeft > PORTFOLIO_SCROLLER_MAX_MARGIN_LEFT )
		{
			marginLeft = PORTFOLIO_SCROLLER_MAX_MARGIN_LEFT;
			this.stop();
		}
		this.__scrollElement.css('margin-left', marginLeft + 'px');
	},
	
	stop: function()
	{
		clearInterval(this.__intervalHandle);
		this.__stopCallback();
	}
});


function SlideShow(initTimeout, nextTimeout, nextCallback, isNextAllowedCallback)
{
	this.__initTimeout = initTimeout;
	this.__nextTimeout = nextTimeout;
	this.__nextCallback = nextCallback;
	this.__isNextAllowedCallback = isNextAllowedCallback;
	this.__initTimeoutHandle = null;
	this.__nextTimeoutHandle = null;
	this.__isEnabled = false;
}
$.extend(SlideShow.prototype, {
	init: function()
	{
		if ( !this.__isNextAllowedCallback() )
		{
			this.stop();
			return;
		}
		
		this.stop();
		if ( this.__isEnabled )
		{
			var self = this;
			this.__initTimeoutHandle = setTimeout(
				function()
				{
					self.start();
				},
				this.__initTimeout
			);
		}
	},
	
	start: function()
	{
		this.stop();
		if ( this.__isEnabled )
		{
			this.next();
		}
	},
	
	next: function()
	{
		if ( !this.__isNextAllowedCallback() )
		{
			this.stop();
			return;
		}
		
		var self = this;
		this.__nextTimeoutHandle = setTimeout(
			function()
			{
				self.__nextCallback(
					function()
					{
						self.next();
					}
				);
			},
			this.__nextTimeout
		);
	},
	
	stop: function()
	{
		clearTimeout(this.__initTimeoutHandle);
		clearTimeout(this.__nextTimeoutHandle);
	},
	
	setEnabled: function(isEnabled)
	{
		this.__isEnabled = isEnabled;
	},
	
	isEnabled: function()
	{
		return this.__isEnabled;
	}
});


var Page = {
	__window: null,
	__document: null,
	__docEl: null,
	__page: null,
	
	init: function()
	{
		this.__window = $(window);
		this.__document = $(document);
		this.__docEl = $(document.documentElement);
		this.__page = $('#page');
	},
	
	scrollToBottom: function(callback)
	{
		var targetScrollTop = this.__page.height() - this.__window.height();
		this.scrollTo(targetScrollTop, callback);
	},
	
	scrollToTop: function(callback)
	{
		this.scrollTo(10, callback);
	},
	
	scrollTo: function(targetScrollTop, callback)
	{
		var currentScrollTop = this.__docEl.scrollTop();
		if ( targetScrollTop == currentScrollTop )
		{
			callback();
		}
		else
		{
			this.__docEl.animate(
				{scrollTop: targetScrollTop},
				PAGE_SCROLL_SPEED,
				callback
			);
		}
	}
};


var Overlay = {
	__element: null,
	
	init: function()
	{
		this.__element = $('#overlay');
	},
	
	click: function(clickEventHandler)
	{
		this.__element.click(clickEventHandler);
	},
	
	show: function(callback)
	{
		this.__element.hide();
		this.__element.removeClass('hidden');
		if ( $.support.opacity )
		{
			this.__element.fadeIn(OVERLAY_FADE_SPEED, callback);
		}
		else
		{
			this.__element.show();
			if ( callback ) callback();
		}
	},
	
	hide: function(callback)
	{
		var self = this;
		var afterHide = function()
		{
			self.__element.addClass('hidden');
			if ( callback ) callback();
		};
		
		if ( $.support.opacity )
		{
			this.__element.fadeOut(OVERLAY_FADE_SPEED, afterHide);
		}
		else
		{
			this.__element.hide();
			afterHide();
		}
	}
};


function CommandQueue()
{
	this.__commands = [];
	this.__isProcessing = false;
}
$.extend(CommandQueue.prototype, {
	add: function(command)
	{
		this.__commands.push(command);
		if ( !this.__isProcessing )
		{
			this.process();
		}
	},
	
	process: function()
	{
		this.__isProcessing = this.__commands.length != 0;
		if ( this.__isProcessing )
		{
			var command = this.__commands.pop();
			command();
		}
	}
});
