/**
  Simple scrolling pane object. This allows scrolling content in a horizontal or
  vertical direction. The content to be scrolled should be contained within a
  div. Upon instantiation, all content of the div will be transfered to a
  'container' div, which is placed in the element. The margins of this container
  are then adjusted to provide the scrolling
*/
var SimpleScroller = Class.create();
var Scroller = Class.create();
Scroller.DIR_UP    = 0;
Scroller.DIR_DOWN  = 1;
Scroller.DIR_LEFT  = 2;
Scroller.DIR_RIGHT = 3;

Object.extend(SimpleScroller.prototype, {
  /**
    Initializes the SimpleScroller object. 'element' is the div that contains the
    content to be scrolled
  */
  initialize: function(element) {
    this.element = $(element);
    this.element.proxy = this;
    
    this.speed = 10;
    this.offset = { x: 0, y: 0 }
    this.scrolling = [false, false, false, false];
    this.running = false;
    
    var divs = document.getElementsByClassName('scroller-container', this.element);
    if (divs.length > 0) {
      this.container = divs[0];
    } else {
      this.container = document.createElement('div');
      this.container.className = 'scroller-container';
      while(this.element.childNodes.length > 0) {
        var node = this.element.childNodes[0];
        this.element.removeChild(node);
        this.container.appendChild(node);
      }
      
      this.element.appendChild(this.container);
    }
  },
  
  /**
    Sets the speed of the scrolling. This value represents how many pixels of
    translation will be applied every 10ms
  */
  setSpeed: function(speed) {
    this.speed = speed;
  },
  
  /**
    Translates the content by the amount specified by setSpeed. Arguments are:
     - direction: One of the Scroller.DIR_* values to specify the direction to scroll
     - skipUpdate: If true, then skip the call to updatePosition. Unless you have
                   a reason not to, just don't pass anything for this argument
  */
  scroll: function(direction, skipUpdate) {
    switch(direction) {
      case 0: this.offset.y -= this.speed; break;
      case 1: this.offset.y += this.speed; break;
      case 2: this.offset.x -= this.speed; break;
      case 3: this.offset.x += this.speed; break;
    }
    if (!skipUpdate) this.updatePosition();
  },
  
  /**
    Start the Scroller scrolling in the specified direction. Scroller.scroll will
    be called every 10ms until the scrolling is stopped.
  */
  startScroll: function(direction) {
    this.scrolling[direction] = true;
    
    if (!this.running) {
      var callback = function() {
        if (!this.running) return;
        
        for (var i=0; i<4; i++)
          if (this.scrolling[i]) this.scroll(i, true);
        
        this.updatePosition();
        window.setTimeout(arguments.callee.bind(this), 10);
      }
      
      this.running = true;
      callback.call(this);
    }
  },
  
  /**
    Stops the scrolling in the specified direction.
  */
  stopScroll: function(direction) {
    this.scrolling[direction] = false;
    
    var still_running = false;
    for (var i=0; i<4; i++) {
      if (this.scrolling[i]) still_running = true;
    }
    
    if (!still_running) this.running = false;
  },
  
  /**
    Scroll to a specified offset. Arguments are:
     - offset: Hash containing the offset of each axis you wish to change (x and y)
     - duration: Length of transition time to new offset (in ms)
  */
  scrollTo: function(offset, duration, oncomplete) {
    var steps = Math.ceil(duration * 0.015); // Assume 15fps
    var frames = [];
    
    oncomplete = oncomplete || function() { /* Do Nothing */ }
    
    for (var axis in offset) {
      var axis_frames = Animator.interpolate.linear(this.offset[axis], offset[axis], steps);
      
      for (var i=0, l=axis_frames.length; i<l; i++) {
        if (!frames[i]) frames[i] = { };
        frames[i][axis] = axis_frames[i];
      }
    }
    
    if (this.anim) this.anim.stop(true);
    
    var _this = this;
    this.anim = new Animator(frames, duration);
    this.anim.onupdate = function(value) {
      for (var axis in value) {
        _this.offset[axis] = Math.round(value[axis], 1);
      }
      
      _this.updatePosition();
      
      return true;
    }
    this.anim.oncomplete = function() { _this.running = false; oncomplete.call(this); }
    
    this.running = true;
    this.anim.run();
  },
  
  /**
    Scroll to a specified offset without animating
  */
  jumpTo: function(offset) {
    for (var axis in offset) this.offset[axis] = Math.round(offset[axis], 1);
    this.updatePosition();
  },
  
  /**
    Constrains the offset values.
  */
  updateOffset: function() {
    if (this.offset.x < 0) this.offset.x = 0;
    if (this.offset.y < 0) this.offset.y = 0;
    
    var elem_pos = Element.getPosition(this.element);
    var elem_dim = Element.getDimensions(this.element);
    var cont_pos = Element.getPosition(this.container);
    var cont_dim = Element.getDimensions(this.container);
    
    var elem_bottom = elem_pos.y + elem_dim.height;
    var cont_bottom = cont_pos.y + cont_dim.height;
    if (cont_bottom < elem_bottom) this.offset.y -= (elem_bottom - cont_bottom);
    
    var elem_right = elem_pos.x + elem_dim.width;
    var cont_right = cont_pos.x + cont_dim.width;
    if (cont_right < elem_right) this.offset.x -= (elem_right - cont_right);
  },
  
  /**
    Constrains the offset values and update the display of the content.
  */
  updatePosition: function() {
    
    this.updateOffset();
    this.container.style.marginTop = (-this.offset.y) + 'px';
    this.container.style.marginLeft = (-this.offset.x) + 'px';
  }
});

