
powerCarousel = function(sElementId, pImageData) {
	if (arguments.length < 2) {
		throw new Error ("Incorrect number of minimum parameters supplied when creating a new Power Carousel.");
	}
	if (!$(sElementId)) {
		throw new Error ("The id [' + sElementId = '] was not found when creating a new Power Carousel.");
	}

	this.oCarousel = $(sElementId);

	this.bInitialised = false;
	this.bAnimationInProgress = false;
	this.bTransitionInProgress = false;
	this.bMouseOverPaused = false;
	this.nCurrentlyDisplayedPowerImage = 0;
	this.nNextPowerImageToDisplay = 1;
	this.nSubmissiveTransitioningImage = null;
	this.bPausedOverNavigationOnly = false;

    var UA = navigator.userAgent;
    //detect if the browser is on a Mac and if it is Firefox 2
    this.bSkipFade = (UA.indexOf('Mac') != -1 && UA.indexOf('Firefox/2') != -1);
    var safari2Mac = (UA.indexOf('Mac') != -1 && UA.indexOf('Version/') == -1 && UA.indexOf('Safari/') != -1);

    this.nTransitionTime = 1;				// seconds to perform the fade/appear transition for the power image - automated fade
	this.nMouseOverTransitionTime = 0;	// seconds to perform the fade/appear transition for the power image - user instigated fade (mouse over thumbnail)
	this.nAnimationDelay = 3000;			// milliseconds to wait between automatic animations

	this.pImageData = pImageData;

    //The reason we don't go from 0 to 1 is because this causes all text on a mac to pulse
    this.nMaxOpacityValue = 0.99;
	this.nMinOpacityValue = 0.01;
	if (Prototype.Browser.IE) {
		this.nMaxOpacityValue = 1;
		this.nMinOpacityValue = 0;
	}

    //fix a firefox 3 bug with tv carousel
    if(UA.indexOf('Firefox/3') != -1 && $('powerImageSummary')) {
        $('powerImageSummary').setStyle({overflow : 'visible'});
    }

    this.sPowerImageSelector = '.powerImageItem img';
	this.sPowerImageSummarySelector = '.powerImageSummary .summary';
	this.sPowerImageNavigationThumbClass = 'powerImageThumb';
	this.sPowerImageNavigationThumbSelector = '.powerImageThumb';
	this.sCarouselNavigatorSelector = '#powerImageNavigatorOuter';

	this.oAnimationTimer = null;
	this.oMouseOverTimer = null;
	this.sQueueName = 'powerImageQueue';
	this.oQueueMap = { scope: this.sQueueName };
};


powerCarousel.prototype.initialise = function() {
	this.pPowerImages = $$(this.sPowerImageSelector);
	this.pPowerImageSummaries = $$(this.sPowerImageSummarySelector);
	this.pNavigationThumbs = $$(this.sPowerImageNavigationThumbSelector);
	this.oCarouselNavigator = $$(this.sCarouselNavigatorSelector)[0];

	if (this.pPowerImages.length != this.pImageData.length) {
		throw new Error ("The number of power image placeholders does not match the amount of data supplied.");
	} else {
		// Preload images. Don't attach onload event to real power images as this can fire twice
		// (once for the 'transparent.gif' and once for the real image)
		var pTemporaryImagePreloadArray = [];
		this.pImageData.each(function(powerImageFilename) {
			var tempImg = new Image();
            if(safari2Mac) {
                tempImg.onload = this.loadedPowerImage.bindAsEventListener(this);
            } else {
                Event.observe(tempImg, 'load', this.loadedPowerImage.bindAsEventListener(this));
            }
            tempImg.src = powerImageFilename;
			pTemporaryImagePreloadArray[pTemporaryImagePreloadArray.length] = tempImg;
		}, this);
		this.bInitialised = true;
	}


};


powerCarousel.prototype.loadedPowerImage = function(eventData) {
	if (this.loadedPowerImageCount == null) {
		this.loadedPowerImageCount = 0;
	}

	this.loadedPowerImageCount++;
	if (this.loadedPowerImageCount == this.pImageData.length) {
		// Now that all images have preloaded, switch the transparent images for the real ones
		var nImageDataIndex = 0;
		this.pPowerImages.each(function(powerImageNode) {
			powerImageNode.src = this.pImageData[nImageDataIndex++];
		}, this);

		this.intialiseRollovers();
		this.startAnimation();
	}
};


