// Copyright 2012 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <map>
#include <set>

#include <gtest/gtest.h>  // for FRIEND_TEST

#include "include/finger_metrics.h"
#include "include/gestures.h"
#include "include/interpreter.h"
#include "include/macros.h"
#include "include/prop_registry.h"
#include "include/tracer.h"
#include "include/vector.h"

#ifndef GESTURES_IMMEDIATE_INTERPRETER_H_
#define GESTURES_IMMEDIATE_INTERPRETER_H_

namespace gestures {

typedef std::set<short> FingerMap;

// This interpreter keeps some memory of the past and, for each incoming
// frame of hardware state, immediately determines the gestures to the best
// of its abilities.

class ImmediateInterpreter;
class MultitouchMouseInterpreter;

class TapRecord {
  FRIEND_TEST(ImmediateInterpreterTest, TapRecordTest);
 public:
  explicit TapRecord(const ImmediateInterpreter* immediate_interpreter)
      : immediate_interpreter_(immediate_interpreter),
        t5r2_(false),
        t5r2_touched_size_(0),
        t5r2_released_size_(0),
        fingers_below_max_age_(true) {}
  void Update(const HardwareState& hwstate,
              const HardwareState& prev_hwstate,
              const std::set<short>& added,
              const std::set<short>& removed,
              const std::set<short>& dead);
  void Clear();

  // if any gesturing fingers are moving
  bool Moving(const HardwareState& hwstate, const float dist_max) const;
  bool Motionless(const HardwareState& hwstate,
                  const HardwareState& prev_hwstate,
                  const float max_speed) const;

  bool TapBegan() const;  // if a tap has begun
  bool TapComplete() const;  // is a completed tap
  // return GESTURES_BUTTON_* value or 0, if tap was too light
  int TapType() const;
  // If any contact has met the minimum pressure threshold
  bool MinTapPressureMet() const;
  bool FingersBelowMaxAge() const;
 private:
  void NoteTouch(short the_id, const FingerState& fs);  // Adds to touched_
  void NoteRelease(short the_id);  // Adds to released_
  void Remove(short the_id);  // Removes from touched_ and released_

  float CotapMinPressure() const;

  std::map<short, FingerState> touched_;
  std::set<short> released_;
  // At least one finger must meet the minimum pressure requirement during a
  // tap. This set contains the fingers that have.
  std::set<short> min_tap_pressure_met_;
  // All fingers must meet the cotap pressure, which is half of the min tap
  // pressure.
  std::set<short> min_cotap_pressure_met_;
  // Used to fetch properties
  const ImmediateInterpreter* immediate_interpreter_;
  // T5R2: For these pads, we try to track individual IDs, but if we get an
  // input event with insufficient data, we switch into T5R2 mode, where we
  // just track the number of contacts. We still maintain the non-T5R2 records
  // which are useful for tracking if contacts move a lot.
  // The following are for T5R2 mode:
  bool t5r2_;  // if set, use T5R2 hacks
  unsigned short t5r2_touched_size_;  // number of contacts that have arrived
  unsigned short t5r2_released_size_;  // number of contacts that have left
  // Whether all the fingers have age less than "Tap Maximum Finger Age".
  bool fingers_below_max_age_;
};

struct ScrollEvent {
  float dx, dy, dt;
  static ScrollEvent Add(const ScrollEvent& evt_a, const ScrollEvent& evt_b);
};
class ScrollEventBuffer {
 public:
  explicit ScrollEventBuffer(size_t size)
      : buf_(new ScrollEvent[size]), max_size_(size), size_(0), head_(0) {}
  void Insert(float dx, float dy, stime_t timestamp, stime_t prev_timestamp);
  void Clear();
  size_t Size() const { return size_; }
  // 0 is newest, 1 is next newest, ..., size_ - 1 is oldest.
  const ScrollEvent& Get(size_t offset) const;
  // For efficiency, returns dist_sq and time of the last num_events events in
  // the buffer, from which speed can be computed.
  void GetSpeedSq(size_t num_events, float* dist_sq, float* dt) const;

 private:
  std::unique_ptr<ScrollEvent[]> buf_;
  size_t max_size_;
  size_t size_;
  size_t head_;
  stime_t last_scroll_timestamp_;
  DISALLOW_COPY_AND_ASSIGN(ScrollEventBuffer);
};

// Circular buffer for storing a rolling backlog of events for analysis
// as well as accessor functions for using the buffer's contents.
class HardwareStateBuffer {
 public:
  explicit HardwareStateBuffer(size_t size);
  ~HardwareStateBuffer();

  size_t Size() const { return size_; }

  void Reset(size_t max_finger_cnt);

  // Does a deep copy of state into states_
  void PushState(const HardwareState& state);
  // Pops most recently pushed state
  void PopState();

  const HardwareState& Get(size_t idx) const {
    return states_[(idx + newest_index_) % size_];
  }

  HardwareState& Get(size_t idx) {
    return const_cast<HardwareState&>(
        const_cast<const HardwareStateBuffer*>(this)->Get(idx));
  }

