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

#include <deque>

#include <gtest/gtest.h>

#include "include/click_wiggle_filter_interpreter.h"
#include "include/gestures.h"
#include "include/macros.h"
#include "include/unittest_util.h"

using std::deque;

namespace gestures {

class ClickWiggleFilterInterpreterTest : public ::testing::Test {};

class ClickWiggleFilterInterpreterTestInterpreter : public Interpreter {
 public:
  ClickWiggleFilterInterpreterTestInterpreter()
      : Interpreter(nullptr, nullptr, false),
        expect_warp_(true),
        expected_fingers_(-1) {}

  virtual void SyncInterpret(HardwareState& hwstate, stime_t* timeout) {
    if (expected_fingers_ >= 0)
      EXPECT_EQ(expected_fingers_, hwstate.finger_cnt);
    if (hwstate.finger_cnt > 0 && expect_warp_) {
      EXPECT_TRUE(hwstate.fingers[0].flags & GESTURES_FINGER_WARP_X);
      EXPECT_TRUE(hwstate.fingers[0].flags & GESTURES_FINGER_WARP_Y);
    }
  }

  virtual void HandleTimer(stime_t now, stime_t* timeout) {
    EXPECT_TRUE(false);
  }

  bool expect_warp_;
  short expected_fingers_;
};

TEST(ClickWiggleFilterInterpreterTest, ClickWiggleRecEquate) {
  ClickWiggleFilterInterpreter::ClickWiggleRec rec1 = {1.0, 1.0, 0, true};
  ClickWiggleFilterInterpreter::ClickWiggleRec rec2 = {1.0, 1.0, 0, true};
  ClickWiggleFilterInterpreter::ClickWiggleRec rec3 = {0.0, 0.0, 0, false};

  ASSERT_EQ(rec1, rec2);
  ASSERT_NE(rec1, rec3);
}

TEST(ClickWiggleFilterInterpreterTest, WiggleSuppressTest) {
  ClickWiggleFilterInterpreterTestInterpreter* base_interpreter =
      new ClickWiggleFilterInterpreterTestInterpreter;
  ClickWiggleFilterInterpreter interpreter(nullptr, base_interpreter, nullptr);
  HardwareProperties hwprops = {
    .right = 92,
    .bottom = 61,
    .res_x = 1,
    .res_y = 1,
    .orientation_minimum = -1,
    .orientation_maximum = 2,
    .max_finger_cnt = 2,
    .max_touch_cnt = 5,
    .supports_t5r2 = 0,
    .support_semi_mt = 0,
    .is_button_pad = 0,
    .has_wheel = 0,
    .wheel_is_hi_res = 0,
    .is_haptic_pad = 0,
  };
  TestInterpreterWrapper wrapper(&interpreter, &hwprops);

  // These values come from a recording of my finger
  FingerState finger_states[] = {
    // TM, Tm, WM, Wm, Press, Orientation, X, Y, TrID
    {0, 0, 0, 0, 38.299999, 0, 43.195655, 32.814815, 1, 0},
    {0, 0, 0, 0, 39.820442, 0, 43.129665, 32.872276, 1, 0},
    {0, 0, 0, 0, 44.924972, 0, 42.881202, 33.077861, 1, 0},
    {0, 0, 0, 0, 52.412372, 0, 42.476348, 33.405296, 1, 0},
    {0, 0, 0, 0, 59.623386, 0, 42.064849, 33.772129, 1, 0},
    {0, 0, 0, 0, 65.317642, 0, 41.741107, 34.157428, 1, 0},
    {0, 0, 0, 0, 69.175155, 0, 41.524814, 34.531333, 1, 0},
    {0, 0, 0, 0, 71.559425, 0, 41.390705, 34.840869, 1, 0},
    {0, 0, 0, 0, 73.018020, 0, 41.294445, 35.082786, 1, 0},
    {0, 0, 0, 0, 73.918144, 0, 41.210456, 35.280235, 1, 0},
    {0, 0, 0, 0, 74.453460, 0, 41.138065, 35.426036, 1, 0},
    {0, 0, 0, 0, 74.585144, 0, 41.084125, 35.506179, 1, 0},
    {0, 0, 0, 0, 74.297470, 0, 41.052356, 35.498870, 1, 0},
    {0, 0, 0, 0, 73.479888, 0, 41.064708, 35.364994, 1, 0},
    {0, 0, 0, 0, 71.686737, 0, 41.178459, 35.072589, 1, 0},
    {0, 0, 0, 0, 68.128448, 0, 41.473480, 34.566291, 1, 0},
    {0, 0, 0, 0, 62.086532, 0, 42.010086, 33.763534, 1, 0},
    {0, 0, 0, 0, 52.739898, 0, 42.745056, 32.644023, 1, 0},
  };
  HardwareState hardware_state[] = {
    // time, buttons, finger count, touch count, finger states pointer
    make_hwstate(1319735240.654559, 1, 1, 1, &finger_states[0]),
    make_hwstate(1319735240.667746, 1, 1, 1, &finger_states[1]),
    make_hwstate(1319735240.680153, 1, 1, 1, &finger_states[2]),
    make_hwstate(1319735240.693717, 1, 1, 1, &finger_states[3]),
    make_hwstate(1319735240.707821, 1, 1, 1, &finger_states[4]),
    make_hwstate(1319735240.720633, 1, 1, 1, &finger_states[5]),
    make_hwstate(1319735240.733183, 1, 1, 1, &finger_states[6]),
    make_hwstate(1319735240.746131, 1, 1, 1, &finger_states[7]),
    make_hwstate(1319735240.758622, 1, 1, 1, &finger_states[8]),
    make_hwstate(1319735240.772690, 1, 1, 1, &finger_states[9]),
    make_hwstate(1319735240.785556, 1, 1, 1, &finger_states[10]),
    make_hwstate(1319735240.798524, 1, 1, 1, &finger_states[11]),
    make_hwstate(1319735240.811093, 1, 1, 1, &finger_states[12]),
    make_hwstate(1319735240.824775, 1, 1, 1, &finger_states[13]),
    make_hwstate(1319735240.837738, 0, 1, 1, &finger_states[14]),
    make_hwstate(1319735240.850482, 0, 1, 1, &finger_states[15]),
    make_hwstate(1319735240.862749, 0, 1, 1, &finger_states[16]),
    make_hwstate(1319735240.876571, 0, 1, 1, &finger_states[17]),
    make_hwstate(1319735240.888128, 0, 0, 0, nullptr),
  };

  for (size_t i = 0; i < arraysize(hardware_state); ++i)
    // Assertions happen in the base interpreter
    wrapper.SyncInterpret(hardware_state[i], nullptr);
}

TEST(ClickWiggleFilterInterpreterTest, OneFingerClickSuppressTest) {
  ClickWiggleFilterInterpreterTestInterpreter* base_interpreter =
      new ClickWiggleFilterInterpreterTestInterpreter;
  ClickWiggleFilterInterpreter interpreter(nullptr, base_interpreter, nullptr);
  HardwareProperties hwprops = {
    .right = 92,
    .bottom = 61,
    .res_x = 1,
    .res_y = 1,
    .orientation_minimum = -1,
    .orientation_maximum = 2,
    .max_finger_cnt = 2,
    .max_touch_cnt = 5,
    .supports_t5r2 = 0,
    .support_semi_mt = 0,
    .is_button_pad = 0,
    .has_wheel = 0,
    .wheel_is_hi_res = 0,
    .is_haptic_pad = 0,
  };
  TestInterpreterWrapper wrapper(&interpreter, &hwprops);

  // These values come from a recording of my finger
  FingerState finger_states[] = {
    // TM, Tm, WM, Wm, Press, Orientation, X, Y, TrID, flags
    {0, 0, 0, 0, 38, 0, 43, 45, 1, 0},  // 0
    {0, 0, 0, 0, 37, 0, 43, 48, 1, 0},  // 1
    {0, 0, 0, 0, 38, 0, 43, 49, 1, 0},  // 2
    {0, 0, 0, 0, 38, 0, 43, 49, 1, 0},  // 3 (same as 2)
    {0, 0, 0, 0, 38, 0, 43, 52, 1, 0},  // 4
    {0, 0, 0, 0, 38, 0, 43, 55, 1, 0},  // 5
    {0, 0, 0, 0, 38, 0, 43, 58, 1, 0},  // 6
    {0, 0, 0, 0, 38, 0, 43, 58, 1, 0},  // 7
  };
  HardwareState hardware_state[] = {
    // time, buttons, finger count, touch count, finger states pointer
    make_hwstate(1.0, 1, 1, 1, &finger_states[0]),  // 0
    make_hwstate(1.1, 1, 1, 1, &finger_states[1]),  // 1
    make_hwstate(1.11, 1, 1, 1, &finger_states[2]),  // 2
    make_hwstate(1.25, 1, 1, 1, &finger_states[3]),
    // 3, stable & > Timeout => no warp
    make_hwstate(1.5, 0, 1, 1, &finger_states[4]),  // 4 button up
    make_hwstate(1.6, 0, 1, 1, &finger_states[5]),  // 5
    make_hwstate(1.61, 0, 1, 1, &finger_states[6]),  // 6
    make_hwstate(1.85, 0, 1, 1, &finger_states[7]),
    // 7, stable & > Timeout => no warp
  };

  interpreter.one_finger_click_wiggle_timeout_.val_ = 0.2;

  for (size_t i = 0; i < arraysize(hardware_state); ++i) {
    // Assertions happen in the base interpreter
    base_interpreter->expect_warp_ = (i != 3 && i != 7);
    wrapper.SyncInterpret(hardware_state[i], nullptr);
  }
}

namespace {
struct ThumbClickTestInput {
  stime_t timestamp_;
  float x_, y_, pressure_;
  short buttons_down_;
};
}  // namespace {}

// This tests uses actual data from a log from Ryan Tabone, where he clicked
// with his thumb and the cursor moved.
TEST(ClickWiggleFilterInterpreter, ThumbClickTest) {
  ClickWiggleFilterInterpreterTestInterpreter* base_interpreter =
      new ClickWiggleFilterInterpreterTestInterpreter;
  ClickWiggleFilterInterpreter interpreter(nullptr, base_interpreter, nullptr);
  HardwareProperties hwprops = {
    .right = 92,
    .bottom = 61,
    .res_x = 1,
    .res_y = 1,
    .orientation_minimum = -1,
    .orientation_maximum = 2,
    .max_finger_cnt = 2,
    .max_touch_cnt = 5,
    .supports_t5r2 = 0,
    .support_semi_mt = 0,
    .is_button_pad = 0,
    .has_wheel = 0,
    .wheel_is_hi_res = 0,
    .is_haptic_pad = 0,
  };

  TestInterpreterWrapper wrapper(&interpreter, &hwprops);

  ThumbClickTestInput inputs[] = {
    { 1.089467, 27.83, 21.20, 11.48, 0 },
    { 1.102349, 28.08, 21.20, 17.30, 0 },
    { 1.115390, 28.83, 19.80, 19.24, 0 },
    { 1.128652, 29.91, 18.60, 19.24, 0 },
    { 1.141682, 30.50, 17.30, 19.24, 0 },
    { 1.154569, 31.33, 16.80, 19.24, 1 },
    { 1.168041, 31.91, 16.20, 21.18, 1 },
    { 1.181294, 32.58, 15.90, 09.54, 1 },
  };
  for (size_t i = 0; i < arraysize(inputs); i++) {
    const ThumbClickTestInput& input = inputs[i];
    FingerState fs = {
      0, 0, 0, 0,  // touch/width major/minor
      input.pressure_,
      0,  // orientation
      input.x_, input.y_,
      1,  // tracking id
      0  // flags
    };
    HardwareState hs =
        make_hwstate(input.timestamp_, input.buttons_down_, 1, 1, &fs);
    wrapper.SyncInterpret(hs, nullptr);
    // Assertions tested in base interpreter
  }
}

// This test makes sure we can do 1-finger movement if the clock goes
// backwards.
TEST(ClickWiggleFilterInterpreter, TimeBackwardsTest) {
  ClickWiggleFilterInterpreterTestInterpreter* base_interpreter =
      new ClickWiggleFilterInterpreterTestInterpreter;
  ClickWiggleFilterInterpreter interpreter(nullptr, base_interpreter, nullptr);
  HardwareProperties hwprops = {
    .right = 92,
    .bottom = 61,
    .res_x = 1,
    .res_y = 1,
    .orientation_minimum = -1,
    .orientation_maximum = 2,
    .max_finger_cnt = 2,
    .max_touch_cnt = 5,
    .supports_t5r2 = 0,
    .support_semi_mt = 0,
    .is_button_pad = 0,
    .has_wheel = 0,
    .wheel_is_hi_res = 0,
    .is_haptic_pad = 0,
  };

  TestInterpreterWrapper wrapper(&interpreter, &hwprops);

  const short kInitialId = 1;
  FingerState fs = { 0, 0, 0, 0, 50, 0, 20, 20, kInitialId, 0 };

  HardwareState hs[] = {
    // click
    make_hwstate(9.00, 1, 1, 1, &fs),
    make_hwstate(9.01, 0, 1, 1, &fs),
    // time goes backwards
    make_hwstate(1.00, 0, 1, 1, &fs),
    // long time passes, shouldn't be wobbling anymore
    make_hwstate(2.01, 0, 1, 1, &fs),
  };

  for (size_t i = 0; i < arraysize(hs); i++) {
    fs.flags = 0;
    if (hs[i].timestamp < hs[0].timestamp) {
      fs.tracking_id = kInitialId + 1;
    }
    if (i == arraysize(hs) - 1)
      base_interpreter->expect_warp_ = false;
    wrapper.SyncInterpret(hs[i], nullptr);
    if (i == arraysize(hs) - 1)
      EXPECT_EQ(0, fs.flags);
  }
}

struct ThumbClickWiggleWithPalmTestInputs {
  stime_t now;
  int buttons_down;
  // finger data for two fingers:
  float x0, y0, p0;
  unsigned flags0;
  float x1, y1, p1;
  unsigned flags1;
};

// Based on a log from real use by Andrew de los Reyes, where a thumb was
// clicking and wiggling a lot. We would have blocked the wiggle (since it
// was the only finger), but part way through a palm appeared, and we didn't
// realize it was a palm, so we started allowing motion again. This test
// makes sure we don't regress on this log.
TEST(ClickWiggleFilterInterpreter, ThumbClickWiggleWithPalmTest) {
  ClickWiggleFilterInterpreterTestInterpreter* base_interpreter =
      new ClickWiggleFilterInterpreterTestInterpreter;
  ClickWiggleFilterInterpreter interpreter(nullptr, base_interpreter, nullptr);
  HardwareProperties hwprops = {
    .right = 106.666672,
    .bottom = 68.000000,
    .res_x = 1.000000,
    .res_y = 1.000000,
    .orientation_minimum = -1,
    .orientation_maximum = 2,
    .max_finger_cnt = 15,
    .max_touch_cnt = 5,
    .supports_t5r2 = 0,
    .support_semi_mt = 0,
    .is_button_pad = 1,
    .has_wheel = 0,
    .wheel_is_hi_res = 0,
    .is_haptic_pad = 0,
  };

  TestInterpreterWrapper wrapper(&interpreter, &hwprops);

  // Ideally flags will not get renumbered, but just in case.
  const unsigned k8 = GESTURES_FINGER_POSSIBLE_PALM;
  const unsigned k41 = GESTURES_FINGER_POSSIBLE_PALM | GESTURES_FINGER_WARP_X;
  const unsigned k107 = GESTURES_FINGER_POSSIBLE_PALM |
      GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y;
  const unsigned k16 = GESTURES_FINGER_PALM;
  const unsigned k49 = GESTURES_FINGER_PALM | GESTURES_FINGER_WARP_X;
  const unsigned k115 = GESTURES_FINGER_PALM |
      GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y;

  ASSERT_EQ(8, k8);
  ASSERT_EQ(41, k41);
  ASSERT_EQ(107, k107);
  ASSERT_EQ(16, k16);
  ASSERT_EQ(49, k49);
  ASSERT_EQ(115, k115);

  ThumbClickWiggleWithPalmTestInputs inputs[] = {
    { 4.5231, 0, 52.0, 59.8, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5325, 0, 52.0, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5418, 0, 51.6, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5511, 0, 51.5, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5603, 0, 51.5, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5696, 0, 51.4, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5788, 0, 51.2, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5881, 0, 51.1, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.5974, 1, 51.1, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6162, 1, 51.0, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6347, 1, 51.0, 59.9, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6533, 1, 51.0, 60.0, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6623, 1, 51.0, 60.2, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6716, 1, 51.0, 60.6, 118.20, k16,   0.0,  0.0,  0.00,    0 },
    { 4.6824, 1, 51.0, 60.9, 118.20, k8,   99.9, 67.2,  3.71,   k8 },
    { 4.6934, 1, 51.9, 61.5, 118.20, k8,   99.9, 67.2,  7.60,   k8 },
    { 4.7042, 1, 53.0, 61.7, 118.20, k8,   99.9, 67.2,  9.54,   k8 },
    { 4.7152, 1, 56.0, 63.4, 118.20, k107, 99.9, 67.2,  9.54,   k8 },
    { 4.7262, 1, 56.4, 63.4, 118.20, k8,  100.4, 67.4, 15.36,  k41 },
    { 4.7372, 0, 57.1, 63.5, 110.43, k8,  100.2, 67.7, 15.36, k107 },
    { 4.7481, 0, 57.8, 64.3,  83.27, k8,  100.2, 67.7, 15.36,   k8 },
    { 4.7591, 0, 60.1, 65.7,  34.76, k41, 100.2, 67.7, 15.36,   k8 },
    { 4.7681, 0,  0.0,  0.0,   0.00, 0,   100.2, 67.7, 15.36,  k49 },
    { 4.7772, 0,  0.0,  0.0,   0.00, 0,   100.1, 67.7, 13.42, k115 },
    { 4.7862, 0,  0.0,  0.0,   0.00, 0,   100.6, 67.7,  9.54,  k49 },
    { 4.7953, 0,  0.0,  0.0,   0.00, 0,   102.2, 67.7,  3.71,  k49 }
  };

  for (size_t i = 0; i < arraysize(inputs); i++) {
    const ThumbClickWiggleWithPalmTestInputs& input = inputs[i];
    FingerState fs[] = {
      { 0, 0, 0, 0, input.p0, 0, input.x0, input.y0, 1, input.flags0 },
      { 0, 0, 0, 0, input.p1, 0, input.x1, input.y1, 2, input.flags1 }
    };
    unsigned short finger_count = (input.p0 == 0.0 || input.p1 == 0.0) ? 1 : 2;
    HardwareState hs = make_hwstate(input.now, input.buttons_down, finger_count,
                                    finger_count,
                                    input.p0 == 0.0 ? &fs[1] : &fs[0]);
    base_interpreter->expect_warp_ = !!input.buttons_down;
    base_interpreter->expected_fingers_ = finger_count;
    wrapper.SyncInterpret(hs, nullptr);
  }
}

}  // namespace gestures
