// Copyright 2020 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.

#include "ash/accelerometer/accelerometer_samples_observer.h"

#include <memory>
#include <utility>

#include "ash/accelerometer/accelerometer_constants.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "chromeos/components/sensors/fake_sensor_device.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

constexpr int kFakeAccelerometerId = 1;

constexpr int64_t kFakeSampleData[] = {1, 2, 3};

constexpr double kFakeScaleValue = 10.0;

class AccelerometerSamplesObserverTest : public ::testing::Test {
 protected:
  void SetChannels(uint32_t num_of_axes) {
    CHECK_LE(num_of_axes, kNumberOfAxes);
    std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data(
        num_of_axes);
    for (uint32_t i = 0; i < num_of_axes; ++i) {
      channels_data[i].id = kAccelerometerChannels[i];
      channels_data[i].sample_data = kFakeSampleData[i];
    }

    sensor_device_ = std::make_unique<chromeos::sensors::FakeSensorDevice>(
        std::move(channels_data));
  }

  void SetObserver(
      mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer) {
    observer_ = std::make_unique<AccelerometerSamplesObserver>(
        kFakeAccelerometerId, std::move(accelerometer), kFakeScaleValue,
        base::BindRepeating(
            &AccelerometerSamplesObserverTest::OnSampleUpdatedCallback,
            base::Unretained(this)));
  }

  void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample) {
    EXPECT_EQ(iio_device_id, kFakeAccelerometerId);
    EXPECT_EQ(sample.size(), kNumberOfAxes);
    for (uint32_t i = 0; i < kNumberOfAxes; ++i)
      EXPECT_EQ(sample[i], kFakeSampleData[i] * kFakeScaleValue);

    ++num_samples_;
  }

  void DisableFirstChannel() {
    sensor_device_->SetChannelsEnabled(
        {0}, false,
        base::BindOnce(
            &AccelerometerSamplesObserverTest::SetChannelsEnabledCallback,
            base::Unretained(this)));
  }

  void SetChannelsEnabledCallback(const std::vector<int32_t>& failed_indices) {
    EXPECT_EQ(failed_indices.size(), 0u);
  }

  std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device_;
  std::unique_ptr<AccelerometerSamplesObserver> observer_;

  int num_samples_ = 0;

  base::test::SingleThreadTaskEnvironment task_environment;
};

TEST_F(AccelerometerSamplesObserverTest, MissingChannels) {
  SetChannels(kNumberOfAxes - 1);

  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());
  SetObserver(std::move(accelerometer));

  // Wait until the mojo connection is reset.
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(sensor_device_->HasReceivers());
}

TEST_F(AccelerometerSamplesObserverTest, StartReadingTwiceError) {
  SetChannels(kNumberOfAxes);

  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());

  mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
      pending_remote;
  auto null_receiver = pending_remote.InitWithNewPipeAndPassReceiver();
  accelerometer->StartReadingSamples(std::move(pending_remote));

  SetObserver(std::move(accelerometer));

  // Wait until the mojo connection is reset.
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(sensor_device_->HasReceivers());
}

TEST_F(AccelerometerSamplesObserverTest, GetSamples) {
  SetChannels(kNumberOfAxes);

  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());

  SetObserver(std::move(accelerometer));
  observer_->SetEnabled(true);

  // Wait until a sample is received.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(sensor_device_->HasReceivers());
  EXPECT_EQ(num_samples_, 1);

  DisableFirstChannel();

  // Wait until a sample is received.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(sensor_device_->HasReceivers());
  // The updated sample is not sent to |OnSampleUpdatedCallback|.
  EXPECT_EQ(num_samples_, 1);

  // Simulate a disconnection of the observer's mojo channel in IIO Service.
  sensor_device_->StopReadingSamples();

  // Wait until the disconnection is done.
  base::RunLoop().RunUntilIdle();

  // OnObserverDisconnect shouldn't reset SensorDevice's mojo endpoint so that
  // LightProviderMojo can get the disconnection.
  EXPECT_TRUE(sensor_device_->HasReceivers());
}

}  // namespace

}  // namespace ash