 private:
  std::unique_ptr<HardwareState[]> states_;
  size_t newest_index_;
  size_t size_;
  size_t max_finger_cnt_;
  DISALLOW_COPY_AND_ASSIGN(HardwareStateBuffer);
};

struct Point {
  Point() : x_(0.0), y_(0.0) {}
  Point(float x, float y) : x_(x), y_(y) {}
  bool operator==(const Point& that) const {
    return x_ == that.x_ && y_ == that.y_;
  }
  bool operator!=(const Point& that) const { return !((*this) == that); }
  float x_, y_;
};

// Helper class for compute scroll and fling.
class ScrollManager {
  FRIEND_TEST(ImmediateInterpreterTest, FlingDepthTest);
  FRIEND_TEST(ImmediateInterpreterTest, ScrollManagerTest);
  FRIEND_TEST(MultitouchMouseInterpreterTest, SimpleTest);

 public:
  explicit ScrollManager(PropRegistry* prop_reg);
  ~ScrollManager() {}

  // Returns true if a finger's movement should be suppressed based on
  // max_stationary_move_* properties below.
  bool SuppressStationaryFingerMovement(const FingerState& fs,
                                        const FingerState& prev,
                                        stime_t dt);

  // Looking at this finger and the previous ones within a small window
  // and returns true iff this finger is stationary and the pressure is
  // changing so quickly that we expect it's arriving on the pad or
  // departing.
  bool StationaryFingerPressureChangingSignificantly(
      const HardwareStateBuffer& state_buffer,
      const FingerState& current) const;

  // Compute a scroll and fill result.  Return false when something goes wrong.
  bool FillResultScroll(const HardwareStateBuffer& state_buffer,
                     const FingerMap& prev_gs_fingers,
                     const FingerMap& gs_fingers,
                     GestureType prev_gesture_type,
                     const Gesture& prev_result,
                     Gesture* result,
                     ScrollEventBuffer* scroll_buffer);

  // Compute a fling and fill result.
  void FillResultFling(const HardwareStateBuffer& state_buffer,
                    const ScrollEventBuffer& scroll_buffer,
                    Gesture* result);

  // Update ScrollEventBuffer when the current gesture type is not scroll.
  void UpdateScrollEventBuffer(GestureType gesture_type,
                               ScrollEventBuffer* scroll_buffer) const;

  void ResetSameFingerState() {
    stationary_start_positions_.clear();
  }

  // Set to true when a scroll or move is blocked b/c of high pressure
  // change or small movement. Cleared when a normal scroll or move
  // goes through.
  bool prev_result_suppress_finger_movement_;

 private:
  // Set to true when generating a non-zero scroll gesture. Reset to false
  // when a fling is generated.
  bool did_generate_scroll_;

  // Returns the number of most recent event events in the scroll_buffer_ that
  // should be considered for fling. If it returns 0, there should be no fling.
  size_t ScrollEventsForFlingCount(const ScrollEventBuffer& scroll_buffer)
    const;

  // Returns a ScrollEvent that contains velocity estimates for x and y based
  // on an N-point linear regression.
  void RegressScrollVelocity(const ScrollEventBuffer& scroll_buffer,
                             int count, ScrollEvent* out) const;

  std::map<short, Point> stationary_start_positions_;

  // In addition to checking for large pressure changes when moving
  // slow, we can suppress all motion under a certain speed, unless
  // the total distance exceeds a threshold.
  DoubleProperty max_stationary_move_speed_;
  DoubleProperty max_stationary_move_speed_hysteresis_;
  DoubleProperty max_stationary_move_suppress_distance_;

  // A finger must change in pressure by less than this per second to trigger
  // motion.
  DoubleProperty max_pressure_change_;
  // If a contact crosses max_pressure_change_, motion continues to be blocked
  // until the pressure change per second goes below
  // max_pressure_change_hysteresis_.
  DoubleProperty max_pressure_change_hysteresis_;
  // Try to look over a period up to this length of time when looking for large
  // pressure change.
  DoubleProperty max_pressure_change_duration_;
  // A fast-swiping finger may generate rapidly changing pressure and we should
  // not report a high pressure change in this case.  This is the maximum
  // speed [mm/s] for which we may consider a finger stationary.
  DoubleProperty max_stationary_speed_;

  // y| V  /
  //  |   /  D   _-
  //  |  /    _-'
  //  | /  _-'
  //  |/_-'   H
  //  |'____________x
  // The above quadrant of a cartesian plane shows the angles where we snap
  // scrolling to vertical or horizontal. Very Vertical or Horizontal scrolls
  // are snapped, while Diagonal scrolls are not. The two properties below
  // are the slopes for the two lines.
  DoubleProperty vertical_scroll_snap_slope_;
  DoubleProperty horizontal_scroll_snap_slope_;

