// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/public/cpp/assistant/assistant_state_base.h"

#include <ostream>
#include <sstream>

#include "ash/public/cpp/accelerators.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece_forward.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"

namespace ash {

namespace {

using assistant::prefs::AssistantOnboardingMode;

#define PRINT_VALUE(value) PrintValue(&result, #value, value())

template <typename T, std::enable_if_t<std::is_enum<T>::value>* = nullptr>
void PrintValue(std::stringstream* result, const absl::optional<T>& value) {
  *result << base::NumberToString(static_cast<int>(value.value()));
}

template <typename T, std::enable_if_t<!std::is_enum<T>::value>* = nullptr>
void PrintValue(std::stringstream* result, const absl::optional<T>& value) {
  *result << value.value();
}

template <typename T>
void PrintValue(std::stringstream* result,
                const std::string& name,
                const absl::optional<T>& value) {
  *result << std::endl << "  " << name << ": ";
  if (value.has_value())
    PrintValue(result, value);
  else
    *result << ("(no value)");
}

}  // namespace

AssistantStateBase::AssistantStateBase() = default;

AssistantStateBase::~AssistantStateBase() {
  for (auto& observer : observers_)
    observer.OnAssistantStateDestroyed();
}

std::string AssistantStateBase::ToString() const {
  std::stringstream result;
  result << "AssistantStatus: ";
  result << assistant_status_;
  PRINT_VALUE(settings_enabled);
  PRINT_VALUE(context_enabled);
  PRINT_VALUE(hotword_enabled);
  PRINT_VALUE(allowed_state);
  PRINT_VALUE(locale);
  PRINT_VALUE(arc_play_store_enabled);
  PRINT_VALUE(locked_full_screen_enabled);
  PRINT_VALUE(onboarding_mode);
  return result.str();
}

void AssistantStateBase::AddObserver(AssistantStateObserver* observer) {
  observers_.AddObserver(observer);
  InitializeObserver(observer);
}

void AssistantStateBase::RemoveObserver(AssistantStateObserver* observer) {
  observers_.RemoveObserver(observer);
}

void AssistantStateBase::RegisterPrefChanges(PrefService* pref_service) {
  pref_change_registrar_.reset();

  if (!pref_service)
    return;

  // Register preference changes.
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(pref_service);
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantConsentStatus,
      base::BindRepeating(&AssistantStateBase::UpdateConsentStatus,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantContextEnabled,
      base::BindRepeating(&AssistantStateBase::UpdateContextEnabled,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantEnabled,
      base::BindRepeating(&AssistantStateBase::UpdateSettingsEnabled,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantHotwordAlwaysOn,
      base::BindRepeating(&AssistantStateBase::UpdateHotwordAlwaysOn,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantHotwordEnabled,
      base::BindRepeating(&AssistantStateBase::UpdateHotwordEnabled,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantLaunchWithMicOpen,
      base::BindRepeating(&AssistantStateBase::UpdateLaunchWithMicOpen,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantNotificationEnabled,
      base::BindRepeating(&AssistantStateBase::UpdateNotificationEnabled,
                          base::Unretained(this)));
  pref_change_registrar_->Add(
      assistant::prefs::kAssistantOnboardingMode,
      base::BindRepeating(&AssistantStateBase::UpdateOnboardingMode,
                          base::Unretained(this)));

  UpdateConsentStatus();
  UpdateContextEnabled();
  UpdateSettingsEnabled();
  UpdateHotwordAlwaysOn();
  UpdateHotwordEnabled();
  UpdateLaunchWithMicOpen();
  UpdateNotificationEnabled();
  UpdateOnboardingMode();
}

bool AssistantStateBase::IsScreenContextAllowed() const {
  return allowed_state() == assistant::AssistantAllowedState::ALLOWED &&
         settings_enabled().value_or(false) &&
         context_enabled().value_or(false);
}

bool AssistantStateBase::HasAudioInputDevice() const {
  ash::AudioDeviceList devices;
  ash::CrasAudioHandler::Get()->GetAudioDevices(&devices);
  for (const AudioDevice& device : devices) {
    if (device.is_input)
      return true;
  }
  return false;
}

void AssistantStateBase::InitializeObserver(AssistantStateObserver* observer) {
  if (consent_status_.has_value())
    observer->OnAssistantConsentStatusChanged(consent_status_.value());
  if (context_enabled_.has_value())
    observer->OnAssistantContextEnabled(context_enabled_.value());
  if (settings_enabled_.has_value())
    observer->OnAssistantSettingsEnabled(settings_enabled_.value());
  if (hotword_always_on_.has_value())
    observer->OnAssistantHotwordAlwaysOn(hotword_always_on_.value());
  if (hotword_enabled_.has_value())
    observer->OnAssistantHotwordEnabled(hotword_enabled_.value());
  if (launch_with_mic_open_.has_value())
    observer->OnAssistantLaunchWithMicOpen(launch_with_mic_open_.value());
  if (notification_enabled_.has_value())
    observer->OnAssistantNotificationEnabled(notification_enabled_.value());
  if (onboarding_mode_.has_value())
    observer->OnAssistantOnboardingModeChanged(onboarding_mode_.value());

  observer->OnAssistantStatusChanged(assistant_status_);
  if (allowed_state_.has_value())
    observer->OnAssistantFeatureAllowedChanged(allowed_state_.value());
  if (locale_.has_value())
    observer->OnLocaleChanged(locale_.value());
  if (arc_play_store_enabled_.has_value())
    observer->OnArcPlayStoreEnabledChanged(arc_play_store_enabled_.value());
}

void AssistantStateBase::UpdateConsentStatus() {
  auto consent_status = pref_change_registrar_->prefs()->GetInteger(
      assistant::prefs::kAssistantConsentStatus);
  if (consent_status_.has_value() &&
      consent_status_.value() == consent_status) {
    return;
  }
  consent_status_ = consent_status;
  for (auto& observer : observers_)
    observer.OnAssistantConsentStatusChanged(consent_status_.value());
}

void AssistantStateBase::UpdateContextEnabled() {
  auto context_enabled = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantContextEnabled);
  if (context_enabled_.has_value() &&
      context_enabled_.value() == context_enabled) {
    return;
  }
  context_enabled_ = context_enabled;
  for (auto& observer : observers_)
    observer.OnAssistantContextEnabled(context_enabled_.value());
}

void AssistantStateBase::UpdateSettingsEnabled() {
  auto settings_enabled = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantEnabled);
  if (settings_enabled_.has_value() &&
      settings_enabled_.value() == settings_enabled) {
    return;
  }
  settings_enabled_ = settings_enabled;
  for (auto& observer : observers_)
    observer.OnAssistantSettingsEnabled(settings_enabled_.value());
}

void AssistantStateBase::UpdateHotwordAlwaysOn() {
  auto hotword_always_on = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantHotwordAlwaysOn);
  if (hotword_always_on_.has_value() &&
      hotword_always_on_.value() == hotword_always_on) {
    return;
  }
  hotword_always_on_ = hotword_always_on;
  for (auto& observer : observers_)
    observer.OnAssistantHotwordAlwaysOn(hotword_always_on_.value());
}

void AssistantStateBase::UpdateHotwordEnabled() {
  auto hotword_enabled = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantHotwordEnabled);
  if (hotword_enabled_.has_value() &&
      hotword_enabled_.value() == hotword_enabled) {
    return;
  }
  hotword_enabled_ = hotword_enabled;
  for (auto& observer : observers_)
    observer.OnAssistantHotwordEnabled(hotword_enabled_.value());
}

void AssistantStateBase::UpdateLaunchWithMicOpen() {
  auto launch_with_mic_open = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantLaunchWithMicOpen);
  if (launch_with_mic_open_.has_value() &&
      launch_with_mic_open_.value() == launch_with_mic_open) {
    return;
  }
  launch_with_mic_open_ = launch_with_mic_open;
  for (auto& observer : observers_)
    observer.OnAssistantLaunchWithMicOpen(launch_with_mic_open_.value());
}

void AssistantStateBase::UpdateNotificationEnabled() {
  auto notification_enabled = pref_change_registrar_->prefs()->GetBoolean(
      assistant::prefs::kAssistantNotificationEnabled);
  if (notification_enabled_.has_value() &&
      notification_enabled_.value() == notification_enabled) {
    return;
  }
  notification_enabled_ = notification_enabled;
  for (auto& observer : observers_)
    observer.OnAssistantNotificationEnabled(notification_enabled_.value());
}

void AssistantStateBase::UpdateOnboardingMode() {
  AssistantOnboardingMode onboarding_mode = assistant::prefs::ToOnboardingMode(
      pref_change_registrar_->prefs()->GetString(
          assistant::prefs::kAssistantOnboardingMode));

  if (onboarding_mode_ == onboarding_mode)
    return;

  onboarding_mode_ = onboarding_mode;
  for (auto& observer : observers_)
    observer.OnAssistantOnboardingModeChanged(onboarding_mode_.value());
}

void AssistantStateBase::UpdateAssistantStatus(
    assistant::AssistantStatus status) {
  assistant_status_ = status;
  for (auto& observer : observers_)
    observer.OnAssistantStatusChanged(assistant_status_);
}

void AssistantStateBase::UpdateFeatureAllowedState(
    assistant::AssistantAllowedState state) {
  allowed_state_ = state;
  for (auto& observer : observers_)
    observer.OnAssistantFeatureAllowedChanged(allowed_state_.value());
}

void AssistantStateBase::UpdateLocale(const std::string& locale) {
  locale_ = locale;
  for (auto& observer : observers_)
    observer.OnLocaleChanged(locale_.value());
}

void AssistantStateBase::UpdateArcPlayStoreEnabled(bool enabled) {
  arc_play_store_enabled_ = enabled;
  for (auto& observer : observers_)
    observer.OnArcPlayStoreEnabledChanged(arc_play_store_enabled_.value());
}

void AssistantStateBase::UpdateLockedFullScreenState(bool enabled) {
  locked_full_screen_enabled_ = enabled;
  for (auto& observer : observers_) {
    observer.OnLockedFullScreenStateChanged(
        locked_full_screen_enabled_.value());
  }
}

}  // namespace ash