/*global defineSuite*/
defineSuite([
        'Core/ScreenSpaceEventHandler',
        'Core/Cartesian2',
        'Core/clone',
        'Core/combine',
        'Core/defined',
        'Core/FeatureDetection',
        'Core/KeyboardEventModifier',
        'Core/ScreenSpaceEventType',
        'Specs/DomEventSimulator'
    ], function(
        ScreenSpaceEventHandler,
        Cartesian2,
        clone,
        combine,
        defined,
        FeatureDetection,
        KeyboardEventModifier,
        ScreenSpaceEventType,
        DomEventSimulator) {
    "use strict";

    var usePointerEvents = FeatureDetection.supportsPointerEvents();
    var element;
    var handler;

    function createCloningSpy(name) {
        var spy = jasmine.createSpy(name);

        var cloningSpy = function() {
            // deep clone arguments so they are captured correctly by the spy
            var args = [].slice.apply(arguments).map(function(arg) {
                return clone(arg, true);
            });

            spy.apply(this, args);
        };

        function createPropertyDescriptor(prop) {
            return {
                get : function() {
                    return spy[prop];
                },
                set : function(value) {
                    spy[prop] = value;
                }
            };
        }

        for ( var prop in spy) {
            if (spy.hasOwnProperty(prop)) {
                Object.defineProperty(cloningSpy, prop, createPropertyDescriptor(prop));
            }
        }

        return cloningSpy;
    }

    var eventsToStop = 'pointerdown pointerup pointermove mousedown mouseup mousemove touchstart touchend touchmove dblclick wheel mousewheel DOMMouseScroll'.split(' ');

    function stop(event) {
        event.stopPropagation();
        event.preventDefault();
    }

    beforeEach(function() {
        // ignore events that bubble up to the document.
        // this prevents triggering the browser's "middle click to scroll" behavior
        eventsToStop.forEach(function(e) {
            document.addEventListener(e, stop, false);
        });

        element = document.createElement('div');
        element.style.position = 'absolute';
        element.style.top = '0';
        element.style.left = '0';
        document.body.appendChild(element);
        element.disableRootEvents = true;

        if (usePointerEvents) {
            spyOn(element, 'setPointerCapture');
        }

        handler = new ScreenSpaceEventHandler(element);
    });

    afterEach(function() {
        document.body.removeChild(element);
        handler = !handler.isDestroyed() && handler.destroy();

        eventsToStop.forEach(function(e) {
            document.removeEventListener(e, stop, false);
        });
    });

    describe('setInputAction', function() {
        it('throws if action is undefined', function() {
            expect(function() {
                handler.setInputAction();
            }).toThrowDeveloperError();
        });

        it('throws if type is undefined', function() {
            expect(function() {
                handler.setInputAction(function() {
                });
            }).toThrowDeveloperError();
        });
    });

    describe('getInputAction', function() {
        it('throws if type is undefined', function() {
            expect(function() {
                handler.getInputAction();
            }).toThrowDeveloperError();
        });
    });

    describe('removeInputAction', function() {
        it('throws if type is undefined', function() {
            expect(function() {
                handler.removeInputAction();
            }).toThrowDeveloperError();
        });
    });

    var MouseButton = {
        LEFT : 0,
        MIDDLE : 1,
        RIGHT : 2
    };

    function keyForValue(obj, val) {
        for ( var key in obj) {
            if (obj[key] === val) {
                return key;
            }
        }
    }

    function createMouseSpec(specFunction, eventType, button, modifier) {
        var specName = keyForValue(ScreenSpaceEventType, eventType) + ' action';
        if (defined(modifier)) {
            specName += ' with ' + keyForValue(KeyboardEventModifier, modifier) + ' modifier';
        }
        it(specName, function() {
            var eventOptions = {
                button : button,
                ctrlKey : modifier === KeyboardEventModifier.CTRL,
                altKey : modifier === KeyboardEventModifier.ALT,
                shiftKey : modifier === KeyboardEventModifier.SHIFT
            };
            specFunction(eventType, modifier, eventOptions);
        });
    }

    function createAllMouseSpecCombinations(specFunction, possibleButtons, possibleModifiers, possibleEventTypes) {
        for (var i = 0; i < possibleButtons.length; ++i) {
            var eventType = possibleEventTypes[i];
            var button = possibleButtons[i];
            for (var j = 0; j < possibleModifiers.length; ++j) {
                var modifier = possibleModifiers[j];
                createMouseSpec(specFunction, eventType, button, modifier);
            }
        }
    }

    function simulateMouseDown(element, options) {
        if (usePointerEvents) {
            DomEventSimulator.firePointerDown(element, combine(options, {
                pointerType : 'mouse'
            }));
        } else {
            DomEventSimulator.fireMouseDown(element, options);
        }
    }

    function simulateMouseUp(element, options) {
        if (usePointerEvents) {
            DomEventSimulator.firePointerUp(element, combine(options, {
                pointerType : 'mouse'
            }));
        } else {
            DomEventSimulator.fireMouseUp(element, options);
        }
    }

    function simulateMouseMove(element, options) {
        if (usePointerEvents) {
            DomEventSimulator.firePointerMove(element, combine(options, {
                pointerType : 'mouse'
            }));
        } else {
            DomEventSimulator.fireMouseMove(element, options);
        }
    }

    describe('handles mouse down', function() {
        function testMouseDownEvent(eventType, modifier, eventOptions) {
            var action = createCloningSpy('action');
            handler.setInputAction(action, eventType, modifier);

            expect(handler.getInputAction(eventType, modifier)).toEqual(action);

            function simulateInput() {
                simulateMouseDown(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
            }

            simulateInput();

            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                position : new Cartesian2(1, 2)
            });

            // should not be fired after removal
            action.calls.reset();

            handler.removeInputAction(eventType, modifier);

            simulateInput();

            expect(action).not.toHaveBeenCalled();
        }

        var possibleButtons = [MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT];
        var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
        var possibleEventTypes = [ScreenSpaceEventType.LEFT_DOWN, ScreenSpaceEventType.MIDDLE_DOWN, ScreenSpaceEventType.RIGHT_DOWN];
        createAllMouseSpecCombinations(testMouseDownEvent, possibleButtons, possibleModifiers, possibleEventTypes);
    });

    describe('handles mouse up', function() {
        function testMouseUpEvent(eventType, modifier, eventOptions) {
            var action = createCloningSpy('action');
            handler.setInputAction(action, eventType, modifier);

            expect(handler.getInputAction(eventType, modifier)).toEqual(action);

            // down, then up
            function simulateInput() {
                simulateMouseDown(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
                simulateMouseUp(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
            }

            simulateInput();

            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                position : new Cartesian2(1, 2)
            });

            action.calls.reset();

            // down, move, then up
            function simulateInput2() {
                simulateMouseDown(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
                simulateMouseMove(element, combine({
                    clientX : 10,
                    clientY : 11
                }, eventOptions));
                simulateMouseUp(element, combine({
                    clientX : 10,
                    clientY : 11
                }, eventOptions));
            }

            simulateInput2();

            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                position : new Cartesian2(10, 11)
            });

            // should not be fired after removal
            action.calls.reset();

            handler.removeInputAction(eventType, modifier);

            simulateInput();
            simulateInput2();

            expect(action).not.toHaveBeenCalled();
        }

        var possibleButtons = [MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT];
        var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
        var possibleEventTypes = [ScreenSpaceEventType.LEFT_UP, ScreenSpaceEventType.MIDDLE_UP, ScreenSpaceEventType.RIGHT_UP];
        createAllMouseSpecCombinations(testMouseUpEvent, possibleButtons, possibleModifiers, possibleEventTypes);
    });

    describe('handles mouse click', function() {
        function testMouseClickEvent(eventType, modifier, eventOptions) {
            var action = createCloningSpy('action');
            handler.setInputAction(action, eventType, modifier);

            expect(handler.getInputAction(eventType, modifier)).toEqual(action);

            // down, then up
            function simulateInput() {
                simulateMouseDown(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
                simulateMouseUp(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
            }

            simulateInput();

            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                position : new Cartesian2(1, 2)
            });

            // mouse clicks are not fired if the mouse moves too far away from the down position
            action.calls.reset();
            simulateMouseDown(element, combine({
                clientX : 1,
                clientY : 2
            }, eventOptions));
            simulateMouseUp(element, combine({
                clientX : 10,
                clientY : 11
            }, eventOptions));

            expect(action).not.toHaveBeenCalled();

            // should not be fired after removal
            action.calls.reset();

            handler.removeInputAction(eventType, modifier);

            simulateInput();

            expect(action).not.toHaveBeenCalled();
        }

        var possibleButtons = [MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT];
        var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
        var possibleEventTypes = [ScreenSpaceEventType.LEFT_CLICK, ScreenSpaceEventType.MIDDLE_CLICK, ScreenSpaceEventType.RIGHT_CLICK];
        createAllMouseSpecCombinations(testMouseClickEvent, possibleButtons, possibleModifiers, possibleEventTypes);
    });

    describe('handles mouse double click', function() {
        function testMouseDoubleClickEvent(eventType, modifier, eventOptions) {
            var action = createCloningSpy('action');
            handler.setInputAction(action, eventType, modifier);

            expect(handler.getInputAction(eventType, modifier)).toEqual(action);

            function simulateInput() {
                DomEventSimulator.fireDoubleClick(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
            }

            simulateInput();

            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                position : new Cartesian2(1, 2)
            });

            // should not be fired after removal
            action.calls.reset();

            handler.removeInputAction(eventType, modifier);

            simulateInput();

            expect(action).not.toHaveBeenCalled();
        }

        var possibleButtons = [MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT];
        var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
        var possibleEventTypes = [ScreenSpaceEventType.LEFT_DOUBLE_CLICK, ScreenSpaceEventType.MIDDLE_DOUBLE_CLICK, ScreenSpaceEventType.RIGHT_DOUBLE_CLICK];
        createAllMouseSpecCombinations(testMouseDoubleClickEvent, possibleButtons, possibleModifiers, possibleEventTypes);
    });

    describe('handles mouse move', function() {
        function testMouseMoveEvent(eventType, modifier, eventOptions) {
            var action = createCloningSpy('action');
            handler.setInputAction(action, eventType, modifier);

            expect(handler.getInputAction(eventType, modifier)).toEqual(action);

            function simulateInput() {
                simulateMouseMove(element, combine({
                    clientX : 1,
                    clientY : 2
                }, eventOptions));
                simulateMouseMove(element, combine({
                    clientX : 2,
                    clientY : 3
                }, eventOptions));
            }

            simulateInput();

            expect(action.calls.count()).toEqual(2);
            expect(action).toHaveBeenCalledWith({
                startPosition : new Cartesian2(1, 2),
                endPosition : new Cartesian2(2, 3)
            });

            // should not be fired after removal
            action.calls.reset();

            handler.removeInputAction(eventType, modifier);

            simulateInput();

            expect(action).not.toHaveBeenCalled();
        }

        var possibleButtons = [undefined];
        var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
        var possibleEventTypes = [ScreenSpaceEventType.MOUSE_MOVE];
        createAllMouseSpecCombinations(testMouseMoveEvent, possibleButtons, possibleModifiers, possibleEventTypes);
    });

    describe('handles mouse wheel', function() {

        if ('onwheel' in document) {

            describe('using standard wheel event', function() {
                function testWheelEvent(eventType, modifier, eventOptions) {
                    var action = createCloningSpy('action');
                    handler.setInputAction(action, eventType, modifier);

                    expect(handler.getInputAction(eventType, modifier)).toEqual(action);

                    function simulateInput() {
                        DomEventSimulator.fireWheel(element, combine({
                            deltaY : 120
                        }, eventOptions));
                    }

                    simulateInput();

                    expect(action.calls.count()).toEqual(1);

                    // NOTE: currently the action is passed the value of 'wheelDelta' which is inverted from the standard 'deltaY'
                    expect(action).toHaveBeenCalledWith(-120);

                    // should not be fired after removal
                    action.calls.reset();

                    handler.removeInputAction(eventType, modifier);

                    simulateInput();

                    expect(action).not.toHaveBeenCalled();
                }

                var possibleButtons = [undefined];
                var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
                var possibleEventTypes = [ScreenSpaceEventType.WHEEL];
                createAllMouseSpecCombinations(testWheelEvent, possibleButtons, possibleModifiers, possibleEventTypes);
            });

        } else if (defined(document.onmousewheel)) {

            describe('using legacy mousewheel event', function() {
                function testMouseWheelEvent(eventType, modifier, eventOptions) {
                    var action = createCloningSpy('action');
                    handler.setInputAction(action, eventType, modifier);

                    expect(handler.getInputAction(eventType, modifier)).toEqual(action);

                    function simulateInput() {
                        DomEventSimulator.fireMouseWheel(element, combine({
                            wheelDelta : -120
                        }, eventOptions));
                    }

                    simulateInput();

                    expect(action.calls.count()).toEqual(1);
                    expect(action).toHaveBeenCalledWith(-120);

                    // should not be fired after removal
                    action.calls.reset();

                    handler.removeInputAction(eventType, modifier);

                    simulateInput();

                    expect(action).not.toHaveBeenCalled();
                }

                var possibleButtons = [undefined];
                var possibleModifiers = [undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT];
                var possibleEventTypes = [ScreenSpaceEventType.WHEEL];
                createAllMouseSpecCombinations(testMouseWheelEvent, possibleButtons, possibleModifiers, possibleEventTypes);
            });

        }
    });

    it('handles touch start', function() {
        var eventType = ScreenSpaceEventType.LEFT_DOWN;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        function simulateInput() {
            var touchStartPosition = {
                clientX : 1,
                clientY : 2
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchStartPosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchStartPosition)]
                });
            }
        }

        simulateInput();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            position : new Cartesian2(1, 2)
        });

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();

        expect(action).not.toHaveBeenCalled();
    });

    it('handles touch move', function() {
        var eventType = ScreenSpaceEventType.MOUSE_MOVE;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        // start, then move
        function simulateInput() {
            var touchStartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touchMovePosition = {
                clientX : 10,
                clientY : 11
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchStartPosition));
                DomEventSimulator.firePointerMove(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchMovePosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchStartPosition)]
                });
                DomEventSimulator.fireTouchMove(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchMovePosition)]
                });
            }
        }

        simulateInput();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            startPosition : new Cartesian2(1, 2),
            endPosition : new Cartesian2(10, 11)
        });

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();

        expect(action).not.toHaveBeenCalled();
    });

    it('handles touch end', function() {
        var eventType = ScreenSpaceEventType.LEFT_UP;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        // start, then end
        function simulateInput() {
            var touchStartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touchEndPosition = {
                clientX : 1,
                clientY : 2
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchStartPosition));
                DomEventSimulator.firePointerUp(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchEndPosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchStartPosition)]
                });
                DomEventSimulator.fireTouchEnd(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchEndPosition)]
                });
            }
        }

        simulateInput();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            position : new Cartesian2(1, 2)
        });

        action.calls.reset();

        // start, move, then end
        function simulateInput2() {
            var touchStartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touchMovePosition = {
                clientX : 10,
                clientY : 11
            };
            var touchEndPosition = {
                clientX : 10,
                clientY : 11
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchStartPosition));
                DomEventSimulator.firePointerMove(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchMovePosition));
                DomEventSimulator.firePointerUp(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchEndPosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchStartPosition)]
                });
                DomEventSimulator.fireTouchMove(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchMovePosition)]
                });
                DomEventSimulator.fireTouchEnd(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchEndPosition)]
                });
            }
        }

        simulateInput2();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            position : new Cartesian2(10, 11)
        });

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();
        simulateInput2();

        expect(action).not.toHaveBeenCalled();
    });

    it('handles touch pinch start', function() {
        var eventType = ScreenSpaceEventType.PINCH_START;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        // touch 1, then touch 2
        function simulateInput() {
            var touch1StartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touch2StartPosition = {
                clientX : 3,
                clientY : 4
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touch1StartPosition));
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 2
                }, touch2StartPosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touch1StartPosition)]
                });
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touch1StartPosition), combine({
                        identifier : 1
                    }, touch2StartPosition)]
                });
            }
        }

        simulateInput();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            position1 : new Cartesian2(1, 2),
            position2 : new Cartesian2(3, 4)
        });

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();

        expect(action).not.toHaveBeenCalled();
    });

    it('handles touch pinch move', function() {
        var eventType = ScreenSpaceEventType.PINCH_MOVE;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        // touch 1, then touch 2, then move
        function simulateInput() {
            var touch1StartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touch2StartPosition = {
                clientX : 4,
                clientY : 3
            };
            var touch1MovePosition = {
                clientX : 10,
                clientY : 11
            };
            var touch2MovePosition = {
                clientX : 21,
                clientY : 20
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touch1StartPosition));
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 2
                }, touch2StartPosition));
                DomEventSimulator.firePointerMove(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touch1MovePosition));
                DomEventSimulator.firePointerMove(element, combine({
                    pointerType : 'touch',
                    pointerId : 2
                }, touch2MovePosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touch1StartPosition)]
                });
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touch1StartPosition), combine({
                        identifier : 1
                    }, touch2StartPosition)]
                });
                DomEventSimulator.fireTouchMove(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touch1MovePosition), combine({
                        identifier : 1
                    }, touch2MovePosition)]
                });
            }
        }

        simulateInput();

        // original delta X: 3
        // original delta Y: 1
        // final delta X: 11
        // final delta Y: 9

        if (usePointerEvents) {
            // because every pointer event is separate, one touch moves, then the other.
            expect(action.calls.count()).toEqual(2);

            // intermediate delta X: -6
            // intermediate delta Y: -8
            expect(action).toHaveBeenCalledWith({
                distance : {
                    startPosition : new Cartesian2(0, Math.sqrt(3 * 3 + 1 * 1) * 0.25),
                    endPosition : new Cartesian2(0, Math.sqrt(-6 * -6 + -8 * -8) * 0.25)
                },
                angleAndHeight : {
                    startPosition : new Cartesian2(Math.atan2(1, 3), (3 + 2) * 0.125),
                    endPosition : new Cartesian2(Math.atan2(-8, -6), (3 + 11) * 0.125)
                }
            });
            expect(action).toHaveBeenCalledWith({
                distance : {
                    startPosition : new Cartesian2(0, Math.sqrt(-6 * -6 + -8 * -8) * 0.25),
                    endPosition : new Cartesian2(0, Math.sqrt(11 * 11 + 9 * 9) * 0.25)
                },
                angleAndHeight : {
                    startPosition : new Cartesian2(Math.atan2(-8, -6), (3 + 11) * 0.125),
                    endPosition : new Cartesian2(Math.atan2(9, 11), (11 + 20) * 0.125)
                }
            });
        } else {
            // touch events can move both touches simultaneously
            expect(action.calls.count()).toEqual(1);
            expect(action).toHaveBeenCalledWith({
                distance : {
                    startPosition : new Cartesian2(0, Math.sqrt(3 * 3 + 1 * 1) * 0.25),
                    endPosition : new Cartesian2(0, Math.sqrt(11 * 11 + 9 * 9) * 0.25)
                },
                angleAndHeight : {
                    startPosition : new Cartesian2(Math.atan2(1, 3), (3 + 2) * 0.125),
                    endPosition : new Cartesian2(Math.atan2(9, 11), (11 + 20) * 0.125)
                }
            });
        }

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();

        expect(action).not.toHaveBeenCalled();
    });

    it('handles touch click', function() {
        var eventType = ScreenSpaceEventType.LEFT_CLICK;

        var action = createCloningSpy('action');
        handler.setInputAction(action, eventType);

        expect(handler.getInputAction(eventType)).toEqual(action);

        // start, then end
        function simulateInput() {
            var touchStartPosition = {
                clientX : 1,
                clientY : 2
            };
            var touchEndPosition = {
                clientX : 1,
                clientY : 2
            };

            if (usePointerEvents) {
                DomEventSimulator.firePointerDown(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchStartPosition));
                DomEventSimulator.firePointerUp(element, combine({
                    pointerType : 'touch',
                    pointerId : 1
                }, touchEndPosition));
            } else {
                DomEventSimulator.fireTouchStart(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchStartPosition)]
                });
                DomEventSimulator.fireTouchEnd(element, {
                    changedTouches : [combine({
                        identifier : 0
                    }, touchEndPosition)]
                });
            }
        }

        simulateInput();

        expect(action.calls.count()).toEqual(1);
        expect(action).toHaveBeenCalledWith({
            position : new Cartesian2(1, 2)
        });

        // should not be fired after removal
        action.calls.reset();

        handler.removeInputAction(eventType);

        simulateInput();

        expect(action).not.toHaveBeenCalled();
    });

    it('sets isDestroyed when destroyed', function() {
        expect(handler.isDestroyed()).toEqual(false);
        handler.destroy();
        expect(handler.isDestroyed()).toEqual(true);
    });

    it('unregisters event listeners when destroyed', function() {
        handler = handler.destroy();

        spyOn(element, 'addEventListener').and.callThrough();
        spyOn(element, 'removeEventListener').and.callThrough();

        handler = new ScreenSpaceEventHandler(element);

        expect(element.addEventListener.calls.count()).not.toEqual(0);
        expect(element.removeEventListener.calls.count()).toEqual(0);

        handler.destroy();

        expect(element.removeEventListener.calls.count()).toEqual(element.addEventListener.calls.count());
    });
});