  // Depth of recent scroll event buffer used to compute Fling velocity.
  // For most systems this will be 3.  However, for systems that use 2x
  // interpolation, this should be 6, to ensure that the scroll events for 3
  // actual hardware states are used.
  IntProperty fling_buffer_depth_;
  // Some platforms report fingers as perfectly stationary for a few frames
  // before they report lift off. We don't include these non-movement
  // frames in the scroll buffer, because that would suppress fling.
  // Platforms with this property should set
  // fling_buffer_suppress_zero_length_scrolls_ to non-zero.
  BoolProperty fling_buffer_suppress_zero_length_scrolls_;
  // When computing a fling, if the fling buffer has an average speed under
  // this threshold, we do not perform a fling. Units are mm/sec.
  DoubleProperty fling_buffer_min_avg_speed_;
};

// Helper class for computing the button type of multi-finger clicks.
class FingerButtonClick {
 public:
  // Describes the three classes of fingers we deal with while determining
  // the type of physical button clicks.
  enum FingerClickStatus {
    // A 'recent' finger has recently touched down on the touchpad.
    STATUS_RECENT,
    // A 'cold' finger has already been on the touchpad for a while,
    // but has not been moved.
    STATUS_COLD,
    // A 'hot' finger has been moved since it touched down.
    STATUS_HOT
  };

  explicit FingerButtonClick(const ImmediateInterpreter* interpreter);
  ~FingerButtonClick() {};

  // Processes the HardwareState finger data. Categorizes fingers into one of
  // the FingerClickStatus and sort them according to their original timestamps.
  // Returns true if further analysis is needed. Returns false in trivial cases
  // where one is safe to use the HardwareState button data directly.
  bool Update(const HardwareState& hwstate, stime_t button_down_time);

  // Returns which button type corresponds to which touch count (e.g. 2f = right
  // click, 3f = middle click).
  int GetButtonTypeForTouchCount(int touch_count) const;

  // All these following button type evaluation functions are guaranteed to
  // return a button but the caller must ensure that the requirements are met.
  //
  // Evaluates the button type for the 2f case. Needs at least 2 fingers.
  int EvaluateTwoFingerButtonType();

  // Evaluates the button type for >=3f cases. Needs at least 3 fingers.
  int EvaluateThreeOrMoreFingerButtonType();

  // Evaluates the button type using finger locations.
  int EvaluateButtonTypeUsingFigureLocation();

  int num_fingers() const { return num_fingers_; }
  int num_recent() const { return num_recent_; }
  int num_cold() const { return num_cold_; }
  int num_hot() const { return num_hot_; }

 private:
  // Used to fetch properties and other finger status.
  const ImmediateInterpreter* interpreter_;

  // Fingers that we are considering for determining the button type.
  FingerState const * fingers_[4];

  // FingerClickStatus of each finger.
  FingerClickStatus fingers_status_[4];

  // Number of fingers we are considering.
  int num_fingers_;

  // Number of fingers of each kind.
  int num_recent_;
  int num_cold_;
  int num_hot_;
};

class ImmediateInterpreter : public Interpreter, public PropertyDelegate {
  FRIEND_TEST(ImmediateInterpreterTest, AmbiguousPalmCoScrollTest);
  FRIEND_TEST(ImmediateInterpreterTest, AvoidAccidentalPinchTest);
  FRIEND_TEST(ImmediateInterpreterTest, ChangeTimeoutTest);
  FRIEND_TEST(ImmediateInterpreterTest, ClickTest);
  FRIEND_TEST(ImmediateInterpreterTest, FlingDepthTest);
  FRIEND_TEST(ImmediateInterpreterTest, GetGesturingFingersTest);
  FRIEND_TEST(ImmediateInterpreterTest, GetGesturingFingersWithEmptyStateTest);
  FRIEND_TEST(ImmediateInterpreterTest, PalmAtEdgeTest);
  FRIEND_TEST(ImmediateInterpreterTest, PalmReevaluateTest);
  FRIEND_TEST(ImmediateInterpreterTest, PalmTest);
  FRIEND_TEST(ImmediateInterpreterTest, PinchTests);
  FRIEND_TEST(ImmediateInterpreterTest, ScrollResetTapTest);
  FRIEND_TEST(ImmediateInterpreterTest, ScrollThenFalseTapTest);
  FRIEND_TEST(ImmediateInterpreterTest, SemiMtActiveAreaTest);
  FRIEND_TEST(ImmediateInterpreterTest, SemiMtNoPinchTest);
  FRIEND_TEST(ImmediateInterpreterTest, StationaryPalmTest);
  FRIEND_TEST(ImmediateInterpreterTest, SwipeTest);
  FRIEND_TEST(ImmediateInterpreterTest, TapRecordTest);
  FRIEND_TEST(ImmediateInterpreterTest, TapToClickKeyboardTest);
  FRIEND_TEST(ImmediateInterpreterTest, TapToClickLowPressureBeginOrEndTest);
  FRIEND_TEST(ImmediateInterpreterTest, ThumbRetainReevaluateTest);
  FRIEND_TEST(ImmediateInterpreterTest, ThumbRetainTest);
  FRIEND_TEST(ImmediateInterpreterTest, WarpedFingersTappingTest);
  FRIEND_TEST(ImmediateInterpreterTest, ZeroClickInitializationTest);
  FRIEND_TEST(ImmediateInterpreterTtcEnableTest, TapToClickEnableTest);
  friend class TapRecord;
  friend class TapToClickStateMachineTest;
  friend class FingerButtonClick;

 public:
  enum TapToClickState {
    kTtcIdle,
    kTtcFirstTapBegan,
    kTtcTapComplete,
    kTtcSubsequentTapBegan,
    kTtcDrag,
    kTtcDragRelease,
    kTtcDragRetouch
  };

