/******************************************************************************
 *
 * SCROLLER
 * Author:  Kerri Shotts
 * Version: 0.1 alpha
 * License: MIT
 *
 * This library is intended to be similar to iScroll-lite in that it should be
 * a relatively fast method of scrolling content without being horribly laggy
 * or cause incorrect "clicks" to be registered. 
 *
 * This library does *NOT* support physics-based scrolling, except for a small
 * inertia animation at the end of a scroll. It is not intended to replicate
 * native scrolling /at all/. There are no bounces at the top or bottom. There
 * is no visible scroll bar either. Essentially, overflow:scroll as supported
 * on iOS 5 with no bounce/inertia scrolling. 
 *
 * Consider this library an experiment. The idea is to be simpler than iScroll
 * to use -- for example, the scroller only needs to be created once -- it does
 * not need to be refreshed when AJAX content loads. It is intended to be at
 * least as fast as iScroll, if not a little faster. It is not, however,
 * intended to be a native scrolling solution. At this time, I do not believe
 * it truly possible or practice, and users will notice any non-native solution
 * that tries to match, so why try?
 *
 * Usage:
 *
 * var yourScroller = new SCROLLER.GenericScroller ( "the_element_to_scroll" );
 *
 * where you have the following DOM tree:
 *
 *     container_element
 *     - the_element_to_scroll
 *
 * Future Goals:
 *
 *  - Detect native physics scrolling and use it when possible
 *  - Detect native overflow:scroll (non-physics) and use it when possible
 *  - Improve the inertial scrolling at end (this is a very rough implementation)
 *  - Become irrelevant. I hope for a day when all mobile browsers can scroll 
 *    complex content natively and smoothly.
 *
 * Supported Platforms:
 * 
 *  - Android 2.3+
 *  - iOS 4.3+
 *  - probably any webkit browser?
 * 
 * Known Issues:
 *  - A little too willing to call a scroll a "click". 
 *
 ******************************************************************************/

var SCROLLER = SCROLLER || {};  // create the namespace

