/*

  Author: mirza@php.co.ba
  Depends on: prototype.js v.1.6 or above

*/

// rewrite of the function because of Firefox 0,1,2
if (Prototype.Browser.Gecko && navigator.userAgent.match(/Firefox.[012]/)) {
  Element.addMethods({
    cumulativeOffset: function(element) {
      var valueT = valueL = 0;
      // add border-width of all ancestors, because
      // ff0,1,2 ignore it when overflow != 'visible'
      element.ancestors().each(function(ancestor) {
        var overflow = ancestor.getStyle('overflow');
        if (!overflow || overflow != 'visible') {
          valueT += parseInt(ancestor.getStyle('border-top-width')) || 0;
          valueL += parseInt(ancestor.getStyle('border-left-width')) || 0;
        }
      })
      do {
        valueT += element.offsetTop  || 0;
        valueL += element.offsetLeft || 0;
      } while (element = element.offsetParent);
      return Element._returnOffset(Number(valueL), Number(valueT));
    }
  });
}

var Scrollbar = Class.create();
Scrollbar.prototype = {
  initialize: function() {
    this.settings = Object.extend({
      area:                   null,        // node which contains content node

      observeChanges:         true,        // adapt scrollbar to area/content size changes

      scrollBarVisibility:    'visible',   // visible or auto (show only if content too big)

      // scrolling by clicking the arrows or by sliding scrollbar
      mouseScrollSpeed:       15,          // higher = slower scrolling
      mouseScrollSize:        5,           // lower = smoother, higher = faster scrolling
      mouseIncrementalSpeed:  true,        // the longer you hold the mouse down, the faster scrolling will be

      // scrollarea/scrollbar/scrollarows
      scrollPosition:         'right',     // scroll position: left or right (standard)
      scrollWidth:            20,          // scroll width in px
      scrollBarHeight:        0,           // if not 0, scrollbar will have this height
      scrollBarMinHeight:     0,           // minimum scrollbar height in px. ignored if 0
      scrollBarMaxHeight:     0,           // maximum scrollbar height in px. ignored if 0
      resetContainerWidth:    true,        // correct container width, because scrollArea is making it widther

      // hash with style values
      scrollAreaDesign:       null,        // alle values in hashes: standard { 'background-color': '#fff' }
      scrollArrowUpDesign:    null,        // possible hashes are: mousedown {}, mousover{}, standard{}
      scrollArrowDownDesign:  null,        // use style in css form ('padding-left' or paddingLeft)
      scrollBarDesign:        null,        // or in camelized form (paddingLeft)

      // scrolling with mouse wheel
      wheelScrollSpeed:      25            // higher = faster scrolling

    }, arguments[0] || {});

    this.area = $(this.settings.area);
    if (!this.area) {
      return;
    }

    if (this.area.firstDescendant()) {
      this.content = this.area.firstDescendant();
    } else {
      return;
    }


    // run this after window load, because the sizes are not
    window.onload = (function() {
      this.contentTop = 0;
      this.isMouseDown = false;

      this.area.setStyle({ overflow: 'hidden' });
      this.content.setStyle({ position: 'relative' });
      this.prepare();

      if (this.settings.observeChanges)
        this.observeChanges();
    }).bind(this);
  },

  prepare: function() {
    this.area.innerHeight = this.area.getHeight();
                              -parseInt(this.area.getStyle('border-top-width'))
                              -parseInt(this.area.getStyle('border-bottom-width'))
                              -parseInt(this.area.getStyle('padding-top'))
                              -parseInt(this.area.getStyle('padding-bottom'));

    if (!this.scrollArea) {
      // do not display if content too small
      if (this.settings.scrollBarVisibility == 'auto'  && this.content.getHeight() <= this.area.innerHeight)
        return;

      this.drawScroll();

      document.observe('mouseup', this.mouseUp.bind(this));
      document.observe('mousemove', this.scrollWithBar.bind(this));
      document.observe('keyup',this.unsetKeys.bind(this));
      document.observe('keydown',this.setKeys.bind(this));
      document.observe('keypress',this.setKeys.bind(this)); // FF & Opera

      if (Prototype.Browser.Gecko) {
        this.area.observe('DOMMouseScroll', this.scrollWithWheel.bind(this));
      } else {
        this.area.observe('mousewheel', this.scrollWithWheel.bind(this));
      }
    } else {
      if (this.scrollArea)
        this.setScrollAreaPosition(true);
    }
  },

  // check 3 times in a second for size changes
  observeChanges: function() {
    if (!this.content.height || !this.area.height ||
        !this.content.width  || !this.area.width) {
      setTimer(this.observeChanges(),333);
      return;
    }
    this.checkSituationTimer = setInterval((function() {
      if (   this.content.height != this.content.getHeight()
          || this.content.width  != this.content.getWidth()
          || this.area.height != this.area.getHeight()
          || this.area.width  != this.area.getWidth()
      ){

        this.prepare();
        this.scrollContentTo(0);

        clearInterval(this.checkSituationTimer);
        this.observeChanges();
      }
    }).bind(this),333);
  },

  scrollWithWheel: function(e) {
    if (!e)
      e = window.e;

    if (e.wheelDelta) { // IE/Opera: multiple of 120
      delta = e.wheelDelta/120;
      // Opera: delta is positive (it should be negative)
      /* if (Prototype.Browser.Opera)
        delta = delta; */
    } else if (e.detail) { // Mozilla
      // Mozilla: delta is multiple of 3
      delta = -e.detail/3;
    }
    if (delta)
      this.scrollUpDown(Number(delta)*this.settings.wheelScrollSpeed);

    e.stop();
  },

  scrollUpDown: function(val) {
    val = parseInt(val);
    if (isNaN(val) || val == 0)
      return;

    this.scrollContentTo(this.contentTop+val,true);
  },

  scrollContentTo: function(val,moveBar) {
    if (this.contentTop == val)
      return;

    if (val > this.minContentTop)
      val = this.minContentTop;

    if (val < this.maxContentTop)
      val = this.maxContentTop;

    this.content.style.top = (this.contentTop = val)+'px';
    if (moveBar)
      this.moveBarTo((this.contentTop-this.minContentTop)*this.scrollBarMaxBottom/(this.maxContentTop-this.minContentTop));
  },

  moveBarTo: function(val) {
    if (val < 0)
      val = 0;

    if (val > this.scrollBarMaxBottom)
      val = this.scrollBarMaxBottom;

    this.scrollBar.setStyle({ top: val+'px' });
  },

  scrollWithKeys: function(key) {
    if (!key || !this.keyDown || key != this.key)
      return;

    var val = 0;
    var scrollSpeed = 333; // lower => faster (miliseconds)

    switch (key) {
        case Event.KEY_PAGEUP:
            this.key = Event.KEY_PAGEUP;
            this.keyDown = true;
            val =  this.scrollBar.height*10;
            break;
        case Event.KEY_PAGEDOWN:
            this.key = Event.KEY_PAGEDOWN;
            this.keyDown = true;
            val = -this.scrollBar.height*10;
            break;
        case Event.KEY_UP:
            this.key = Event.KEY_UP;
            this.keyDown = true;
            val =  5;
            scrollSpeed = 5;
            break;
        case Event.KEY_DOWN:
            this.key = Event.KEY_UP;
            this.keyDown = true;
            val = -5;
            scrollSpeed = 5;
            break;
        case Event.KEY_END:
            this.scrollContentTo(this.maxContentTop,true);
            return;
        case Event.KEY_HOME:
            this.scrollContentTo(this.minContentTop,true);
            return;
        default:
            return;
    }

    this.scrollUpDown(val);
    setTimeout((function() { this.scrollWithKeys(key); }).bind(this),scrollSpeed);
  },

  setKeys: function(e) {
    if (!e)
      return;

    this.key = e.keyCode;
    this.keyDown = true;
    this.scrollWithKeys(this.key);
  },

  unsetKeys: function(e) {
    this.keyDown = false;
    this.key = 0;
  },

  scrollWithBar: function(e) {
    if (!e || !this.isMouseDown || !this.scrollBarActive)
      return;

    var mouseY = Event.pointerY(e);
    var scrollTopPosition = Number(mouseY-this.scrollBar.topDiff);

    this.moveBarTo(scrollTopPosition);
    this.scrollContentTo(((this.maxContentTop-this.minContentTop)*scrollTopPosition/this.scrollBarMaxBottom)+this.minContentTop);

    /*

      Formula for the ratio between scrollbar and content:

      [this.contentTop]-this.minContentTop   this.maxContentTop-this.minContentTop
      ------------------------------------ = -------------------------------------
            [scrollTopPosition]                  this.scrollBarMaxBottom

      this.contentTop   = content top position
      scrollTopPosition = scrollBar top position

    */
  },

  scrollWithArea: function(mouseY,init,up) {
    if (!mouseY || !this.isMouseDown || !this.scrollAreaActive)
      return;

    var scrollBarTop = this.scrollBar.cumulativeOffset().top
    var scrollTopPosition = scrollBarTop-this.scrollBarZeroTop;
    var new_up;

    // click above scrollbar
    if (scrollBarTop < mouseY) {
      scrollTopPosition += this.scrollBar.height;
      new_up = true;
    } else
    // click below scrollbar
    if (scrollBarTop+this.scrollBar.height > mouseY) {
      scrollTopPosition -= this.scrollBar.height;
      new_up = false;
    } else {
      return;
    }

    // up/new_up to disable "jumping" of the bar in the scrollarea
    if ((!init && ((!up && !new_up) || (up && new_up))) || init) {
      this.moveBarTo(scrollTopPosition);
      this.scrollContentTo(((this.maxContentTop-this.minContentTop)*scrollTopPosition/this.scrollBarMaxBottom)+this.minContentTop);
      setTimeout((function() {this.scrollWithArea(mouseY,false,new_up);}).bind(this),140);
    }
  },

  scrollWithArrow: function(val) {
    if (!this.isMouseDown)
      return;

    if (this.activeScroller.isMouseOver) {
      this.scrollUpDown(val);
    } else {
      val = (val < 0) ? -this.settings.mouseScrollSize : this.settings.mouseScrollSize;
    }

    setTimeout((function() {
        if (this.settings.mouseIncrementalSpeed)
          val -= (val < 0) ? 0.33 : -0.33;

        this.scrollWithArrow(val);
    }).bind(this),this.settings.mouseScrollSpeed);
  },

  mouseUp: function(e) {
    if (!e)
      e = window.e;

    if (Event.isLeftClick(e)) {
      this.isMouseDown      = false;
      this.scrollBarActive  = false;
      this.scrollAreaActive = false;

      if (this.activeScroller)
        if (this.activeScroller.design.standard)
          this.activeScroller.setStyle(this.activeScroller.design.standard);

      this.activeScroller = null;
    }
  },

  mouseDown: function(e) {
    if (!e)
      e = window.e;

    var target = this.target(e);
    if (Event.isLeftClick(e)) {
      this.isMouseDown = true;

      if (target.hasClassName(this.scrollArrowDownIdentifier)) {
        this.activeScroller = target;
        this.scrollWithArrow(-this.settings.mouseScrollSize);
      }

      if (target.hasClassName(this.scrollArrowUpIdentifier)) {
        this.activeScroller = target;
        this.scrollWithArrow(this.settings.mouseScrollSize);
      }

      if (target.hasClassName(this.scrollBarIdentifier)) {
        this.activeScroller = target;
        this.scrollBarActive = true;
        this.scrollBar.topDiff = Event.pointerY(e)
                                  -this.scrollBar.cumulativeOffset().top
                                  +this.scrollBarZeroTop;
      }
      if (target.hasClassName(this.scrollAreaIdentifier)) {
        this.activeScroller = target;
        this.scrollAreaActive = true;
        this.scrollWithArea(Event.pointerY(e),true);
      }

      if (this.activeScroller)
        if (this.activeScroller.design.mousedown)
          target.setStyle(this.activeScroller.design.mousedown);

      e.stop();
    }
  },

  mouseOut: function(e) {
    if (!e)
      e = window.e;

    if (this.isMouseDown)
      return;

    var target = this.target(e);
    target.isMouseOver = false;

    if (target.design.standard)
      target.setStyle(target.design.standard);
  },

  mouseOver: function(e) {
    if (!e)
      e = window.e;

    if (this.isMouseDown)
      return;

    var target = this.target(e);
    target.isMouseOver = true;

    if (target.design.mouseover)
      target.setStyle(target.design.mouseover);
  },

  target: function target(e) {
    return e.target || e.srcElement;
  },

  setScrollAreaPosition: function(resize) {
    // set all unset borderWidths to 0px because of IE[67]
    if (Prototype.Browser.IE) {
      var IEstyles = new Array('borderTopWidth','borderRightWidth','borderBottomWidth','borderLeftWidth');
      for (var i=0, len=IEstyles.length; i<len; i++) {
        if (isNaN(parseInt(this.area.getStyle(IEstyles[i]))))
          eval("this.area.setStyle({ "+IEstyles[i]+": '0px' })");
      }
    }

    // 1. set scrollArea height
    this.scrollArea.height = Number(
                              this.area.getHeight()
                              -parseInt(this.area.getStyle('border-top-width'))
                              -parseInt(this.area.getStyle('border-bottom-width'))
                              -parseInt(this.scrollArea.getStyle('border-top-width'))
                              -parseInt(this.scrollArea.getStyle('border-bottom-width'))
                            );
    this.scrollArea.setStyle({ height: this.scrollArea.height+'px' });

    // 2. set top position
    this.scrollArea.setStyle({ top: '-'+this.area.getStyle('padding-top') });

    // 3/4. set padding/width only once on load
    if (!resize) {
      if (!this.area.paddingLeft || !this.area.paddingRight) {
        this.area.paddingLeft  = parseInt(this.area.getStyle('padding-left'));
        this.area.paddingRight = parseInt(this.area.getStyle('padding-right'));
      }
      if (this.settings.scrollPosition == 'left') {
        // 3. set new area leftPadding: paddingLeft+scrollArea.getWidth()-scrollArea.border(Left+Right)Width
        this.area.setStyle({
          paddingLeft: Number(
                          parseInt(this.area.getStyle('padding-left'))
                          +this.scrollArea.getWidth()
                          -parseInt(this.scrollArea.getStyle('border-left-width'))
                          -parseInt(this.scrollArea.getStyle('border-right-width'))
                        )+'px'
        });

      } else {
        // 3. set new area rightPadding: paddingRight+scrollArea.getWidth()-scrollArea.border(Left+Right)Width
        this.area.setStyle({
          paddingRight: Number(
                          parseInt(this.area.getStyle('padding-right'))
                          +this.scrollArea.getWidth()
                          -parseInt(this.scrollArea.getStyle('border-left-width'))
                          -parseInt(this.scrollArea.getStyle('border-right-width'))
                        )+'px'
        });
      }


      // 4. set new width for the container to avoid changing width because of scrollArea
      if (this.settings.resetContainerWidth && !Prototype.Browser.IE) {
        this.area.setStyle({
            width:      Number(
                          this.area.getWidth()
                          -parseInt(this.area.getStyle('border-left-width'))
                          -parseInt(this.area.getStyle('border-right-width'))
                          -parseInt(this.area.getStyle('padding-left'))
                          -parseInt(this.area.getStyle('padding-right'))
                          -this.scrollArea.getWidth()
                          +parseInt(this.scrollArea.getStyle('border-left-width'))
                          +parseInt(this.scrollArea.getStyle('border-right-width'))
                        )+'px'
        });
      }
    }

    // 5. set left position
    if (this.settings.scrollPosition == 'left') {
      this.scrollArea.setStyle({
        left:         '-'+this.area.getStyle('padding-left')
      });
    } else {
      this.scrollArea.setStyle({
        left:         Number(
                      this.area.getWidth()
                      -parseInt(this.area.getStyle('border-left-width'))
                      -parseInt(this.area.getStyle('border-right-width'))
                      -this.scrollArea.getWidth()
                      -parseInt(this.area.getStyle('padding-left'))
                      )+'px'
      });
    }

    // 6. move content up, because it was moved down by scrollArea
    this.minContentTop = -this.scrollArea.getHeight();
    this.content.setStyle({ top: this.minContentTop+'px' });

    this.maxContentTop = -this.content.getHeight()
                          -parseInt(this.area.getStyle('padding-top'))
                          -parseInt(this.area.getStyle('padding-bottom'));

    // 7. set scrollbar height and position vars
    contentVisiblePercent = (this.scrollArea.getHeight()
                            -parseInt(this.area.getStyle('padding-top'))
                            -parseInt(this.area.getStyle('padding-bottom')))/
                            this.content.getHeight();

    if (contentVisiblePercent < 1) {
      if (this.settings.scrollBarMinHeight <= 0) {
        this.scrollBar.height = Math.round((this.scrollArea.getHeight()
                                            -this.scrollArrowUp.getHeight()
                                            -this.scrollArrowDown.getHeight()
                                            )*contentVisiblePercent
                                );
        // calculate new minimum size
        var scrollBarMinHeight = this.settings.scrollBarMinHeight
                                  -parseInt(this.scrollBar.getStyle('border-top-width'))
                                  -parseInt(this.scrollBar.getStyle('border-bottom-width'))
                                  -parseInt(this.scrollBar.getStyle('padding-top'))
                                  -parseInt(this.scrollBar.getStyle('padding-bottom'));

        if (this.scrollBar.height < scrollBarMinHeight)
          this.scrollBar.height = scrollBarMinHeight;

        if (this.scrollBar.height > this.settings.scrollBarMaxHeight && this.settings.scrollBarMaxHeight != 0)
          this.scrollBar.height = this.settings.scrollBarMaxHeight;

      } else {
          this.scrollBar.height = this.settings.scrollBarHeight;
      }

      this.scrollBar.setStyle({ height:this.scrollBar.height+'px' });
    }

    this.scrollBarZeroTop = this.scrollBar.cumulativeOffset().top;
    this.scrollBarMaxBottom = this.scrollArea.height
                                -this.scrollArrowUp.getHeight()
                                -this.scrollArrowDown.getHeight()
                                -this.scrollBar.getHeight();

    // 8. set arrowDown position
    this.scrollArrowDown.setStyle({
      top:    this.scrollArea.height
                -this.scrollArrowDown.getHeight()
                -this.scrollArrowUp.getHeight()
                -this.scrollBar.getHeight()
              +'px' });

    // 9. set vars for the observe interval
    this.content.height = this.content.getHeight();
    this.content.width = this.content.getWidth();
    this.area.height = this.area.getHeight();
    this.area.width  = this.area.getWidth();
  },

  drawScroll: function() {
    var identifierTime = new Date();

    // scrollarea
    this.scrollArea = new Element('div');
    this.scrollArea.design = this.settings.scrollAreaDesign;
    this.scrollArea.setStyle({ borderWidth: '0px' }); // IE fix
    if (this.settings.scrollAreaDesign.standard)
      this.scrollArea.setStyle(this.settings.scrollAreaDesign.standard);
    this.scrollArea.setStyle({
      width:      this.settings.scrollWidth+'px',
      position:   'relative',
      left:       '-10000px'
    });
    this.scrollAreaIdentifier = 'scrollarea'+identifierTime.getTime();
    this.scrollArea.addClassName(this.scrollAreaIdentifier);
    this.scrollArea.observe('mousedown', this.mouseDown.bind(this));
    this.scrollArea.observe('mouseout', this.mouseOut.bind(this));
    this.scrollArea.observe('mouseover', this.mouseOver.bind(this));
    this.content.insert({ before: this.scrollArea });

    // arrow up
    this.scrollArrowUp = new Element('div');
    $(this.scrollArrowUp).setStyle({ width: '100%', height: this.settings.scrollWidth+'px'});
    this.scrollArrowUp.design = this.settings.scrollArrowUpDesign;
    if (this.settings.scrollArrowUpDesign.standard)
      this.scrollArrowUp.setStyle(this.settings.scrollArrowUpDesign.standard);
    this.scrollArrowUp.setStyle({ position: 'relative' });
    this.scrollArea.appendChild(this.scrollArrowUp);
    this.scrollArrowUp.height = parseInt(this.scrollArrowUp.getHeight());
    this.scrollArrowUpIdentifier = 'scrollbar-arrow-up'+identifierTime.getTime();
    this.scrollArrowUp.addClassName(this.scrollArrowUpIdentifier);
    this.scrollArrowUp.observe('mousedown', this.mouseDown.bind(this));
    this.scrollArrowUp.observe('mouseout', this.mouseOut.bind(this));
    this.scrollArrowUp.observe('mouseover', this.mouseOver.bind(this));

    // scrollbar
    this.scrollBar = new Element('div');
    $(this.scrollBar).setStyle({ width: '100%' });
    this.scrollBar.design = this.settings.scrollBarDesign;
    if (this.settings.scrollBarDesign.standard)
      this.scrollBar.setStyle(this.settings.scrollBarDesign.standard);
    this.scrollBar.setStyle({ position: 'relative' });
    this.scrollArea.appendChild(this.scrollBar);
    this.scrollBarIdentifier = 'scrollbar'+identifierTime.getTime();
    this.scrollBar.addClassName(this.scrollBarIdentifier);
    this.scrollBar.observe('mousedown', this.mouseDown.bind(this));
    this.scrollBar.observe('mouseout', this.mouseOut.bind(this));
    this.scrollBar.observe('mouseover', this.mouseOver.bind(this));

    // arrow down
    this.scrollArrowDown = new Element('div');
    $(this.scrollArrowDown).setStyle({ width: '100%', height: this.settings.scrollWidth+'px' });
    this.scrollArrowDown.design = this.settings.scrollArrowDownDesign;
    if (this.settings.scrollArrowDownDesign.standard)
      this.scrollArrowDown.setStyle(this.settings.scrollArrowDownDesign.standard);
    this.scrollArrowDown.setStyle({ position: 'relative' });
    this.scrollArea.appendChild(this.scrollArrowDown);
    this.scrollArrowDown.height = parseInt(this.scrollArrowDown.getHeight());
    this.scrollArrowDownIdentifier = 'scrollbar-arrow-down'+identifierTime.getTime();
    this.scrollArrowDown.addClassName(this.scrollArrowDownIdentifier);
    this.scrollArrowDown.observe('mousedown', this.mouseDown.bind(this));
    this.scrollArrowDown.observe('mouseout', this.mouseOut.bind(this));
    this.scrollArrowDown.observe('mouseover', this.mouseOver.bind(this));

    this.setScrollAreaPosition();
  }
}