powerCarousel.prototype.intialiseRollovers = function() {
	this.pNavigationThumbs.each(function(powerImageThumbNode) {
		powerImageThumbNode.observe('mouseover', this.mouseOverPowerImageThumb.bindAsEventListener(this));
	}, this);

	if (!this.bPausedOverNavigationOnly) {
		this.oCarousel.observe('mouseover', this.mouseOverCarousel.bindAsEventListener(this));
		this.oCarousel.observe('mouseout', this.mouseOutCarousel.bindAsEventListener(this));
	}

	this.oCarouselNavigator.observe('mouseover', this.mouseOverCarousel.bindAsEventListener(this));
	this.oCarouselNavigator.observe('mouseout', this.mouseOutCarousel.bindAsEventListener(this));
};


powerCarousel.prototype.mouseOverCarousel = function(eventData) {
	if (this.oMouseOverTimer != null) {
		clearTimeout(this.oMouseOverTimer);
		this.oMouseOverTimer = null;
	}

	if (this.bAnimationInProgress) {
		this.stopAnimation();
	}
	this.bMouseOverPaused = true;
};


powerCarousel.prototype.mouseOutCarousel = function(eventData) {
	if (this.oMouseOverTimer != null) {
		clearTimeout(this.oMouseOverTimer);
		this.oMouseOverTimer = null;
	}

	this.oMouseOverTimer = setTimeout(this.actualMouseOutCarousel.bind(this), 100);
};


powerCarousel.prototype.actualMouseOutCarousel = function() {
	this.bMouseOverPaused = false;
	this.startAnimation();
};


powerCarousel.prototype.startAnimation = function() {
	this.bAnimationInProgress = true;
	this.nMouseOverThumbEventTriggered = null;

	if (this.oAnimationTimer == null) {
		this.oAnimationTimer = setTimeout(this.showNextItem.bind(this), this.nAnimationDelay);
	}
};


powerCarousel.prototype.showNextItem = function(eventData) {
	this.oAnimationTimer = null;
	this.nNextPowerImageToDisplay = (this.nCurrentlyDisplayedPowerImage + 1) % this.pPowerImages.length;
	this.showPowerImage(this.nNextPowerImageToDisplay);
	this.showPowerImageSummary(this.nNextPowerImageToDisplay);
};


powerCarousel.prototype.stopAnimation = function() {
	if (this.oAnimationTimer != null) {
		clearTimeout(this.oAnimationTimer);
		this.oAnimationTimer = null;
	}
	this.bAnimationInProgress = false;
};


powerCarousel.prototype.mouseOverPowerImageThumb = function(eventData) {
	var oNode = Event.element(eventData);
	while (!Element.hasClassName(oNode, this.sPowerImageNavigationThumbClass)) oNode = oNode.parentNode;
	var nNodeIndex = parseInt(oNode.id.substring(oNode.id.lastIndexOf('_') + 1), 10);


	if (this.nMouseOverThumbEventTriggered != nNodeIndex) {
		this.nMouseOverThumbEventTriggered = nNodeIndex;


		if (this.nCurrentlyDisplayedPowerImage != nNodeIndex || this.bTransitionInProgress) {

			var nFadeFromOpacityValue;
			var nAppearFromOpacityValue;

			if (this.bTransitionInProgress) {

				if (this.nCurrentlyDisplayedPowerImage == nNodeIndex) {
					this.nCurrentlyDisplayedPowerImage = this.nSubmissiveTransitioningImage;
				}

				nFadeFromOpacityValue = Element.extend(this.pPowerImages[this.nCurrentlyDisplayedPowerImage].parentNode).getOpacity();
				nAppearFromOpacityValue = 1 - nFadeFromOpacityValue;

				// Cancel effect queue if fade is currently in progress
				this.cancelEffects();

				if (this.nSubmissiveTransitioningImage != nNodeIndex) {
					this.instantFadeSubmissiveImage();
					this.instantFadeSubmissiveSummary();
				}

			}

			this.nNextPowerImageToDisplay = nNodeIndex;
			this.showPowerImage(this.nNextPowerImageToDisplay, nFadeFromOpacityValue, nAppearFromOpacityValue);
			this.showPowerImageSummary(this.nNextPowerImageToDisplay);
		}
	}
};


