// 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 <algorithm>
#include <map>
#include <memory>

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

#include "include/filter_interpreter.h"
#include "include/finger_metrics.h"
#include "include/gestures.h"
#include "include/prop_registry.h"
#include "include/tracer.h"
#include "include/util.h"

#ifndef GESTURES_LOOKAHEAD_FILTER_INTERPRETER_H_
#define GESTURES_LOOKAHEAD_FILTER_INTERPRETER_H_

namespace gestures {

class LookaheadFilterInterpreter : public FilterInterpreter {
  FRIEND_TEST(LookaheadFilterInterpreterTest, CyapaQuickTwoFingerMoveTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, DrumrollTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, InterpolateHwStateTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, InterpolateTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, InterpolationOverdueTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, NoTapSetTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, QuickMoveTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, QuickSwipeTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, SemiMtNoTrackingIdAssignmentTest);
  FRIEND_TEST(LookaheadFilterInterpreterParmTest, SimpleTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, SpuriousCallbackTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, VariableDelayTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, AddFingerFlingTest);
  FRIEND_TEST(LookaheadFilterInterpreterTest, ConsumeGestureTest);

 public:
  LookaheadFilterInterpreter(PropRegistry* prop_reg, Interpreter* next,
                             Tracer* tracer);
  virtual ~LookaheadFilterInterpreter() {}

 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);

 private:
  struct QState {
    QState();
    explicit QState(unsigned short max_fingers);

    // Deep copy of new_state to state_
    void set_state(const HardwareState& new_state);

    HardwareState state_;
    unsigned short max_fingers_;
    std::unique_ptr<FingerState[]> fs_;
    std::map<short, short> output_ids_;  // input tracking ids -> output

    stime_t due_;
    bool completed_ = false;
  };

  void LogVectors();

  // Produces a tapdown fling gesture if we just got a new hardware state
  // with a finger missing from the previous, or a null gesture otherwise.
  void TapDownOccurringGesture(stime_t now);

  // Looks at the most recent 2 states in the queue (one of which may have
  // already completed), and if they are separated by split_min_period_ time,
  // tries to insert an interpolated event in the middle.
  void AttemptInterpolation();

  // Reassigns tracking IDs, assigning them in such a way to avoid problems
  // of drumroll.
  void AssignTrackingIds();

  // For drumroll. Edits a QState node's fingerstate to have a new tracking id.
  void SeparateFinger(QState* node, FingerState* fs, short input_id);

  // Looks for a finger possibly lifting off the pad. If found, returns true.
  bool LiftoffJumpStarting(const HardwareState& hs,
                           const HardwareState& prev_hs,
                           const HardwareState& prev2_hs) const;

  // Returns a new tracking id for a contact.
  short NextTrackingId();

  // Interpolates first and second, storing the result into out.
  // first and second must have the same the same number of fingers and
  // have the same tracking_ids for all fingers.
  static void Interpolate(const HardwareState& first,
                          const HardwareState& second,
                          HardwareState* out);

  void UpdateInterpreterDue(stime_t new_interpreter_due,
                            stime_t now,
                            stime_t* timeout);
  void ConsumeGesture(const Gesture& gesture);

  stime_t ExtraVariableDelay() const;

  List<QState> queue_;

  // The last id assigned to a contact (part of drumroll suppression)
  short last_id_;

  unsigned short max_fingers_per_hwstate_;

  // Last detected due_ time as an absolute deadline
  stime_t interpreter_due_deadline_;

  // We want to present time to next_ in a monotonically increasing manner,
  // so this keeps track of the most recent timestamp we've given next_.
  stime_t last_interpreted_time_;

  Gesture result_;

  DoubleProperty min_nonsuppress_speed_;
  DoubleProperty min_delay_;
  // On some platforms, min_delay_ is very small, and sometimes we would like
  // temporary extra delay to avoid problems, so we can in those cases add
  // a delay specified by max_delay_. It's okay for max_delay_ to be less
  // than min_delay_. In that case, it simply has no effect.
  DoubleProperty max_delay_;
  // If this much time passes between consecutive events, interpolate.
  DoubleProperty split_min_period_;
  // If set to false, tracking IDs are not reassigned
  BoolProperty drumroll_suppression_enable_;
  // If a contact appears to move faster than this, the drumroll detector may
  // consider it a new contact.
  DoubleProperty drumroll_speed_thresh_;
  // If one contact's speed is more than drumroll_max_speed_ratio_ times the
  // previous speed, the drumroll detector may consider it a new contact.
  DoubleProperty drumroll_max_speed_ratio_;
  // If during 3 consecutive HardwareState, one contact moves more than
  // quick_move_thresh_ distance along the same direction on either x or y
  // axis, both between the 1st and 2nd HardwareState, and the 2nd and 3rd
  // HardwareState, it is considered to be a quick move and the tracking ID
  // reassignment due to drumroll detection may get corrected.
  DoubleProperty quick_move_thresh_;
  // If we're going to drumroll-suppress a finger that is moving too much,
  // we abort said suppression if it's moving less than co_move_ratio_ *
  // distance of another non-drumroll-suppressed finger.
  DoubleProperty co_move_ratio_;
  // Temporary property to turn on/off the generation of TapDown gestures
  // (i.e., stop flinging gestures).
  BoolProperty suppress_immediate_tapdown_;
  // If we should add extra delay when we think a finger may be lifting off.
  BoolProperty delay_on_possible_liftoff_;
  // If looking for a possible liftoff-move, the speed a finger is moving
  // relative to the previous speed, such that it's a possible leave.
  DoubleProperty liftoff_speed_increase_threshold_;
};

}  // namespace gestures

#endif  // GESTURES_LOOKAHEAD_FILTER_INTERPRETER_H_
