/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 */

/**
 * scroller.js
 * 
 * Based on DDJS.Effects.Scroller from the DDJS library by Edward Vermillion
 *
 * @author Edward Vermillion <evermillion@doggydoo.net>
 */

/**
 * @constructor Scroller
 *
 * @param {HTMLElement} outer
 * @param {HTMLElement} inner
 * @param {String} objName
 * @return {Object}
 */
var Scroller = function (outer, inner, objName) {

    this._outer = outer;

    this._inner = inner;
    
    this._objName = objName;

	this._innerStyle = window.getComputedStyle(this._inner,'');

	if (this._innerStyle.fontSize.replace(/(?:px|pt)/, '') != 0) {
		this._defaultGain = Math.round(this._innerStyle.fontSize.replace(/(?:px|pt)/, '')/10);
	}
	this._gain = this._defaultGain

	this._vertScrollDistance = this._outer.scrollHeight - this._outer.offsetHeight;
	if (this._vertScrollDistance < 0) {
		this._vertScrollDistance = 0;
	}
	
	this._horzScrollDistance = this._outer.scrollWidth - this._outer.offsetWidth;
	if (this._horzScrollDistance < 0) {
		this._horzScrollDistance = 0;
	}

}; // End Scroller


/**
 * @object Scroller.prototype
 *
 * @prototype
 */
