/**
 * 
 * Find more about the scrolling function at
 * http://cubiq.org/iscroll
 *
 * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
 * Released under MIT license
 * http://cubiq.org/dropbox/mit-license.txt
 * 
 * Version 3.6 - Last updated: 2010.08.24
 * 
 */

(function(){
function iScroll (el, options) {
    var that = this, i;
    that.element = typeof el == 'object' ? el : document.getElementById(el);
    that.wrapper = that.element.parentNode;

    that.element.style.webkitTransitionProperty = '-webkit-transform';
    that.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
    that.element.style.webkitTransitionDuration = '0';
    that.element.style.webkitTransform = translateOpen + '0,0' + translateClose;

    // Default options
    that.options = {
        bounce: has3d,
        momentum: has3d,
        checkDOMChanges: true,
        topOnDOMChanges: false,
        hScrollbar: has3d,
        vScrollbar: has3d,
        fadeScrollbar: isIphone || isIpad || !isTouch,
        shrinkScrollbar: isIphone || isIpad || !isTouch,
        desktopCompatibility: false,
        overflow: 'hidden',
        snap: false
    };
    
    // User defined options
    if (typeof options == 'object') {
        for (i in options) {
            that.options[i] = options[i];
        }
    }

    if (that.options.desktopCompatibility) {
        that.options.overflow = 'hidden';
    }
    
    that.wrapper.style.overflow = that.options.overflow;
    
    that.refresh();

    window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);

    if (isTouch || that.options.desktopCompatibility) {
        that.element.addEventListener(START_EVENT, that, false);
        that.element.addEventListener(MOVE_EVENT, that, false);
        that.element.addEventListener(END_EVENT, that, false);
    }
    
    if (that.options.checkDOMChanges) {
        that.element.addEventListener('DOMSubtreeModified', that, false);
    }
}

iScroll.prototype = {
    x: 0,
    y: 0,
    enabled: true,

    handleEvent: function (e) {
        var that = this;

        switch (e.type) {
            case START_EVENT:
                that.touchStart(e);
                break;
            case MOVE_EVENT:
                that.touchMove(e);
                break;
            case END_EVENT:
                that.touchEnd(e);
                break;
            case 'webkitTransitionEnd':
                that.transitionEnd();
                break;
            case 'orientationchange':
            case 'resize':
                that.refresh();
                break;
            case 'DOMSubtreeModified':
                that.onDOMModified(e);
                break;
        }
    },
    
    onDOMModified: function (e) {
        var that = this;

        // (Hopefully) execute onDOMModified only once
        if (e.target.parentNode != that.element) {
            return;
        }

        setTimeout(function () { that.refresh(); }, 0);

        if (that.options.topOnDOMChanges && (that.x!=0 || that.y!=0)) {
            that.scrollTo(0,0,'0');
        }
    },

    refresh: function () {
        var that = this,
            resetX = this.x, resetY = this.y,
            snap;
        
        that.scrollWidth = that.wrapper.clientWidth;
        that.scrollHeight = that.wrapper.clientHeight;
        that.scrollerWidth = that.element.offsetWidth;
        that.scrollerHeight = that.element.offsetHeight;
        that.maxScrollX = that.scrollWidth - that.scrollerWidth;
        that.maxScrollY = that.scrollHeight - that.scrollerHeight;
        that.directionX = 0;
        that.directionY = 0;

        if (that.scrollX) {
            if (that.maxScrollX >= 0) {
                resetX = 0;
            } else if (that.x < that.maxScrollX) {
                resetX = that.maxScrollX;
            }
        }
        if (that.scrollY) {
            if (that.maxScrollY >= 0) {
                resetY = 0;
            } else if (that.y < that.maxScrollY) {
                resetY = that.maxScrollY;
            }
        }
        // Snap
        if (that.options.snap) {
            that.maxPageX = -Math.floor(that.maxScrollX/that.scrollWidth);
            that.maxPageY = -Math.floor(that.maxScrollY/that.scrollHeight);

            snap = that.snap(resetX, resetY);
            resetX = snap.x;
            resetY = snap.y;
        }

        if (resetX!=that.x || resetY!=that.y) {
            that.setTransitionTime('0');
            that.setPosition(resetX, resetY, true);
        }
        
        that.scrollX = that.scrollerWidth > that.scrollWidth;
        that.scrollY = !that.scrollX || that.scrollerHeight > that.scrollHeight;

        // Update horizontal scrollbar
        if (that.options.hScrollbar && that.scrollX) {
            that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar);
            that.scrollBarX.init(that.scrollWidth, that.scrollerWidth);
        } else if (that.scrollBarX) {
            that.scrollBarX = that.scrollBarX.remove();
        }

        // Update vertical scrollbar
        if (that.options.vScrollbar && that.scrollY && that.scrollerHeight > that.scrollHeight) {
            that.scrollBarY = that.scrollBarY || new scrollbar('vertical', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar);
            that.scrollBarY.init(that.scrollHeight, that.scrollerHeight);
        } else if (that.scrollBarY) {
            that.scrollBarY = that.scrollBarY.remove();
        }
    },

    setPosition: function (x, y, hideScrollBars) {
        var that = this;
        
        that.x = x;
        that.y = y;

        that.element.style.webkitTransform = translateOpen + that.x + 'px,' + that.y + 'px' + translateClose;

        // Move the scrollbars
        if (!hideScrollBars) {
            if (that.scrollBarX) {
                that.scrollBarX.setPosition(that.x);
            }
            if (that.scrollBarY) {
                that.scrollBarY.setPosition(that.y);
            }
        }
    },
    
    setTransitionTime: function(time) {
        var that = this;
        
        time = time || '0';
        that.element.style.webkitTransitionDuration = time;
        
        if (that.scrollBarX) {
            that.scrollBarX.bar.style.webkitTransitionDuration = time;
            that.scrollBarX.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
        }
        if (that.scrollBarY) {
            that.scrollBarY.bar.style.webkitTransitionDuration = time;
            that.scrollBarY.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
        }
    },
        
    touchStart: function(e) {
        var that = this,
            matrix;

        e.preventDefault();
        e.stopPropagation();
        
        if (!that.enabled) {
            return;
        }

        that.scrolling = true;      // This is probably not needed, but may be useful if iScroll is used in conjuction with other frameworks

        that.moved = false;
        that.dist = 0;

        that.setTransitionTime('0');

        // Check if the scroller is really where it should be
        if (that.options.momentum || that.options.snap) {
            matrix = new WebKitCSSMatrix(window.getComputedStyle(that.element).webkitTransform);
            if (matrix.e != that.x || matrix.f != that.y) {
                document.removeEventListener('webkitTransitionEnd', that, false);
                that.setPosition(matrix.e, matrix.f);
                that.moved = true;
            }
        }

        that.touchStartX = isTouch ? e.changedTouches[0].pageX : e.pageX;
        that.scrollStartX = that.x;

        that.touchStartY = isTouch ? e.changedTouches[0].pageY : e.pageY;
        that.scrollStartY = that.y;

        that.scrollStartTime = e.timeStamp;

        that.directionX = 0;
        that.directionY = 0;
    },
    
    touchMove: function(e) {
        var that = this,
            pageX = isTouch ? e.changedTouches[0].pageX : e.pageX,
            pageY = isTouch ? e.changedTouches[0].pageY : e.pageY,
            leftDelta = that.scrollX ? pageX - that.touchStartX : 0,
            topDelta = that.scrollY ? pageY - that.touchStartY : 0,
            newX = that.x + leftDelta,
            newY = that.y + topDelta;

        if (!that.scrolling) {
            return;
        }

        //e.preventDefault();
        e.stopPropagation();    // Stopping propagation just saves some cpu cycles (I presume)

        that.touchStartX = pageX;
        that.touchStartY = pageY;

        // Slow down if outside of the boundaries
        if (newX >= 0 || newX < that.maxScrollX) {
            newX = that.options.bounce ? Math.round(that.x + leftDelta / 3) : (newX >= 0 || that.maxScrollX>=0) ? 0 : that.maxScrollX;
        }
        if (newY >= 0 || newY < that.maxScrollY) { 
            newY = that.options.bounce ? Math.round(that.y + topDelta / 3) : (newY >= 0 || that.maxScrollY>=0) ? 0 : that.maxScrollY;
        }

        if (that.dist > 5) {            // 5 pixels threshold is needed on Android, but also on iPhone looks more natural
            that.setPosition(newX, newY);
            that.moved = true;
            that.directionX = leftDelta > 0 ? -1 : 1;
            that.directionY = topDelta > 0 ? -1 : 1;
        } else {
            that.dist+= Math.abs(leftDelta) + Math.abs(topDelta);
        }
    },
    
    touchEnd: function(e) {
        var that = this,
            time = e.timeStamp - that.scrollStartTime,
            point = isTouch ? e.changedTouches[0] : e,
            target, ev,
            momentumX, momentumY,
            newDuration = 0,
            newPositionX = that.x, newPositionY = that.y,
            snap;

        if (!that.scrolling) {
            return;
        }
        that.scrolling = false;

        if (!that.moved) {
            that.resetPosition();

            if (isTouch) {
                // Find the last touched element
                target = point.target;
                while (target.nodeType != 1) {
                    target = target.parentNode;
                }

                // Create the fake event
                target.style.pointerEvents = 'auto';
                ev = document.createEvent('MouseEvents');
                ev.initMouseEvent('click', true, true, e.view, 1,
                    point.screenX, point.screenY, point.clientX, point.clientY,
                    e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
                    0, null);
                ev._fake = true;
                target.dispatchEvent(ev);
            }

            return;
        }

        if (!that.options.snap && time > 250) {         // Prevent slingshot effect
            that.resetPosition();
            return;
        }

        if (that.options.momentum) {
            momentumX = that.scrollX === true
                ? that.momentum(that.x - that.scrollStartX,
                                time,
                                that.options.bounce ? -that.x + that.scrollWidth/5 : -that.x,
                                that.options.bounce ? that.x + that.scrollerWidth - that.scrollWidth + that.scrollWidth/5 : that.x + that.scrollerWidth - that.scrollWidth)
                : { dist: 0, time: 0 };

            momentumY = that.scrollY === true
                ? that.momentum(that.y - that.scrollStartY,
                                time,
                                that.options.bounce ? -that.y + that.scrollHeight/5 : -that.y,
                                that.options.bounce ? (that.maxScrollY < 0 ? that.y + that.scrollerHeight - that.scrollHeight : 0) + that.scrollHeight/5 : that.y + that.scrollerHeight - that.scrollHeight)
                : { dist: 0, time: 0 };

            newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1);        // The minimum animation length must be 1ms
            newPositionX = that.x + momentumX.dist;
            newPositionY = that.y + momentumY.dist;
        }

        if (that.options.snap) {
            snap = that.snap(newPositionX, newPositionY);
            newPositionX = snap.x;
            newPositionY = snap.y;
            newDuration = Math.max(snap.time, newDuration);
        }

        that.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
    },

    transitionEnd: function () {
        var that = this;
        document.removeEventListener('webkitTransitionEnd', that, false);
        that.resetPosition();
    },

    resetPosition: function () {
        var that = this,
            resetX = that.x,
            resetY = that.y;

        if (that.x >= 0) {
            resetX = 0;
        } else if (that.x < that.maxScrollX) {
            resetX = that.maxScrollX;
        }

        if (that.y >= 0 || that.maxScrollY > 0) {
            resetY = 0;
        } else if (that.y < that.maxScrollY) {
            resetY = that.maxScrollY;
        }
        
        if (resetX != that.x || resetY != that.y) {
            that.scrollTo(resetX, resetY);
        } else {
            if (that.moved) {
                that.onScrollEnd();     // Execute custom code on scroll end
                that.moved = false;
            }

            // Hide the scrollbars
            if (that.scrollBarX) {
                that.scrollBarX.hide();
            }
            if (that.scrollBarY) {
                that.scrollBarY.hide();
            }
        }
    },
    
    snap: function (x, y) {
        var that = this, time;

        if (that.directionX > 0) {
            x = Math.floor(x/that.scrollWidth);
        } else if (that.directionX < 0) {
            x = Math.ceil(x/that.scrollWidth);
        } else {
            x = Math.round(x/that.scrollWidth);
        }
        that.pageX = -x;
        x = x * that.scrollWidth;
        if (x > 0) {
            x = that.pageX = 0;
        } else if (x < that.maxScrollX) {
            that.pageX = that.maxPageX;
            x = that.maxScrollX;
        }

        if (that.directionY > 0) {
            y = Math.floor(y/that.scrollHeight);
        } else if (that.directionY < 0) {
            y = Math.ceil(y/that.scrollHeight);
        } else {
            y = Math.round(y/that.scrollHeight);
        }
        that.pageY = -y;
        y = y * that.scrollHeight;
        if (y > 0) {
            y = that.pageY = 0;
        } else if (y < that.maxScrollY) {
            that.pageY = that.maxPageY;
            y = that.maxScrollY;
        }

        // Snap with constant speed (proportional duration)
        time = Math.round(Math.max(
                Math.abs(that.x - x) / that.scrollWidth * 500,
                Math.abs(that.y - y) / that.scrollHeight * 500
            ));
            
        return { x: x, y: y, time: time };
    },

    scrollTo: function (destX, destY, runtime) {
        var that = this;

        if (that.x == destX && that.y == destY) {
            that.resetPosition();
            return;
        }

        that.moved = true;
        that.setTransitionTime(runtime || '350ms');
        that.setPosition(destX, destY);

        if (runtime==='0' || runtime=='0s' || runtime=='0ms') {
            that.resetPosition();
        } else {
            document.addEventListener('webkitTransitionEnd', that, false);  // At the end of the transition check if we are still inside of the boundaries
        }
    },
    
    scrollToPage: function (pageX, pageY, runtime) {
        var that = this, snap;

        if (!that.options.snap) {
            that.pageX = -Math.round(that.x / that.scrollWidth);
            that.pageY = -Math.round(that.y / that.scrollHeight);
        }

        if (pageX == 'next') {
            pageX = ++that.pageX;
        } else if (pageX == 'prev') {
            pageX = --that.pageX;
        }

        if (pageY == 'next') {
            pageY = ++that.pageY;
        } else if (pageY == 'prev') {
            pageY = --that.pageY;
        }

        pageX = -pageX*that.scrollWidth;
        pageY = -pageY*that.scrollHeight;

        snap = that.snap(pageX, pageY);
        pageX = snap.x;
        pageY = snap.y;

        that.scrollTo(pageX, pageY, runtime || '500ms');
    },

    scrollToElement: function (el, runtime) {
        el = typeof el == 'object' ? el : this.element.querySelector(el);

        if (!el) {
            return;
        }

        var that = this,
            x = that.scrollX ? -el.offsetLeft : 0,
            y = that.scrollY ? -el.offsetTop : 0;

        if (x >= 0) {
            x = 0;
        } else if (x < that.maxScrollX) {
            x = that.maxScrollX;
        }

        if (y >= 0) {
            y = 0;
        } else if (y < that.maxScrollY) {
            y = that.maxScrollY;
        }

        that.scrollTo(x, y, runtime);
    },

    momentum: function (dist, time, maxDistUpper, maxDistLower) {
        var friction = 2.5,
            deceleration = 1.2,
            speed = Math.abs(dist) / time * 1000,
            newDist = speed * speed / friction / 1000,
            newTime = 0;

        // Proportinally reduce speed if we are outside of the boundaries 
        if (dist > 0 && newDist > maxDistUpper) {
            speed = speed * maxDistUpper / newDist / friction;
            newDist = maxDistUpper;
        } else if (dist < 0 && newDist > maxDistLower) {
            speed = speed * maxDistLower / newDist / friction;
            newDist = maxDistLower;
        }
        
        newDist = newDist * (dist < 0 ? -1 : 1);
        newTime = speed / deceleration;

        return { dist: Math.round(newDist), time: Math.round(newTime) };
    },
    
    onScrollEnd: function () {},
    
    destroy: function (full) {
        var that = this;

        window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);      
        that.element.removeEventListener(START_EVENT, that, false);
        that.element.removeEventListener(MOVE_EVENT, that, false);
        that.element.removeEventListener(END_EVENT, that, false);
        document.removeEventListener('webkitTransitionEnd', that, false);

        if (that.options.checkDOMChanges) {
            that.element.removeEventListener('DOMSubtreeModified', that, false);
        }

        if (that.scrollBarX) {
            that.scrollBarX = that.scrollBarX.remove();
        }

        if (that.scrollBarY) {
            that.scrollBarY = that.scrollBarY.remove();
        }
        
        if (full) {
            that.wrapper.parentNode.removeChild(that.wrapper);
        }
        
        return null;
    }
};