  ImmediateInterpreter(PropRegistry* prop_reg, Tracer* tracer);
  virtual ~ImmediateInterpreter() {}

 protected:
  virtual void SyncInterpretImpl(HardwareState& hwstate, stime_t* timeout);

  virtual void HandleTimerImpl(stime_t now, stime_t* timeout);

  virtual void Initialize(const HardwareProperties* hwprops,
                          Metrics* metrics, MetricsProperties* mprops,
                          GestureConsumer* consumer);

 public:
  TapToClickState tap_to_click_state() const { return tap_to_click_state_; }

  float tap_min_pressure() const { return tap_min_pressure_.val_; }

  stime_t tap_max_finger_age() const { return tap_max_finger_age_.val_; }

  bool device_reports_pressure() const { return hwprops_->reports_pressure; }

  stime_t finger_origin_timestamp(short tracking_id) const {
    return metrics_->GetFinger(tracking_id)->origin_time();
  }

 private:
  // Reset the member variables corresponding to same-finger state and
  // updates changed_time_ to |now|.
  void ResetSameFingersState(const HardwareState& hwstate);

  // Reset the member variables which track old timestamps.  Called when the
  // clock changes backward.
  void ResetTime();

  // Sets pointing_.
  void UpdatePointingFingers(const HardwareState& hwstate);

  // Gets the hardware button type (RIGHT, LEFT) based on the
  // first finger's position.
  int GetButtonTypeFromPosition(const HardwareState& hwstate);

  // Returns the square of the distance that this contact has travelled since
  // fingers changed (origin=false) or since they touched down (origin=true).
  // If permit_warp is true, we ignore the GESTURES_FINGER_WARP_X/Y flags
  // unless the more strict GESTURES_FINGER_WARP_TELEPORTATION flag is set.
  float DistanceTravelledSq(const FingerState& fs,
                            bool origin,
                            bool permit_warp = false) const;

  // Returns the vector that this finger has travelled since
  // fingers changed (origin=false) or since they touched down (origin=true).
  // If permit_warp is true, we ignore the GESTURES_FINGER_WARP_X/Y flags
  // unless the more strict GESTURES_FINGER_WARP_TELEPORTATION flag is set.
  Point FingerTraveledVector(const FingerState& fs,
                             bool origin,
                             bool permit_warp = false) const;

  // Returns true if there is a potential for pinch zoom but still it's too
  // early to decide. In this case, there shouldn't be any move or scroll
  // event.
  bool EarlyZoomPotential(const HardwareState& hwstate) const;

  // Returns true if there are two fingers moving in opposite directions.
  // Moreover, this function makes sure that fingers moving direction hasn't
  // changed recently.
  bool ZoomFingersAreConsistent(const HardwareStateBuffer& state_buffer) const;

  // Returns true if the given finger is moving sufficiently upwards to be
  // considered the bottom finger of an inward pinch.
  bool InwardPinch(const HardwareStateBuffer& state_buffer,
                   const FingerState& fs) const;

  // Returns Cos(A) where A is the angle between the move vector of two fingers
  float FingersAngle(const FingerState* before1, const FingerState* before2,
                     const FingerState* curr1, const FingerState* curr2) const;

  // Returns true if fingers are not moving in opposite directions.
  bool ScrollAngle(const FingerState& finger1, const FingerState& finger2);

  // Returns the square of distance between two fingers.
  // Returns -1 if not exactly two fingers are present.
  float TwoFingerDistanceSq(const HardwareState& hwstate) const;

  // Returns the square of distance between two given fingers.
  // Returns -1 if fingers don't present in the hwstate.
  float TwoSpecificFingerDistanceSq(const HardwareState& hwstate,
                                    const FingerMap& fingers) const;

  // Updates thumb_ below.
  void UpdateThumbState(const HardwareState& hwstate);

  // Returns true iff the keyboard has been recently used.
  bool KeyboardRecentlyUsed(stime_t now) const;

  // Updates non_gs_fingers based on a new hardware state. Removes missing and
  // newly moving fingers from non_gs_fingers.
  void UpdateNonGsFingers(const HardwareState& hwstate);

  // Gets the finger or fingers we should consider for gestures.
  // Currently, it fetches the (up to) two fingers closest to the keyboard
  // that are not palms. There is one exception: for t5r2 pads with > 2
  // fingers present, we return all fingers.
  FingerMap GetGesturingFingers(const HardwareState& hwstate) const;

  // Updates current_gesture_type_ based on passed-in hwstate and
  // considering the passed in fingers as gesturing.
  // Returns the finger(s) that are performing the gesture in
  // active_gs_fingers.
  void UpdateCurrentGestureType(const HardwareState& hwstate,
                                const FingerMap& gs_fingers,
                                FingerMap* active_gs_fingers);

  // Checks if gesture_type is one of kGestureTypeScroll, kGestureTypeSwipe, or
  // kGestureTypeFourFingerSwipe
  bool IsScrollOrSwipe(const GestureType gesture_type);

  // Checks if a scroll or swipe has ended, and replaces current_gesture_type_
  // with the appropriate finger lift gesture.
  void GenerateFingerLiftGesture();

