/**
 * This module is an animation utility providing Slide and Fade effects.
 * Note: It is expected that each image is inside an anchor element, which are inside the "innerContainer".
 * Note: Any problem could possibly be because of the violation of the above expectation or some timing issues. 
 */

/**>---Constructor---<
 * The constructor creates instance variables out of the arguments passed.
 * Uses default values if arguments are missing.
 * Constructs an array of images(frames).
 * Finally checks for the number of images(frames) and initializes the animation.
 * >---Argument description---<
 * @requires /../yui/../animation.js (worked fine with v_0.11.3)
 * @requires /../yui/../yahoo.js(v_0.11.4) & dom.js(v_0.11.3) & event.js(v_0.11.4) OR /../yui/../yahoo-dom-event.js
 * @param {String} innerContainer: the innermost div containing the tiles.
 * @param {String} statusBar: the div containing the buttons and tile numbers.
 * @param {String} leftButton: the left button ID.
 * @param {String} rightButton: the right button ID.
 * @param {String} effect: "Fade" | "Slide".
 * @param {String} slideType: "Horizontal" | "Vertical" (applicable only for "Slide").
 * @param {Number} duration: the animation duration in seconds.
 * @param {Number} interval: the interval between successive animations in milliseconds(i.e. the pause).
 * @param {Number} tilesInViewport: the number of tiles seen in the viewport. 
 */

var self;  //always points to the instantiated object(in case of 'this' scope was getting changed).

var tilesAnimation = function(innerContainer, statusBar, leftButton, rightButton, effect, slideType, duration, interval,tilesInViewport)
{
  self = this;
  self.FADE = "Fade";
  self.SLIDE = "Slide";
  self.SLIDE_HORIZONTAL = "SlideLeft";
  self.SLIDE_VERTICAL = "SlideUp";
  self.tileOut;  //at any point there is only one animation instance which moves out(or fades out) of the viewport.
  self.tileIn = new Array;  //there could be more than one animation instance which moves inside the viewport(in case of slide where tilesInViewport > 1).
  self.currentTile = -1;  //Points to the current tile that is present in the viewport(to the left in case of multi-tile display).
  self.isFirstClick = true;  //Boolean to check whether the button is clilcked for the first time.
  self.setTimeoutHandler1;  //handler for setTimeout(in play).
  self.setTimeoutHandler2;  //handler for setTimeout(in resume).
  self.isReverse = false;  //Boolean to determine the animation transition.
  self.isPaused = false;  //Boolean to determine whether the animation is paused forcefully.
  self.isHalted = true;  //Boolean to determine whether the animation is paused in the natural flow.
  self.isMouseOver = false;  //Boolean to avoid pause to be called continuosly when mouseover.
  self.isOneThreadRunning = false;  //Boolean to ensure that at all times only one thread is running.
  self.innerContainer = innerContainer;
  self.statusBar = statusBar;
  self.leftButton = leftButton;
  self.rightButton = rightButton;
  self.frames = YAHOO.util.Dom.getElementsBy(function()  //Fetch the array of images.
                                             {
                                               return true;
                                             }, "img", self.innerContainer);
  var defaulttilesInViewport = 1;
  self.tilesInViewport = (tilesInViewport == null)? defaulttilesInViewport: tilesInViewport;
  var defaultEffect = self.SLIDE;
  self.effect = (effect == null || effect == '')? defaultEffect: effect;
  if (self.effect == self.SLIDE)  //If effect is slide then check for slide type and update "self.effect".
  {
    self.effect = (slideType == "Horizontal")? self.SLIDE_HORIZONTAL: SLIDE_VERTICAL;
  }
  var defaultDuration = 1;  //seconds.
  self.duration = (duration == null)? defaultDuration: duration;
  var defaultInterval = 5000;  //milliseconds.
  self.interval = (interval == null)? defaultInterval : interval;
  //Determine the position shift factor.
  var paddingAcrossTiles = 7; //Gap between images.
  if (self.effect == self.SLIDE_HORIZONTAL)
  {
    self.posShift = paddingAcrossTiles + parseInt(YAHOO.util.Dom.getStyle(self.frames[0], "width"));
  }
  if(self.effect == self.SLIDE_VERTICAL)
  {
    self.posShift = paddingAcrossTiles + parseInt(YAHOO.util.Dom.getStyle(self.frames[0], "height"));
  }
  /**Assumption: there are tilesInViewport + 2 positions for all Slide animations.
   * The extra 2 positions "finalPosition" and "initialPosition" are outside the viewport.
   * Then "positions" is an array of positions within the viewport, equal to its length. 
   */
  self.positions = new Array;
  self.positions[0] = 0;
  self.finalPosition = self.positions[0] - self.posShift;
  self.initialPosition = self.positions[0] + self.posShift * (self.tilesInViewport);
  for (var i = 1; i < self.tilesInViewport; i++)
  {
    self.positions[i] = self.positions[i - 1] + self.posShift;
  }
  if (self.frames.length > self.tilesInViewport)
  {
    self.init();
  }
};