function scrollbar (dir, wrapper, fade, shrink) {
    var that = this, style;
    
    that.dir = dir;
    that.fade = fade;
    that.shrink = shrink;
    that.uid = ++uid;

    // Create main scrollbar
    that.bar = document.createElement('div');

    style = 'position:absolute;top:0;left:0;-webkit-transition-timing-function:cubic-bezier(0,0,0.25,1);pointer-events:none;-webkit-transition-duration:0;-webkit-transition-delay:0;-webkit-transition-property:-webkit-transform;z-index:10;background:rgba(0,0,0,0.5);' +
        '-webkit-transform:' + translateOpen + '0,0' + translateClose + ';' +
        (dir == 'horizontal' ? '-webkit-border-radius:3px 2px;min-width:6px;min-height:5px' : '-webkit-border-radius:2px 3px;min-width:5px;min-height:6px');

    that.bar.setAttribute('style', style);

    // Create scrollbar wrapper
    that.wrapper = document.createElement('div');
    style = '-webkit-mask:-webkit-canvas(scrollbar' + that.uid + that.dir + ');position:absolute;z-index:10;pointer-events:none;overflow:hidden;opacity:0;-webkit-transition-duration:' + (fade ? '300ms' : '0') + ';-webkit-transition-delay:0;-webkit-transition-property:opacity;' +
        (that.dir == 'horizontal' ? 'bottom:2px;left:2px;right:7px;height:5px' : 'top:2px;right:2px;bottom:7px;width:5px;');
    that.wrapper.setAttribute('style', style);

    // Add scrollbar to the DOM
    that.wrapper.appendChild(that.bar);
    wrapper.appendChild(that.wrapper);
}

