// 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 <iterator>
#include <utility>

#include "ash/accelerometer/accelerometer_constants.h"
#include "ash/shell.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/bind.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chromeos/components/sensors/ash/sensor_hal_dispatcher.h"

namespace ash {

namespace {

// Delay of the reconnection to Sensor Hal Dispatcher.
constexpr base::TimeDelta kDelayReconnect =
    base::TimeDelta::FromMilliseconds(1000);

// Timeout for the late-present devices: 10 seconds.
constexpr base::TimeDelta kNewDevicesTimeout =
    base::TimeDelta::FromMilliseconds(10000);

}  // namespace

AccelerometerProviderMojo::AccelerometerProviderMojo() = default;

void AccelerometerProviderMojo::PrepareAndInitialize() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  RegisterSensorClient();
}

void AccelerometerProviderMojo::TriggerRead() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED)
    EnableAccelerometerReading();
}

void AccelerometerProviderMojo::CancelRead() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED)
    DisableAccelerometerReading();
}

void AccelerometerProviderMojo::SetUpChannel(
    mojo::PendingRemote<chromeos::sensors::mojom::SensorService>
        pending_remote) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (sensor_service_remote_.is_bound()) {
    LOG(ERROR) << "Ignoring the second Remote<SensorService>";
    return;
  }

  sensor_service_remote_.Bind(std::move(pending_remote));
  sensor_service_remote_.set_disconnect_handler(base::BindOnce(
      &AccelerometerProviderMojo::OnSensorServiceDisconnect, this));
  SetNewDevicesObserver();

  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::UNKNOWN) {
    sensor_service_remote_->GetDeviceIds(
        chromeos::sensors::mojom::DeviceType::ANGL,
        base::BindOnce(&AccelerometerProviderMojo::GetLidAngleIdsCallback,
                       this));
  }

  sensor_service_remote_->GetDeviceIds(
      chromeos::sensors::mojom::DeviceType::ACCEL,
      base::BindOnce(&AccelerometerProviderMojo::GetAccelerometerIdsCallback,
                     this));
}

void AccelerometerProviderMojo::OnNewDeviceAdded(
    int32_t iio_device_id,
    const std::vector<chromeos::sensors::mojom::DeviceType>& types) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_NE(initialization_state_, MojoState::ANGL_LID);

  for (const auto& type : types) {
    if (type == chromeos::sensors::mojom::DeviceType::ACCEL) {
      if (initialization_state_ == MojoState::LID_BASE) {
        // Don't need a new accelerometer.
        continue;
      }

      if (accelerometers_.find(iio_device_id) != accelerometers_.end())
        continue;

      RegisterAccelerometerWithId(iio_device_id);
    } else if (type == chromeos::sensors::mojom::DeviceType::ANGL) {
      SetECLidAngleDriverSupported();
    }
  }
}

MojoState AccelerometerProviderMojo::GetInitializationStateForTesting() const {
  return initialization_state_;
}

bool AccelerometerProviderMojo::ShouldDelayOnTabletPhysicalStateChanged() {
  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::UNKNOWN) {
    pending_on_tablet_physical_state_changed_ = true;
    return true;
  }

  return false;
}

AccelerometerProviderMojo::AccelerometerData::AccelerometerData() = default;
AccelerometerProviderMojo::AccelerometerData::~AccelerometerData() = default;

AccelerometerProviderMojo::~AccelerometerProviderMojo() = default;

void AccelerometerProviderMojo::RegisterSensorClient() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto* dispatcher = chromeos::sensors::SensorHalDispatcher::GetInstance();
  if (!dispatcher) {
    // In unit tests, SensorHalDispatcher is not initialized.
    return;
  }

  dispatcher->RegisterClient(sensor_hal_client_.BindNewPipeAndPassRemote());

  sensor_hal_client_.set_disconnect_handler(base::BindOnce(
      &AccelerometerProviderMojo::OnSensorHalClientFailure, this));
}

void AccelerometerProviderMojo::OnSensorHalClientFailure() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR) << "OnSensorHalClientFailure";

  ResetSensorService();
  sensor_hal_client_.reset();

  ui_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient, this),
      kDelayReconnect);
}

void AccelerometerProviderMojo::OnSensorServiceDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR) << "OnSensorServiceDisconnect";

  ResetSensorService();
}

void AccelerometerProviderMojo::ResetSensorService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (auto& accelerometer : accelerometers_) {
    accelerometer.second.remote.reset();
    accelerometer.second.samples_observer.reset();
  }

  new_devices_observer_.reset();
  sensor_service_remote_.reset();
}