  // Sorts the fingers referred to in finger_ids (whose details are in hwstate)
  // according to prodimity and places the sorted range into out_sorted_ids.
  // The sort first finds the two closes points and includes them first.
  // Then, it finds the point closest to any included point, repeating until
  // all points are included.
  static void SortFingersByProximity(
      const FingerMap& finger_ids,
      const HardwareState& hwstate,
      vector<short, kMaxGesturingFingers>* out_sorted_ids);

  // If the finger is likely to be a palm and that its contact size/pressure
  // is diminishing/increasing, we suppress the cursor movement. A real
  // intentional 1f cursor move near the touchpad boundary usually has a
  // stationary finger contact size/pressure.
  bool PalmIsArrivingOrDeparting(const FingerState& finger) const;

  // Check if a finger is close to any known thumb. Can be used to detect some
  // thumb split cases.
  bool IsTooCloseToThumb(const FingerState& finger) const;

  // If the fingers are near each other in location and pressure and might
  // to be part of a 2-finger action, returns true. The function can also
  // be used to check whether the gesture is a left or a right button click
  // with the parameter checking_button_type.
  bool TwoFingersGesturing(const FingerState& finger1,
                           const FingerState& finger2,
                           bool check_button_type) const;

  // Given that TwoFingersGesturing returns true for 2 fingers,
  // This will further look to see if it's really 2 finger scroll or not.
  // Returns the current state (move or scroll) or kGestureTypeNull if
  // unknown.
  GestureType GetTwoFingerGestureType(const FingerState& finger1,
                                      const FingerState& finger2);

  // Check for a pinch gesture and update the state machine for detection.
  // If a pinch was detected it will return true. False otherwise.
  // To reset the state machine call with reset=true
  bool UpdatePinchState(const HardwareState& hwstate, bool reset,
                        const FingerMap& gs_fingers);

  // Returns a gesture assuming that at least one of the fingers performing
  // current_gesture_type has left
  GestureType GetFingerLiftGesture(GestureType current_gesture_type);

  // Returns the current multi-finger gesture, or kGestureTypeNull if no gesture
  // should be produced. num_fingers can be 3 or 4.
  GestureType GetMultiFingerGestureType(const FingerState* const fingers[],
                                        const int num_fingers);

  const char* TapToClickStateName(TapToClickState state);

  stime_t TimeoutForTtcState(TapToClickState state);

  void SetTapToClickState(TapToClickState state,
                          stime_t now);

  void UpdateTapGesture(const HardwareState* hwstate,
                        const FingerMap& gs_fingers,
                        const bool same_fingers,
                        stime_t now,
                        stime_t* timeout);

  void UpdateTapState(const HardwareState* hwstate,
                      const FingerMap& gs_fingers,
                      const bool same_fingers,
                      stime_t now,
                      unsigned* buttons_down,
                      unsigned* buttons_up,
                      stime_t* timeout);

  // Returns true iff the given finger is too close to any other finger to
  // realistically be doing a tap gesture.
  bool FingerTooCloseToTap(const HardwareState& hwstate, const FingerState& fs);

  // Returns true iff finger is in the bottom, dampened zone of the pad
  bool FingerInDampenedZone(const FingerState& finger) const;

  // Called when fingers have changed to fill start_positions_
  // and origin_positions_.
  void FillStartPositions(const HardwareState& hwstate);

  // Fills the origin_* member variables.
  void FillOriginInfo(const HardwareState& hwstate);

  // Fills moving_ with any moving fingers.
  FingerMap UpdateMovingFingers(const HardwareState& hwstate);

  // Update started_moving_time_ to now if any gesturing fingers started moving.
  void UpdateStartedMovingTime(stime_t now,
                               const FingerMap& gs_fingers,
                               const FingerMap& newly_moving_fingers);

  // Updates the internal button state based on the passed in |hwstate|.
  // Can optionally request a timeout by setting *timeout.
  void UpdateButtons(const HardwareState& hwstate, stime_t* timeout);

  // Called when the timeout is fired for UpdateButtons.
  void UpdateButtonsTimeout(stime_t now);

  // By looking at |hwstate| and internal state, determins if a button down
  // at this time would correspond to a left/middle/right click. Returns
  // GESTURES_BUTTON_{LEFT,MIDDLE,RIGHT}.
  int EvaluateButtonType(const HardwareState& hwstate,
                         stime_t button_down_time);

  // Precondition: current_mode_ is set to the mode based on |hwstate|.
  // Computes the resulting gesture, storing it in result_.
  void FillResultGesture(const HardwareState& hwstate,
                         const FingerMap& fingers);

  virtual void IntWasWritten(IntProperty* prop);

  // Fingers which are prohibited from ever tapping.
  std::set<short> tap_dead_fingers_;

  // Active gs fingers are the subset of gs_fingers that are actually performing
  // a gesture
  FingerMap prev_active_gs_fingers_;

  // Fingers that would be considered as possibly gesturing, but others fingers
  // did the gesturing.
  FingerMap non_gs_fingers_;

