/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 */
package com.sun.glass.ui;

import com.sun.glass.events.GestureEvent;
import com.sun.glass.events.TouchEvent;

/**
 * @author Alexey Semenyuk
 */
public class GestureSupport
{
    private final static double THRESHOLD_SCROLL = 1.0;
    private final static double THRESHOLD_SCALE = 0.01;
    private final static double THRESHOLD_EXPANSION = 0.01;
    private final static double THRESHOLD_ROTATE = Math.toDegrees(Math.PI / 180);

    private boolean scrolling = false;
    private boolean rotating = false;
    private boolean zooming = false;
    private boolean swiping = false;

    private double totalScrollX = Double.NaN;
    private double totalScrollY = Double.NaN;
    private double totalScale = 1.0;
    private double totalExpansion = Double.NaN;
    private double totalRotation = 0.0;

    private boolean zoomWithExpansion;

    private static double multiplicativeDelta(double from, double to) {
        if (from == 0.0) {
            return View.GESTURE_NO_DOUBLE_VALUE;
        }
        return (to / from);
    }

    private static int getIdForTheFirstEvent(boolean isInertia) {
        if (isInertia) {
            return GestureEvent.GESTURE_PERFORMED;
        }
        return GestureEvent.GESTURE_STARTED;
    }

    private int setScrolling(boolean isInertia) {
        int eventID = GestureEvent.GESTURE_PERFORMED;
        if (!scrolling) {
            eventID = getIdForTheFirstEvent(isInertia);
            scrolling = true;
        }
        return eventID;
    }

    private int setRotating(boolean isInertia) {
        int eventID = GestureEvent.GESTURE_PERFORMED;
        if (!rotating) {
            eventID = getIdForTheFirstEvent(isInertia);
            rotating = true;
        }
        return eventID;
    }

    private int setZooming(boolean isInertia) {
        int eventID = GestureEvent.GESTURE_PERFORMED;
        if (!zooming) {
            eventID = getIdForTheFirstEvent(isInertia);
            zooming = true;
        }
        return eventID;
    }

    private int setSwiping(boolean isInertia) {
        int eventID = GestureEvent.GESTURE_PERFORMED;
        if (!swiping) {
            eventID = getIdForTheFirstEvent(isInertia);
            swiping = true;
        }
        return eventID;
    }

    public GestureSupport(boolean zoomWithExpansion) {
        this.zoomWithExpansion = zoomWithExpansion;
    }

    public boolean isScrolling() {
        return scrolling;
    }

    public boolean isRotating() {
        return rotating;
    }

    public boolean isZooming() {
        return zooming;
    }

    public boolean isSwiping() {
        return swiping;
    }

    public void handleScrollingEnd(View view, int modifiers, int touchCount,
                                   boolean isDirect, boolean isInertia, int x,
                                   int y, int xAbs, int yAbs) {
        scrolling = false;
        if (isInertia) {
            return;
        }
        view.notifyScrollGestureEvent(GestureEvent.GESTURE_FINISHED, modifiers,
                                      isDirect, isInertia, touchCount, x, y,
                                      xAbs, yAbs, 0, 0,
                                      totalScrollX, totalScrollY);
    }

    public void handleRotationEnd(View view, int modifiers, boolean isDirect,
                                  boolean isInertia, int x, int y, int xAbs,
                                  int yAbs) {
        rotating = false;
        if (isInertia) {
            return;
        }
        view.notifyRotateGestureEvent(GestureEvent.GESTURE_FINISHED, modifiers,
                                      isDirect, isInertia, x, y, xAbs, yAbs, 0,
                                      totalRotation);
    }

    public void handleZoomingEnd(View view, int modifiers, boolean isDirect,
                                 boolean isInertia, int x, int y, int xAbs,
                                 int yAbs) {
        zooming = false;
        if (isInertia) {
            return;
        }
        view.notifyZoomGestureEvent(GestureEvent.GESTURE_FINISHED, modifiers,
                                    isDirect, isInertia, x, y, xAbs, yAbs,
                                    View.GESTURE_NO_DOUBLE_VALUE, 0, totalScale,
                                    totalExpansion);
    }

    public void handleSwipeEnd(View view, int modifiers, boolean isDirect,
                               boolean isInertia, int x, int y, int xAbs,
                               int yAbs) {
        swiping = false;
        if (isInertia) {
            return;
        }
        view.notifySwipeGestureEvent(GestureEvent.GESTURE_FINISHED, modifiers,
                                     isDirect, isInertia, View.GESTURE_NO_VALUE,
                                     View.GESTURE_NO_VALUE, x, y, xAbs, yAbs);
    }

    public void handleTotalZooming(View view, int modifiers, boolean isDirect,
                                   boolean isInertia, int x, int y, int xAbs,
                                   int yAbs, double scale, double expansion) {

        double baseScale = totalScale;
        double baseExpansion = totalExpansion;
        if (!zooming) {
            baseScale = 1.0;
            baseExpansion = 0.0;
        }

        if (Math.abs(scale - baseScale) < THRESHOLD_SCALE &&
                (!zoomWithExpansion ||
                    Math.abs(expansion - baseExpansion) < THRESHOLD_SCALE)) {
            return;
        }

        double deltaExpansion = View.GESTURE_NO_DOUBLE_VALUE;
        if (zoomWithExpansion) {
            deltaExpansion = expansion - baseExpansion;
        } else {
            expansion = View.GESTURE_NO_DOUBLE_VALUE;
        }

        totalScale = scale;
        totalExpansion = expansion;
        final int eventID = setZooming(isInertia);

        view.notifyZoomGestureEvent(eventID, modifiers, isDirect, isInertia, x,
                                    y, xAbs, yAbs,
                                    multiplicativeDelta(baseScale, totalScale),
                                    deltaExpansion, scale, expansion);
    }

