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

#include <memory>
#include <utility>

#include "ash/accelerometer/accelerometer_constants.h"
#include "ash/accelerometer/accelerometer_reader.h"
#include "ash/test/ash_test_helper.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "chromeos/components/sensors/ash/sensor_hal_dispatcher.h"
#include "chromeos/components/sensors/fake_sensor_device.h"
#include "chromeos/components/sensors/fake_sensor_hal_server.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

constexpr double kFakeScaleValue = 10.0;

constexpr int kFakeLidAccelerometerId = 1;
constexpr int kFakeBaseAccelerometerId = 2;
constexpr int kFakeLidAngleId = 3;

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

class FakeObserver : public AccelerometerReader::Observer {
 public:
  void OnECLidAngleDriverStatusChanged(bool is_supported) override {
    is_supported_ = is_supported;
  }
  void OnAccelerometerUpdated(const AccelerometerUpdate& update) override {
    for (uint32_t index = 0; index < ACCELEROMETER_SOURCE_COUNT; ++index) {
      auto source = static_cast<AccelerometerSource>(index);
      if (!update.has(source))
        continue;

      EXPECT_EQ(update.get(source).x, kFakeSampleData[0] * kFakeScaleValue);
      EXPECT_EQ(update.get(source).y, kFakeSampleData[1] * kFakeScaleValue);
      EXPECT_EQ(update.get(source).z, kFakeSampleData[2] * kFakeScaleValue);
    }

    update_ = update;
  }

  base::Optional<bool> is_supported_;
  AccelerometerUpdate update_;
};

}  // namespace

class AccelerometerProviderMojoTest : public ::testing::Test {
 protected:
  void SetUp() override {
    ash::AshTestHelper::InitParams init_params;
    init_params.start_session = false;
    ash_test_helper_.SetUp(std::move(init_params));

    sensor_hal_server_ =
        std::make_unique<chromeos::sensors::FakeSensorHalServer>();
    provider_ = new AccelerometerProviderMojo();

    chromeos::sensors::SensorHalDispatcher::Initialize();
    provider_->PrepareAndInitialize();
    provider_->AddObserver(&observer_);
  }

  void TearDown() override {
    chromeos::sensors::SensorHalDispatcher::Shutdown();
  }

  void AddDevice(int32_t iio_device_id,
                 chromeos::sensors::mojom::DeviceType type,
                 base::Optional<std::string> scale,
                 base::Optional<std::string> location) {
    std::set<chromeos::sensors::mojom::DeviceType> types;
    types.emplace(type);

    std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data;
    if (type == chromeos::sensors::mojom::DeviceType::ACCEL) {
      channels_data.resize(kNumberOfAxes);
      for (uint32_t i = 0; i < kNumberOfAxes; ++i) {
        channels_data[i].id = kAccelerometerChannels[i];
        channels_data[i].sample_data = kFakeSampleData[i];
      }
    }

    std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device(
        new chromeos::sensors::FakeSensorDevice(std::move(channels_data)));
    if (scale.has_value())
      sensor_device->SetAttribute(chromeos::sensors::mojom::kScale,
                                  scale.value());
    if (location.has_value())
      sensor_device->SetAttribute(chromeos::sensors::mojom::kLocation,
                                  location.value());

    sensor_hal_server_->GetSensorService()->SetDevice(
        iio_device_id, std::move(types), std::move(sensor_device));
  }

  void AddLidAccelerometer() {
    AddDevice(kFakeLidAccelerometerId,
              chromeos::sensors::mojom::DeviceType::ACCEL,
              base::NumberToString(kFakeScaleValue),
              kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]);
  }

  void TriggerNewDevicesTimeout() {
    provider_->OnNewDevicesTimeout();

    // Wait until task |OnECLidAngleDriverStatusChanged| arrives at |observer_|.
    base::RunLoop().RunUntilIdle();
  }

  void TriggerSamples() {
    // Simulate a disconnection of IIO Service.
    sensor_hal_server_->GetSensorService()->ClearReceivers();
    sensor_hal_server_->OnServerDisconnect();

    // Wait until the disconnect arrives at the dispatcher.
    base::RunLoop().RunUntilIdle();

    chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
        sensor_hal_server_->PassRemote());

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

  FakeObserver observer_;
  std::unique_ptr<chromeos::sensors::FakeSensorHalServer> sensor_hal_server_;
  scoped_refptr<AccelerometerProviderMojo> provider_;

  base::test::TaskEnvironment task_environment{
      base::test::TaskEnvironment::MainThreadType::UI};
  ash::AshTestHelper ash_test_helper_;
};