scrollbar.prototype = {
    init: function (scroll, size) {
        var that = this,
            ctx;

        // Create scrollbar mask
        if (that.dir == 'horizontal') {
            if (that.maxSize != that.wrapper.offsetWidth) {
                that.maxSize = that.wrapper.offsetWidth;
                ctx = document.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, that.maxSize, 5);
                ctx.fillStyle = "rgb(0,0,0)";
                ctx.beginPath();
                ctx.arc(2.5, 2.5, 2.5, Math.PI/2, -Math.PI/2, false);
                ctx.lineTo(that.maxSize-2.5, 0);
                ctx.arc(that.maxSize-2.5, 2.5, 2.5, -Math.PI/2, Math.PI/2, false);
                ctx.closePath();
                ctx.fill();
            }
        } else {
            if (that.maxSize != that.wrapper.offsetHeight) {
                that.maxSize = that.wrapper.offsetHeight;
                ctx = document.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, 5, that.maxSize);
                ctx.fillStyle = "rgb(0,0,0)";
                ctx.beginPath();
                ctx.arc(2.5, 2.5, 2.5, Math.PI, 0, false);
                ctx.lineTo(5, that.maxSize-2.5);
                ctx.arc(2.5, that.maxSize-2.5, 2.5, 0, Math.PI, false);
                ctx.closePath();
                ctx.fill();
            }
        }

        that.size = Math.max(Math.round(that.maxSize * that.maxSize / size), 6);
        that.maxScroll = that.maxSize - that.size;
        that.toWrapperProp = that.maxScroll / (scroll - size);
        that.bar.style[that.dir == 'horizontal' ? 'width' : 'height'] = that.size + 'px';
    },
    
    setPosition: function (pos) {
        var that = this;
        
        if (that.wrapper.style.opacity != '1') {
            that.show();
        }

        pos = Math.round(that.toWrapperProp * pos);

        if (pos < 0) {
            pos = that.shrink ? pos + pos*3 : 0;
            if (that.size + pos < 7) {
                pos = -that.size + 6;
            }
        } else if (pos > that.maxScroll) {
            pos = that.shrink ? pos + (pos-that.maxScroll)*3 : that.maxScroll;
            if (that.size + that.maxScroll - pos < 7) {
                pos = that.size + that.maxScroll - 6;
            }
        }

        pos = that.dir == 'horizontal'
            ? translateOpen + pos + 'px,0' + translateClose
            : translateOpen + '0,' + pos + 'px' + translateClose;

        that.bar.style.webkitTransform = pos;
    },

    show: function () {
        if (has3d) {
            this.wrapper.style.webkitTransitionDelay = '0';
        }
        this.wrapper.style.opacity = '1';
    },

    hide: function () {
        if (has3d) {
            this.wrapper.style.webkitTransitionDelay = '350ms';
        }
        this.wrapper.style.opacity = '0';
    },
    
    remove: function () {
        this.wrapper.parentNode.removeChild(this.wrapper);
        return null;
    }
};