powerCarousel.prototype.showPowerImage = function(nNodeIndex, nFadeFromOpacityValue, nAppearFromOpacityValue) {
    var oNodeToFadeFrom = Element.extend(this.pPowerImages[this.nCurrentlyDisplayedPowerImage].parentNode);
	var oNodeToFadeTo = Element.extend(this.pPowerImages[nNodeIndex].parentNode);

    if(this.bSkipFade) {
        oNodeToFadeFrom.setStyle({zIndex: 2}).hide();
	    oNodeToFadeTo.setStyle({zIndex: 3}).show();

        this.pNavigationThumbs[this.nCurrentlyDisplayedPowerImage].removeClassName('selected');
        this.pNavigationThumbs[nNodeIndex].addClassName('selected');

        this.nextItemFullyLoaded();
        return;
    }

    nFadeFromOpacityValue = nFadeFromOpacityValue || this.nMaxOpacityValue;
	nAppearFromOpacityValue = nAppearFromOpacityValue || this.nMinOpacityValue;

	var timeToFade = (this.bMouseOverPaused) ? this.nMouseOverTransitionTime : this.nTransitionTime;

	var bSwappedAt50Percent = false;
	Effect.Appear(oNodeToFadeTo, {from: nAppearFromOpacityValue, to: this.nMaxOpacityValue, duration: timeToFade, queue: this.oQueueMap });
	Effect.Fade(oNodeToFadeFrom, {from: nFadeFromOpacityValue, to: this.nMinOpacityValue, duration: timeToFade, queue: this.oQueueMap,
		beforeStart: function(effectObj) {
			this.nSubmissiveTransitioningImage = nNodeIndex;
			this.bTransitionInProgress = true;
			oNodeToFadeFrom.setStyle({zIndex: 3}).show();
			oNodeToFadeTo.setStyle({zIndex: 2}).show();
		}.bind(this),
		beforeUpdate: function(effectObj) {

			var bAfterHalfwayPoint = effectObj.totalFrames / 2 <= effectObj.currentFrame;
			if (bAfterHalfwayPoint && !bSwappedAt50Percent) {
				bSwappedAt50Percent = true;
				this.nSubmissiveTransitioningImage = this.nCurrentlyDisplayedPowerImage;
				this.nCurrentlyDisplayedPowerImage = nNodeIndex;

				// update the navigation highlight for the thumbs
				this.pNavigationThumbs[this.nSubmissiveTransitioningImage].removeClassName('selected');
				this.pNavigationThumbs[this.nCurrentlyDisplayedPowerImage].addClassName('selected');
				oNodeToFadeFrom.setStyle({zIndex: 2});
				oNodeToFadeTo.setStyle({zIndex: 3});
			}
		}.bind(this),
		afterFinish: function(effectObj) {
			this.nSubmissiveTransitioningImage = null;
			this.bTransitionInProgress = false;
			this.nextItemFullyLoaded();
			oNodeToFadeFrom.hide();	// This dopesn't get done for us if we don't fade to 0% opacity...
		}.bind(this)
	});
};


powerCarousel.prototype.nextItemFullyLoaded = function() {
	if (!this.bMouseOverPaused) {
		this.startAnimation();
	}
};


powerCarousel.prototype.cancelEffects = function() {
	var queue = Effect.Queues.get(this.sQueueName);
	queue.each(function(e) {
		e.cancel();
	});
	this.bTransitionInProgress = false;
};


powerCarousel.prototype.instantFadeSubmissiveImage = function() {
	var oNode = Element.extend(this.pPowerImages[this.nSubmissiveTransitioningImage].parentNode);
	oNode.setStyle({opacity: this.nMinOpacityValue, zIndex:1});
	oNode.hide();
	this.pNavigationThumbs[this.nSubmissiveTransitioningImage].removeClassName('selected');
};


powerCarousel.prototype.instantFadeSubmissiveSummary = function() {
	var oNode = this.pPowerImageSummaries[this.nSubmissiveTransitioningImage];
	oNode.setStyle({opacity: this.nMinOpacityValue, zIndex:1});
	oNode.hide();
};


powerCarousel.prototype.showPowerImageSummary = function(nNodeIndex) {
	var oldNode = this.pPowerImageSummaries[this.nCurrentlyDisplayedPowerImage];
	var newNode = this.pPowerImageSummaries[nNodeIndex];

    if(this.bSkipFade){
        oldNode.hide();
        newNode.show();
        this.nCurrentlyDisplayedPowerImage = nNodeIndex;
        return;
    }
    var timeToFade = (this.bMouseOverPaused) ? this.nMouseOverTransitionTime : this.nTransitionTime;

	Effect.Appear(newNode, {from: this.nMinOpacityValue, to: this.nMaxOpacityValue, duration: timeToFade, queue: this.oQueueMap,
		beforeStart: function() {
			oldNode.hide();
		}
	});
};