void AccelerometerProviderMojo::SetECLidAngleDriverSupported() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED)
    return;

  DCHECK_NE(initialization_state_, MojoState::ANGL);
  DCHECK_NE(initialization_state_, MojoState::ANGL_LID);

  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::NOT_SUPPORTED) {
    // |GetECLidAngleDriverStatus()| will be set to NOT_SUPPORTED when waiting
    // for new devices is timed out. However, this function may still be called
    // after the timeout and when that happens, we'll need to overwrite the
    // status and revert some changes.
    LOG(WARNING) << "Overwriting ECLidAngleDriverStatus from NOT_SUPPORTED "
                    "to SUPPORTED";

    // Restarts to listen to TabletPhysicalStateChanged from
    // TabletModeController. Allows the enabled samples when setting
    // ECLidAngleDriverStatus to SUPPORTED.
    StartListenToTabletModeController();
  }

  SetECLidAngleDriverStatus(ECLidAngleDriverStatus::SUPPORTED);

  if (pending_on_tablet_physical_state_changed_)
    OnTabletPhysicalStateChanged();

  UpdateStateWithECLidAngleDriverSupported();
}

void AccelerometerProviderMojo::UpdateStateWithECLidAngleDriverSupported() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (initialization_state_) {
    case MojoState::INITIALIZING:
      initialization_state_ = MojoState::ANGL;
      break;

    case MojoState::BASE: {
      initialization_state_ = MojoState::ANGL;

      // Ignores the base-accelerometer as it's no longer needed for the only
      // use case: calculating the angle between the lid and the base, which is
      // substituted by the driver.
      auto it = location_to_accelerometer_id_.find(
          ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
      DCHECK(it != location_to_accelerometer_id_.end());
      IgnoreAccelerometer(it->second);
      break;
    }

    case MojoState::LID:
      initialization_state_ = MojoState::ANGL_LID;
      break;

    case MojoState::LID_BASE: {
      initialization_state_ = MojoState::ANGL_LID;

      // Ignores the base-accelerometer as it's no longer needed for the only
      // use case: calculating the angle between the lid and the base, which is
      // substituted by the driver.
      auto it = location_to_accelerometer_id_.find(
          ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
      DCHECK(it != location_to_accelerometer_id_.end());
      IgnoreAccelerometer(it->second);
      break;
    }

    default:
      LOG(FATAL) << "Unexpected state: "
                 << static_cast<int32_t>(initialization_state_);
      break;
  }

  if (initialization_state_ == MojoState::ANGL_LID)
    new_devices_observer_.reset();
}

void AccelerometerProviderMojo::UpdateStateWithLidAccelerometer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (initialization_state_) {
    case MojoState::INITIALIZING:
      initialization_state_ = MojoState::LID;
      break;

    case MojoState::BASE: {
      initialization_state_ = MojoState::LID_BASE;

      auto it = location_to_accelerometer_id_.find(
          ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
      DCHECK(it != location_to_accelerometer_id_.end());
      if (accelerometers_[it->second].samples_observer.get())
        accelerometers_[it->second].samples_observer->SetEnabled(true);

      break;
    }

    case MojoState::ANGL:
      initialization_state_ = MojoState::ANGL_LID;
      new_devices_observer_.reset();
      break;

    default:
      LOG(FATAL) << "Unexpected state: "
                 << static_cast<int32_t>(initialization_state_);
      break;
  }
}

void AccelerometerProviderMojo::UpdateStateWithBaseAccelerometer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (initialization_state_) {
    case MojoState::INITIALIZING:
      initialization_state_ = MojoState::BASE;
      break;

    case MojoState::LID:
      initialization_state_ = MojoState::LID_BASE;
      break;

    case MojoState::ANGL:
    case MojoState::ANGL_LID: {
      // Ignores the base-accelerometer as it's no longer needed for the only
      // use case: calculating the angle between the lid and the base, which is
      // substituted by the driver.
      auto it = location_to_accelerometer_id_.find(
          ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
      DCHECK(it != location_to_accelerometer_id_.end());
      IgnoreAccelerometer(it->second);
      break;
    }

    default:
      LOG(FATAL) << "Unexpected state: "
                 << static_cast<int32_t>(initialization_state_);
      break;
  }
}

void AccelerometerProviderMojo::SetNewDevicesObserver() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_service_remote_.is_bound());
  DCHECK(!new_devices_observer_.is_bound());

  if (initialization_state_ == MojoState::ANGL_LID) {
    // Don't need any further devices.
    return;
  }

  sensor_service_remote_->RegisterNewDevicesObserver(
      new_devices_observer_.BindNewPipeAndPassRemote());
  new_devices_observer_.set_disconnect_handler(base::BindOnce(
      &AccelerometerProviderMojo::OnNewDevicesObserverDisconnect, this));

  ui_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&AccelerometerProviderMojo::OnNewDevicesTimeout, this),
      kNewDevicesTimeout);
}

