// Touchy
// Simplifies touch events used for dragging and swiping
(function($) {
    $.fn.touchy = function() {
        // check for touchability
        if (!("TouchEvent" in window)) this;
        
        return this.each(function() {
            var $this = $(this);
            var firstTouch, lastTouch, currentTouch;
            var startedDrag;
            
            function cancelEvents() {
                $this.unbind("touchmove.touchy");
                $this.unbind("touchend.touchy");
                $this.unbind("touchcancel.touchy");
            }
            
            function getCurrentTouch(e) {
                var touches = e.originalEvent.changedTouches;
                if (touches.length == 1) {
                    var touch = touches[0];
                    return {
                        x: touch.pageX,
                        y: touch.pageY
                    };
                } else {
                    return false;
                }
            }
            
            $this.bind("touchstart.touchy", function(e) {
                currentTouch = getCurrentTouch(e);
                startedDrag = false;
                if (currentTouch) {
                    firstTouch = currentTouch;
                    lastTouch = currentTouch;
                } else {
                    return;
                }
                
                // bind touch events to capture dragging
                $this.bind({
                    "touchmove.touchy": function(e) {
                        e.preventDefault();
                        if (!startedDrag) {
                            startedDrag = true;
                            $this.triggerHandler("touchdragstart", [firstTouch]);
                        }

                        if (currentTouch) lastTouch = currentTouch;

                        currentTouch = getCurrentTouch(e);
                        if (currentTouch) {
                            $this.triggerHandler("touchdragmove", [currentTouch]);
                        }
                    },
                    
                    "touchend.touchy": function(e) {
                        var currentTouch = getCurrentTouch(e);
                        if (currentTouch) {
                            var dx = currentTouch.x - lastTouch.x;
                            var dy = currentTouch.y - lastTouch.y;
                            var distance = Math.sqrt(dx * dx + dy * dy);
                            var ang = Math.round(Math.atan2(dx, dy) / (Math.PI * 0.5));
                            if (distance > 12) {
                                switch (ang) {
                                    case 0:
                                        $this.triggerHandler("touchswipedown");
                                        break;
                                    case 1:
                                        $this.triggerHandler("touchswiperight");
                                        break;
                                    case -1:
                                        $this.triggerHandler("touchswipeleft");
                                        break;
                                    case -2:
                                    case 2:
                                        $this.triggerHandler("touchswipeup");
                                        break;
                                }
                            }
                        }

                        if (startedDrag) {
                            e.preventDefault()
                            $this.triggerHandler("touchdragend", [currentTouch]);
                        }

                        cancelEvents();
                    },
                    
                    "touchcancel.touchy": function(e) {
                        cancelEvents();
                    }
                });
            });
            
            // stops clicks from happening if we drag
            $this.bind("click", function(e) {
                if (startedDrag) e.preventDefault();
            });
        });
    }
})(jQuery);


// Touchyscroll
// adds touch scrolling to an element and its content
(function($) {
    
    var actions = {
        init: function(content) {
            return this.each(function(i) {
                var self = $(this);
                self.data("scrollbox", new scrollbox(self, content.eq(i)));
            });
        },
        left: function(n) {
            if (typeof n === "undefined") {
                return this.data("scrollbox").left();
            } else {
                this.data("scrollbox").left(n);
                return this;
            }
        },
        stop: function() {
            $(this).data("scrollbox").stop();
            return this;
        }
    };
    
    function scrollbox(container, content) {
        this.container = container;
        this.content = content;
        this.velocity = 0;
        this.container.get(0).scrollboxLeft = 0;
        
        var self = this;
        
        var startPos;
        var firstTouch, curTouch, lastTouch;
        
        this.left(0);
        
        this.container.touchy().bind({
            'touchdragstart': function(e, touch) {
                self.stop();
                startPos = self.left();
                firstTouch = touch;
            },
            'touchdragmove': function(e, touch) {
                if (curTouch) lastTouch = curTouch;
                curTouch = touch;
                
                var x = firstTouch.x - touch.x + startPos;
                var maxLeft = Math.max(0, self.content.width() - self.container.width());
                
                if (x < 0) {
                    x = 0 - Math.pow(Math.abs(x), 0.8);
                } else if (x > maxLeft) {
                    x = x - maxLeft;
                    x = maxLeft + Math.pow(x, 0.8);
                }
                
                self.left(x);
            },
            'touchdragend': function(e, touch) {
                self.velocity = lastTouch.x - touch.x;
                self.swipe();
            }
        });
    }
    
    scrollbox.prototype = {
        left: function(n) {
            // get
            if (typeof n === "undefined") {
                return this.container.get(0).scrollboxLeft;
            }
            // set
            this.container.get(0).scrollboxLeft = n;
            this.content.css("webkitTransform", "translate3d("+Math.round(-n)+"px, 0, 0)");
            
            this.container.triggerHandler("touchyscroll");
        },
        stop: function() {
            clearInterval(this.animationTimer);
            this.container.stop();
        },
        swipe: function() {
            this.stop();
            if (this.velocity == 0) return;
            
            this.animationTimer = setInterval($.proxy(function() {
                var friction = this.velocity > 0 ? -1 : 1;
                friction = Math.max(Math.abs(this.velocity) * 0.075, 0.5) * friction;
                this.velocity += friction;
                this.velocity = friction > 0 ? Math.min(0, this.velocity) : Math.max(0, this.velocity);

                this.left(this.left() + this.velocity);
                
                if (this.velocity == 0) {
                    clearInterval(this.animationTimer);
                }
                
                // check for snap
                var n = this.left();
                if (n < 0 || n > Math.max(0, this.content.width() - this.container.width())) {
                    if (Math.abs(this.velocity) > 0) {
                        this.velocity *= 0.5;
                        return // dont snap
                    } else {
                        this.snap();
                    }
                    
                }
            }, this), 13);
        },
        snap: function() {
            this.stop();
            
            var self = this;
            var n = this.left();
            var maxLeft = Math.max(0, this.content.width() - this.container.width());
            var target;

            if (n < 0) {
                target = 0;
            } else if(n > maxLeft) {
                target = maxLeft;
            }
            
            this.container.animate({'scrollboxLeft': target}, {
                duration: 200,
                step: function(now) {
                    self.left(now)
                }
            });
        }
    };
    
    $.fn.touchyscroll = function(action) {
        // check for touchability
        if (!("TouchEvent" in window)) this;
        
        if ( actions[action] ) {
            return actions[ action ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof action === 'object' || ! action ) {
            return actions.init.apply( this, arguments );
        } else {
            $.error( 'Action ' +  action + ' does not exist on jQuery.touchyscroll' );
        }
    };
    
})(jQuery);
