// 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 <utility>

#include "base/bind.h"

namespace ash {

namespace {

constexpr int kTimeoutToleranceInMilliseconds = 500;
constexpr double kReadFrequencyInHz = 10.0;

}  // namespace

AccelerometerSamplesObserver::AccelerometerSamplesObserver(
    int iio_device_id,
    mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote,
    float scale,
    OnSampleUpdatedCallback on_sample_updated_callback)
    : iio_device_id_(iio_device_id),
      sensor_device_remote_(std::move(sensor_device_remote)),
      scale_(scale),
      on_sample_updated_callback_(std::move(on_sample_updated_callback)) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_device_remote_.is_bound());

  sensor_device_remote_->GetAllChannelIds(
      base::BindOnce(&AccelerometerSamplesObserver::GetAllChannelIdsCallback,
                     weak_factory_.GetWeakPtr()));
}

AccelerometerSamplesObserver::~AccelerometerSamplesObserver() = default;

void AccelerometerSamplesObserver::SetEnabled(bool enabled) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (enabled_ == enabled)
    return;

  enabled_ = enabled;

  UpdateSensorDeviceFrequency();
}

void AccelerometerSamplesObserver::OnSampleUpdated(
    const base::flat_map<int32_t, int64_t>& sample) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (sample.size() != kNumberOfAxes) {
    LOG(ERROR) << "Invalid sample with size: " << sample.size();
    OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
    return;
  }

  auto it = sample.begin();
  std::vector<float> output_sample;
  for (size_t axes = 0; axes < kNumberOfAxes; ++axes) {
    if (axes != 0)
      ++it;

    if (it == sample.end() || it->first != channel_indices_[axes])
      it = sample.find(channel_indices_[axes]);

    if (it == sample.end()) {
      LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axes]
                 << " in sample";
      OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
      return;
    }

    output_sample.push_back(it->second * scale_);
  }

  on_sample_updated_callback_.Run(iio_device_id_, output_sample);
}

void AccelerometerSamplesObserver::OnErrorOccurred(
    chromeos::sensors::mojom::ObserverErrorType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (type) {
    case chromeos::sensors::mojom::ObserverErrorType::ALREADY_STARTED:
      LOG(ERROR) << "Device " << iio_device_id_
                 << ": Another observer has already started to read samples";
      Reset();
      break;

    case chromeos::sensors::mojom::ObserverErrorType::FREQUENCY_INVALID:
      if (!enabled_)  // It's normal if this observer is not enabled
        break;

      LOG(ERROR) << "Device " << iio_device_id_
                 << ": Observer started with an invalid frequency";
      UpdateSensorDeviceFrequency();

      break;

    case chromeos::sensors::mojom::ObserverErrorType::NO_ENABLED_CHANNELS:
      LOG(ERROR) << "Device " << iio_device_id_
                 << ": Observer started with no channels enabled";
      if (sensor_device_remote_.is_bound()) {
        sensor_device_remote_->SetChannelsEnabled(
            std::vector<int32_t>(channel_indices_,
                                 channel_indices_ + kNumberOfAxes),
            /*enable=*/true,
            base::BindOnce(
                &AccelerometerSamplesObserver::SetChannelsEnabledCallback,
                weak_factory_.GetWeakPtr()));
      }

      break;

    case chromeos::sensors::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED:
      LOG(ERROR) << "Device " << iio_device_id_
                 << ": Failed to set frequency to the physical device";
      break;

    case chromeos::sensors::mojom::ObserverErrorType::GET_FD_FAILED:
      LOG(ERROR) << "Device " << iio_device_id_
                 << ": Failed to get the device's fd to poll on";
      break;

    case chromeos::sensors::mojom::ObserverErrorType::READ_FAILED:
      LOG(ERROR) << "Device " << iio_device_id_ << ": Failed to read a sample";
      break;

    case chromeos::sensors::mojom::ObserverErrorType::READ_TIMEOUT:
      LOG(ERROR) << "Device " << iio_device_id_ << ": A read timed out";
      break;

    default:
      LOG(ERROR) << "Device " << iio_device_id_ << ": error "
                 << static_cast<int>(type);
      break;
  }
}

void AccelerometerSamplesObserver::Reset() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR) << "Resetting AccelerometerSamplesObserver: " << iio_device_id_;
  receiver_.reset();
  sensor_device_remote_.reset();
}

void AccelerometerSamplesObserver::GetAllChannelIdsCallback(
    const std::vector<std::string>& iio_channel_ids) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_device_remote_.is_bound());

  iio_channel_ids_ = std::move(iio_channel_ids);

  for (size_t axis = 0; axis < kNumberOfAxes; ++axis) {
    bool found = false;
    for (size_t channel_index = 0; channel_index < iio_channel_ids_.size();
         ++channel_index) {
      if (iio_channel_ids_[channel_index].compare(
              kAccelerometerChannels[axis]) == 0) {
        found = true;
        channel_indices_[axis] = channel_index;
        break;
      }
    }

    if (!found) {
      LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axis];
      Reset();
      return;
    }
  }

  sensor_device_remote_->SetChannelsEnabled(
      std::vector<int32_t>(channel_indices_, channel_indices_ + kNumberOfAxes),
      /*enable=*/true,
      base::BindOnce(&AccelerometerSamplesObserver::SetChannelsEnabledCallback,
                     weak_factory_.GetWeakPtr()));

  StartReading();
}

void AccelerometerSamplesObserver::StartReading() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_device_remote_.is_bound());

  sensor_device_remote_->SetTimeout(kTimeoutToleranceInMilliseconds);

  sensor_device_remote_->StartReadingSamples(GetPendingRemote());
}

void AccelerometerSamplesObserver::UpdateSensorDeviceFrequency() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!sensor_device_remote_.is_bound())
    return;

  sensor_device_remote_->SetFrequency(
      enabled_ ? kReadFrequencyInHz : 0.0,
      base::BindOnce(&AccelerometerSamplesObserver::SetFrequencyCallback,
                     weak_factory_.GetWeakPtr()));
}

mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
AccelerometerSamplesObserver::GetPendingRemote() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto pending_remote = receiver_.BindNewPipeAndPassRemote();

  receiver_.set_disconnect_handler(
      base::BindOnce(&AccelerometerSamplesObserver::OnObserverDisconnect,
                     weak_factory_.GetWeakPtr()));
  return pending_remote;
}

void AccelerometerSamplesObserver::OnObserverDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR) << "OnObserverDisconnect error, assuming IIO Service crashes and "
                "waiting for its relaunch.";
  // Don't reset |sensor_device_remote_| so that LightProviderMojo can get the
  // disconnection.
  receiver_.reset();
}

void AccelerometerSamplesObserver::SetFrequencyCallback(
    double result_frequency) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if ((result_frequency > 0.0 && enabled_) ||
      (result_frequency == 0.0 && !enabled_)) {
    return;
  }

  LOG(ERROR) << "Failed to set frequency: " << result_frequency
             << " with the samples observer enabled: "
             << (enabled_ ? "true" : "false");
  Reset();
}

void AccelerometerSamplesObserver::SetChannelsEnabledCallback(
    const std::vector<int32_t>& failed_indices) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (int32_t index : failed_indices)
    LOG(ERROR) << "Failed to enable " << iio_channel_ids_[index];

  if (!failed_indices.empty())
    Reset();
}

}  // namespace ash