void AccelerometerProviderMojo::AccelerometerProviderMojo::
    OnNewDevicesObserverDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR)
      << "OnNewDevicesObserverDisconnect, resetting SensorService as IIO "
         "Service should be destructed and waiting for the relaunch of it.";
  ResetSensorService();
}

void AccelerometerProviderMojo::OnNewDevicesTimeout() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!sensor_service_remote_.is_bound()) {
    // Skips and waits for the next timeout for the case that IIO Service
    // disconnects after the first connection.
    return;
  }

  if (GetECLidAngleDriverStatus() != ECLidAngleDriverStatus::UNKNOWN)
    return;

  if (initialization_state_ == MojoState::INITIALIZING ||
      initialization_state_ == MojoState::BASE ||
      initialization_state_ == MojoState::ANGL) {
    LOG(ERROR) << "Unfinished initialization after timeout: "
               << static_cast<int32_t>(initialization_state_);
  }

  SetECLidAngleDriverStatus(ECLidAngleDriverStatus::NOT_SUPPORTED);
  EnableAccelerometerReading();

  if (pending_on_tablet_physical_state_changed_)
    OnTabletPhysicalStateChanged();
}

void AccelerometerProviderMojo::GetLidAngleIdsCallback(
    const std::vector<int32_t>& lid_angle_ids) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!lid_angle_ids.empty())
    SetECLidAngleDriverSupported();
}

void AccelerometerProviderMojo::GetAccelerometerIdsCallback(
    const std::vector<int32_t>& accelerometer_ids) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (int32_t id : accelerometer_ids)
    RegisterAccelerometerWithId(id);
}

void AccelerometerProviderMojo::RegisterAccelerometerWithId(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto& accelerometer = accelerometers_[id];

  if (accelerometer.ignored) {
    // Something went wrong in the previous initialization. Ignoring this accel.
    return;
  }

  DCHECK(!accelerometer.remote.is_bound());
  DCHECK(!accelerometer.samples_observer.get());

  if (!sensor_service_remote_.is_bound()) {
    // Something went wrong. Skipping here.
    return;
  }

  accelerometer.remote.reset();

  sensor_service_remote_->GetDevice(
      id, accelerometer.remote.BindNewPipeAndPassReceiver());
  accelerometer.remote.set_disconnect_handler(base::BindOnce(
      &AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect, this, id));

  std::vector<std::string> attr_names;
  if (!accelerometer.location.has_value())
    attr_names.push_back(chromeos::sensors::mojom::kLocation);
  if (!accelerometer.scale.has_value())
    attr_names.push_back(chromeos::sensors::mojom::kScale);

  if (attr_names.empty()) {
    // Create the observer directly if the attributes have already been
    // retrieved.
    CreateAccelerometerSamplesObserver(id);

    return;
  }

  if (initialization_state_ == MojoState::ANGL_LID ||
      initialization_state_ == MojoState::LID_BASE) {
    // No need of new accelerometers.
    return;
  }

  accelerometer.remote->GetAttributes(
      attr_names,
      base::BindOnce(&AccelerometerProviderMojo::GetAttributesCallback, this,
                     id));
}

void AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR)
      << "OnAccelerometerRemoteDisconnect: " << id
      << ", resetting SensorService as IIO Service should be destructed and "
         "waiting for the relaunch of it.";
  ResetSensorService();
}