  FingerMap prev_gs_fingers_;
  FingerMap prev_tap_gs_fingers_;
  HardwareProperties hw_props_;
  Gesture result_;
  Gesture prev_result_;

  // Total distance travelled by a finger since its origin timestamp.
  std::map<short, float> distance_walked_;

  // Button data
  // Which button we are going to send/have sent for the physical btn press
  int button_type_;  // left, middle, or right

  FingerButtonClick finger_button_click_;

  // If we have sent button down for the currently down button
  bool sent_button_down_;

  // If we haven't sent a button down by this time, send one
  stime_t button_down_deadline_;

  // When fingers change, we record the time
  stime_t changed_time_;

  // When gesturing fingers move after change, we record the time.
  stime_t started_moving_time_;
  // Record which fingers have started moving already.
  std::set<short> moving_;

  // When different fingers are gesturing, we record the time
  stime_t gs_changed_time_;

  // When fingers leave, we record the time
  stime_t finger_leave_time_;

  // When fingers change, we keep track of where they started.
  // Map: Finger ID -> (x, y) coordinate
  std::map<short, Point> start_positions_;

  // Keep track of finger position from when three fingers began moving in the
  // same direction.
  // Map: Finger ID -> (x, y) coordinate
  std::map<short, Point> three_finger_swipe_start_positions_;

  // Keep track of finger position from when four fingers began moving in the
  // same direction.
  // Map: Finger ID -> (x, y) coordinate
  std::map<short, Point> four_finger_swipe_start_positions_;

  // We keep track of where each finger started when they touched.
  // Map: Finger ID -> (x, y) coordinate.
  std::map<short, Point> origin_positions_;

  // tracking ids of known fingers that are not palms, nor thumbs.
  std::set<short> pointing_;
  // tracking ids of known non-palms. But might be thumbs.
  std::set<short> fingers_;
  // contacts believed to be thumbs, and when they were inserted into the map
  std::map<short, stime_t> thumb_;
  // Timer of the evaluation period for contacts believed to be thumbs.
  std::map<short, stime_t> thumb_eval_timer_;

  // once a moving finger is determined lock onto this one for cursor movement.
  short moving_finger_id_;

  // Tap-to-click
  // The current state:
  TapToClickState tap_to_click_state_;

  // When we entered the state:
  stime_t tap_to_click_state_entered_;

  TapRecord tap_record_;

  // Record time when the finger showed motion (uses different motion detection
  // than last_movement_timestamp_)
  stime_t tap_drag_last_motion_time_;

  // True when the finger was stationary for a while during tap to drag
  bool tap_drag_finger_was_stationary_;

  // Time when the last motion (scroll, movement) occurred
  stime_t last_movement_timestamp_;

  bool swipe_is_vertical_;

  // If we are currently pointing, scrolling, etc.
  GestureType current_gesture_type_;
  // Previous value of current_gesture_type_
  GestureType prev_gesture_type_;

  // Cache for distance between fingers at previous pinch gesture event, or
  // start of pinch detection
  float pinch_prev_distance_sq_;

  HardwareStateBuffer state_buffer_;
  ScrollEventBuffer scroll_buffer_;

  FingerMetrics* finger_metrics_;
  std::unique_ptr<FingerMetrics> test_finger_metrics_;

  // There are three pinch guess states before locking:
  //   pinch_guess_start_ == -1: No definite guess made about pinch
  //   pinch_guess_start_ > 0:
  //     pinch_guess_ == true:  Guess there is a pinch
  //     pinch_guess_ == false: Guess there is no pinch

  // When guessing a pinch gesture. Do we guess pinch (true) or no-pinch?
  bool pinch_guess_;
  // Time when pinch guess was made. -1 if no guess has been made yet.
  stime_t pinch_guess_start_;
  // True when the pinch decision has been locked.
  bool pinch_locked_;
  // Pinch status: GESTURES_ZOOM_START, _UPDATE, or _END
  unsigned pinch_status_;
  // Direction of previous pinch update:
  //   0: No previous update
  //   1: Outward
  //  -1: Inward
  int pinch_prev_direction_;
  // Timestamp of previous pinch update
  float pinch_prev_time_;

  // Keeps track of if there was a finger seen during a physical click
  bool finger_seen_shortly_after_button_down_;

  bool is_haptic_pad_;

  // See keyboard_touched_* properties
  stime_t keyboard_touched_;

  ScrollManager scroll_manager_;

  // Properties

