/*
 * Copyright (c) 2010, 2011, Oracle  and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 */

package com.sun.javafx.tk.quantum;

import java.security.AccessController;
import java.security.PrivilegedAction;

import javafx.geometry.Point2D;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;

import com.sun.glass.events.GestureEvent;
import com.sun.glass.events.KeyEvent;
import com.sun.glass.events.MouseEvent;
import com.sun.glass.events.ViewEvent;
import com.sun.glass.events.TouchEvent;
import com.sun.glass.events.SwipeGesture;
import com.sun.glass.ui.Clipboard;
import com.sun.glass.ui.ClipboardAssistance;
import com.sun.glass.ui.View;
import com.sun.prism.impl.PrismSettings;
import com.sun.javafx.PlatformUtil;
import javafx.scene.input.TouchPoint;
import javafx.event.EventType;
import javafx.scene.input.SwipeEvent;
import javafx.scene.input.RotateEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.ZoomEvent;

public class GlassViewEventHandler extends View.EventHandler {

    private static boolean verbose = 
        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override public Boolean run() {
                return Boolean.getBoolean("quantum.verbose");
            }
        });

    private ViewScene scene;
    private int initialWidth;
    private int initialHeight;
    private final GlassSceneDnDEventHandler dndHandler;

    private GestureRecognizers gestures = new GestureRecognizers();

    public GlassViewEventHandler(final ViewScene scene) {
        this.scene = scene;
        this.initialWidth = 0;
        this.initialHeight = 0;
        
        dndHandler = new GlassSceneDnDEventHandler(scene, null, verbose);

        if (PlatformUtil.isWindows()) {
            this.gestures.add(new SwipeGestureRecognizer(scene));
        }
    }

    // Default fullscreen allows limited keyboard input.
    // It will only receive events from the following keys:
    // DOWN, UP, LEFT, RIGHT, SPACE, TAB, PAGE_UP, PAGE_DOWN,
    // HOME, END, ENTER.
    private boolean allowableFullScreenKeys(int key) {
        switch (key) {
            case KeyEvent.VK_DOWN:
            case KeyEvent.VK_UP:
            case KeyEvent.VK_LEFT:
            case KeyEvent.VK_RIGHT:
            case KeyEvent.VK_SPACE:
            case KeyEvent.VK_TAB:
            case KeyEvent.VK_PAGE_DOWN:
            case KeyEvent.VK_PAGE_UP:
            case KeyEvent.VK_HOME:
            case KeyEvent.VK_END:
            case KeyEvent.VK_ENTER:
                return true;
        }
        return false;
    }

    private boolean checkFullScreenKeyEvent(int type, int key, char chars[], int modifiers) {
        if (!scene.getWindowStage().isTrustedFullScreen()) {
            return allowableFullScreenKeys(key);
        }
        return true;
    }

    private final QuantumToolkit toolkit   = (QuantumToolkit)QuantumToolkit.getToolkit();
    private final PaintCollector collector = PaintCollector.getInstance();

    private boolean toolkit() {
        return (toolkit.getFxUserThread() != null);
    }
 
    @Override public void handleKeyEvent(View view, long time, int type, int key,
                                         char characters[], int modifiers) {
        if (toolkit() == false) {
            return;
        }

        GlassPrismKeyEvent event = null;
        WindowStage stage = scene.getWindowStage();
        try {
            if (stage != null) {
                stage.setInEventHandler(true);
            }
            switch (type) {
                case com.sun.glass.events.KeyEvent.PRESS:
                    if (key == com.sun.glass.events.KeyEvent.VK_ESCAPE &&
                            view.isInFullscreen() && stage != null) {
                        stage.exitFullScreen();
                    }
                    // Fall through to the next case
                case com.sun.glass.events.KeyEvent.RELEASE:
                case com.sun.glass.events.KeyEvent.TYPED:
                    if (view.isInFullscreen()) {
                        if (!checkFullScreenKeyEvent(type, key, characters, modifiers)) {
                            break;
                        }
                    }
                    if (scene.sceneListener != null) {
                        event = new GlassPrismKeyEvent(scene, type, key, characters, modifiers);
                        scene.sceneListener.keyEvent(event);
                    }
                    break;
                default:
                    if (verbose) {
                        System.out.println("handleKeyEvent: unhandled type: " + type);
                    }
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        } finally {
            if (stage != null) {
                stage.setInEventHandler(false);
            }
        }
    }

    // Used to determine whether a synthetic CLICK is generated or not on UP event
    private boolean dragPerformed = false;

    // Used to determine whether a DRAG operation has been initiated on this window
    private int mouseButtonPressedMask = 0;

    @Override
    public void handleMouseEvent(View view, long time, int type, int button,
                                 int x, int y, int xAbs, int yAbs,
                                 int clickCount, int modifiers,
                                 boolean isPopupTrigger, boolean isSynthesized) {

        if (toolkit() == false) {
            return;
        }

        final boolean dragPerformed = this.dragPerformed;
        GlassPrismMouseEvent event = null;

        int buttonMask;
        switch (button) {
            case MouseEvent.BUTTON_LEFT:
                buttonMask = KeyEvent.MODIFIER_BUTTON_PRIMARY;
                break;
            case MouseEvent.BUTTON_OTHER:
                buttonMask = KeyEvent.MODIFIER_BUTTON_MIDDLE;
                break;
            case MouseEvent.BUTTON_RIGHT:
                buttonMask = KeyEvent.MODIFIER_BUTTON_SECONDARY;
                break;
            default:
                buttonMask = 0;
                break;
        }

        switch (type) {
            case MouseEvent.MOVE:
                if (button != MouseEvent.BUTTON_NONE) {
                    //RT-11305: the drag hasn't been started on this window -- ignore the event
                    return;
                }
                break;
            case MouseEvent.UP:
                if ((mouseButtonPressedMask & buttonMask) == 0) {
                    //RT-11305: the mouse button hasn't been pressed on this window -- ignore the event
                    return;
                }
                mouseButtonPressedMask &= ~buttonMask;
                break;
            case MouseEvent.DRAG:
                // Disable generating of a CLICK
                this.dragPerformed = true;
                break;
            case MouseEvent.DOWN:
                // Prepare to generate a CLICK
                this.dragPerformed = false;
                mouseButtonPressedMask |= buttonMask;
                break;
            case MouseEvent.ENTER:
            case MouseEvent.EXIT:
                break;
            default:
                if (verbose) {
                    System.out.println("handleMouseEvent: unhandled type: " + type);
                }
        }

        WindowStage stage = scene.getWindowStage();
        try {
            if (stage != null) {
                stage.setInEventHandler(true);
            }
            if (scene.sceneListener != null) {
                event = new GlassPrismMouseEvent(scene, type, button, x, y, xAbs, yAbs,
                                                 clickCount, modifiers, isPopupTrigger,
                                                 isSynthesized, 0.0);
                scene.sceneListener.mouseEvent(event);
            }

            if (type == MouseEvent.UP && !dragPerformed) {
                // synthesize click

                if (scene.sceneListener != null && toolkit()) {
                    event = new GlassPrismMouseEvent(scene, MouseEvent.CLICK, button,
                                                     x, y, xAbs, yAbs,
                                                     clickCount, modifiers, isPopupTrigger,
                                                     isSynthesized, 0.0);
                    scene.sceneListener.mouseEvent(event);
                }
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            if (stage != null) {
                stage.setInEventHandler(false);
            }
        }
    }

    @Override public void handleMenuEvent(View view, int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger)
    {
        if (toolkit() == false) {
            return;
        }
        WindowStage stage = scene.getWindowStage();
        try {
            if (stage != null) {
                stage.setInEventHandler(true);
            }
            if (scene.sceneListener != null) {
                scene.sceneListener.menuEvent(x, y, xAbs, yAbs, isKeyboardTrigger);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            if (stage != null) {
                stage.setInEventHandler(false);
            }
        }
    }

    @Override public void handleScrollEvent(View view, long time,
                                           int x, int y, int xAbs, int yAbs,
                                           double deltaX, double deltaY, int modifiers, 
                                           int lines, int chars,
                                           int defaultLines, int defaultChars,
                                           double xMultiplier, double yMultiplier)
    {
        if (toolkit() == false) {
            return;
        }

        GlassPrismMouseEvent event = null;
        WindowStage stage = scene.getWindowStage();
        try {
            if (stage != null) {
                stage.setInEventHandler(true);
            }
            if (scene.sceneListener != null) {
                scene.sceneListener.scrollEvent(javafx.scene.input.ScrollEvent.SCROLL,
                        deltaX, deltaY, 0, 0,
                        xMultiplier, yMultiplier,
                        0, // touchCount
                        chars, lines, defaultChars, defaultLines,
                        x, y, xAbs, yAbs,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
                        false, // this is always indirect
                        false); // this has no inertia
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            if (stage != null) {
                stage.setInEventHandler(false);
            }
        }
    }

    @Override public void handleInputMethodEvent(long time, String text,
                                                 int[] clauseBoundary,
                                                 int[] attrBoundary, byte[] attrValue,
                                                int commitCount, int cursorPos) {
        try {
            if (scene.sceneListener != null && toolkit()) {
                GlassPrismInputMethodEvent event = 
                    new GlassPrismInputMethodEvent(scene, text, clauseBoundary, attrBoundary,
                                                   attrValue, commitCount, cursorPos);
                scene.sceneListener.inputMethodEvent(event);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
    }

    @Override
    public double[] getInputMethodCandidatePos(int offset) {
        Point2D p2d = scene.inputMethodRequests.getTextLocation(offset);
        double[] ret = new double[2];
        ret[0] = p2d.getX();
        ret[1] = p2d.getY();
        return ret;
    }

    @Override public int handleDragEnter(View view, int x, int y, int xAbs, int yAbs,
                                         int recommendedDropAction,
                                         ClipboardAssistance dropTargetAssistant) {

        final TransferMode action =
                dndHandler.handleDragEnter(x, y, xAbs, yAbs,
                                           recommendedDropAction,
                                           dropTargetAssistant);
        return dndHandler.TransferModeToAction(action);
    }

    /* ViewScene -> QuantumToolkit -> PrismEventUtils -> TK normalized DragEvent */

    @Override public void handleDragLeave(View view, ClipboardAssistance dropTargetAssistant) {
        dndHandler.handleDragLeave(dropTargetAssistant);
    }

    @Override public int handleDragDrop(View view, int x, int y, int xAbs, int yAbs,
                                    int recommendedDropAction,
                                    ClipboardAssistance dropTargetAssistant) {
        final TransferMode action =
                dndHandler.handleDragDrop(x, y, xAbs, yAbs,
                                          recommendedDropAction,
                                          dropTargetAssistant);
        return dndHandler.TransferModeToAction(action);
    }

    @Override public int handleDragOver(View view, int x, int y, int xAbs, int yAbs,
                                        int recommendedDropAction,
                                        ClipboardAssistance dropTargetAssistant) {

        final TransferMode action =
                dndHandler.handleDragOver(x, y, xAbs, yAbs,
                                          recommendedDropAction,
                                          dropTargetAssistant);
        return dndHandler.TransferModeToAction(action);
    }

    private ClipboardAssistance dropSourceAssistant;
    
    @Override public void handleDragStart(View view, int button, int x, int y, int xAbs, int yAbs,
                                          ClipboardAssistance assistant) {
        dropSourceAssistant = assistant;
        dndHandler.handleDragStart(button, x, y, xAbs, yAbs, assistant);
    }

    @Override public void handleDragEnd(View view, int performedAction) {
        dndHandler.handleDragEnd(performedAction, dropSourceAssistant);
    }

    // TODO - dropTargetListener.dropActionChanged
    // TODO - dragSourceListener.dropActionChanged

    @Override public void handleViewEvent(View view, long time, final int type) {
        if (verbose) {
            System.err.println("handleViewEvent(" + System.nanoTime() + "): " + 
                               GlassEventUtils.getViewEventString(type) + PaintCollector.sceneSize(scene) +
                               " closed: " + view.isClosed());
        }
        if (scene.sceneListener == null || toolkit() == false) {
            return;
        }
        try {
            switch (type) {
                case ViewEvent.REPAINT:
                    scene.entireSceneNeedsRepaint();
                    if (PlatformUtil.isMac()) {
                        collector.liveRepaintRenderJob(scene);
                    }
                    break;
                case ViewEvent.RESIZE:
                    if (PrismSettings.verbose) {
                        System.out.println("RESIZE: " + System.nanoTime() +
                                           " w: " + view.getWidth() + " h: " + view.getHeight());
                    }
                    scene.sceneListener.changedSize(view.getWidth(), view.getHeight());
                    scene.entireSceneNeedsRepaint();
                    break;
                case ViewEvent.MOVE:
                    scene.sceneListener.changedLocation(view.getX(), view.getY());
                    break;
                case ViewEvent.FULLSCREEN_ENTER:
                case ViewEvent.FULLSCREEN_EXIT:
                    if (scene.getWindowStage() != null) {
                        scene.getWindowStage().fullscreenChanged(type == ViewEvent.FULLSCREEN_ENTER);
                    }
                    break;
                case ViewEvent.ADD:
                case ViewEvent.REMOVE:
                case ViewEvent.LOCKED:
                case ViewEvent.UNLOCKED:
                    // unhandled
                    break;
                default:
                    throw new RuntimeException("handleViewEvent: unhandled type: " + type);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
    }

    @Override public void handleScrollGestureEvent(View view, long time, int type,
            int modifiers, boolean isDirect, boolean isInertia, int touchCount,
            int x, int y, int xAbs, int yAbs, double dx, double dy,
            double totaldx, double totaldy) {

        if (toolkit() == false) {
            return;
        }

        GlassPrismMouseEvent event = null;
        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                EventType<ScrollEvent> eventType = ScrollEvent.SCROLL;
                switch(type) {
                    case GestureEvent.GESTURE_STARTED:
                        eventType = ScrollEvent.SCROLL_STARTED;
                        break;
                    case GestureEvent.GESTURE_PERFORMED:
                        eventType = ScrollEvent.SCROLL;
                        break;
                    case GestureEvent.GESTURE_FINISHED:
                        eventType = ScrollEvent.SCROLL_FINISHED;
                        break;
                    default:
                        throw new RuntimeException("Unknown scroll event type: " + type);
                }
                scene.sceneListener.scrollEvent(eventType, dx, dy, totaldx, totaldy,
                        1.0, 1.0,
                        touchCount,
                        0, 0, 0, 0,
                        x == View.GESTURE_NO_VALUE ? Double.NaN : x,
                        y == View.GESTURE_NO_VALUE ? Double.NaN : y,
                        xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs,
                        yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
                        isDirect, isInertia);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }
    }

    @Override public void handleZoomGestureEvent(View view, long time, int type,
            int modifiers, boolean isDirect, boolean isInertia,
            int originx, int originy,
            int originxAbs, int originyAbs,
            double scale, double expansion,
            double totalscale, double totalexpansion) {

        if (toolkit() == false) {
            return;
        }

        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                EventType<ZoomEvent> eventType = ZoomEvent.ZOOM;
                switch(type) {
                    case GestureEvent.GESTURE_STARTED:
                        eventType = ZoomEvent.ZOOM_STARTED;
                        break;
                    case GestureEvent.GESTURE_PERFORMED:
                        eventType = ZoomEvent.ZOOM;
                        break;
                    case GestureEvent.GESTURE_FINISHED:
                        eventType = ZoomEvent.ZOOM_FINISHED;
                        break;
                    default:
                        throw new RuntimeException("Unknown scroll event type: " + type);
                }
                scene.sceneListener.zoomEvent(eventType, scale, totalscale,
                        originx == View.GESTURE_NO_VALUE ? Double.NaN : originx,
                        originy == View.GESTURE_NO_VALUE ? Double.NaN : originy,
                        originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs,
                        originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
                        isDirect, isInertia);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }
    }

    @Override public void handleRotateGestureEvent(View view, long time, int type,
            int modifiers, boolean isDirect, boolean isInertia,
            int originx, int originy,
            int originxAbs, int originyAbs,
            double dangle, double totalangle) {
        if (toolkit() == false) {
            return;
        }

        GlassPrismMouseEvent event = null;
        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                EventType<RotateEvent> eventType = RotateEvent.ROTATE;
                switch(type) {
                    case GestureEvent.GESTURE_STARTED:
                        eventType = RotateEvent.ROTATION_STARTED;
                        break;
                    case GestureEvent.GESTURE_PERFORMED:
                        eventType = RotateEvent.ROTATE;
                        break;
                    case GestureEvent.GESTURE_FINISHED:
                        eventType = RotateEvent.ROTATION_FINISHED;
                        break;
                    default:
                        throw new RuntimeException("Unknown scroll event type: " + type);
                }
                scene.sceneListener.rotateEvent(eventType, dangle, totalangle,
                        originx == View.GESTURE_NO_VALUE ? Double.NaN : originx,
                        originy == View.GESTURE_NO_VALUE ? Double.NaN : originy,
                        originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs,
                        originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
                        isDirect, isInertia);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }
    }

    @Override public void handleSwipeGestureEvent(View view, long time, int type,
                                        int modifiers, boolean isDirect,
                                        boolean isInertia, int touchCount, 
                                        int dir, int x, int y, int xAbs, int yAbs) {
        if (toolkit() == false) {
            return;
        }

        GlassPrismMouseEvent event = null;
        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                EventType<SwipeEvent> eventType = null;
                switch(dir) {
                    case SwipeGesture.DIR_UP:
                        eventType = SwipeEvent.SWIPE_UP;
                        break;
                    case SwipeGesture.DIR_DOWN:
                        eventType = SwipeEvent.SWIPE_DOWN;
                        break;
                    case SwipeGesture.DIR_LEFT:
                        eventType = SwipeEvent.SWIPE_LEFT;
                        break;
                    case SwipeGesture.DIR_RIGHT:
                        eventType = SwipeEvent.SWIPE_RIGHT;
                        break;
                    default:
                        throw new RuntimeException("Unknown swipe event direction: " + dir);
                }

                scene.sceneListener.swipeEvent(eventType, touchCount,
                        x == View.GESTURE_NO_VALUE ? Double.NaN : x,
                        y == View.GESTURE_NO_VALUE ? Double.NaN : y,
                        xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs,
                        yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
                        isDirect);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }
    }

    @Override public void handleBeginTouchEvent(View view, long time, int modifiers,
            boolean isDirect, int touchEventCount) {

        if (toolkit() == false) {
            return;
        }

        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                scene.sceneListener.touchEventBegin(time, touchEventCount,
                        isDirect,
                        (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
                        (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
                        (modifiers & KeyEvent.MODIFIER_ALT) != 0,
                        (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }

        gestures.notifyBeginTouchEvent(time, modifiers, isDirect, touchEventCount);
    }

    @Override public void handleNextTouchEvent(View view, long time, int type, long touchId,
            int x, int y, int xAbs, int yAbs) {

        if (toolkit() == false) {
            return;
        }

        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                TouchPoint.State state = null;
                switch(type) {
                    case TouchEvent.TOUCH_PRESSED:
                        state = TouchPoint.State.PRESSED;
                        break;
                    case TouchEvent.TOUCH_MOVED:
                        state = TouchPoint.State.MOVED;
                        break;
                    case TouchEvent.TOUCH_STILL:
                        state = TouchPoint.State.STATIONARY;
                        break;
                    case TouchEvent.TOUCH_RELEASED:
                        state = TouchPoint.State.RELEASED;
                        break;
                    default:
                        throw new RuntimeException("Unknown touch state: " + state);
                }

                scene.sceneListener.touchEventNext(state, touchId,
                        x, y, xAbs, yAbs);
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }

        gestures.notifyNextTouchEvent(time, type, touchId, x, y, xAbs, yAbs);
    }

    @Override public void handleEndTouchEvent(View view, long time) {

        if (toolkit() == false) {
            return;
        }

        WindowStage stage = scene.getWindowStage();
        try {
            stage.setInEventHandler(true);
            if (scene.sceneListener != null) {
                scene.sceneListener.touchEventEnd();
            }
        } catch (RuntimeException ex) {
            if (verbose) {
                ex.printStackTrace();
            }
            throw ex;
        } catch (Throwable t) {
            if (verbose) {
                t.printStackTrace();
            }
            throw new RuntimeException(t);
        }
        finally {
            stage.setInEventHandler(false);
        }

        gestures.notifyEndTouchEvent(time);
    }
}