// Is translate3d compatible?
var has3d = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),
    // Device sniffing
    isIphone = (/iphone/gi).test(navigator.appVersion),
    isIpad = (/ipad/gi).test(navigator.appVersion),
    isAndroid = (/android/gi).test(navigator.appVersion),
    isTouch = isIphone || isIpad || isAndroid,
    // Event sniffing
    START_EVENT = isTouch ? 'touchstart' : 'mousedown',
    MOVE_EVENT = isTouch ? 'touchmove' : 'mousemove',
    END_EVENT = isTouch ? 'touchend' : 'mouseup',
    // Translate3d helper
    translateOpen = 'translate' + (has3d ? '3d(' : '('),
    translateClose = has3d ? ',0)' : ')',
    // Unique ID
    uid = 0;

// Expose iScroll to the world
window.iScroll = iScroll;
})();

/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/core
 * @require prototype.js
 */

if(typeof(Control) == 'undefined')
    Control = {};
    
var $proc = function(proc){
    return typeof(proc) == 'function' ? proc : function(){return proc};
};

var $value = function(value){
    return typeof(value) == 'function' ? value() : value;
};

Object.Event = {
    extend: function(object){
        object._objectEventSetup = function(event_name){
            this._observers = this._observers || {};
            this._observers[event_name] = this._observers[event_name] || [];
        };
        object.observe = function(event_name,observer){
            if(typeof(event_name) == 'string' && typeof(observer) != 'undefined'){
                this._objectEventSetup(event_name);
                if(!this._observers[event_name].include(observer))
                    this._observers[event_name].push(observer);
            }else
                for(var e in event_name)
                    this.observe(e,event_name[e]);
        };
        object.stopObserving = function(event_name,observer){
            this._objectEventSetup(event_name);
            if(event_name && observer)
                this._observers[event_name] = this._observers[event_name].without(observer);
            else if(event_name)
                this._observers[event_name] = [];
            else
                this._observers = {};
        };
        object.observeOnce = function(event_name,outer_observer){
            var inner_observer = function(){
                outer_observer.apply(this,arguments);
                this.stopObserving(event_name,inner_observer);
            }.bind(this);
            this._objectEventSetup(event_name);
            this._observers[event_name].push(inner_observer);
        };
        object.notify = function(event_name){
            this._objectEventSetup(event_name);
            var collected_return_values = [];
            var args = $A(arguments).slice(1);
            try{
                for(var i = 0; i < this._observers[event_name].length; ++i)
                    collected_return_values.push(this._observers[event_name][i].apply(this._observers[event_name][i],args) || null);
            }catch(e){
                if(e == $break)
                    return false;
                else
                    throw e;
            }
            return collected_return_values;
        };
        if(object.prototype){
            object.prototype._objectEventSetup = object._objectEventSetup;
            object.prototype.observe = object.observe;
            object.prototype.stopObserving = object.stopObserving;
            object.prototype.observeOnce = object.observeOnce;
            object.prototype.notify = function(event_name){
                if(object.notify){
                    var args = $A(arguments).slice(1);
                    args.unshift(this);
                    args.unshift(event_name);
                    object.notify.apply(object,args);
                }
                this._objectEventSetup(event_name);
                var args = $A(arguments).slice(1);
                var collected_return_values = [];
                try{
                    if(this.options && this.options[event_name] && typeof(this.options[event_name]) == 'function')
                        collected_return_values.push(this.options[event_name].apply(this,args) || null);
                    var callbacks_copy = this._observers[event_name]; // since original array will be modified after observeOnce calls
                    for(var i = 0; i < callbacks_copy.length; ++i)
                        collected_return_values.push(callbacks_copy[i].apply(callbacks_copy[i],args) || null);
                }catch(e){
                    if(e == $break)
                        return false;
                    else
                        throw e;
                }
                return collected_return_values;
            };
        }
    }
};

