/* whSlideshow

   public api:

   whSlideshow(settings)
     settings:
       imagelist: initial image list
       mousepause: element, which if mouse enters, will stop the slide timer when the mouse enters, and reset it when the mouse leaves
       waitinterval: wait time between animations. set to -1 to disable automatic animation
       backwardseffect: effect to use when moving backwards through the list

   setImageList(imagelist)

   gotoSlide(slidenum, animated)

   getCurrentSlidePosition
     returns slidenum
*/


function whSlideshow(settings)
{
  var localthis = this;
  this.container = settings.container;
  this.setImageList(settings.imagelist);

  this.currentpos = settings.initialpos ? settings.initialpos : 0;

  this.effect = settings.effect ? settings.effect : 'west';
  this.backwardseffect = settings.backwardseffect ? settings.backwardseffect : this.effect;
  this.algorithm = settings.algorithm;
  this.algorithm_factor = settings.algorithm_factor;
  this.resizemethod = settings.resizemethod;
  this.resizebackground = settings.resizebackground;
  this.animationinterval = settings.animationinterval ? settings.animationinterval : 10;
  this.animationspeed = settings.animationspeed ? settings.animationspeed : 1;
  this.waitinterval = typeof settings.waitinterval != 'undefined' ? settings.waitinterval : 1000;
  this.onstartslide = settings.onstartslide;
  this.onfinishslide = settings.onfinishslide;
  this.suspended = false; //allow new timeouts to start;

  this.nextimageloaded = false;
  this.activeeffect = this.effect;
  this.imagewaitexpired = false;

  if(settings.mousepause)
  {
    this.mousepause = settings.mousepause;
    this.onmouseover_callback = function(e){localthis.onmouseover(e);}
    this.onmouseout_callback = function(e){localthis.onmouseout(e);}
    //ADDME attach instead of blind overwrite of event handlers
    this.mousepause.onmouseover = this.onmouseover_callback;
    this.mousepause.onmouseout = this.onmouseout_callback;
  }

  if(settings.randomize_sequence)
  {
    this.randomize_sequence = settings.randomize_sequence;
    this.currentpos = Math.floor(Math.random() * this.imagelist.length);
  }

  this.imgload_callback = function(){localthis.gotslideinimage();}
  this.endwait_callback = function(){localthis.endwait();}
  this.interval_callback = function(){localthis.intervalfunc();}

  if (this.imagelist.length == 0)
    return;

  // prevent a possible race condition with setTimeout
  // (when using mySlideshow = new whSlideshow(...), a cached image may call the imgload_callback
  // in IE before the slideshow is returned to mySlideshow. When the onfinishslide callback
  // then tries to use mySlideshow, you'll get an '... is unspecified' error)
  setTimeout(function(){localthis.start(settings.loadfirstimage);}, 1);
}
whSlideshow.prototype.start = function whSlideshow_start(loadfirstimage)
{
  if(loadfirstimage)
  {
    this.skipnextanimation = true;
    this.startimageload(this.currentpos);
  }
  else
  {
    this.startwait();
    this.startimageload(this.getnextimage());
  }
}
whSlideshow.prototype.setImageList = function whSlideshow_setImageList(imagelist)
{
  this.imagelist = imagelist;
}
whSlideshow.prototype.startwait = function whSlideshow_startwait()
{
  if(this.betweenimgtimer || this.waitinterval<0)
    return; //guard againts duplicate calls

  //console.log('start this.betweenimgtimer');
  if(this.suspended)
  {
    this.shouldstartwait = true;
    return;
  }

  //console.log('setting timeout');
  this.betweenimgtimer = window.setTimeout(this.endwait_callback, this.waitinterval);
  this.shouldstartwait = false;
}
whSlideshow.prototype.endwait = function whSlideshow_endwait()
{
  if(!this.betweenimgtimer)
     return; //spurious endwait

  //console.log('finished timeout');
  this.betweenimgtimer = null;
  this.imagewaitexpired = true;
  if(this.nextimageloaded)
    this.startanimation();
}
whSlideshow.prototype.startimageload = function whSlideshow_startimageload(pos)
{
  if(this.incomingcontainer) //guard against duplicate loading, it would be a bug though
    return;
  //console.log('startimageload ' + pos);

  this.nextimageloaded = false;
  this.loadpos = pos % this.imagelist.length;

  var img = document.createElement('img');
  this.incomingcontainer = img;

  img.style.position = 'absolute';
  img.style.zIndex = 2;

  switch(this.activeeffect) //ADDME share first step calculation with the later step code
  {
    case 'west':
      this.imgwidth = this.container.offsetWidth;

      img.style.top = '0px';
      img.style.left = this.imgwidth + 'px';
      break;

    case 'east':
      this.imgwidth = this.container.offsetWidth;

      img.style.top = '0px';
      img.style.left = (-this.imgwidth) + 'px';
      break;

    case 'fade':
      img.style.opacity = 0;
      img.style.filter = 'alpha(opacity=0)';

      img.style.top = '0px';
      img.style.left = '0px';
      break;
  }

  this.container.insertBefore(this.incomingcontainer, this.container.firstChild);
  img.onload = this.imgload_callback;
  img.src = this.imagelist[this.loadpos].src;
}
whSlideshow.prototype.getCurrentSlidePosition = function whSlideshow_getCurrentSlidePosition()
{
  return this.currentpos;
}
whSlideshow.prototype.getnextimage = function whSlideshow_getnextimage()
{
  if(this.randomize_sequence)
  {
    var newpos = Math.floor(Math.random() * this.imagelist.length);
    //noone likes the exact same image being slided in, so correct it if that happens
    return newpos!=this.currentpos ? newpos : newpos+1;
  }
  else
  {
    return this.currentpos + 1;
  }
}
whSlideshow.prototype.gotslideinimage = function whSlideshow_gotslideinimage()
{
  var img = this.incomingcontainer;
  //console.log('got image ' + this.loadpos + ' ' + img.offsetWidth + 'x' + img.offsetHeight);

  var canvaswidth = this.container.offsetWidth;
  var canvasheight = this.container.offsetHeight;

  if(img.offsetWidth != canvaswidth || img.offsetHeight != canvasheight)
  {
    if(this.resizemethod == 'scaletofit')
    {
      //Calculate new dimensions
      var scale_x = canvaswidth / img.offsetWidth;
      var scale_y = canvasheight / img.offsetHeight;
      var scale = scale_x < scale_y ? scale_x : scale_y;
      //console.log(scale_x,' ',scale_y,' ',scale,' ',canvaswidth,' ',canvasheight);
      var finalwidth = Math.ceil(img.offsetWidth * scale);
      var finalheight = Math.ceil(img.offsetHeight * scale);
      img.style.width = finalwidth + 'px';
      img.style.height = finalheight + 'px';

      //Figure out how to center it by padding
      var padleft = Math.floor((canvaswidth - finalwidth)/2);
      var padtop = Math.floor((canvasheight - finalheight)/2);

      //console.log(finalwidth,' ',finalheight,' ',padleft,' ',padtop);
      img.style.paddingLeft = padleft+'px';
      img.style.paddingTop = padtop+'px';
      img.style.paddingRight = (canvaswidth - finalwidth - padleft)+'px';
      img.style.paddingBottom = (canvasheight - finalheight - padtop)+'px';
      img.style.background = this.resizebackground;
    }
    else
    {
      img.style.width = canvaswidth + 'px';
      img.style.height = canvasheight + 'px';
    }
  }
  else
  {
    img.style.width = canvaswidth + 'px';
    img.style.height = canvasheight + 'px';
  }

  this.nextimageloaded = true;
  if(this.imagewaitexpired || this.skipnextanimation)
    this.startanimation();
}
whSlideshow.prototype.startanimation = function whSlideshow_startanimation()
{
  this.imagewaitexpired = false;
  if(this.onstartslide)
    this.onstartslide(this.loadpos);

  if(this.skipnextanimation)
  {
    this.skipnextanimation = false;
    this.finishanimation(true);
    return;
  }

  if(this.suspended)
    return;

  //now start moving it in
  this.currentstep = 0;
  this.currentanimator = window.setInterval(this.interval_callback, this.animationinterval);
}
whSlideshow.prototype.finishanimation = function whSlideshow_finishanimation(loadnext)
{
  //console.log('finish animation');

  var currentfactor = 1000;
  window.clearInterval(this.currentanimator);
  this.currentanimator = null;

  //destroy unused nodes. as we inserted ourself before firstChild, we just delete everything else. also kills any running load
  while(this.container.childNodes.length>1)
  {
    this.container.removeChild(this.container.lastChild);
  }

  this.incomingcontainer.style.zIndex = 1; //move it to the standard zindex so the next image can move in
  this.incomingcontainer.style.position = 'static';
  this.incomingcontainer.style.opacity = 1;
  this.incomingcontainer.style.filter = '';
  this.incomingcontainer = null;

  this.currentpos = this.loadpos;
  if(this.onfinishslide)
    this.onfinishslide(this.currentpos);

  if(loadnext)
  {
    this.startwait();
    this.activeeffect = this.effect;
    this.startimageload(this.getnextimage());
  }

  return currentfactor;
}
whSlideshow.prototype.update = function whSlideshow_update(factor)
{
  switch(this.activeeffect)
  {
    case 'west':
      this.incomingcontainer.style.left = parseInt( (1 - factor/1000) * this.imgwidth) + 'px';
      break;
    case 'east':
      this.incomingcontainer.style.left = parseInt( (1 - factor/1000) * -this.imgwidth) + 'px';
      break;
    case 'fade':
      this.incomingcontainer.style.opacity = factor / 1000;
      this.incomingcontainer.style.filter = 'alpha(opacity=' + factor / 10 + ')';
      break;
  }
}
whSlideshow.prototype.intervalfunc = function whSlideshow_intervalfunc()
{
  var currentfactor;

  this.currentstep = this.currentstep + this.animationspeed;
  var finished = this.currentstep >= 1000;
  if(!finished)
  {
    switch(this.algorithm)
    {
      case 'sinus':
        //scale 0 .. maxstep to 0 .. 0.5pi
        currentfactor = 1000 * Math.pow(Math.sin( (this.currentstep / 1000.0) * ( 0.5 * Math.PI ) ), this.algorithm_factor);
        break;

      default: //linear
        currentfactor = this.currentstep;
        break;
    }
    this.update(currentfactor);
  }
  else //finished animation
  {
    this.finishanimation(true);
  }
}
whSlideshow.prototype.gotoSlide = function whSlideshow_gotoSlide(pos, animated)
{
  //console.log('gotoSlide',pos, this.loadpos);
  // don't attempt if there are no images in the list
  if (this.imagelist.length == 0)
    return;

  this.activeeffect = pos >= this.currentpos ? this.effect : this.backwardseffect;
  pos = (pos + this.imagelist.length) % this.imagelist.length;

  if(this.betweenimgtimer) //kill current mid-image waiter, if any
  {
    window.clearTimeout(this.betweenimgtimer);
    this.betweenimgtimer = null;
  }

  /* Scenario's:
     - Is the selected image next? (pos == this.loadpos)
       - If yes: Is it already loaded?
         - If yes: finish animation NOW.
         - If no: just mark the animation skippable
       - If no: The image is not next.
         - Is another image animating? Finish it up if it is
         - Is an image incoming? Abort that
         - start loading the requested image. mark animation as skippable
  */

  if(pos == this.loadpos)
  {
    if(!animated || this.currentanimator)
    {
      this.skipnextanimation = true;
      if(this.nextimageloaded)
        this.startanimation();
    }
    else
    {
      this.imagewaitexpired = true;
      if(this.nextimageloaded)
        this.startanimation();
    }
  }
  else
  {
    if(this.currentanimator)
    {
     this.finishanimation(false);
    }
    else if(this.incomingcontainer)
    {
      this.incomingcontainer.onload = null;
      this.incomingcontainer.parentNode.removeChild(this.incomingcontainer);
      this.incomingcontainer = null;
    }
    if(!animated)
      this.skipnextanimation = true;
    else
      this.imagewaitexpired = true

    this.startimageload(pos);
  }
}
whSlideshow.prototype.onmouseover = function whSlideshow_onmouseover(e)
{
  //console.log('onmouseover');
  if(!this.suspended)
  {
    this.suspended = true;
    if(this.betweenimgtimer)
    {
      //console.log('cancelling timeout');
      window.clearTimeout(this.betweenimgtimer);
      this.betweenimgtimer = null;
      this.shouldstartwait = true;
    }
  }
  return true;
}
whSlideshow.prototype.containselement = function whSlideshow_containselement(obj)
{
  for(;obj;obj = obj.parentNode)
    if(obj == this.mousepause)
      return true;
  return false;
}
whSlideshow.prototype.onmouseout = function whSlideshow_onmouseout(e)
{
  if(!e) e=window.event;
  if(this.containselement(e.relatedTarget || e.toElement))
    return; //still inside..

  //console.log('onmouseout');
  if(this.suspended)
  {
    this.suspended = false;
    if(this.shouldstartwait)
      this.startwait();
  }
  return true;
}