//Object.extend(Scroller.prototype, SimpleScroller.prototype);
Object.extend(Scroller.prototype, {
  /**
    Initializes the Scroller object. 'element' is the div that contains the
    content to be scrolled
  */
  initialize: function(element, options) {
    SimpleScroller.prototype.initialize.call(this,element);
    this.options = {
      layout: 'vertical',
      dimensions: {
        element: Element.getDimensions(this.element),
        container: Element.getDimensions(this.container)
      },
      columnWidth: 0,
      loop: false,
      animate: false,
      duration: 1000
    }
    Object.extend(this.options, options || {});

    // if dimensions are 0 try to pull from style properties
    if(!this.options.dimensions.element.width) this.options.dimensions.element.width = parseInt(Element.getStyle(this.element,'width'));
    if(!this.options.dimensions.element.height) this.options.dimensions.element.height = parseInt(Element.getStyle(this.element, 'height'));
    if(this.options.speed) this.setSpeed(this.options.speed);
    this.dimensions = this.options.dimensions;


    this.makePositioned();

    if(this.options.layout.match(/horizontal/) && this.options.columnWidth) {
      this.setupHorizontal();
      if(this.options.loop) this.setupHorizontalLoop();
    }
  },
  setSpeed: function(speed) {
    this.speed = speed;
  },
  makePositioned: function(){
		var epos=Element.getStyle(this.element,'position');
		if(epos==null) epos = "";
    if(!epos.match(/(relative|absolute)/)) {
      Element.setStyle(this.element, {position: 'relative', display: ""});
    }

		var cpos=Element.getStyle(this.container,'position');
		if(cpos==null) cpos = "";
    if(!cpos.match(/absolute/)) {
      Element.setStyle(this.container, {position: "absolute", left: "0px", display: "block"});
    }
  }, 
  
  setupHorizontalLoop: function() {
    this.cloneContainer = this.container.cloneNode(true);
    var left = parseInt(Element.getStyle(this.container,'left')) - this.dimensions.container.width;
    Element.setStyle(this.cloneContainer, {left: left+'px'});
    this.element.appendChild(this.cloneContainer);
  },
  
  setupHorizontal: function() {
    var nodes = this.container.childNodes;
    nodes = $A(nodes).findAll(function(node) {
      return (node.nodeType == 1);
    });
    
    var containerWidth = nodes.length * this.options.columnWidth;
    this.dimensions.container.width = containerWidth;
  
    Element.setStyle(this.container, {width: containerWidth + 'px', height: this.dimensions.element.height + 'px'});

    var left = 0;
    var self = this;
    nodes._each(function(node) {
      Element.setStyle(node, {position: 'absolute', left: left + 'px'})
      left += self.options.columnWidth;
    });
  },

  scroll: function(direction, skipUpdate){
    this.direction = direction;
    SimpleScroller.prototype.scroll.call(this, direction, skipUpdate);
  },
  
  swapContainers: function() {
    var c = this.container;
    this.container = this.cloneContainer;
    this.cloneContainer = c;
  },
  
  updateOffset: function() {
    var elem_pos = Element.getPosition(this.element);
    var elem_dim = Element.getDimensions(this.element);
    var cont_pos = Element.getPosition(this.container);
    var cont_dim = Element.getDimensions(this.container);
    
    var elem_bottom = elem_pos.y + elem_dim.height;
    var cont_bottom = cont_pos.y + cont_dim.height;
    
    var elem_right = elem_pos.x + elem_dim.width;
    var cont_right = cont_pos.x + cont_dim.width;
    
    
    if(!this.options.loop) {
      if(this.offset.x < 0) this.offset.x = 0;
      if (this.offset.y < 0) this.offset.y = 0;
      if (cont_bottom < elem_bottom) this.offset.y -= (elem_bottom - cont_bottom);
      if (cont_right < elem_right) this.offset.x -= (elem_right - cont_right);
    } else {
      if((Math.abs(this.offset.x) >= this.dimensions.container.width)) {
        this.swapContainers();
        if(this.direction == Scroller.DIR_LEFT) {
          this.cloneContainer.style.left = -this.dimensions.container.width + 'px';
          this.offset.x = parseInt(Element.getStyle(this.container,'left'))+this.speed;
        } else if(this.direction == Scroller.DIR_RIGHT) {
          this.cloneContainer.style.left = -this.dimensions.element.width + 'px';
          this.offset.x = parseInt(Element.getStyle(this.container,'left'))-this.speed;
        }
      }
    }
    
  },
  scrollFor: function(delta){
    var objs = [this.container];
    if(this.options.loop) objs.push(this.cloneContainer);
    if(this.options.layout.match(/horizontal/)) {
      var slide = new Transitions.HorizontalSlide(objs, delta, this.options.duration);
      slide.run();
    } else {
      var slide = new Transitions.VerticalSlide(objs, delta, this.options.duration);
      slide.run();
    }
  },
  /**
    Constrains the offset values and update the display of the content.
  */
  updatePosition: function() {
    this.updateOffset();
    
    if(this.options.animate && this.options.layout.match(/horizontal/)) {
      var left = parseInt(Element.getStyle(this.container,'left'));
      if(isNaN(left)) left = 0
      var delta = -parseInt(this.offset.x) - left;
    } else if(this.options.animate && this.options.layout.match(/vertical/)) {
      var top = parseInt(Element.getStyle(this.container,'top'));
      if(isNaN(top)) top = 0;
      var delta =  -parseInt(this.offset.y) - top;
    }
    if(!this.options.animate) {
      Element.setStyle(this.container, {
          top: (-this.offset.y) + 'px', 
          left: (-this.offset.x) + 'px'}
      );
    }
        
    if(this.options.loop && this.options.layout.match(/horizontal/)) {
      var mod = (this.direction == Scroller.DIR_LEFT) ? -1:1;
      var left = parseInt(Element.getStyle(this.container,'left')) + (mod*this.dimensions.container.width);
      Element.setStyle(this.cloneContainer, {left: left + 'px'});
    }

    if(this.options.animate) {
      this.scrollFor(delta);
    }
  }
});