/* Begin Core Extensions */

//Element.observeOnce
Element.addMethods({
    observeOnce: function(element,event_name,outer_callback){
        var inner_callback = function(){
            outer_callback.apply(this,arguments);
            Element.stopObserving(element,event_name,inner_callback);
        };
        Element.observe(element,event_name,inner_callback);
    }
});

//mouse:wheel
(function(){
    function wheel(event){
        var delta, element, custom_event;
        // normalize the delta
        if (event.wheelDelta) { // IE & Opera
            delta = event.wheelDelta / 120;
        } else if (event.detail) { // W3C
            delta =- event.detail / 3;
        }
        if (!delta) { return; }
        element = Event.extend(event).target;
        element = Element.extend(element.nodeType === Node.TEXT_NODE ? element.parentNode : element);
        custom_event = element.fire('mouse:wheel',{ delta: delta });
        if (custom_event.stopped) {
            Event.stop(event);
            return false;
        }
    }
    document.observe('mousewheel',wheel);
    document.observe('DOMMouseScroll',wheel);
})();

/* End Core Extensions */

//from PrototypeUI
var IframeShim = Class.create({
    initialize: function() {
        this.element = new Element('iframe',{
            style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none',
            src: 'javascript:void(0);',
            frameborder: 0 
        });
        $(document.body).insert(this.element);
    },
    hide: function() {
        this.element.hide();
        return this;
    },
    show: function() {
        this.element.show();
        return this;
    },
    positionUnder: function(element) {
        var element = $(element);
        var offset = element.cumulativeOffset();
        var dimensions = element.getDimensions();
        this.element.setStyle({
            left: offset[0] + 'px',
            top: offset[1] + 'px',
            width: dimensions.width + 'px',
            height: dimensions.height + 'px',
            zIndex: element.getStyle('zIndex') - 1
        }).show();
        return this;
    },
    setBounds: function(bounds) {
        for(prop in bounds)
            bounds[prop] += 'px';
        this.element.setStyle(bounds);
        return this;
    },
    destroy: function() {
        if(this.element)
            this.element.remove();
        return this;
    }
});


