/**
 *  Copyright (c) 2011 Zauber S.A. <http://www.zaubersoftware.com/>
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  @author Guido Marucci Blas - guido@zaubersoftware.com
 *  @description Adds a handler for a custom event 'taphold' that handles a
 *  tap and hold on touch interfaces.
 */
(function($) {
    var TAP_AND_HOLD_TRIGGER_TIMER = 1000;
    var MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT = 5;
    var TOUCHSTART = "touchstart";
    var TOUCHEND = "touchend";
    var TOUCHMOVE = "touchmove";

    // For debugging only
    // var TOUCHSTART = "mousedown";
    // var TOUCHEND = "mouseup";
    // var TOUCHMOVE = "mousemove";

    var tapAndHoldTimer = null;

    function calculateEuclideanDistance(x1, y1, x2, y2) {
        var diffX = (x2 - x1);
        var diffY = (y2 - y1);
        return Math.sqrt((diffX * diffX) + (diffY * diffY));
    };

    function onTouchStart(event) {
        var e = event.originalEvent;

        // Only start detector if and only if one finger is over the widget
        if (!e.touches || (e.targetTouches.length === 1 && e.touches.length === 1)) {
            startTapAndHoldDetector.call(this, event)
            var element = $(this);
            element.bind(TOUCHMOVE, onTouchMove);
            element.bind(TOUCHEND, onTouchEnd);
        } else {
            stopTapAndHoldDetector.call(this);
        }
    };

    function onTouchMove(event) {
        if (tapAndHoldTimer == null) {
            return;
        }

        var e = event.originalEvent;
        var x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
        var y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;

        var tapAndHoldPoint = $(this).data("taphold.point");
        var euclideanDistance = calculateEuclideanDistance(tapAndHoldPoint.x, tapAndHoldPoint.y, x, y);

        if (euclideanDistance > MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT) {
            stopTapAndHoldDetector.call(this);
        }
    };

    function onTouchEnd(event) {
        stopTapAndHoldDetector.call(this);
    };

    function onTapAndHold(event) {
        clear.call(this);
        $(this).data("taphold.handler").call(this, event);
    };

    function clear() {
        tapAndHoldTimer = null;
        $(this).unbind(TOUCHMOVE, onTouchMove);
        $(this).unbind(TOUCHEND, onTouchEnd);
    };

    function startTapAndHoldDetector(event) {
        if (tapAndHoldTimer != null) {
            return;
        }
        var self = this;
        tapAndHoldTimer = setTimeout(function(){
            onTapAndHold.call(self, event)
        }, TAP_AND_HOLD_TRIGGER_TIMER);

        // Stores tap x & y
        var e = event.originalEvent;
        var tapAndHoldPoint = {};
        tapAndHoldPoint.x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
        tapAndHoldPoint.y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
        $(this).data("taphold.point", tapAndHoldPoint);
    };

    function stopTapAndHoldDetector() {
        clearTimeout(tapAndHoldTimer);
        clear.call(this);
    };

    $.event.special["taphold"] = {
        setup: function() {

        },

        add: function(handleObj) {
            $(this).data("taphold.handler", handleObj.handler);
            if (handleObj.data) {
                $(this).bind(TOUCHSTART, handleObj.data, onTouchStart);
            } else {
                $(this).bind(TOUCHSTART, onTouchStart);
            }
        },

        remove: function(handleObj) {
            stopTapAndHoldDetector.call(this);
            if (handleObj.data) {
                $(this).unbind(TOUCHSTART, handleObj.data, onTouchStart);
            } else {
                $(this).unbind(TOUCHSTART, onTouchStart);
            }
        },

        teardown: function() {

        }
    };

})(jQuery);