SCROLLER.GenericScroller = function ( element )
{
    var self = this;
    
    // the element that should scroll
    self.theElement = {};
    
    // various touch-related coords.
    self._touchX = -1;
    self._touchY = -1;
    self._touchStartX = 0;
    self._touchStartY = 0;
    self._actualX = 0;
    self._actualY = 0;

    // delta changes; totalDelta should be total change from start to end.
    self._deltaX = 0;
    self._deltaY = 0;
    self._totalDeltaX = 0;
    self._totalDeltaY = 0;

    // inertial animation
    self._timer = -1;
    self._step = 0;
    
    /**
     *
     * Attaches the scroller to a new element. The element MUST be contained within
     * another element that can support the setting of scrollTop/Left.
     *
     * Any overflow values will be overridden. Any webkit transforms may also be
     * overridden.
     */
    self.attachToElement = function ( element)
    {
        // get our element
        self.theElement = document.getElementById(element);
        
        // attach our listeners
        self.theElement.addEventListener ( "touchstart", self.touchStart, false );
        self.theElement.addEventListener ( "touchmove",  self.touchMove,  false );
        self.theElement.addEventListener ( "touchend",   self.touchEnd,   false );
        
        // turn overflow to hidden; if it is auto or scroll, the native scrolling
        // might kick in (if supported) and confuse us. IF YOU WANT NATIVE SCROLLING,
        // DON'T USE THIS SCROLLER.
        self.theElement.parentNode.style.overflow = "hidden";
        
        // A quandary. on iOS, it is faster to have translate3d(0,0,0) enabled, 
        // but you have more visual glitches. Without it, it is a little slower,
        // but with no visual glitches. For Android, we leave it on, just in case
        // it can be used.
        if (device)
        {
            if (device.platform == "Android")
            {
                self.theElement.style.webkitTransform = "translate3d(0,0,0)";
            }
        }
        else
        {
            // if we can't detect the device, we'll always use it.
            self.theElement.style.webkitTransform = "translate3d(0,0,0)";
        }
        
        console.log ("Scroller initiated for element " + element);
    }
    
    /**
     *
     * Get the scroll position
     *
     */
    self.getScrollTop = function ()
    {
        return self.theElement.parentNode.scrollTop;
    }
    self.getScrollLeft = function ()
    {
        return self.theElement.parentNode.scrollLeft;
    }
    
    /**
     *
     * Scroll to a given location. If the location can't be scrolled to,
     * the nearest location will be used.
     *
     */
    self.scrollTo = function (left, top)
    {
        self.theElement.parentNode.scrollTop = top;
        self.theElement.parentNode.scrollLeft = left;
        self._actualX = -left;
        self._actualY = -top;
    }
    
    /**
     *
     * touchStart initializes all our values when a touch is received.
     *
     */
    self.touchStart = function (event)
    {
        // if an inertia animation is underway, clear it.
        if (self._timer!=-1) { clearInterval (self._timer); }
        self._timer = -1;
        
        // record our touches.
        self._touchX = event.touches[0].screenX;
        self._touchY = event.touches[0].screenY;
        self._touchStartX = self._touchX;
        self._touchStartY = self._touchY;
        
        // zero the deltas
        self._deltaX = 0;
        self._deltaY = 0;
        self._totalDeltaX = 0;
        self._totalDeltaY = 0;
        
        // get our actual scroll position
        self._actualX = -self.theElement.parentNode.scrollLeft;
        self._actualY = -self.theElement.parentNode.scrollTop;
    }
    
    /**
     *
     * When a touch moves, we'll receive the event here.
     *
     */
    self.touchMove = function (event)
    {
        // calculate the delta between our last and current
        // position
        self._deltaX = self._touchX - event.touches[0].screenX;
        self._deltaY = self._touchY - event.touches[0].screenY;
        
        // update totalDelta
        self._totalDeltaX -= self._deltaX;
        self._totalDeltaY -= self._deltaY;
        
        // update our actual scroll position
        self._actualX -= self._deltaX;
        self._actualY -= self._deltaY;
        
        // store our current screen position
        self._touchX = event.touches[0].screenX;
        self._touchY = event.touches[0].screenY;
        
        // scroll to the new position
        self.theElement.parentNode.scrollTop = -self._actualY;
        self.theElement.parentNode.scrollLeft = -self._actualX;
        
        // if there is any movement, prevent the default.
        if ( Math.sqrt((self._totalDeltaX * self._totalDeltaX) + (self._totalDeltaY * self._totalDeltaY)) > 0 )
        {
            event.preventDefault ();
        }
    }
    
    /**
     *
     * When a finger is lifted from the screen, we'll get this event.
     * We can determine whether or not it was a click if we scrolled at all,
     * and if we scrolled a certain distance, we'll do a little inertial
     * scrolling
     */
    self.touchEnd = function (event)
    {
        // were we just a click? if so, this'll be zero -- otherwise prevent the default.
        if ( Math.sqrt((self._totalDeltaX * self._totalDeltaX) + (self._totalDeltaY * self._totalDeltaY)) > 0 )
        {
            event.preventDefault ();
        }
        
        // how far did we scroll just prior to getting the end event? Is it far enough to have
        // some inertia?
        if ( Math.sqrt((self._deltaX * self._deltaX) + (self._deltaY * self._deltaY)) > 10 )
        {
            // yes, set up our animation
            self._step = 0;
            self._timer = setInterval ( function () 
                                        {
                                          // increment the frame
                                          self._step = self._step + 1;
                                          if (self._step<10)
                                          {
                                            // we'll permit 10 frames of animation
                                            self._actualX -= self._deltaX/(self._step);
                                            self._actualY -= self._deltaY/(self._step);
                                            self.theElement.parentNode.scrollTop = -self._actualY;
                                            self.theElement.parentNode.scrollLeft = -self._actualX;
                                          }
                                          else
                                          {
                                            // animation complete
                                            clearInterval (self._timer);
                                            self._timer = -1;
                                          }
                                        }, 10 );    // 10 = as fast as possible
        }
    }

    // attach to the element passed in the constructor.
    self.attachToElement ( element );
}