/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/control/scrollbar
 * @require prototype.js, slider.js, livepipe.js
 */

if(typeof(Prototype) == "undefined")
    throw "Control.ScrollBar requires Prototype to be loaded.";
if(typeof(Control.Slider) == "undefined")
    throw "Control.ScrollBar requires Control.Slider to be loaded.";
if(typeof(Object.Event) == "undefined")
    throw "Control.ScrollBar requires Object.Event to be loaded.";

Control.ScrollBar = Class.create({
    initialize: function(container,track,options){
        Control.ScrollBar.instances.push(this);
        this.enabled = false;
        this.notificationTimeout = false;
        this.container = $(container);
        this.boundMouseWheelEvent = this.onMouseWheel.bindAsEventListener(this);
        this.boundResizeObserver = this.onWindowResize.bind(this);
        this.track = $(track);
        this.handle = this.track.firstDescendant();
        this.options = Object.extend({
            active_class_name: 'scrolling',
            apply_active_class_name_to: this.container,
            notification_timeout_length: 125,
            handle_minimum_height: 25,
            scroll_to_smoothing: 0.01,
            scroll_to_steps: 15,
            proportional: true,
            custom_event: null,
            custom_event_handler: null,
            slider_options: {}
        },options || {});
        this.slider = new Control.Slider(this.handle,this.track,Object.extend({
            axis: 'vertical',
            onSlide: this.onChange.bind(this),
            onChange: this.onChange.bind(this)
        },this.options.slider_options));
        this.recalculateLayout();
        Event.observe(window,'resize',this.boundResizeObserver);
        if (this.options.custom_event) {
            if (Object.isFunction(this.options.custom_event_handler)) {
                this.container.observe(this.options.custom_event, this.options.custom_event_handler);
            } else {
                this.container.observe(this.options.custom_event, this.boundResizeObserver);
            }
        }
        this.handle.observe('mousedown',function(){
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
        }.bind(this));
    },
    destroy: function(){
        Event.stopObserving(window,'resize',this.boundResizeObserver);
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        if (this.options.custom_event) {
            this.container.stopObserving(this.options.custom_event);
        }
    },
    enable: function(){
        this.enabled = true;
        this.container.observe('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setEnabled();
        this.track.show();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).addClassName(this.options.active_class_name);
        this.notify('enabled');
    },
    disable: function(){
        this.enabled = false;
        this.container.stopObserving('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setDisabled();
        this.track.hide();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        this.notify('disabled');
        this.reset();
    },
    reset: function(){
        this.slider.setValue(0);
    },
    recalculateLayout: function(){
        if(this.container.scrollHeight <= this.container.offsetHeight)
            this.disable();
        else{
            this.enable();
            this.slider.trackLength = this.slider.maximumOffset() - this.slider.minimumOffset();
            if(this.options.proportional){
                this.handle.style.height = Math.max(this.container.offsetHeight * (this.container.offsetHeight / this.container.scrollHeight),this.options.handle_minimum_height) + 'px';
                this.slider.handleLength = this.handle.style.height.replace(/px/,'');
            }
        }
    },
    onWindowResize: function(){
        this.recalculateLayout();
        this.scrollBy(0);
    },
    onMouseWheel: function(event){
        if(this.auto_sliding_executer)
            this.auto_sliding_executer.stop();
        this.slider.setValueBy(-(event.memo.delta / 20)); //put in math to account for the window height
        event.stop();
        return false;
    },
    onChange: function(value){
        this.container.scrollTop = Math.round(value / this.slider.maximum * (this.container.scrollHeight - this.container.offsetHeight));
        if(this.notification_timeout)
            window.clearTimeout(this.notificationTimeout);
        this.notificationTimeout = window.setTimeout(function(){
            this.notify('change',value);
        }.bind(this),this.options.notification_timeout_length);
    },
    getCurrentMaximumDelta: function(){
        return this.slider.maximum * (this.container.scrollHeight - this.container.offsetHeight);
    },
    getDeltaToElement: function(element){
        return this.slider.maximum * ((element.positionedOffset().top + (element.getHeight() / 2)) - (this.container.getHeight() / 2));
    },
    scrollTo: function(y,animate){
        var current_maximum_delta = this.getCurrentMaximumDelta();
        if(y == 'top')
            y = 0;
        else if(y == 'bottom')
            y = current_maximum_delta;
        else if(typeof(y) != "number")
            y = this.getDeltaToElement($(y));
        if(this.enabled){
            y = Math.max(0,Math.min(y,current_maximum_delta));
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
            var target_value = y / current_maximum_delta;
            var original_slider_value = this.slider.value;
            var delta = (target_value - original_slider_value) * current_maximum_delta;
            if(animate){
                this.auto_sliding_executer = new PeriodicalExecuter(function(){
                    if(Math.round(this.slider.value * 100) / 100 < Math.round(target_value * 100) / 100 || Math.round(this.slider.value * 100) / 100 > Math.round(target_value * 100) / 100){
                        this.scrollBy(delta / this.options.scroll_to_steps);
                    }else{
                        this.auto_sliding_executer.stop();
                        this.auto_sliding_executer = null;
                        if(typeof(animate) == "function")
                            animate();
                    }            
                }.bind(this),this.options.scroll_to_smoothing);
            }else
                this.scrollBy(delta);
        }else if(typeof(animate) == "function")
            animate();
    },
    scrollBy: function(y){
        if(!this.enabled)
            return false;
        this.slider.setValueBy(y / (this.getCurrentMaximumDelta() == 0 ? 1 : this.getCurrentMaximumDelta()) );
    }
});
Object.extend(Control.ScrollBar,
{
    instances: [],

    findByElementId: function(id)
    {
        return Control.ScrollBar.instances.find(function(instance)
        {
            return (instance.container.id && instance.container.id == id);
        });
    }
});
Object.Event.extend(Control.ScrollBar);