TEST_F(AccelerometerProviderMojoTest, CheckNoScale) {
  AddLidAccelerometer();
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL, base::nullopt,
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until all tasks done and no samples updated.
  base::RunLoop().RunUntilIdle();

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID);
}

TEST_F(AccelerometerProviderMojoTest, CheckNoLocation) {
  AddLidAccelerometer();
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue), base::nullopt);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until all tasks done and no samples updated.
  base::RunLoop().RunUntilIdle();

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID);
}

TEST_F(AccelerometerProviderMojoTest, GetSamplesOfOneAccel) {
  AddLidAccelerometer();
  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

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

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
}

TEST_F(AccelerometerProviderMojoTest, GetSamplesWithNoLidAngle) {
  AddLidAccelerometer();
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue),
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until samples are received.
  base::RunLoop().RunUntilIdle();

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID_BASE);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  // Simulate a disconnection of the accelerometer's mojo channel in IIO
  // Service.
  AddLidAccelerometer();

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

  EXPECT_FALSE(sensor_hal_server_->GetSensorService()->HasReceivers());
}

TEST_F(AccelerometerProviderMojoTest, GetSamplesWithLidAngle) {
  AddLidAccelerometer();
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue),
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);
  AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL,
            base::nullopt, base::nullopt);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until all setups are finished and no samples updated.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_TRUE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL_LID);
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  provider_->TriggerRead();

  // Wait until samples are received.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  // Simulate a disconnection of IIO Service.
  sensor_hal_server_->GetSensorService()->ClearReceivers();
  sensor_hal_server_->OnServerDisconnect();

  // Wait until the disconnect arrives at the dispatcher.
  base::RunLoop().RunUntilIdle();

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until samples are received.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
}

TEST_F(AccelerometerProviderMojoTest, GetSamplesOfNewDevices) {
  // New device: lid-accelerometer.
  AddLidAccelerometer();
  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

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

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID);

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  // New device: base-accelerometer.
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue),
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);

  TriggerSamples();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID_BASE);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  // New device: EC Lid Angle Driver.
  AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL,
            base::nullopt, base::nullopt);

  TriggerSamples();

  EXPECT_TRUE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL_LID);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
}

TEST_F(AccelerometerProviderMojoTest, NoSamplesFromBaseOnly) {
  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

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

  EXPECT_EQ(provider_->GetInitializationStateForTesting(),
            MojoState::INITIALIZING);

  EXPECT_FALSE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  // New device: base-accelerometer.
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue),
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);

  // Wait until all setups are finished and no samples updated.
  base::RunLoop().RunUntilIdle();

  // Simulate timeout to check |ec_lid_angle_driver_status_|.
  TriggerNewDevicesTimeout();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::BASE);

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_FALSE(observer_.is_supported_.value());
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  // New device: lid-accelerometer.
  AddLidAccelerometer();

  TriggerSamples();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::LID_BASE);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  observer_.update_.Reset();

  // New device: EC Lid Angle Driver.
  AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL,
            base::nullopt, base::nullopt);

  TriggerSamples();

  EXPECT_TRUE(observer_.is_supported_.value());
  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL_LID);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
}

TEST_F(AccelerometerProviderMojoTest, NoSamplesFromLidAngle) {
  // New device: EC Lid Angle Driver.
  AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL,
            base::nullopt, base::nullopt);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
      sensor_hal_server_->PassRemote());

  // Wait until all setups are finished and no samples updated.
  base::RunLoop().RunUntilIdle();

  provider_->TriggerRead();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL);

  EXPECT_TRUE(observer_.is_supported_.has_value());
  EXPECT_TRUE(observer_.is_supported_.value());
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  // New device: base-accelerometer.
  AddDevice(kFakeBaseAccelerometerId,
            chromeos::sensors::mojom::DeviceType::ACCEL,
            base::NumberToString(kFakeScaleValue),
            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);

  // Wait until all setups are finished and no samples updated.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL);

  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));

  // New device: lid-accelerometer.
  AddLidAccelerometer();

  TriggerSamples();

  EXPECT_EQ(provider_->GetInitializationStateForTesting(), MojoState::ANGL_LID);

  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
}

}  // namespace ash
