// Copyright 2014 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_reader.h"

#include "ash/shell.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/no_destructor.h"
#include "base/sequenced_task_runner.h"
#include "base/task/current_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chromeos/components/sensors/buildflags.h"
#if BUILDFLAG(USE_IIOSERVICE)
#include "ash/accelerometer/accelerometer_provider_mojo.h"
#else  // !BUILDFLAG(USE_IIOSERVICE)
#include "ash/accelerometer/accelerometer_file_reader.h"
#endif  // BUILDFLAG(USE_IIOSERVICE)

namespace ash {

// static
AccelerometerReader* AccelerometerReader::GetInstance() {
  static base::NoDestructor<AccelerometerReader> accelerometer_reader;
  return accelerometer_reader.get();
}

void AccelerometerReader::Initialize() {
  accelerometer_provider_->PrepareAndInitialize();
}

void AccelerometerReader::AddObserver(Observer* observer) {
  accelerometer_provider_->AddObserver(observer);
}

void AccelerometerReader::RemoveObserver(Observer* observer) {
  accelerometer_provider_->RemoveObserver(observer);
}

void AccelerometerReader::StartListenToTabletModeController() {
  accelerometer_provider_->StartListenToTabletModeController();
}

void AccelerometerReader::StopListenToTabletModeController() {
  accelerometer_provider_->StopListenToTabletModeController();
}

void AccelerometerReader::SetEnabled(bool enabled) {
  accelerometer_provider_->SetEmitEvents(enabled);
}

void AccelerometerReader::SetECLidAngleDriverStatusForTesting(
    ECLidAngleDriverStatus ec_lid_angle_driver_status) {
  accelerometer_provider_->SetECLidAngleDriverStatusForTesting(  // IN-TEST
      ec_lid_angle_driver_status);
}

AccelerometerReader::AccelerometerReader() {
#if BUILDFLAG(USE_IIOSERVICE)
  accelerometer_provider_ = new AccelerometerProviderMojo();
#else   // !BUILDFLAG(USE_IIOSERVICE)
  accelerometer_provider_ = new AccelerometerFileReader();
#endif  // BUILDFLAG(USE_IIOSERVICE)
}

AccelerometerReader::~AccelerometerReader() = default;

void AccelerometerProviderInterface::OnTabletPhysicalStateChanged() {
  DCHECK(base::CurrentUIThread::IsSet());

  if (ShouldDelayOnTabletPhysicalStateChanged())
    return;

  // When CrOS EC lid angle driver is not present, accelerometer read is always
  // ON and can't be tuned. Thus this object no longer listens to tablet mode
  // event.
  auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED) {
    tablet_mode_controller->RemoveObserver(this);
    return;
  }

  // Auto rotation is turned on when the device is physically used as a tablet
  // (i.e. flipped or detached), regardless of the UI state (i.e. whether tablet
  // mode is turned on or off).
  if (tablet_mode_controller->is_in_tablet_physical_state())
    TriggerRead();
  else
    CancelRead();
}

void AccelerometerProviderInterface::AddObserver(
    AccelerometerReader::Observer* observer) {
  DCHECK(base::CurrentUIThread::IsSet());

  if (ec_lid_angle_driver_status_ != ECLidAngleDriverStatus::UNKNOWN) {
    observer->OnECLidAngleDriverStatusChanged(
        ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED);
  }

  observers_.AddObserver(observer);
}

void AccelerometerProviderInterface::RemoveObserver(
    AccelerometerReader::Observer* observer) {
  DCHECK(base::CurrentUIThread::IsSet());
  observers_.RemoveObserver(observer);
}

void AccelerometerProviderInterface::StartListenToTabletModeController() {
  Shell::Get()->tablet_mode_controller()->AddObserver(this);
}

void AccelerometerProviderInterface::StopListenToTabletModeController() {
  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
}

void AccelerometerProviderInterface::SetEmitEvents(bool emit_events) {
  DCHECK(base::CurrentUIThread::IsSet());
  emit_events_ = emit_events;
}

void AccelerometerProviderInterface::SetECLidAngleDriverStatusForTesting(
    ECLidAngleDriverStatus status) {
  SetECLidAngleDriverStatus(status);
}

AccelerometerProviderInterface::AccelerometerProviderInterface()
    : ui_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
  DCHECK(base::CurrentUIThread::IsSet());
}

AccelerometerProviderInterface::~AccelerometerProviderInterface() = default;

bool AccelerometerProviderInterface::ShouldDelayOnTabletPhysicalStateChanged() {
  return false;
}

void AccelerometerProviderInterface::SetECLidAngleDriverStatus(
    ECLidAngleDriverStatus status) {
  DCHECK(base::CurrentUIThread::IsSet());
  DCHECK_NE(status, ECLidAngleDriverStatus::UNKNOWN);

  if (status == ec_lid_angle_driver_status_)
    return;

  ec_lid_angle_driver_status_ = status;

  for (auto& observer : observers_) {
    observer.OnECLidAngleDriverStatusChanged(ec_lid_angle_driver_status_ ==
                                             ECLidAngleDriverStatus::SUPPORTED);
  }
}

ECLidAngleDriverStatus
AccelerometerProviderInterface::GetECLidAngleDriverStatus() const {
  DCHECK(base::CurrentUIThread::IsSet());

  return ec_lid_angle_driver_status_;
}

void AccelerometerProviderInterface::NotifyAccelerometerUpdated(
    const AccelerometerUpdate& update) {
  DCHECK(base::CurrentUIThread::IsSet());
  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);

  if (!emit_events_)
    return;

  for (auto& observer : observers_)
    observer.OnAccelerometerUpdated(update);
}

}  // namespace ash