tilesAnimation.prototype = {
  /**
   * Initializes the viewport, calls play and then sets the listeners.
   */ 
  init: function()
  {
    ++self.currentTile;
    self.setViewport();
    YAHOO.util.Dom.get(self.statusBar).style.display = "block";
    YAHOO.util.Dom.get(self.leftButton).style.display = "inline";
    YAHOO.util.Dom.get(self.rightButton).style.display = "inline";
    //Logic to initially display the tile number in the viewport.
    if(YAHOO.util.Dom.get("activeNo"))
    {
      YAHOO.util.Dom.get("activeNo").innerHTML = self.currentTile + 1;
      YAHOO.util.Dom.get("passiveNo").innerHTML = self.frames.length;
    }
    setTimeout(function()
               {
                 self.play();
                 self.setListeners();  //The listeners are active only after play(animation) has started.  
               }, self.interval);
  },

  /**
   * A check for fade effect.
   */
  isEffectFade: function()
  {
    return (self.effect == self.FADE);
  },

  /**
   * A check for slide effect.
   */
  isEffectSlide: function()
  {
    return (self.effect == self.SLIDE_HORIZONTAL || self.effect == self.SLIDE_VERTICAL);
  },

  /**
   * Sets the viewport based on the animation effect.
   */
  setViewport: function()
  {
    //extract the anchors containing images and set the display style to inline.
    var nodes = YAHOO.util.Dom.getElementsBy(function(){ return true; }, "A", self.innerContainer);
    for (var i = 0; i < nodes.length; i++)
    {
      nodes[i].style.display = "inline";
    }
    if (self.isEffectFade())
    {
      for (var i = 0; i < self.frames.length; i++)
      {
        self.frames[i].style.display = "none";
        YAHOO.util.Dom.setStyle(self.frames[i], "opacity", 0);
      }
      self.frames[self.currentTile].style.display = "inline";
      YAHOO.util.Dom.setStyle(self.frames[self.currentTile], "opacity", 1);
    }
    if (self.isEffectSlide())
    {
      var par = self.effect == self.SLIDE_HORIZONTAL? "left": "top";
      for (var i = 0; i < self.frames.length; i++)
      {
        YAHOO.util.Dom.setStyle(self.frames[i], 'position', 'absolute');
        YAHOO.util.Dom.setStyle(self.frames[i], par, self.initialPosition + "px");
      }
      for (var i = 0; i < self.tilesInViewport; i++)
      {
        YAHOO.util.Dom.setStyle(self.frames[i], par, self.positions[i] + "px");
      }
    }
  },

  /**
   * Sets the listeners.
   */
  setListeners: function()
  {
    YAHOO.util.Event.addListener(self.innerContainer, "mouseover", self.pause);
    YAHOO.util.Event.addListener(self.innerContainer, "mouseout", self.resume);
    YAHOO.util.Event.addListener(self.statusBar, "mouseout", self.resume);
    YAHOO.util.Event.addListener(self.leftButton, "click", self.goBackward);
    YAHOO.util.Event.addListener(self.rightButton, "click", self.goForward);
  },

  /**
   * This covers a major portion of the logic for the entire utility.
   * It basically creates the animation instances for the next play(animation).
   */
  setTiles: function()
  {
    var increment = self.isReverse == false ? true : false;
    //In regular transitions "currentTile" always points to the leftmost image in the viewport.
    //Here since isReverse is true, "currentTile" is set to the rightmost image in the viewport.
    if(self.isReverse == true)
    {
      for(var i = 0; i < self.tilesInViewport; i++)
      {
        self.setNextTileIndex(true);
      }
    }
    if (self.isEffectFade())
    {
      self.setFadeTiles(increment);
    }
    if (self.isEffectSlide())
    {
      self.setSlideTiles(increment);
    }   
  },

  /**
   * Creates the animation instances for fade.
   * @param {Object} increment: Boolean to determine whether to increment or decrement the tile index.
   */
  setFadeTiles: function(increment)
  {
    self.tileOut = new YAHOO.util.Anim(self.frames[self.currentTile], {
                                         opacity: { to: 0 } }, self.duration, YAHOO.util.Easing.EaseIn);
    var tileOut = self.currentTile;
    self.tileOut.onComplete.subscribe(function() {
      self.frames[tileOut].style.display = "none";
    })
    self.setNextTileIndex(increment);
    for (var i = 0; i < self.tilesInViewport; i++)
    {
      self.frames[self.currentTile + i].style.display = "inline";
      self.tileIn[i] = new YAHOO.util.Anim(self.frames[self.currentTile + i], {
                                             opacity: { to: 1 } }, self.duration, YAHOO.util.Easing.EaseIn);
    }
  },

  /**
   * Creates the animation instances for slide.
   * @param {Object} increment: Boolean to determine whether to increment or decrement the tile index.
   */
  setSlideTiles: function(increment)
  {
    var pos = self.isReverse == false ? self.finalPosition : self.initialPosition;
    var tileOutParam = (self.effect == self.SLIDE_HORIZONTAL)? {left: {to: pos}}: {top: {to: pos}};
    self.tileOut = new YAHOO.util.Anim(self.frames[self.currentTile], tileOutParam, self.duration, YAHOO.util.Easing.EaseIn);
    if (self.isReverse == false)
    {
      self.setNextTileIndex(increment);
    } 
    for (var i = 0; i < self.tilesInViewport; i++)
    {
      var tile;
      var pos;
      if(self.isReverse == true)
      {
        self.setNextTileIndex(increment);
        tile = self.currentTile;
        pos = (self.tilesInViewport - 1) - i;  //We are shifting the positions from right to left in the viewport.
      }
      else
      {
        tile = (self.currentTile + i) % self.frames.length;
        pos = i;
      }
      var tileInParam = (self.effect == self.SLIDE_HORIZONTAL)? {left: {to: self.positions[pos]}}: {top: {to: self.positions[pos]}};
      self.tileIn[i] = new YAHOO.util.Anim(self.frames[tile], tileInParam, self.duration, YAHOO.util.Easing.EaseIn);
    }
  },

  /**
   * Sets the next tile(image) index.
   * @param {Object} increment: Boolean to determine whether to increment or decrement the tile index.
   */
  setNextTileIndex: function(increment)
  {
    if (increment == true)
    {
      ++(self.currentTile);
      self.currentTile = self.currentTile % self.frames.length;
    }
    else
    {
      --(self.currentTile);
      self.currentTile = (self.currentTile == -1) ? (self.frames.length - 1) : self.currentTile;
    }
  },

  /**
   * Resets the slide in tile(not applicable to fade).
   * This resets the previous tile(why? : because this is called everytime when the animation is completed).
   * i.e. once "currentTile" animates out of the viewport(now it is previous tile) it is reset. 
   */
  resetSlideInTile: function()
  {
    var previousTile = ((self.currentTile - 1) == -1) ? (self.frames.length - 1) : (self.currentTile - 1);
    if (self.isReverse == true)
    {
      var pos = self.finalPosition;
    }
    else
    {
      var pos = self.initialPosition;
    }
    if(self.effect == self.SLIDE_HORIZONTAL)
    {
      YAHOO.util.Dom.setStyle(self.frames[previousTile], "left", pos + "px");
    }
    if(self.effect == self.SLIDE_VERTICAL)
    {
      YAHOO.util.Dom.setStyle(self.frames[previousTile], "top", pos + "px");
    }
  },

  /**
   * This is the normal control, a loop function.
   * This creates our animation thread and ensures that at any time only one thread is running.
   */
  play: function()
  {
    if (self.isPaused == true || self.isOneThreadRunning == true) return;
    self.isOneThreadRunning = true;
    self.isHalted = false;
    self.setTiles();
    self.tileOut.onStart.subscribe(function(){
      self.tileIn[self.tilesInViewport - 1].onComplete.subscribe(function(){
        if (self.isEffectSlide() && self.isPaused == false)
        {
          self.resetSlideInTile();
        }
        if (YAHOO.util.Dom.get("activeNo"))  //i.e. if tile numbers is supported do this.
        {
          self.changeTileNo();
        }
        if (self.isPaused == false)
        {
          self.isHalted = true;
          clearTimeout(self.setTimeoutHandler1);  //Destroy the previous timer.
          self.setTimeoutHandler1 = setTimeout(function()
                              {
                                self.isOneThreadRunning = false;
                                self.play();
                              }, self.interval);
        }
      });
      for (var i = 0; i < self.tilesInViewport; i++)
      {
        self.tileIn[i].animate();
      }
    });
    self.tileOut.animate();
  },

  /**
   * This calls play once and pauses.
   */
  actionPause: function()
  {
    var dur = self.duration;
    self.duration = 0.25;  //This is the animation duration for actionPause 
    self.isPaused = false;
    self.play();
    var processingTime = 250;  //milliseconds(This is the assumed(not exact but sufficient) time for the initial statements in "play".
    var interval = (self.duration * 1000) + processingTime;  //milliseconds.
    setTimeout(function()  //Timing issue over here.
               {           //Here self.isPaused must be set to true only after "interval".
                 self.isPaused = true;
               }, interval);
    self.duration = dur;  //Reset self.duration to initial value.
    self.isOneThreadRunning = false;
  },

  /**
   * Stops the animation.
   */
  stop: function()
  {
    self.isPaused = true;
    self.tileOut.stop();
    for (var i = 0; i < self.tilesInViewport; i++)
    {
      self.tileIn[i].stop();
    }
  },

  /**
   * Makes an estimate of what percent of the previous tile is visible in the viewport and
   * determines whether to bring it back into the viewport while paused.
   */
  shouldGetInPreviousTile: function()
  {
    var previousTile = ((self.currentTile - 1) == -1) ? (self.frames.length - 1) : (self.currentTile - 1);
    if (self.isEffectFade() && YAHOO.util.Dom.getStyle(self.frames[previousTile], "opacity") > 0.25)
    {
      return true;
    }
    else if (self.isEffectSlide())
    {
      var par = self.effect == self.SLIDE_HORIZONTAL? "left": "top";
      if (((-self.finalPosition) - (-parseInt(YAHOO.util.Dom.getStyle(self.frames[previousTile], par),10))) > (0.25 * self.posShift))
      {
        return true;
      }
    }
    return false;
  },

  /**
   * Brings in the previous tile.
   */
  getInPreviousTile: function()
  {
    self.setNextTileIndex(false);
    self.isReverse = !(self.isReverse);
    self.actionPause();
    self.isReverse = !(self.isReverse);
  },

  /**
   * Brings in the next tile.
   */
  getInNextTile: function()
  {
    self.setNextTileIndex(false);
    self.actionPause();
  },

  /**
   * Pauses the animation.
   * If paused while animation is still in progress , it makes the calculations and either accelerates or reverses the current animation and pauses.
   */
  pause: function()
  {
    clearTimeout(self.setTimeoutHandler1);
    clearTimeout(self.setTimeoutHandler2);
    self.isOneThreadRunning = false;
    if (self.isPaused == false && self.isMouseOver == false && self.isHalted == false)
    {
      self.stop();
      self.isMouseOver = true;
      if (self.shouldGetInPreviousTile())
      {
        self.getInPreviousTile();
      }
      else
      {
        self.getInNextTile();
      }
    }
  },

  /**
   * Resumes the animation after reseting the parameters.
   */
  resume: function()
  {
    self.isFirstClick = true;
    clearTimeout(self.setTimeoutHandler2);
    self.setTimeoutHandler2 = setTimeout(function()
                         {
                           self.isMouseOver = false;
                           self.isPaused = false;
                           self.play();
                         }, self.interval);
  },

  /**
   * Updates the tile number. 
   */
  changeTileNo: function()
  {
    YAHOO.util.Dom.get("activeNo").innerHTML = self.currentTile + 1;
  },

  /**
   * The animation goes one step backward.
   */
  goBackward: function()
  {
    clearTimeout(self.setTimeoutHandler1);
    clearTimeout(self.setTimeoutHandler2);
    self.stop(); 
    if (self.isFirstClick == true && self.isHalted == false)
    {
      self.isFirstClick = false;
      self.isOneThreadRunning = false;
      self.getInPreviousTile();
      return;
    }    
    self.isReverse = !(self.isReverse);
    if (self.isEffectSlide())
    {
      self.resetSlideInTile();
    }
    self.setNextTileIndex(false);
    self.actionPause();
    self.isReverse = !(self.isReverse);
  },

  /**
   * The animation goes one step forward.
   */
  goForward: function()
  {
    clearTimeout(self.setTimeoutHandler1);
    clearTimeout(self.setTimeoutHandler2);
    self.stop(); 
    if (self.isFirstClick == true && self.isHalted == false)
    {
      self.isFirstClick = false;
      self.isOneThreadRunning = false;
      self.getInNextTile();
      return;
    }    
    if (self.isEffectSlide())
    {
      self.resetSlideInTile();
    }
    self.actionPause();
  }
}