/**
* @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_length: 25,
            scroll_to_smoothing: 0.01,
            scroll_to_steps: 15,
            scroll_to_precision: 100,
            proportional: true,
            custom_event: null,
            custom_event_handler: null,
            scroll_axis: 'vertical',
            slider_options: {}
        },options || {});
        this.slider = new Control.Slider(this.handle,this.track,Object.extend({
            axis: this.options.scroll_axis,
            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));
        
        this.addEventListener($("right"),'touchstart', this.touchHandler, this);
		this.addEventListener($("right"),'touchmove', this.touchHandler, this);
		this.addEventListener($("right"),'touchend', this.touchHandler, this);
		this.addEventListener($("right"),'touchcancel', this.touchHandler, this);

    },
    
    addEventListener: function(element, eventName, eventHandler, scope) {
		var scopedEventHandler = scope ? function(e) {
			eventHandler.apply(scope, [e]);
		} : eventHandler;
		if(document.addEventListener)
			element.addEventListener(eventName, scopedEventHandler, false);
		else if(document.attachEvent)
			element.attachEvent("on"+eventName, scopedEventHandler);
	},

	touchHandler: function(event) {
		var touches = event.changedTouches,
		first = touches[0],
		type = "";

		switch(event.type) {
			case "touchstart":
				type = "mousedown";
				break;
			case "touchmove":
				type="mousemove";
				break;
			case "touchend":
				type="mouseup";
				break;
			default:
				return;
		}

		//initMouseEvent(type, canBubble, cancelable, view, clickCount,
		//           screenX, screenY, clientX, clientY, ctrlKey,
		//           altKey, shiftKey, metaKey, button, relatedTarget);

		var simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent(type, true, true, window, 1,
		first.screenX, first.screenY,
		first.clientX, first.clientY, false,
		false, false, false, 0/*left*/, null);

		first.target.dispatchEvent(simulatedEvent);
		event.preventDefault();
	},

    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);
        }
    },
    scrollLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.scrollHeight : this.container.scrollWidth;
    },
    offsetLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.offsetHeight : this.container.offsetWidth;
    },
    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.scrollLength() <= this.offsetLength())
            this.disable();
        else{
            this.enable();
            this.slider.trackLength = this.slider.maximumOffset() - this.slider.minimumOffset();
            if(this.options.proportional){
                this.slider.handleLength = Math.max(this.offsetLength() * (this.offsetLength() / this.scrollLength()),this.options.handle_minimum_length);
                if (this.options.scroll_axis == 'vertical')
                    this.handle.style.height = this.slider.handleLength + 'px';
                else
                    this.handle.style.width = this.slider.handleLength + 'px';
            }
            this.scrollBy(0);
        }
    },
    onWindowResize: function(){
        this.recalculateLayout();
        this.scrollBy(0);
    },
    onMouseWheel: function(event){
        if(this.auto_sliding_executer)
            this.auto_sliding_executer.stop();
        // Move the content by 30 pixels each wheel event
        this.slider.setValueBy(-(30 * event.memo.delta / (this.scrollLength()-this.slider.trackLength)));
        event.stop();
        return false;
    },
    onChange: function(value){
        var scroll_pos = Math.round(value / this.slider.maximum * (this.scrollLength() - this.offsetLength()));
        if (this.options.scroll_axis == 'vertical')
            this.container.scrollTop = scroll_pos;
        else
            this.container.scrollLeft = scroll_pos;
        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.scrollLength() - this.offsetLength());
    },
    getContainerOffset: function(element) {
        var offset = element.positionedOffset();
        while (element.getOffsetParent() != this.container)
        {
            element = element.getOffsetParent();
            offset[0] += element.positionedOffset()[0];
            offset[1] += element.positionedOffset()[1];
            offset.top += element.positionedOffset().top;
            offset.left += element.positionedOffset().left;
        }
        return offset;
    },
    getDeltaToElement: function(element){

        if (this.options.scroll_axis == 'vertical')
            return this.slider.maximum * ((this.getContainerOffset(element).top + (element.getHeight() / 2)) - (this.container.getHeight() / 2));
        else
            return this.slider.maximum * ((this.getContainerOffset(element).left + (element.getWidth() / 2)) - (this.container.getWidth() / 2));
    },
    scrollTo: function(y,animate){
        var precision = this.options.scroll_to_precision,
            current_maximum_delta = this.getCurrentMaximumDelta();
        if (precision == 'auto')
            precision = Math.pow(10, Math.ceil(Math.log(current_maximum_delta)/Math.log(10)));
        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 * precision) / precision < Math.round(target_value * precision) / precision || Math.round(this.slider.value * precision) / precision > Math.round(target_value * precision) / precision){
                        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);



