// 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 "include/sensor_jump_filter_interpreter.h"

#include "include/tracer.h"
#include "include/util.h"

namespace gestures {

SensorJumpFilterInterpreter::SensorJumpFilterInterpreter(PropRegistry* prop_reg,
                                                         Interpreter* next,
                                                         Tracer* tracer)
    : FilterInterpreter(nullptr, next, tracer, false),
      enabled_(prop_reg, "Sensor Jump Filter Enable", false),
      min_warp_dist_non_move_(prop_reg, "Sensor Jump Min Dist Non-Move", 0.9),
      max_warp_dist_non_move_(prop_reg, "Sensor Jump Max Dist Non-Move", 7.5),
      similar_multiplier_non_move_(prop_reg,
                                   "Sensor Jump Similar Multiplier Non-Move",
                                   0.9),
      min_warp_dist_move_(prop_reg, "Sensor Jump Min Dist Move", 0.9),
      max_warp_dist_move_(prop_reg, "Sensor Jump Max Dist Move", 7.5),
      similar_multiplier_move_(prop_reg,
                               "Sensor Jump Similar Multiplier Move",
                               0.9),
      no_warp_min_dist_move_(prop_reg,
                             "Sensor Jump No Warp Min Dist Move",
                             0.21) {
  InitName();
}

void SensorJumpFilterInterpreter::SyncInterpretImpl(HardwareState& hwstate,
                                                        stime_t* timeout) {
  const char name[] = "SensorJumpFilterInterpreter::SyncInterpretImpl";
  LogHardwareStatePre(name, hwstate);

  if (!enabled_.val_) {
    next_->SyncInterpret(hwstate, timeout);
    return;
  }

  RemoveMissingIdsFromMap(&previous_input_[0], hwstate);
  RemoveMissingIdsFromMap(&previous_input_[1], hwstate);
  RemoveMissingIdsFromSet(&first_flag_[0], hwstate);
  RemoveMissingIdsFromSet(&first_flag_[1], hwstate);
  RemoveMissingIdsFromSet(&first_flag_[2], hwstate);
  RemoveMissingIdsFromSet(&first_flag_[3], hwstate);

  std::map<short, FingerState> current_input;

  for (size_t i = 0; i < hwstate.finger_cnt; i++)
    current_input[hwstate.fingers[i].tracking_id] = hwstate.fingers[i];

  for (size_t i = 0; i < hwstate.finger_cnt; i++) {
    short tracking_id = hwstate.fingers[i].tracking_id;
    if (!MapContainsKey(previous_input_[1], tracking_id) ||
        !MapContainsKey(previous_input_[0], tracking_id))
      continue;
    FingerState* fs[] = {
      &hwstate.fingers[i],  // newest
      &previous_input_[0][tracking_id],
      &previous_input_[1][tracking_id],  // oldest
    };
    float FingerState::* const fields[] = { &FingerState::position_x,
                                            &FingerState::position_y,
                                            &FingerState::position_x,
                                            &FingerState::position_y };

    unsigned warp[] = { GESTURES_FINGER_WARP_X_NON_MOVE,
                        GESTURES_FINGER_WARP_Y_NON_MOVE,
                        GESTURES_FINGER_WARP_X_MOVE,
                        GESTURES_FINGER_WARP_Y_MOVE };

    for (size_t f_idx = 0; f_idx < arraysize(fields); f_idx++) {
      float FingerState::* const field = fields[f_idx];
      const float val[] = {
        fs[0]->*field,  // newest
        fs[1]->*field,
        fs[2]->*field,  // oldest
      };
      const float delta[] = {
        val[0] - val[1],  // newer
        val[1] - val[2],  // older
      };

      bool warp_move = (warp[f_idx] == GESTURES_FINGER_WARP_X_MOVE ||
                        warp[f_idx] == GESTURES_FINGER_WARP_Y_MOVE);
      float min_warp_dist = warp_move ? min_warp_dist_move_.val_ :
          min_warp_dist_non_move_.val_;
      float max_warp_dist = warp_move ? max_warp_dist_move_.val_ :
          max_warp_dist_non_move_.val_;
      float similar_multiplier = warp_move ? similar_multiplier_move_.val_ :
          similar_multiplier_non_move_.val_;

      const float kAllowableChange = fabsf(delta[1] * similar_multiplier);

      bool should_warp = false;
      bool should_store_flag = false;
      if (delta[0] * delta[1] < 0.0) {
        // switched direction
        // Don't mark direction change with small delta with WARP_*_MOVE.
        if (!warp_move || !(fabsf(delta[0]) < no_warp_min_dist_move_.val_ &&
                            fabsf(delta[1]) < no_warp_min_dist_move_.val_))
          should_store_flag = should_warp = true;
      } else if (fabsf(delta[0]) < min_warp_dist ||
                 fabsf(delta[0]) > max_warp_dist) {
        // acceptable movement
      } else if (fabsf(delta[0] - delta[1]) <= kAllowableChange) {
        if (SetContainsValue(first_flag_[f_idx], tracking_id)) {
          // Was flagged last time. Flag one more time
          should_warp = true;
        }
      } else {
        should_store_flag = should_warp = true;
      }
      if (should_warp) {
        fs[0]->flags |= (warp[f_idx] | GESTURES_FINGER_WARP_TELEPORTATION);
        // Warping moves here get tap warped, too
        if (warp_move) {
          fs[0]->flags |= warp[f_idx] == GESTURES_FINGER_WARP_X_MOVE ?
              GESTURES_FINGER_WARP_X_TAP_MOVE : GESTURES_FINGER_WARP_Y_TAP_MOVE;
        }
      }
      if (should_store_flag)
        first_flag_[f_idx].insert(tracking_id);
      else
        first_flag_[f_idx].erase(tracking_id);
    }
  }

  // Update previous input/output state
  previous_input_[1] = previous_input_[0];
  previous_input_[0] = current_input;

  LogHardwareStatePost(name, hwstate);
  next_->SyncInterpret(hwstate, timeout);
}

}  // namespace gestures