  // Is Tap-To-Click enabled
  BoolProperty tap_enable_;
  // Allows Tap-To-Click to be paused
  BoolProperty tap_paused_;
  // General time limit [s] for tap gestures
  DoubleProperty tap_timeout_;
  // General time limit [s] for time between taps.
  DoubleProperty inter_tap_timeout_;
  // Time [s] before a tap gets recognized as a drag.
  DoubleProperty tap_drag_delay_;
  // Time [s] it takes to stop dragging when you let go of the touchpad
  DoubleProperty tap_drag_timeout_;
  // True if tap dragging is enabled. With it disbled we can respond quickly
  // to tap clicks.
  BoolProperty tap_drag_enable_;
  // True if drag lock is enabled
  BoolProperty drag_lock_enable_;
  // Time [s] the finger has to be stationary to be considered dragging
  DoubleProperty tap_drag_stationary_time_;
  // Distance [mm] a finger can move and still register a tap
  DoubleProperty tap_move_dist_;
  // Minimum pressure a finger must have for it to click when tap to click is on
  DoubleProperty tap_min_pressure_;
  // Maximum distance [mm] per frame that a finger can move and still be
  // considered stationary.
  DoubleProperty tap_max_movement_;
  // Maximum finger age for a finger to trigger tap.
  DoubleProperty tap_max_finger_age_;
  // If three finger click should be enabled. This is a temporary flag so that
  // we can deploy this feature behind a file while we work out the bugs.
  BoolProperty three_finger_click_enable_;
  // If zero finger click should be enabled. On some platforms, bending the
  // case may accidentally cause a physical click.  This supresses all clicks
  // that do not have at least 1 finger detected on the touchpad.
  BoolProperty zero_finger_click_enable_;
  // If T5R2 should support three-finger click/tap, which can in some situations
  // be unreliable.
  BoolProperty t5r2_three_finger_click_enable_;
  // Distance [mm] a finger must move after fingers change to count as real
  // motion
  DoubleProperty change_move_distance_;
  // Speed [mm/s] a finger must move to lock on to that finger
  DoubleProperty move_lock_speed_;
  // Speed [mm/s] a finger must move to lock on to that finger, when another
  // finger is already locked.
  DoubleProperty move_change_lock_speed_;
  // How much faster a finger must move than the currently locked finger to
  // switch the lock.
  DoubleProperty move_change_lock_ratio_;
  // Distance [mm] a finger must move to report that movement
  DoubleProperty move_report_distance_;
  // Time [s] to block movement after number or identify of fingers change
  DoubleProperty change_timeout_;
  // Time [s] to wait before locking on to a gesture
  DoubleProperty evaluation_timeout_;
  // Time [s] to wait before deciding if the pinch zoom is happening.
  DoubleProperty pinch_evaluation_timeout_;
  // Time [s] to wait before decide if a thumb is doing a pinch
  DoubleProperty thumb_pinch_evaluation_timeout_;
  // Minimum movement that a thumb should have to be a gesturing finger.
  DoubleProperty thumb_pinch_min_movement_;
  // If the ratio of gesturing fingers movement to thumb movement is greater
  // than this number, then we can't have pinch with thumb.
  DoubleProperty thumb_pinch_movement_ratio_;
  // Ratio of Distance_sq * Time * Time for two fingers. This measure is used
  // to compare the slow movement of two fingers.
  DoubleProperty thumb_slow_pinch_similarity_ratio_;
  // If a thumb arrives at the same time as the other fingers, the
  // thumb_pinch_evaluation_timeout_ is multiplied by this factor
  DoubleProperty thumb_pinch_delay_factor_;
  // Minimum movement that fingers must have before we consider their
  // relative direction. If the movement is smaller than this number, it
  // will considered as noise.
  DoubleProperty minimum_movement_direction_detection_;
  // A finger in the damp zone must move at least this much as much as
  // the other finger to count toward a gesture. Should be between 0 and 1.
  DoubleProperty damp_scroll_min_movement_factor_;
  // If two fingers have a pressure difference greater than diff thresh and
  // the larger is more than diff factor times the smaller, we assume the
  // larger is a thumb.
  DoubleProperty two_finger_pressure_diff_thresh_;
  DoubleProperty two_finger_pressure_diff_factor_;
  // Click-and-drags are sometimes wrongly classified as right-clicks if the
  // physical-clicking finger arrives at the pad later than or at roughly the
  // same time of the other finger. To distinguish between the two cases, we
  // use the pressure difference and the fingers' relative positions.
  DoubleProperty click_drag_pressure_diff_thresh_;
  DoubleProperty click_drag_pressure_diff_factor_;
  // Mininum slope of the line connecting two fingers that can qualify a click-
  // and-drag gesture.
  DoubleProperty click_drag_min_slope_;
  // If a large contact moves more than this much times the lowest-pressure
  // contact, consider it not to be a thumb.
  DoubleProperty thumb_movement_factor_;
  // If a large contact moves faster than this much times the lowest-pressure
  // contact, consider it not to be a thumb.
  DoubleProperty thumb_speed_factor_;
  // This much time after fingers change, stop allowing contacts classified
  // as thumb to be classified as non-thumb.
  DoubleProperty thumb_eval_timeout_;
  // If thumb is doing an inward pinch, the thresholds for distance and speed
  // that thumb needs to move to be a gesturing finger are multiplied by this
  // factor
  DoubleProperty thumb_pinch_threshold_ratio_;
  // If a finger is recognized as thumb, it has only this much time to change
  // its status and perform a click
  DoubleProperty thumb_click_prevention_timeout_;
  // Consider scroll vs pointing if finger moves at least this distance [mm]
  DoubleProperty two_finger_scroll_distance_thresh_;
  // Consider move if there is no scroll and one finger moves at least this
  // distance [mm]
  DoubleProperty two_finger_move_distance_thresh_;
  // Minimum distance [mm] one of the three fingers must move to perform a
  // swipe gesture.
  DoubleProperty three_finger_swipe_distance_thresh_;
  // Minimum distance [mm] one of the four fingers must move to perform a
  // four finger swipe gesture.
  DoubleProperty four_finger_swipe_distance_thresh_;
  // Minimum ratio between least and most moving finger to perform a
  // three finger swipe gesture.
  DoubleProperty three_finger_swipe_distance_ratio_;
  // Minimum ratio between least and most moving finger to perform a
  // four finger swipe gesture.
  DoubleProperty four_finger_swipe_distance_ratio_;
  // If three-finger swipe should be enabled
  BoolProperty three_finger_swipe_enable_;
  // Height [mm] of the bottom zone
  DoubleProperty bottom_zone_size_;
  // Time [s] to after button down to evaluate number of fingers for a click
  DoubleProperty button_evaluation_timeout_;
  // Time [s] to evaluate number of fingers for a click after a new touch has
  // been registered
  DoubleProperty button_finger_timeout_;
  // Distance [mm] a finger can move to still be considered for a button click
  DoubleProperty button_move_dist_;
  // Distance [mm] a finger can be away from it's expected location to be
  // considered part of the same finger group
  DoubleProperty button_max_dist_from_expected_;
  // Flag to enable the right click on the right side of the hardware button
  BoolProperty button_right_click_zone_enable_;
  // The size of the right click zone on the right side of the hardware button
  DoubleProperty button_right_click_zone_size_;
  // Timeval of time when keyboard was last touched. After the low one is set,
  // the two are converted into an stime_t and stored in keyboard_touched_.
  IntProperty keyboard_touched_timeval_high_;  // seconds
  IntProperty keyboard_touched_timeval_low_;  // microseconds
  // During this timeout, which is time [s] since the keyboard has been used,
  // we are extra aggressive in palm detection. If this time is > 10s apart
  // from now (either before or after), it's disregarded. We disregard old
  // values b/c they no longer apply. Because of delays in other interpreters
  // (LooaheadInterpreter), it's possible to get "future" keyboard used times.
  // We wouldn't want a single bad future value to stop all tap-to-click, so
  // we sanity check.
  DoubleProperty keyboard_palm_prevent_timeout_;
  // Motion (pointer movement, scroll) must halt for this length of time [s]
  // before a tap can generate a click.
  DoubleProperty motion_tap_prevent_timeout_;
  // A finger must be at least this far from other fingers when it taps [mm].
  DoubleProperty tapping_finger_min_separation_;