    public void handleTotalRotation(View view, int modifiers, boolean isDirect,
                                    boolean isInertia, int x, int y, int xAbs,
                                    int yAbs, double rotation) {

        double baseRotation = totalRotation;
        if (!rotating) {
            baseRotation = 0.0;
        }

        if (Math.abs(rotation - baseRotation) < THRESHOLD_ROTATE) {
            return;
        }

        totalRotation = rotation;
        final int eventID = setRotating(isInertia);

        view.notifyRotateGestureEvent(eventID, modifiers, isDirect, isInertia, x,
                                      y, xAbs, yAbs, rotation - baseRotation,
                                      rotation);
    }

    public void handleTotalScrolling(View view, int modifiers, boolean isDirect,
                                     boolean isInertia, int touchCount, int x,
                                     int y, int xAbs, int yAbs,
                                     double dx, double dy) {
        double baseScrollX = totalScrollX;
        double baseScrollY = totalScrollY;
        if (!scrolling) {
            baseScrollX = 0;
            baseScrollY = 0;
        }

        if (Math.abs(dx - totalScrollX) < THRESHOLD_SCROLL &&
                Math.abs(dy - totalScrollY) < THRESHOLD_SCROLL) {
            return;
        }

        totalScrollX = dx;
        totalScrollY = dy;
        final int eventID = setScrolling(isInertia);

        view.notifyScrollGestureEvent(eventID, modifiers, isDirect, isInertia,
                                      touchCount, x, y, xAbs, yAbs,
                                      dx - baseScrollX,
                                      dy - baseScrollY, dx, dy);
    }

    public void handleDeltaZooming(View view, int modifiers, boolean isDirect,
                                   boolean isInertia, int x, int y, int xAbs,
                                   int yAbs, double scale, double expansion) {

        double baseScale = totalScale;
        double baseExpansion = totalExpansion;
        if (!zooming) {
            baseScale = 1.0;
            baseExpansion = 0.0;
        }

        totalScale = baseScale + scale;
        if (zoomWithExpansion) {
            totalExpansion = baseExpansion + expansion;
        } else {
            totalExpansion = View.GESTURE_NO_DOUBLE_VALUE;
        }

        final int eventID = setZooming(isInertia);

        view.notifyZoomGestureEvent(eventID, modifiers, isDirect, isInertia, x,
                                    y, xAbs, yAbs,
                                    multiplicativeDelta(baseScale, totalScale),
                                    expansion, totalScale, totalExpansion);
    }

    public void handleDeltaRotation(View view, int modifiers, boolean isDirect,
                                    boolean isInertia, int x, int y, int xAbs,
                                    int yAbs, double rotation) {

        double baseRotation = totalRotation;
        if (!rotating) {
            baseRotation = 0.0;
        }

        totalRotation = baseRotation + rotation;
        final int eventID = setRotating(isInertia);

        view.notifyRotateGestureEvent(eventID, modifiers, isDirect, isInertia, x,
                                      y, xAbs, yAbs, rotation, totalRotation);
    }

    public void handleDeltaScrolling(View view, int modifiers, boolean isDirect,
                                     boolean isInertia, int touchCount, int x,
                                     int y, int xAbs, int yAbs,
                                     double dx, double dy) {
        double baseScrollX = totalScrollX;
        double baseScrollY = totalScrollY;
        if (!scrolling) {
            baseScrollX = 0;
            baseScrollY = 0;
        }

        totalScrollX = baseScrollX + dx;
        totalScrollY = baseScrollY + dy;
        final int eventID = setScrolling(isInertia);

        view.notifyScrollGestureEvent(eventID, modifiers, isDirect, isInertia,
                                      touchCount, x, y, xAbs, yAbs, dx, dy,
                                      totalScrollX, totalScrollY);
    }

    public void handleSwipe(View view, int modifiers, boolean isDirect,
                            boolean isInertia, int touchCount, int dir, int x,
                            int y, int xAbs, int yAbs) {
        final int eventID = setSwiping(isInertia);
        view.notifySwipeGestureEvent(eventID, modifiers, isDirect, isInertia,
                                     touchCount, dir, x, y, xAbs, yAbs);
    }

    public static void handleSwipePerformed(View view, int modifiers,
                                            boolean isDirect, boolean isInertia,
                                            int touchCount, int dir, int x,
                                            int y, int xAbs, int yAbs) {
        view.notifySwipeGestureEvent(GestureEvent.GESTURE_PERFORMED, modifiers,
                                     isDirect, isInertia, touchCount, dir, x, y,
                                     xAbs, yAbs);
    }

    public static void handleScrollingPerformed(View view, int modifiers,
                                                boolean isDirect,
                                                boolean isInertia, int x, int y,
                                                int xAbs,
                                                int yAbs, double dx, double dy) {
        view.notifyScrollGestureEvent(GestureEvent.GESTURE_PERFORMED, modifiers,
                                      isDirect, isInertia, 0, x, y, xAbs, yAbs,
                                      dx, dy, dx, dy);
    }

    public static int getScrollGestureTouchCount(TouchInputSupport touches) {
        final int touchCount = touches.getTouchCount();
        if (touchCount == 0) {
            return View.GESTURE_NO_VALUE;
        }
        return touchCount;
    }
}
