// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "ui/events/gesture_detection/gesture_detection_export.h"
#include "ui/events/gesture_detection/velocity_tracker_state.h"

namespace ui {

class MotionEvent;

// Port of GestureDetector.java from Android
// * platform/frameworks/base/core/java/android/view/GestureDetector.java
// * Change-Id: Ib470735ec929b0b358fca4597e92dc81084e675f
// * Please update the Change-Id as upstream Android changes are pulled.
class GestureDetector {
 public:
  struct GESTURE_DETECTION_EXPORT Config {
    Config();
    ~Config();

    base::TimeDelta longpress_timeout;
    base::TimeDelta showpress_timeout;
    base::TimeDelta double_tap_timeout;

    // The minimum duration between the first tap's up event and the second
    // tap's down event for an interaction to be considered a double-tap.
    base::TimeDelta double_tap_min_time;

    // Distance a touch can wander before a scroll will occur (in dips).
    float touch_slop;

    // Distance the first touch can wander before it is no longer considered a
    // double tap (in dips).
    float double_tap_slop;

    // Minimum velocity to initiate a fling (in dips/second).
    float minimum_fling_velocity;

    // Maximum velocity of an initiated fling (in dips/second).
    float maximum_fling_velocity;

    // Whether |OnSwipe| should be called after a secondary touch is released
    // while a logical swipe gesture is active. Defaults to false.
    bool swipe_enabled;

    // Minimum velocity to initiate a swipe (in dips/second).
    float minimum_swipe_velocity;

    // Maximum angle of the swipe from its dominant component axis, between
    // (0, 45] degrees. The closer this is to 0, the closer the dominant
    // direction of the swipe must be to up, down left or right.
    float maximum_swipe_deviation_angle;

    // Whether |OnTwoFingerTap| should be called for two finger tap
    // gestures. Defaults to false.
    bool two_finger_tap_enabled;

    // Maximum distance between pointers for a two finger tap.
    float two_finger_tap_max_separation;

    // Maximum time the second pointer can be active for a two finger tap.
    base::TimeDelta two_finger_tap_timeout;
  };

  class GestureListener {
   public:
    virtual ~GestureListener() {}
    virtual bool OnDown(const MotionEvent& e) = 0;
    virtual void OnShowPress(const MotionEvent& e) = 0;
    virtual bool OnSingleTapUp(const MotionEvent& e) = 0;
    virtual void OnLongPress(const MotionEvent& e) = 0;
    virtual bool OnScroll(const MotionEvent& e1,
                          const MotionEvent& e2,
                          float distance_x,
                          float distance_y) = 0;
    virtual bool OnFling(const MotionEvent& e1,
                         const MotionEvent& e2,
                         float velocity_x,
                         float velocity_y) = 0;
    // Added for Chromium (Aura).
    virtual bool OnSwipe(const MotionEvent& e1,
                         const MotionEvent& e2,
                         float velocity_x,
                         float velocity_y) = 0;
    virtual bool OnTwoFingerTap(const MotionEvent& e1,
                                const MotionEvent& e2) = 0;
  };

  class DoubleTapListener {
   public:
    virtual ~DoubleTapListener() {}
    virtual bool OnSingleTapConfirmed(const MotionEvent& e) = 0;
    virtual bool OnDoubleTap(const MotionEvent& e) = 0;
    virtual bool OnDoubleTapEvent(const MotionEvent& e) = 0;
  };

  // A convenience class to extend when you only want to listen for a subset
  // of all the gestures. This implements all methods in the
  // |GestureListener| and |DoubleTapListener| but does
  // nothing and returns false for all applicable methods.
  class SimpleGestureListener : public GestureListener,
                                public DoubleTapListener {
   public:
    // GestureListener implementation.
    virtual bool OnDown(const MotionEvent& e) OVERRIDE;
    virtual void OnShowPress(const MotionEvent& e) OVERRIDE;
    virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE;
    virtual void OnLongPress(const MotionEvent& e) OVERRIDE;
    virtual bool OnScroll(const MotionEvent& e1,
                          const MotionEvent& e2,
                          float distance_x,
                          float distance_y) OVERRIDE;
    virtual bool OnFling(const MotionEvent& e1,
                         const MotionEvent& e2,
                         float velocity_x,
                         float velocity_y) OVERRIDE;
    virtual bool OnSwipe(const MotionEvent& e1,
                         const MotionEvent& e2,
                         float velocity_x,
                         float velocity_y) OVERRIDE;
    virtual bool OnTwoFingerTap(const MotionEvent& e1,
                                const MotionEvent& e2) OVERRIDE;

    // DoubleTapListener implementation.
    virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE;
    virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE;
    virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE;
  };

  GestureDetector(const Config& config,
                  GestureListener* listener,
                  DoubleTapListener* optional_double_tap_listener);
  ~GestureDetector();

  bool OnTouchEvent(const MotionEvent& ev);

  // Setting a valid |double_tap_listener| will enable double-tap detection,
  // wherein calls to |OnSimpleTapConfirmed| are delayed by the tap timeout.
  // Note: The listener must never be changed while |is_double_tapping| is true.
  void SetDoubleTapListener(DoubleTapListener* double_tap_listener);

  bool has_doubletap_listener() const { return double_tap_listener_ != NULL; }

  bool is_double_tapping() const { return is_double_tapping_; }

  void set_longpress_enabled(bool enabled) { longpress_enabled_ = enabled; }

 private:
  void Init(const Config& config);
  void OnShowPressTimeout();
  void OnLongPressTimeout();
  void OnTapTimeout();
  void Cancel();
  void CancelTaps();
  bool IsConsideredDoubleTap(const MotionEvent& first_down,
                             const MotionEvent& first_up,
                             const MotionEvent& second_down) const;
  bool HandleSwipeIfNeeded(const MotionEvent& up, float vx, float vy);

  class TimeoutGestureHandler;
  scoped_ptr<TimeoutGestureHandler> timeout_handler_;
  GestureListener* const listener_;
  DoubleTapListener* double_tap_listener_;

  float touch_slop_square_;
  float double_tap_touch_slop_square_;
  float double_tap_slop_square_;
  float two_finger_tap_distance_square_;
  float min_fling_velocity_;
  float max_fling_velocity_;
  float min_swipe_velocity_;
  float min_swipe_direction_component_ratio_;
  base::TimeDelta double_tap_timeout_;
  base::TimeDelta two_finger_tap_timeout_;
  base::TimeDelta double_tap_min_time_;

  bool still_down_;
  bool defer_confirm_single_tap_;
  bool always_in_tap_region_;
  bool always_in_bigger_tap_region_;
  bool two_finger_tap_allowed_for_gesture_;

  scoped_ptr<MotionEvent> current_down_event_;
  scoped_ptr<MotionEvent> previous_up_event_;
  scoped_ptr<MotionEvent> secondary_pointer_down_event_;

  // True when the user is still touching for the second tap (down, move, and
  // up events). Can only be true if there is a double tap listener attached.
  bool is_double_tapping_;

  float last_focus_x_;
  float last_focus_y_;
  float down_focus_x_;
  float down_focus_y_;

  bool longpress_enabled_;
  bool swipe_enabled_;
  bool two_finger_tap_enabled_;

  // Determines speed during touch scrolling.
  VelocityTrackerState velocity_tracker_;

  DISALLOW_COPY_AND_ASSIGN(GestureDetector);
};

}  // namespace ui

#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