Scroller.prototype = {
    
    /**
     * @property _outer
     *
     * The outer container for the scroller object
     *
     * @type {HTMLDivElement}
     */
    _outer: null,

    /**
     * @property _inner
     *
     * The inner scroller object
     *
     * @type {HTMLDivElement}
     */
    _inner: null,
    
    /**
     * @property _objName
     * 
     * The name of the object created from this class
     * 
     * Used in the setTimeout() call
     * 
     * @Type {String}
     */
    _objName: '',

    /**
     * @property _innerStyle
     *
     * The computed style object for the scrolled div
     *
     * @type {CSSStyleDeclaration}
     */
    _innerStyle: null,

    /**
     * @method _state
     *
     * The 'state' of the scroller object, one of 'start', 'end', 'scrolling' or
     * 'halted'
     *
     * @type {String}
     */
    _state: 'start',

    /**
     * @property _vertScrollDistance
     *
     * The vertical distance that the scrolled object needs to be scrolled
     *
     * @type {Number}
     */
    _vertScrollDistance: null,
    
    /**
     * @property _horzScrollDistance
     *
     * The horizontal distance that the scrolled object needs to be scrolled
     *
     * @type {Number}
     */
    _horzScrollDistance: null,

    /**
     * @property _defaultGain
     *
     * The default setting for the gain. 
     *
     * @type {Number}
     */
    _defaultGain: 2,

    /**
     * @property _gain
     *
     * The distance, in pixels, moved on each iteration
     *
     * @type {Number}
     */
    _gain: null,

    /**
     * @property _defaultFrequency
     *
     * The default setting for the frequency
     *
     * @type {Number}
     */
    _defaultFrequency: 10,

    /**
     * @property _frequency
     *
     * The time, in milliseconds, between iterations
     *
     * @type {Number}
     */
    _frequency: 10,

    /**
     * @property _scrollCount
     *
     * The number of times that _doScroll() has been called
     *
     * @type {Number}
     */
    _scrollCount: 0,

    /**
     * @property _scrollAccel
     *
     * The number of times that _doScroll() is called before starting the
     * acceleration
     *
     * @type {Number}
     */
    _scrollAccel: 100,

    /**
     * @property _timerID
     *
     * The setTimeout() id
     *
     * @type {Resource}
     */
    _timerID: null,

    /**
     * @property _left
     *
     * The 'left' position of the scrolled element in pixels
     *
     * @type {Number}
     */
    _left: 0,

    /**
     * @property _top
     *
     * The 'top' position of the scrolled element in pixels
     *
     * @type {Number}
     */
    _top: 0,
    
    /**
     * @method reinit
     * 
     * Re-inbitializes the scroller object to recalculate scroll distances
     * 
     * Used when an element that doesen't have layout is given layout, such as 
     * when the display of the outer element is changed from 'none' to 'block'.
     * 
     * @return {void}
     */
    reinit: function () {
        
        this._innerStyle = window.getComputedStyle(this._inner,'');

        if (this._innerStyle.fontSize.replace(/(?:px|pt)/, '') != 0) {
            this._defaultGain = Math.round(this._innerStyle.fontSize.replace(/(?:px|pt)/, '')/10);
        }
        this._gain = this._defaultGain
    
        this._vertScrollDistance = this._outer.scrollHeight - this._outer.offsetHeight;
        if (this._vertScrollDistance < 0) {
            this._vertScrollDistance = 0;
        }
        
        this._horzScrollDistance = this._outer.scrollWidth - this._outer.offsetWidth;
        if (this._horzScrollDistance < 0) {
            this._horzScrollDistance = 0;
        }
    },
    
    mouseScroll: function () {
        
        
    },
    
    /**
     * @method scrollToElement
     * 
     * Scrolls to a contained elements position, or the end of the scrolled
     * area. Similar to the scrollIntoView() javascript function.
     * 
     * @param {HTMLElement} elem
     * 
     * @return {void}
     */
    scrollToElement: function (elem) {
        
        if (elem == null) {
            return;
        }
        
        if (elem.parentNode == this._inner) {
        
            var elTop = elem.offsetTop;
            var elLeft = elem.offsetLeft;
            
            if (this._vertScrollDistance) {
            
                if (elTop > 0 && elTop < this._vertScrollDistance) {
                
                    this._top = -elTop;
                    
                } else if (elTop >= this._vertScrollDistance) {
                
                    this._top = -this._vertScrollDistance;
                }
                
            } else if (this._horzScrollDistance) {
            
                if (elLeft > 0 && elLeft < this._horzScrollDistance) {
                
                    this._left = -elLeft;
                    
                } else if (elLeft > this._horzScrollDistance) {
                
                    this._left = -this._horzScrollDistance;
                }
            }
            
            this._doScroll();
        }
    },

    /**
     * @method showScrollers
     *
     * Determine if the scroller arrows are needed. If the browser reports an
     * empty scroll distance we return false
     *
     * @return {Boolean}
     */
    showScrollers: function () {

        if (this._vertScrollDistance == 0 && this._horzScrollDistance == 0) {
            return false;
        }
        return true;
    },

    /**
     * @method setScrollerAccel
     *
     * Sets the scroller acceleration
     *
     * @param {Number} val
     * @return void
     */
    setScrollerAccel: function (val) {
        this._scrollAccel = Math.abs(val);
    },
    
    /**
     * @method setDefaultGain
     * 
     * Sets the default gain for the scroller object
     * 
     * @param {Integer} num     The value to set _defaultGain to
     * @param {Boolean} setGain If true will also set _gain
     */
    setDefaultGain: function (num, setGain) {
    	if (num != null) {
    		this._defaultGain = num;
    		if (setGain) {
    			this._gain = num;
    		}
    	}
    },

    /**
     * @method getState
     *
     * Returns the 'state' of the scroller object, one of 'start', 'end',
     * 'scrolling' or 'halted'
     *
     * @return String
     */
    getState: function () {
        return this._state;
    },

    /**
     * @method scroll
     *
     * Scrolls the element
     *
     * @param {String} direction
     * @return void
     */
    scroll: function (direction) {

        var callString = this._objName + ".scroll('" + direction + "');";

        if (this._timerID != null) {
            window.clearTimeout(this._timerID);
            this._timerID = null;
        }

        if (direction == 'start' ) {

            if (this._vertScrollDistance) {
                if (this._top < 0) {
                    this._top = this._top + this._gain;
                    if (this._top > 0) {
                        this._top = 0;
                    }

                } else {
                    this._state = 'start';
                }

            } else if (this._horzScrollDistance) {
                if (this._left < 0) {
                    this._left = this._left + this._gain;
                    if (this._left > 0) {
                        this._left = 0;
                    }

                } else {
                    this._state = 'start';
                }
            }


        } else if (direction == 'end') {

            if (this._vertScrollDistance) {

                if (Math.abs(this._top) < this._vertScrollDistance) {
                    this._top = this._top - this._gain;
                    if (Math.abs(this._top) > this._vertScrollDistance) {
                        this._top = -this._vertScrollDistance;
                    }

                } else {
                    this._state = 'end';
                }

            } else if (this._horzScrollDistance) {

                if (Math.abs(this._left) < this._horzScrollDistance) {
                    this._left = this._left - this._gain;
                    if (Math.abs(this._left) > this._horzScrollDistance) {
                        this._left = -this._horzScrollDistance;
                    }

                } else {
                    this._state = 'end';
                }
            }
        }

        if ((direction == 'start' && this._state != 'start') ||
            (direction == 'end' && this._state != 'end')) {

            this._doScroll();

            if (this._scrollCount % this._scrollAccel == 0) {
                this._gain = Math.round(this._gain + 2);
            }

            this._timerID = window.setTimeout(callString, this._frequency);

        } else {

            window.clearTimeout(this._timerID);
            this._timerID = null;
        }

    },

    /**
     * @method halt
     *
     * Stops the scrolling action
     *
     * @return void
     */
    halt: function () {
        window.clearTimeout(this._timerID);
        this._timerID = null;
        this._scrollCount = 0;
        this._frequency = this._defaultFrequency;
        this._gain = this._defaultGain;
        this._state = 'halted';
    },

    /**
     * @method zoom
     *
     * 'Zooms' to the start or end of the element
     *
     * @param {String} direction
     * @return void
     */
    zoom: function (direction) {

        if (this._timerID != null) {
            window.clearTimeout(this._timerID);
            this._timerID = null;
        }

        if (direction == 'start') {
            this._left = 0;
            this._top = 0;

        } else if (direction = 'end') {

            if (this._vertScrollDistance) {
                this._top = -this._vertScrollDistance;

            } else if (this._horzScrollDistance) {
                this._left = -this._horzScrollDistance;
            }
        }
        this._doScroll();
    },

    /**
     * @method toString
     *
     * @return {String}
     */
    toString: function () {
        return '[Object] DDJS.Effects.Scroller';
    },

    /**
     * @method _doScroll
     *
     * Executes a single scroll step
     *
     * @return void
     * @private
     */
    _doScroll: function () {
        this._inner.style.top = this._top.toString() + 'px';
        this._inner.style.left = this._left.toString() + 'px';
        this._state = 'scrolling';
        this._scrollCount++;
    }

}; // End Scroller.prototype