void AccelerometerProviderMojo::GetAttributesCallback(
    int32_t id,
    const std::vector<base::Optional<std::string>>& values) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto& accelerometer = accelerometers_[id];
  DCHECK(accelerometer.remote.is_bound());

  size_t index = 0;
  if (!accelerometer.location.has_value()) {
    if (index >= values.size()) {
      LOG(ERROR) << "values doesn't contain location attribute.";
      IgnoreAccelerometer(id);
      return;
    }

    if (!values[index].has_value()) {
      LOG(WARNING) << "No location attribute for accel with id: " << id;
      IgnoreAccelerometer(id);
      return;
    }

    auto* it = base::ranges::find(kLocationStrings, values[index]);
    if (it == std::end(kLocationStrings)) {
      LOG(WARNING) << "Unrecognized location: " << values[index].value()
                   << " for device with id: ";
      IgnoreAccelerometer(id);
      return;
    }

    AccelerometerSource source = static_cast<AccelerometerSource>(
        std::distance(std::begin(kLocationStrings), it));
    accelerometer.location = source;

    if (location_to_accelerometer_id_.find(source) !=
        location_to_accelerometer_id_.end()) {
      LOG(WARNING) << "Duplicated location source " << source
                   << " of accel id: " << id << ", and accel id: "
                   << location_to_accelerometer_id_[source];
      IgnoreAccelerometer(id);
      return;
    }

    location_to_accelerometer_id_[source] = id;
    ++index;
  }

  if (!accelerometer.scale.has_value()) {
    if (index >= values.size()) {
      LOG(ERROR) << "values doesn't contain scale attribute.";
      IgnoreAccelerometer(id);
      return;
    }

    double scale = 0.0;
    if (!values[index].has_value() ||
        !base::StringToDouble(values[index].value(), &scale)) {
      LOG(ERROR) << "Invalid scale: " << values[index].value_or("")
                 << ", for accel with id: " << id;
      IgnoreAccelerometer(id);
      return;
    }
    accelerometer.scale = scale;

    ++index;
  }

  if (accelerometer.location == ACCELEROMETER_SOURCE_SCREEN) {
    UpdateStateWithLidAccelerometer();
  } else {
    DCHECK_EQ(accelerometer.location.value(),
              ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
    UpdateStateWithBaseAccelerometer();
  }

  if (accelerometer.ignored) {
    // base-accelerometer is not needed if EC Lid Angle Driver is supported.
    return;
  }

  CreateAccelerometerSamplesObserver(id);
}

void AccelerometerProviderMojo::IgnoreAccelerometer(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto& accelerometer = accelerometers_[id];

  LOG(WARNING) << "Ignoring accel with id: " << id;
  accelerometer.ignored = true;
  accelerometer.remote.reset();
  accelerometer.samples_observer.reset();
}

void AccelerometerProviderMojo::CreateAccelerometerSamplesObserver(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto& accelerometer = accelerometers_[id];
  DCHECK(accelerometer.remote.is_bound());
  DCHECK(!accelerometer.ignored);
  DCHECK(accelerometer.scale.has_value() && accelerometer.location.has_value());

  if (accelerometer.location == ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD &&
      GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED) {
    // Skipping as it's only needed if lid-angle is not supported.
    // |GetLidAngleIdsCallback| will call this function with this |id| again if
    // it's found not supported.
    return;
  }

  accelerometer.samples_observer =
      std::make_unique<AccelerometerSamplesObserver>(
          id, std::move(accelerometer.remote), accelerometer.scale.value(),
          base::BindRepeating(
              &AccelerometerProviderMojo::OnSampleUpdatedCallback, this));

  if (initialization_state_ == MojoState::BASE) {
    DCHECK_EQ(accelerometer.location.value(),
              ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
    // Don't need base-accelerometer's samples without lid-accelerometer.
    return;
  }

  if (accelerometer_read_on_)
    accelerometer.samples_observer->SetEnabled(true);
}

void AccelerometerProviderMojo::EnableAccelerometerReading() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_NE(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::UNKNOWN);
  if (accelerometer_read_on_)
    return;

  accelerometer_read_on_ = true;

  if (initialization_state_ == MojoState::BASE) {
    // Don't need base-accelerometer's samples without lid-accelerometer.
    return;
  }

  for (auto& accelerometer : accelerometers_) {
    if (!accelerometer.second.samples_observer.get())
      continue;

    accelerometer.second.samples_observer->SetEnabled(true);
  }
}

void AccelerometerProviderMojo::DisableAccelerometerReading() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::SUPPORTED);
  if (!accelerometer_read_on_)
    return;

  accelerometer_read_on_ = false;

  for (auto& accelerometer : accelerometers_) {
    if (!accelerometer.second.samples_observer.get())
      continue;

    accelerometer.second.samples_observer->SetEnabled(false);
  }
}

void AccelerometerProviderMojo::OnSampleUpdatedCallback(
    int iio_device_id,
    std::vector<float> sample) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(sample.size(), kNumberOfAxes);

  auto& accelerometer = accelerometers_[iio_device_id];
  DCHECK(accelerometer.location.has_value());
  DCHECK(accelerometer.location == ACCELEROMETER_SOURCE_SCREEN ||
         initialization_state_ == MojoState::LID_BASE);

  if (!accelerometer_read_on_) {
    // This sample is not needed.
    return;
  }

  update_.Set(accelerometers_[iio_device_id].location.value(), sample[0],
              sample[1], sample[2]);

  if (initialization_state_ == MojoState::LID_BASE &&
      (!update_.has(ACCELEROMETER_SOURCE_SCREEN) ||
       !update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))) {
    // Wait for the other accelerometer to be updated.
    return;
  }

  NotifyAccelerometerUpdated(update_);
  update_.Reset();
}

}  // namespace ash
