(function(global) {
  var TIMEOUT_MS = 10,
    DECAY = 0.93,
    MOUSE_DOWN_DECAY = 0.5,
    SPEED_SPRINGNESS = 0.4,
    BOUNCING_SPRINGESS = 0.08,
    HIDE_SCROLLBAR_MS = 1000;
  
  var s150 = global.s150 || (global.s150 = {}), $ = global.jQuery;

  function getScrollOffset(win) {
    var scrOfX = 0, scrOfY = 0;
    if (typeof(win.pageYOffset) == 'number') {
      scrOfX = win.pageXOffset;
      scrOfY = win.pageYOffset;            
    } else if (win.document.body && (win.document.body.scrollLeft || win.document.body.scrollTop)) {
      scrOfX = win.document.body.scrollLeft;
      scrOfY = win.document.body.scrollTop;
    } else if (win.document.documentElement && (win.document.documentElement.scrollLeft || win.document.documentElement.scrollTop)) {
      scrOfX = win.document.documentElement.scrollLeft;
      scrOfY = win.document.documentElement.scrollTop;
    }
    return { left: scrOfX, top: scrOfY }
  }        
  
  function addCssRules(win, rules) {
    var stylesheet, head = win.document.getElementsByTagName('head')[0];

    stylesheet = win.document.createElement('style');
    stylesheet.type = 'text/css';
    head.appendChild(stylesheet);
    stylesheet = stylesheet.styleSheet || stylesheet.sheet;
    
    for (var selector in rules) {
      if (typeof stylesheet.insertRule != 'undefined') {
        stylesheet.insertRule(selector + ' { ' + rules[selector] + ' } ', stylesheet.cssRules.length);
      } else {
        stylesheet.addRule(selector, rules[selector], -1);
      }        
    }
    
    return win;
  }
  
  function getScrollHeight(win) {
    return Math.max(win.document.body.scrollHeight, win.document.documentElement.scrollHeight);
  }
  
  function getScrollTop(win) {
    return Math.max(win.document.body.scrollTop, win.document.documentElement.scrollTop);
  }
  
  function handleMouseWheelScroll(e){
    var delta = 0, scrollHeight = getScrollHeight(this.iframe);
    
    if (!e) { /* For IE. */
      e = this.iframe.event;
    }
    
    if (e.preventDefault) {
      e.preventDefault();
    }
    
    e.returnValue = false;
    
    // we need to control how many times this event is fired. in safari, it seems
    // to be fired every 1ms or less which is way to fast.
    if (this.lastScrollTime && (+new Date - this.lastScrollTime) < 50) {
      return false
    }
            
    if (e.wheelDelta) {
      delta = e.wheelDelta / 120;
      
      if (this.iframe.opera) {
        delta = -delta;
      }
    } else if (e.detail) {
      delta = -e.detail / 3;
    }
    
    // we also need to control the delta to make sure it doesn't get too much.
    delta = delta > 5 ? 5 : delta < -5 ? -5 : delta;
    
    this.lastScrollTop += (delta * -100);
    
    // check to make sure we don't scroll past the max or less than the top
    if (this.lastScrollTop < 0) {
      this.lastScrollTop = 0;
    } else if (this.lastScrollTop > scrollHeight) {
      this.lastScrollTop = scrollHeight;
    }
    
    this.iframe.scrollTo(0, this.lastScrollTop);
    redrawScrollbar.call(this);
    
    if (!this.lastScrollTime) {
      showScrollbar.call(this);
      this.timerId = global.setTimeout($.proxy(checkScrollingState, this), 500);        
    }
    
    this.lastScrollTime = +new Date;
  }  
  
  function checkScrollingState() {
    if (this.timerId) {
      global.clearTimeout(this.timerId);
      this.timerId = null;
    }
    
    if ((+new Date - this.lastScrollTime) > HIDE_SCROLLBAR_MS) {
      this.lastScrollTime = null;
      hideScrollbar.call(this);
    } else {
      this.timerId = global.setTimeout($.proxy(checkScrollingState, this), 500);
    }
  }
  
  function setupMouseWheelSupport() {
    if (this.iframe.addEventListener) {
      this.iframe.addEventListener('DOMMouseScroll', $.proxy(handleMouseWheelScroll, this), false);
    }
    
    this.iframe.onmousewheel = this.iframe.document.onmousewheel = $.proxy(handleMouseWheelScroll, this);    
  }  
  
  function redrawScrollbar() {
    var scrollHeight = getScrollHeight(this.iframe), 
      scrollTop = this.lastScrollTop, 
      iframeHeight = this.iframeElement.height(),
      scrollBarHeight = 0, scrollBarTop = 0, maxBarTop = iframeHeight;

    // update scrollbar height based on scroll height and iframe height
    this.scrollbar.css('height', (scrollBarHeight = (iframeHeight / scrollHeight) * iframeHeight) + 'px');
       
    maxBarTop -= scrollBarHeight;
    
    // update scrollbar position based on scroll top and scroll height
    scrollBarTop = ((scrollTop / scrollHeight) * iframeHeight);
    if (scrollBarTop < 0) {
      scrollBarTop = 0;
    } else if (scrollBarTop > maxBarTop) {
      scrollBarTop = maxBarTop;
    }
    
    this.scrollbar.css('top', scrollBarTop + 'px');
  }
  
  function showScrollbar() {
    this.scrollbar.animate({ 'opacity': '0.5' });    
  }
  
  function hideScrollbar() {
    if (this.timerId) {
      global.clearTimeout(this.timerId);
      this.timerId = null;
    }
    
    this.scrollbar.animate({ 'opacity': '0' });
  }
  
  function accelerateScrolling() {
    if (this.timerId) {
      global.clearTimeout(this.timerId);
      this.timerId = null;
    }
    
    if (this.isMouseDown) {
      this.velocity *= MOUSE_DOWN_DECAY;
    } else {
      this.velocity *= DECAY;

      if (this.velocity > 5 && this.velocity < -5) {
        this.iframe.scrollTo(0, (this.lastScrollTop += this.velocity));
      }
    }
              
    this.timerId = global.setTimeout($.proxy(accelerateScrolling, this), TIMEOUT_MS);
  }
  
  function setupIframe() {
    var head = this.iframe.document.getElementsByTagName('head')[0],
      body = $(this.iframe.document.body);
    
    addCssRules(this.iframe, { 
      'body': 'overflow: hidden',
      'body *': '-moz-user-select: none; -webkit-user-select: none; user-select: none;' 
    });
   
    // prevent text selection
    body.bind('selectstart', function() {
      return false;
    });
            
    // prevent object dragging
    body.bind('dragstart', function() {
      return false;
    });
    
    setupMouseWheelSupport.call(this);
    //timerId = global.setTimeout(accelerateScrolling, TIMEOUT_MS);            
  }

  function ScrollFrame(name, id) {
    if (!id) {
      id = name;
    }
    
    this.name = name;
    this.id = id;

    this.iframe = global.frames[name];
    this.iframeElement = $('#' + id);
  
    this.iframeElement.after((this.scrollbar = $('<div class="scrollbar"></div>')));
  
    this.isMouseDown = false;
    this.mouseDownClientY = 0;
    this.lastClientY = 0;
    this.mouseDownScrollTop = 0;
    this.lastScrollTop = 0;
    this.iframeOffsetTop = 0; 
    this.windowScrollTop = 0;
    this.timerId;
    this.velocity = 0;    
    
    $(this.iframeElement).bind('load', $.proxy(function(e) {
      this.iframe = e.target.contentWindow;
      setupIframe.call(this);
    }, this));
    
  }
  
  s150.ScrollFrame = ScrollFrame;
})(this);
