/*
    This file is part of chrisAdoyle's SmoothGallery v0.1.

    chrisAdoyle's SmoothGallery is essentially a port of JonDesign's SmoothGallery,
    using the Prototype javascript framework instead of Moo, and is based extensively on
    the excellent work of Jonathon Schemoul.

    chrisAdoyle's SmoothGallery is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    chrisAdoyle's SmoothGallery is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with chrisAdoyle's SmoothGallery; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    chrisAdoyle's SmoothGallery Main Developer:  Chris Doyle (http://www.chrisAdoyle.com/)
    JonDesign's SmoothGallery Main Developer: Jonathan Schemoul (JonDesign: http://www.jondesign.net/)
    Contributed code by:
    - Christian Ehret (bugfix)
	- Nitrix (bugfix)
	- Valerio from Mad4Milk for his great help with the carousel scrolling and many other things.
	- Archie Cowan for helping me find a bugfix on carousel inner width problem.
	Many thanks to:
	- The mootools team for the great mootools lib, and it's help and support throughout the project.
*/

var Gallery = Class.create({
	initialize: function(element, options) {
    this.options = Object.extend({
			showArrows: true,
			showCarousel: true,
			showInfopane: true,
			thumbHeight: 75,
			thumbWidth: 125,
			thumbSpacing: 10,
			preloader: true,
			/* Data retrieval */
      manualData: [],
			destroyAfterPopulate: true,
			elementSelector: "div.imageElement",
			titleSelector: "h3",
			subtitleSelector: "p",
			linkSelector: "a.open",
			imageSelector: "img.full",
			thumbnailSelector: "img.thumbnail",
			/* InfoPane options */
			slideInfoZoneOpacity: 0.7,
			slideInfoZoneSlide: true,
			/* Carousel options */
			carouselMinimizedOpacity: 0.4,
			carouselMinimizedHeight: 20,
			carouselMaximizedOpacity: 0.9,
			textShowCarousel: 'Pictures',
			showCarouselLabel: true,
			useThumbGenerator: false,
			thumbGenerator: 'resizer.php',
			useExternalCarousel: false,
			carouselElement: false,
			activateCarouselScroller: true,
			/* CSS Classes */			
      baseClass: 'jdGallery',
			withArrowsClass: 'withArrows',
      timed: false,
      delay: 10000
		}, options || {});
        
    this.waitingForEffect = 0;
		this.currentIter = 0;
		this.lastIter = 0;
		this.maxIter = 0;
		this.galleryElement = element;
		this.galleryData = this.options.manualData;
		this.galleryInit = 1;
		this.galleryElements = Array();
		this.thumbnailElements = Array();
		this.galleryElement.addClassName(this.options.baseClass);
        
    this.populateFrom = element;
    this.populateData();
		element.style.display = "block";
        
		this.constructElements();
    if ((this.galleryData.length>1)&&(this.options.showArrows)) {
			this.leftArrow = new Element('a').addClassName('left');
      element.insert(this.leftArrow);
      this.leftArrow.observe('click', this.prevItem.bind(this));
            
			this.rightArrow = new Element('a').addClassName('right');
      element.insert(this.rightArrow);
      this.rightArrow.observe('click', this.nextItem.bind(this));
			
			this.galleryElement.addClassName(this.options.withArrowsClass);
		}
        
		this.loadingElement = new Element('div').addClassName('loadingElement');
        element.insert(this.loadingElement);
		if (this.options.showInfopane) this.initInfoSlideshow();
		if ((this.galleryData.length>1)&&(this.options.showCarousel)) this.initCarousel();
		this.doSlideShow(1);
    },

	nextItem: function() {
//		this.fireEvent('onNextCalled');
		this.nextIter = this.currentIter+1;
		if (this.nextIter >= this.maxIter)
			this.nextIter = 0;
		this.galleryInit = 0;
		this.goTo(this.nextIter);
	},

	prevItem: function() {
//		this.fireEvent('onPreviousCalled');
		this.nextIter = this.currentIter-1;
		if (this.nextIter <= -1)
			this.nextIter = this.maxIter - 1;
		this.galleryInit = 0;
		this.goTo(this.nextIter);
	},

	goTo: function(num) {
		this.clearTimer();
		if (this.options.embedLinks){
			this.clearLink();
		}
		if (this.options.showInfopane) {
			this.hideInfoSlideShow();
			this.changeItem(num);
		}else{
			this.changeItem.delay(500, this, num);
		}
		if (this.options.embedLinks) {
			this.makeLink(num);
		}
    if (this.options.showCarousel) {
			this.hideCarousel();
		}
		this.prepareTimer();
	},

	changeItem: function(num) {
//		this.fireEvent('onStartChanging');
		this.galleryInit = 0;
		if (this.currentIter != num){
			for(i=0;i<this.maxIter;i++){
				if ((i != this.currentIter)) {
					// Start hiding all elements instead of setting their opacity to 1 for better performance. NUBIS
					this.galleryElements[i].hide()
        }
			}
      
      // This is where Jon had support for different forward/backward transitions
      this.addEffectWaiter();
      new Effect.Parallel([
        new Effect.Appear(this.galleryElements[num], {sync: true}),
        new Effect.Fade(this.galleryElements[this.currentIter], {sync: true})
			], {afterFinish: this.removeEffectWaiter.bind(this)});
			//this.galleryElements[this.currentIter].hide();
			//new Effect.Appear(this.galleryElements[num],{afterFinish: this.removeEffectWaiter.bind(this)});
			
			this.currentIter = num;
		}
		this.doSlideShow.bind(this)();
	},

	startSlideShow: function() {
		//~ this.fireEvent('onStart');
		this.loadingElement.style.display = "none";
		this.lastIter = this.maxIter - 1;
		this.currentIter = 0;
		this.galleryInit = 0;
		
    // this.galleryElements[parseInt(this.currentIter)].setStyle({'opacity': 1});
		// Removed animation for performance NUBIS 11/04/09
		this.galleryElements[parseInt(this.currentIter)].show();
		
		if (this.options.showInfopane)
			this.showInfoSlideShow();
		if (this.options.embedLinks)
			this.makeLink(this.currentIter);
    this.prepareTimer();
	},

	initInfoSlideshow: function() {
		if (this.slideInfoZone)
			this.slideInfoZone.remove();
		this.slideInfoZone = new Element('div').addClassName('slideInfoZone');
    this.galleryElement.insert(this.slideInfoZone);
    var slideInfoZoneLink = new Element('a')
    this.slideInfoZone.insert(slideInfoZoneLink)
		var slideInfoZoneTitle = new Element('h2');
    slideInfoZoneLink.insert(slideInfoZoneTitle);
		var slideInfoZoneDescription = new Element('p');
    slideInfoZoneLink.insert(slideInfoZoneDescription);
		this.slideInfoZone.normalHeight = this.slideInfoZone.offsetHeight;
    var offset = this.galleryElement.getHeight() - this.slideInfoZone.offsetHeight; 
    // Added px to offset to fix Safari bug ERN 7/19/09 
	  // Info slideshow doesn't change opacity, for better performance NUBIS 10/04/09
    this.slideInfoZone.setStyle({top: offset + 'px'});
	},

  showInfoSlideShow: function() {
//		this.fireEvent('onShowInfopane');
		element = this.slideInfoZone;
    element.select('a')[0].href = this.galleryData[this.currentIter].link;
		element.select('a h2')[0].innerHTML = this.galleryData[this.currentIter].title;
		element.select('a p')[0].innerHTML = this.galleryData[this.currentIter].description;
        
    this.addEffectWaiter();
    this.slideInfoZone.setStyle({'height': this.slideInfoZone.normalHeight});
    // Info SlideShow only changes size, not opacity. NUBIS 11/04/09
	  new Effect.MoveBy(this.slideInfoZone, -this.slideInfoZone.normalHeight, 0 , {afterFinish: this.removeEffectWaiter.bind(this)});

    return this.slideInfoZone;
	},

    addEffectWaiter: function() {
        this.waitingForEffect += 1;
        if (this.waitingForEffect == 1)
            this.disableNavigation();
    },
    
    removeEffectWaiter: function() {
        this.waitingForEffect -= 1;
        if (this.waitingForEffect < 0)
            this.waitingForEffect = 0;
        if (this.waitingForEffect == 0)
            this.enableNavigation();
    },

    disableNavigation: function() {
        if (this.leftArrow)
            this.leftArrow.stopObserving('click');
        if (this.rightArrow)
            this.rightArrow.stopObserving('click');
    },

    enableNavigation: function() {
        if (this.leftArrow)
            this.leftArrow.observe('click', this.prevItem.bind(this));
        if (this.rightArrow)
            this.rightArrow.observe('click', this.nextItem.bind(this));
    },


	hideInfoSlideShow: function() {
//		this.fireEvent('onHideInfopane');
//		this.slideInfoZone.clearTimer();
    this.addEffectWaiter();
		// Info SlideShow only changes size, not opacity. NUBIS 11/04/09
    new Effect.MoveBy(this.slideInfoZone, this.slideInfoZone.normalHeight, 0, {afterFinish: this.removeEffectWaiter.bind(this)});
		return this.slideInfoZone;
	},
	clearTimer: function() {
		if (this.options.timed) {
			clearInterval(this.timer);
    }
	},
	prepareTimer: function() {
		if (this.options.timed) {
      this.timer = setInterval(this.nextItem.bind(this), this.options.delay);
    }
	},
	doSlideShow: function(position) {
		if (this.galleryInit == 1){
			imgPreloader = new Image();
			imgPreloader.onload=function(){
				this.startSlideShow.bind(this).delay(1);
			}.bind(this);
			imgPreloader.src = this.galleryData[0].image;
      new Effect.MoveBy(this.slideInfoZone, this.slideInfoZone.normalHeight, 0);
		} else {
			if (this.options.showInfopane){
				this.showInfoSlideShow.bind(this).delay(1);
			} else if ((this.options.showCarousel)&&(this.options.activateCarouselScroller)){
				this.centerCarouselOn(position);
			}
		}
	},

	initCarousel: function () {
		var carouselElement;
		if (!this.options.useExternalCarousel)
		{
			var carouselContainerElement = new Element('div').addClassName('carouselContainer');
      this.carouselContainer = carouselContainerElement;
      this.galleryElement.insert(carouselContainerElement);
			this.carouselContainer.normalHeight = carouselContainerElement.offsetHeight;
			this.carouselContainer.setStyle({'opacity': this.options.carouselMinimizedOpacity, 'top': (this.options.carouselMinimizedHeight - this.carouselContainer.normalHeight) + "px"});
			this.carouselBtn = new Element('a').addClassName('carouselBtn');
      this.carouselBtn.title = this.options.textShowCarousel;
      this.carouselBtn.innerHTML = this.options.textShowCarousel;
      carouselContainerElement.insert(this.carouselBtn);
			this.carouselBtn.observe('click', 
				function () {
					this.toggleCarousel();
				}.bind(this)
			);
			this.carouselActive = false;
			carouselElement = new Element('div').addClassName('carousel');
            carouselContainerElement.insert(carouselElement);
            this.carousel = carouselElement;
            
		} else {
			carouselElement = this.options.carouselElement.addClass('jdExtCarousel');
		}
		this.carouselWrapper = new Element('div').addClassName('carouselWrapper');
        carouselElement.insert(this.carouselWrapper);
		this.carouselInner = new Element('div').addClassName('carouselInner');
        this.carouselWrapper.insert(this.carouselInner);
		if (this.options.activateCarouselScroller)
		{
            this.carousel.observe('mousemove', function(event) {this.scrollCarousel(event)}.bind(this));
            this.carousel.observe('mouseout', function(event) {this.stopScroll(event)}.bind(this));
		}
		this.constructThumbnails();
        
		this.carouselInner.style.width = ((this.maxIter * (this.options.thumbWidth + this.options.thumbSpacing + 2))+this.options.thumbSpacing) + "px";
	},

	showCarouselFn: function () {
//		this.fireEvent('onShowCarousel');
/*		this.carouselContainer.custom({
			'opacity': this.options.carouselMaximizedOpacity,
			'top': 0
		}).addEvent('onComplete', function() { this.carouselActive = true; this.carouselWrapper.scroller.start(); }.bind(this));
*/
        this.addEffectWaiter();
        this.slideInfoZone.setStyle({'height': this.slideInfoZone.normalHeight});
        new Effect.Parallel([
            new Effect.Opacity(this.carouselContainer, {from: this.options.carouselMinimizedOpacity, to: this.options.carouselMaximizedOpacity}),
            new Effect.MoveBy(this.carouselContainer, this.carousel.getHeight(), 0)], {afterFinish: this.removeEffectWaiter.bind(this)});
        //~ this.carouselContainer.setStyle({'opacity': this.options.carouselMaximizedOpacity, 'top': 0});
        this.carouselActive = true;
//        this.carouselWrapper.scroller.start();
	},

	toggleCarousel: function() {
		if (this.carouselActive) {
			this.hideCarousel();
		} else {
			this.showCarouselFn();
        }
	},

	hideCarousel: function () {
//		this.fireEvent('onHideCarousel');
        if (this.carouselActive) {
            this.addEffectWaiter();
            new Effect.Parallel([
                new Effect.Opacity(this.carouselContainer, {from: this.options.carouselMaximizedOpacity, to: this.options.carouselMinimizedOpacity}),
                new Effect.MoveBy(this.carouselContainer, -this.carousel.getHeight(), 0)], {afterFinish: this.removeEffectWaiter.bind(this)});
            //~ this.carouselContainer.setStyle({
                //~ 'opacity': this.options.carouselMinimizedOpacity,
                //~ 'top': (this.options.carouselMinimizedHeight - this.carouselContainer.normalHeight)
            //~ });
            this.carouselActive = false;
        }
	},

    scrollCarousel: function(event) {
        if (this.carouselInner.getWidth() > this.carousel.getWidth()) {
            scrollWidth = 100;
            inLeftScroll = false;
            inRightScroll = false;
            
            // Check if the mouse is in the left scroll area
            top_position = this.carousel.cumulativeOffset().top - this.carousel.cumulativeScrollOffset().top;
            left_position = this.carousel.cumulativeOffset().left - this.carousel.cumulativeScrollOffset().left;
            if (event.clientX > left_position && event.clientX < left_position + scrollWidth) {
                if (event.clientY > top_position && event.clientY < top_position + scrollWidth) {
                    inLeftScroll = true;
                }
            }
            
            // Check if the mouse is in the right scroll area
            left_position += this.carousel.getWidth();
            if (event.clientX < left_position && event.clientX > left_position - scrollWidth) {
                if (event.clientY > top_position && event.clientY < top_position + scrollWidth) {
                    inRightScroll = true;
                }
            }
            if (inLeftScroll) this.startLeftScroll();
            if (inRightScroll) this.startRightScroll();
            if (!inLeftScroll && !inRightScroll) this.stopScroll(event);
        }
    },

    startLeftScroll: function() {
        maxScroll = this.carousel.cumulativeOffset().left - this.carouselInner.cumulativeOffset().left;
        new Effect.Move(this.carouselInner, {x: maxScroll, queue: {scope: 'carouselScroll', limit: 1}});
    },

    startRightScroll: function() {
        maxScroll = this.carouselInner.cumulativeOffset().left + this.carouselInner.getWidth() - (this.carousel.cumulativeOffset().left + this.carousel.getWidth());
        new Effect.Move(this.carouselInner, {x: -maxScroll, queue: {scope: 'carouselScroll', limit: 1}});
    },

    stopScroll: function (event) {
        if (navigator.appName=="Microsoft Internet Explorer") {
            Effect.Queues.get('carouselScroll').each(function(e){e.cancel()});
        } else {
            if (event.type == 'mousemove') {
                Effect.Queues.get('carouselScroll').each(function(e){e.cancel()});
            }
            if (event.type == 'mouseout' && event.rangeParent.hasClassName && !event.rangeParent.hasClassName('carousel')) {
                if (!event.target.hasClassName('carouselWrapper')) {
                    Effect.Queues.get('carouselScroll').each(function(e){e.cancel()});
                }
            }
        }
    },

	  constructThumbnails: function () {
		  element = this.carouselInner;
		  for(i=0;i<this.galleryData.length;i++)
		  {
			  var currentImg = new Element('div').addClassName("thumbnail").setStyle({
					  backgroundImage: "url('" + this.galleryData[i].thumbnail + "')",
					  backgroundPosition: "center center",
					  backgroundRepeat: 'no-repeat',
					  marginLeft: this.options.thumbSpacing + "px",
					  width: this.options.thumbWidth + "px",
					  height: this.options.thumbHeight + "px",
            opacity: 0.4
				  });
              element.insert(currentImg);
			  currentImg.observe(
				  'mouseover', function (myself) {
  //					myself.clearTimer();
					  myself.target.setStyle({'opacity': 0.99});
  /*					if (this.options.showCarouselLabel)
						  $(this.carouselLabel).innerHTML = '<span class="number">' + (myself.relatedImage.number + 1) + "/" + this.maxIter + ":</span> " + myself.relatedImage.title;
  */				});
              currentImg.observe(
				  'mouseout', function (myself) {
  //					myself.clearTimer();
					  myself.target.setStyle({'opacity': 0.4});
				  });
              currentImg.observe(
				  'click', function (myself) {
					  this.goTo(myself.target.relatedImage.number);
				  }.bind(this)
                  );
			  currentImg.relatedImage = this.galleryData[i];
			  this.thumbnailElements[parseInt(i)] = currentImg;
		  }
	  },

	  clearThumbnailsHighlights: function()
	  {
		  for(i=0;i<this.galleryData.length;i++)
		  {
			  this.thumbnailElements[i].clearTimer();
			  this.thumbnailElements[i].custom(0.2);
		  }
	  },

	  centerCarouselOn: function(num) {
		  var carouselElement = this.thumbnailElements[num];
		  var position = carouselElement.element.offsetLeft + (carouselElement.element.offsetWidth / 2);
		  var carouselWidth = this.carouselWrapper.offsetWidth;
		  var carouselInnerWidth = this.carouselInner.offsetWidth;
		  var diffWidth = carouselWidth / 2;
		  var scrollPos = position-diffWidth;
		  this.carouselWrapper.elementScroller.scrollTo(scrollPos,0);
	  },

	  populateData: function() {
		  currentArrayPlace = this.galleryData.length;
		  options = this.options;
		  var data = this.galleryData;
		  this.populateFrom.select(options.elementSelector).each(function(el) {
			  elementDict = {
				  image: el.select(options.imageSelector)[0].readAttribute('src'),
				  number: currentArrayPlace
			  };
			  if ((options.showInfopane) | (options.showCarousel))
				  Object.extend(elementDict, {
					  title: el.select(options.titleSelector)[0].innerHTML,
					  description: el.select(options.subtitleSelector)[0].innerHTML,
            link: el.select(options.linkSelector)[0].href
				  });
			  if ((!options.useThumbGenerator) && (options.showCarousel))
				  Object.extend(elementDict, {
					  thumbnail: el.select(options.thumbnailSelector)[0].src
				  });
			
			  data[currentArrayPlace] = elementDict;
			  currentArrayPlace++;
			  if (this.options.destroyAfterPopulate)
				  el.remove();
		  });
		  this.galleryData = data;
  //		this.fireEvent('onPopulated');
	  },
    makeResizeImageCallback: function(el, img, currImg, idx){
			var currentIter = this.currentIter;
      return function(){
        if (img.width > parseInt(el.getStyle('width'))) {
          img.setStyle({'height':'auto', 'width':'100%'})
        }

        var left = (parseInt(el.getStyle('width')) - img.width) / 2;
        var top = (parseInt(el.getStyle('height')) - img.height) / 2;
        img.setStyle({'left':left+'px', 'top':top+'px'});
				if(currentIter != idx){
					img.hide();
				}
      }
    },
	  constructElements: function() {
		  var el = this.galleryElement;
		  this.maxIter = this.galleryData.length;
		  for(var i=0;i<this.galleryData.length;i++)
		  {
				var currentImg = new Element('img');
				currentImg.addClassName('slideElement');
				currentImg.setStyle({
          'position':'absolute',
          'left':'0px',
          'right':'0px',
          'margin':'0px',
          'padding':'0px'
        });
				
				Event.observe(window, 'load', this.makeResizeImageCallback(el, currentImg, i));
				
				currentImg.src = this.galleryData[i].image;
				currentImgLink = new Element('a');
				currentImgLink.href = this.galleryData[parseInt(i)].link;
				currentImgLink.insert(currentImg);
        el.insert(currentImgLink);
			  this.galleryElements[parseInt(i)] = currentImg; 
		  }
	  },

    // Set default options, and override with specified options if applicable
    //     I assume Moo does this automatically, maybe prototype does too?  Eh.
    setOptions: function(defaults, options) {
      for (var option in defaults) {
        if (typeof(options[option]) != 'undefined') {
          this[option] = options[option];
        } else {
          this[option] = defaults[option];
        }
      }
      
    }
});