  // Sum of squares of movement [mm] that is considered as noise during pinch
  // detection
  DoubleProperty pinch_noise_level_sq_;
  // Minimal distance [mm] fingers have to move to indicate a pinch gesture.
  DoubleProperty pinch_guess_min_movement_;
  // Minimal distance [mm] a thumb have to move to do a pinch gesture.
  DoubleProperty pinch_thumb_min_movement_;
  // Minimal distance [mm] fingers have to move to lock a pinch gesture.
  DoubleProperty pinch_certain_min_movement_;
  // Minimum Cos(A) that is acceptable for an inward pinch zoom, where A
  // is the angle between the lower finger and a vertical vector directed
  // from top to bottom.
  DoubleProperty inward_pinch_min_angle_;
  // Maximum Cos(A) to perform a pinch zoom, where A is the angle between
  // two fingers.
  DoubleProperty pinch_zoom_max_angle_;
  // Minimum Cos(A) to perform a scroll gesture when pinch is enabled,
  // where A is the angle between two fingers.
  DoubleProperty scroll_min_angle_;
  // Minimum movement ratio between fingers before we call it a consistent move
  // for a pinch.
  DoubleProperty pinch_guess_consistent_mov_ratio_;
  // Minimum number of touch events needed to start a pinch zoom
  IntProperty pinch_zoom_min_events_;
  // If a pinch is determined quickly we use the original landing position to
  // determing original pinch width. But if they landed too long ago we use the
  // pinch width at detection. Inverse of time in seconds.
  DoubleProperty pinch_initial_scale_time_inv_;
  // Resolution of pinch events: minimum change in squared pinch scale required
  // to send a pinch update.
  DoubleProperty pinch_res_;
  // Change in squared pinch scale required to send a pinch update after fingers
  // stay stationary.
  DoubleProperty pinch_stationary_res_;
  // Time fingers should remain motionless before being treated as stationary.
  DoubleProperty pinch_stationary_time_;
  // Change in squared pinch scale required to send a pinch update after fingers
  // change direction.
  DoubleProperty pinch_hysteresis_res_;
  // Temporary flag to turn pinch on/off while we tune it.
  BoolProperty pinch_enable_;

  // Short start time diff of fingers for a two-finger click that indicates
  // a right click
  DoubleProperty right_click_start_time_diff_;
  // Second finger comes down for a while then button clicks down that indicates
  // a right click
  DoubleProperty right_click_second_finger_age_;
  // Suppress moves with a speed more than this much times the previous speed.
  DoubleProperty quick_acceleration_factor_;
};

bool AnyGesturingFingerLeft(const HardwareState& state,
                            const FingerMap& prev_gs_fingers);

}  // namespace gestures

#endif  // GESTURES_IMMEDIATE_INTERPRETER_H_
