
/* global jQuery */

(function($) {
    'use strict';

    var options = {
        pan: {
            enableTouch: false
        }
    };

    function init(plot) {
        plot.hooks.processOptions.push(initTouchNavigation);
    }

    function initTouchNavigation(plot, options) {
        var gestureState = {
                twoTouches: false,
                currentTapStart: { x: 0, y: 0 },
                currentTapEnd: { x: 0, y: 0 },
                prevTap: { x: 0, y: 0 },
                currentTap: { x: 0, y: 0 },
                interceptedLongTap: false,
                allowEventPropagation: false,
                prevTapTime: null,
                tapStartTime: null,
                longTapTriggerId: null
            },
            maxDistanceBetweenTaps = 20,
            maxIntervalBetweenTaps = 500,
            maxLongTapDistance = 20,
            minLongTapDuration = 1500,
            pressedTapDuration = 125,
            mainEventHolder;

        function interpretGestures(e) {
            var o = plot.getOptions();

            if (!o.pan.active && !o.zoom.active) {
                return;
            }

            updateOnMultipleTouches(e);
            mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));

            if (isPinchEvent(e)) {
                executeAction(e, 'pinch');
            } else {
                executeAction(e, 'pan');
                if (!wasPinchEvent(e)) {
                    if (isDoubleTap(e)) {
                        executeAction(e, 'doubleTap');
                    }
                    executeAction(e, 'tap');
                    executeAction(e, 'longTap');
                }
            }
        }

        function executeAction(e, gesture) {
            switch (gesture) {
                case 'pan':
                    pan[e.type](e);
                    break;
                case 'pinch':
                    pinch[e.type](e);
                    break;
                case 'doubleTap':
                    doubleTap.onDoubleTap(e);
                    break;
                case 'longTap':
                    longTap[e.type](e);
                    break;
                case 'tap':
                    tap[e.type](e);
                    break;
                default:
                    break;
            }
        }

        function bindEvents(plot, eventHolder) {
            mainEventHolder = eventHolder[0];
            eventHolder[0].addEventListener('touchstart', interpretGestures, false);
            eventHolder[0].addEventListener('touchmove', interpretGestures, false);
            eventHolder[0].addEventListener('touchend', interpretGestures, false);
        }

        function shutdown(plot, eventHolder) {
            eventHolder[0].removeEventListener('touchstart', interpretGestures);
            eventHolder[0].removeEventListener('touchmove', interpretGestures);
            eventHolder[0].removeEventListener('touchend', interpretGestures);
            if (gestureState.longTapTriggerId) {
                clearTimeout(gestureState.longTapTriggerId);
                gestureState.longTapTriggerId = null;
            }
        }

        var pan = {
            touchstart: function(e) {
                updatePrevForDoubleTap();
                updateCurrentForDoubleTap(e);
                updateStateForLongTapStart(e);

                mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
            },

            touchmove: function(e) {
                preventEventPropagation(e);

                updateCurrentForDoubleTap(e);
                updateStateForLongTapEnd(e);

                if (!gestureState.allowEventPropagation) {
                    mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));
                }
            },

            touchend: function(e) {
                preventEventPropagation(e);

                if (wasPinchEvent(e)) {
                    mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));
                    mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
                } else if (noTouchActive(e)) {
                    mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));
                }
            }
        };

        var pinch = {
            touchstart: function(e) {
                mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));
            },

            touchmove: function(e) {
                preventEventPropagation(e);
                gestureState.twoTouches = isPinchEvent(e);
                if (!gestureState.allowEventPropagation) {
                    mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));
                }
            },

            touchend: function(e) {
                preventEventPropagation(e);
            }
        };

        var doubleTap = {
            onDoubleTap: function(e) {
                preventEventPropagation(e);
                mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));
            }
        };

        var longTap = {
            touchstart: function(e) {
                longTap.waitForLongTap(e);
            },

            touchmove: function(e) {
            },

            touchend: function(e) {
                if (gestureState.longTapTriggerId) {
                    clearTimeout(gestureState.longTapTriggerId);
                    gestureState.longTapTriggerId = null;
                }
            },

            isLongTap: function(e) {
                var currentTime = new Date().getTime(),
                    tapDuration = currentTime - gestureState.tapStartTime;
                if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
                        gestureState.interceptedLongTap = true;
                        return true;
                    }
                }
                return false;
            },

            waitForLongTap: function(e) {
                var longTapTrigger = function() {
                    if (longTap.isLongTap(e)) {
                        mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));
                    }
                    gestureState.longTapTriggerId = null;
                };
                if (!gestureState.longTapTriggerId) {
                    gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
                }
            }
        };

        var tap = {
            touchstart: function(e) {
                gestureState.tapStartTime = new Date().getTime();
            },

            touchmove: function(e) {
            },

            touchend: function(e) {
                if (tap.isTap(e)) {
                    mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));
                    preventEventPropagation(e);
                }
            },

            isTap: function(e) {
                var currentTime = new Date().getTime(),
                    tapDuration = currentTime - gestureState.tapStartTime;
                if (tapDuration <= pressedTapDuration) {
                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
                        return true;
                    }
                }
                return false;
            }
        };

        if (options.pan.enableTouch === true) {
            plot.hooks.bindEvents.push(bindEvents);
            plot.hooks.shutdown.push(shutdown);
        };

        function updatePrevForDoubleTap() {
            gestureState.prevTap = {
                x: gestureState.currentTap.x,
                y: gestureState.currentTap.y
            };
        };

        function updateCurrentForDoubleTap(e) {
            gestureState.currentTap = {
                x: e.touches[0].pageX,
                y: e.touches[0].pageY
            };
        }

        function updateStateForLongTapStart(e) {
            gestureState.tapStartTime = new Date().getTime();
            gestureState.interceptedLongTap = false;
            gestureState.currentTapStart = {
                x: e.touches[0].pageX,
                y: e.touches[0].pageY
            };
            gestureState.currentTapEnd = {
                x: e.touches[0].pageX,
                y: e.touches[0].pageY
            };
        };

        function updateStateForLongTapEnd(e) {
            gestureState.currentTapEnd = {
                x: e.touches[0].pageX,
                y: e.touches[0].pageY
            };
        };

        function isDoubleTap(e) {
            var currentTime = new Date().getTime(),
                intervalBetweenTaps = currentTime - gestureState.prevTapTime;

            if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
                if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
                    e.firstTouch = gestureState.prevTap;
                    e.secondTouch = gestureState.currentTap;
                    return true;
                }
            }
            gestureState.prevTapTime = currentTime;
            return false;
        }

        function preventEventPropagation(e) {
            if (!gestureState.allowEventPropagation) {
                e.preventDefault();
                e.stopPropagation();
            }
        }

        function distance(x1, y1, x2, y2) {
            return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        }

        function noTouchActive(e) {
            return (e.touches && e.touches.length === 0);
        }

        function wasPinchEvent(e) {
            return (gestureState.twoTouches && e.touches.length === 1);
        }

        function updateOnMultipleTouches(e) {
            if (e.touches.length >= 3) {
                gestureState.allowEventPropagation = true;
            } else {
                gestureState.allowEventPropagation = false;
            }
        }

        function isPinchEvent(e) {
            if (e.touches && e.touches.length >= 2) {
                if (e.touches[0].target === plot.getEventHolder() &&
                    e.touches[1].target === plot.getEventHolder()) {
                    return true;
                }
            }
            return false;
        }
    }

    $.plot.plugins.push({
        init: init,
        options: options,
        name: 'navigateTouch',
        version: '0.3'
    });
})(jQuery);
