var RelatedPhotogallery = new Class({

  Implements : [ Options, Events ],
  
  options : {
    caption:true,              //if a caption must displayed on top of imges when mouseovered
    thumbSize:112,              //Maximum width/height of a thumbnail
    missingThumbClass:'missing-thumbnail',  //CSS class assigned to the missing thumbnails. Style can then be modified in the css sheet by modifying the matching class
    errorMessageClass:'error-message',    //CSS class assigned to the error message displayed when a picture can't be found. Style can then be modified in the css sheet by modifying the matching class
    captionClass:'caption',
    selectedIndex:0
  },
  
  initialize:function(el, options){
    this.el = document.id(el);
    this.setOptions(options);
    this.container = this.el.getElement('.gallery');
    this.scrollArea = this.el.getElement('.scrollArea');
    this.scrollUp = this.scrollArea.getElement('.scrollUp');
    this.scrollDown = this.scrollArea.getElement('.scrollDown');
    //this is a secutriy: the container requires its position to be set. If not done with the css, we force it here
    this.container.setStyles({
      'position': (this.container.getStyle('position')=='absolute'?'absolute':'relative'),
      'width':(this.container.getStyle('width')=='auto'?this.container.getSize().x:this.container.getStyle('width')),
      'height':(this.container.getStyle('height')=='auto'?this.container.getSize().y:this.container.getStyle('height'))
    });
    // extract the anchors, set the hashes, and call the building functions
    this.anchors = this.container.getElements('a').dispose();
    this.nbrImages = this.anchors.length;
    this.images = new Hash();
    if(this.options.caption){
      this.captions = new Hash();
    }
    this.anchors.each(this.getImageInfo,this);
    this.loaded=false;
    //prepare new elements
    this.build();
    //load the thumbnails
    this.load();
  },
  
  getImageInfo:function(anchor){
    //for each anchor, we extract the target image and the thumbnail, and we associate them in a hash
    var fullImage = anchor.get('href');
    if(Browser.Engine.trident){
      //fix when dealing with local files in IE. Mostly for dev purpose
      fullImage = fullImage.replace('about:',''); 
    }
    var image = anchor.getFirst('img');
    var thumb = image.get('src');
    this.images.set(thumb,fullImage);
    // if the caption is required, we extract the alt property
    if(this.options.caption){
      var caption = image.get('alt');
      if ($defined(caption)){
        this.captions.set(fullImage,caption);
      }
    }
  },
  
  build:function(){
    // the error message might never be displyed, but we build it anyway
    this.error = new Element('div',{
      'html':'the image cannot be loaded.',
      'class':this.options.errorMessageClass,
      'styles':{
        'top':0,
        'left':0,
        'padding-left':this.options.thumbSize
      }
    });
    //The resize function is called when window is resized, to update the dimmension values. But we need to call it at least once, to initialize those values.
    this.resize();
    this.originalHeight=this.windowSize.y;
  },
  
  //instead of calculating dimensions all the time, we set object global variable which are refreshed everytime the window is resized
  resize:function(){
    this.windowSize = this.container.getSize();
  },
  
  //load the thumbnail gallery
  load:function(){
    this.ready=false;
    this.loadedThumbs = new Asset.images(this.images.getKeys(),{
      onProgress:this.displayThumb.bind(this),
      onComplete:this.imagesLoaded.bind(this)
    });
  },

  //when a thumbnail is loaded
  displayThumb:function(counter,index){
    //grab the image
    var image = this.loadedThumbs[index];
    //calculate the image aspect ratio
    var imageRatio = image.width/image.height;
    //if it's portrait oriented
    if (imageRatio <1 ) {
      var turned=true;
      var tag_height = this.options.thumbSize;
      var tag_width = this.options.thumbSize*imageRatio;
      var add2left=((this.options.thumbSize-tag_width)/2).toInt();
      var add2top=0;
    }
    //if it's lanscape oriented
    else {
      var turned=false;
      var tag_width = this.options.thumbSize;
      var tag_height = (this.options.thumbSize/imageRatio).toInt();
      var add2top= ((this.options.thumbSize-tag_height)/2).toInt();
      var add2left=0;
    }
    //We store a bunch of data directly inside the image, so we can request them later for animations
    image.store('width',image.width);
    image.store('height',image.height);
    var top = 0;
    var left = 0;
    image.setStyles({
      'opacity':0,
      'width':tag_width,
      'height':tag_height,
      'position': 'absolute',
      'top':top,
      'left':left,
      'zIndex': 0,
      'cursor': 'pointer'
    }).inject(this.container);
    image.store('tag_width',tag_width);
    image.store('tag_height',tag_height);
    image.store('top',top);
    image.store('left',left);
    image.store('turned',turned);
    image.store('add2top',add2top);
    image.store('add2left',add2left);
    image.store('path',this.images.getKeys()[index]);
    image.set('morph',{link:'cancel'});
    image.get('morph').start({opacity:1}).chain(function(){this.attachDefaultEvents(image)}.bind(this));
  },
  
  //all thumbnails are loaded
  imagesLoaded: function (){
    this.loaded=true;
    this.ready=true;
    this.imageFx = new Fx.Elements(this.loadedThumbs,{link:'cancel'});
    this.animations = {};
    //this.repositionImages();
    if (this.loadedThumbs.length) this.toMenu({
      stop: $empty,
      target: this.loadedThumbs[this.options.selectedIndex]
    });
  },
  
  //this function transform the gallery to slideshow
  toMenu:function(e){
    if(!this.ready){return false;}
    this.index = -1;
    this.scrollArea.addEvent('mousewheel',this.scrollSideBar.bindWithEvent(this));
    this.scrollUp.addEvents({
      mouseenter: this.handleScrollEnter.bind(this, 1),
      mouseleave: this.handleScrollLeave.bind(this)
    });
    this.scrollDown.addEvents({
      mouseenter: this.handleScrollEnter.bind(this, -1),
      mouseleave: this.handleScrollLeave.bind(this)
    });
    this.loadedThumbs.each(this.attachScrollEvents,this);
    this.scrollSideBar(e);
  },

  handleScrollEnter: function(dir){
    this.repositionHandler = this.handleReposition.bind(this, dir);
    this.addEvent('scrollEnd', this.repositionHandler);
    this.repositionHandler();
  },

  handleScrollLeave: function(){
    this.removeEvent('scrollEnd', this.repositionHandler);
  },

  handleReposition: function(dir){
    this.scrollSideBar({ stop: $empty, wheel: dir });
  },

  //new event for thumbnails when in slideshow mode
  attachScrollEvents:function(image){
    image.removeEvents();
    var morph = image.get('morph');
    morph.cancel().clearChain();
    image.addEvents({
      'mouseenter':function(){
        morph.start({opacity:1});
        this.thumbOpacChain(image);
        image.store('timer',this.thumbOpacChain.periodical(1000,this,image));
      }.bind(this),
      'mouseleave':function(){
        $clear(image.retrieve('timer'));
        morph.start({opacity:this.retrieve('opa')});
      },
      'click':(function(e){
        this.scrollSideBar(e);
        this.select();
      }).bindWithEvent(this)
    });
  },
  
  //function for loop opacity effect on thumbnail in slideshow mode
  thumbOpacChain: function(image) {
    var morph = image.get('morph');
    morph.start({opacity:1}).chain(function(){morph.start({opacity:image.retrieve('opa')});});
  },
  
  //scroll the sidebar up or down, depending on the event passed as argument, and load the corresponding fullsize picture
  scrollSideBar:function(e){
    this.mouseDown = false;
    e.stop();
    var thumbs = this.images.getKeys();
    var index = this.getImgForScroll(e,thumbs);
    if (index==this.index){return;}
    this.index=index;
    this.start = this.max(this.index-3,0);
    this.stop = this.min(this.index+3,thumbs.length-1);
    this.repositionImages();
  },
  
  //uses the event to find the slideshow index. Test if event is mousewheel up or down, or a click
  getImgForScroll:function(event,thumbs){
    if (event.wheel<0) {
      return this.min(thumbs.length-1,this.index+1);
    } else if (event.wheel>0) {
      return this.max(0,this.index-1);
    } else {
      return thumbs.indexOf(document.id(event.target).retrieve('path'));
    }
  },
  
  //reposition all images.
  repositionImages:function(){
    this.fireEvent('scrollStart');
    this.resize();
    this.ready=false;

    this.loadedThumbs.each(function(image,i){
      var z = i-this.index;
      var funct_val = 1/(1+0.5*(Math.abs(z)));
      var top_val = this.windowSize.y*(1+1.1*z/(2+(Math.abs(z))))/2-this.options.thumbSize/2;
      image.setStyles({
        'display':'block',
        'visibility':'visible',
        'z-index':this.loadedThumbs.length-Math.abs(z)
      });
      if ((i<this.start)||(i>this.stop)){
        var opacity = 0;
      }
      else{
        var opacity = funct_val;
      }
      image.store('opa',opacity);
      this.animations[i]={
        'top':(top_val+(this.options.thumbSize-image.retrieve('tag_height')*funct_val)/2).toInt(), 
        'left':(10+(this.options.thumbSize-image.retrieve('tag_width')*funct_val)/2).toInt(), 
        'width':(image.retrieve('tag_width')*funct_val).toInt(), 
        'height':(image.retrieve('tag_height')*funct_val).toInt(),
        'opacity':opacity
      };
    },this);
    var push = false;

    this.imageFx.start(this.animations).chain(function(){
      this.ready=true;
      this.fireEvent('scrollEnd', [ this.index, this.loadedThumbs[this.index] ]);
    }.bind(this));
  },

  //in case you want to remove the gallery dynamcally.
  destroy:function(){
    this.container.empty();
    this.scrollArea.removeEvents('mousewheel');
  },

  //utility function to return the minimum between two values
  min:function(a,b) {
    return( (a<b) ? a : b );  
  },
  
  //utility function to return the maximum between two values
  max:function(a,b){
    return( (a>b) ? a : b );  
  },

  select: function(){
    var url = this.images.getValues()[this.index];
    this.fireEvent('select', [ url, this.captions.get(url) ]);
  }
});
