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

#include "device/bluetooth/floss/floss_manager_client.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "device/bluetooth/bluez/bluez_features.h"
#include "device/bluetooth/chromeos_platform_features.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
#include "device/bluetooth/floss/floss_features.h"

namespace floss {

using internal::AdapterWithEnabled;

template <>
bool FlossDBusClient::ReadDBusParam(dbus::MessageReader* reader,
                                    AdapterWithEnabled* adapter) {
  static FlossDBusClient::StructReader<AdapterWithEnabled> struct_reader({
      {"hci_interface", CreateFieldReader(&AdapterWithEnabled::adapter)},
      {"enabled", CreateFieldReader(&AdapterWithEnabled::enabled)},
  });

  return struct_reader.ReadDBusParam(reader, adapter);
}

template <>
const DBusTypeInfo& GetDBusTypeInfo<AdapterWithEnabled>(
    const AdapterWithEnabled* unused) {
  static DBusTypeInfo info{"a{sv}", "AdapterWithEnabled"};
  return info;
}

// static
const char FlossManagerClient::kExportedCallbacksPath[] =
    "/org/chromium/bluetooth/managerclient";

// static
const char FlossManagerClient::kObjectManagerPath[] = "/";

// static
const int FlossManagerClient::kSetFlossRetryCount = 3;

// static
const int FlossManagerClient::kSetFlossRetryDelayMs = 500;

// static
const int FlossManagerClient::kSetFlossEnabledDBusTimeoutMs = 10000;

FlossManagerClient::FlossManagerClient() = default;

FlossManagerClient::~FlossManagerClient() {
  if (object_manager_) {
    object_manager_->UnregisterInterface(kManagerInterface);
  }

  if (bus_) {
    bus_->UnregisterExportedObject(dbus::ObjectPath(kExportedCallbacksPath));
  }
}

void FlossManagerClient::AddObserver(FlossManagerClient::Observer* observer) {
  observers_.AddObserver(observer);
}

void FlossManagerClient::RemoveObserver(
    FlossManagerClient::Observer* observer) {
  observers_.RemoveObserver(observer);
}

std::vector<int> FlossManagerClient::GetAdapters() const {
  std::vector<int> adapters;
  for (auto& [adapter, enabled] : adapter_to_powered_) {
    adapters.push_back(adapter);
  }

  return adapters;
}

int FlossManagerClient::GetDefaultAdapter() const {
  return default_adapter_;
}

bool FlossManagerClient::GetAdapterPresent(int adapter) const {
  return base::Contains(adapter_to_powered_, adapter);
}

bool FlossManagerClient::GetAdapterEnabled(int adapter) const {
  auto iter = adapter_to_powered_.find(adapter);
  if (iter != adapter_to_powered_.end()) {
    return iter->second;
  }

  return false;
}

void FlossManagerClient::GetFlossEnabledWithTarget(bool target,
                                                   int retry,
                                                   int retry_wait_ms) {
  CallManagerMethod<bool>(
      base::BindOnce(&FlossManagerClient::HandleGetFlossEnabled,
                     weak_ptr_factory_.GetWeakPtr(), target, retry,
                     retry_wait_ms),
      manager::kGetFlossEnabled);
}

void FlossManagerClient::SetFlossEnabled(
    bool enabled,
    int retry,
    int retry_wait_ms,
    absl::optional<ResponseCallback<bool>> cb) {
  if (cb) {
    set_floss_enabled_callback_ =
        WeaklyOwnedResponseCallback<bool>::Create(std::move(*cb));
  }

  CallManagerMethod<Void>(
      base::BindOnce(&FlossManagerClient::HandleSetFlossEnabled,
                     weak_ptr_factory_.GetWeakPtr(), enabled, retry,
                     retry_wait_ms),
      manager::kSetFlossEnabled, enabled);
}

void FlossManagerClient::SetAdapterEnabled(int adapter,
                                           bool enabled,
                                           ResponseCallback<Void> callback) {
  if (adapter != GetDefaultAdapter()) {
    return;
  }

  DVLOG(1) << __func__;

  powered_callback_ = WeaklyOwnedResponseCallback<Void>::CreateWithTimeout(
      std::move(callback), kAdapterPowerTimeoutMs,
      base::unexpected(Error(kErrorNoResponse, "")));

  const char* command = enabled ? manager::kStart : manager::kStop;
  CallManagerMethod<Void>(
      base::BindOnce(&FlossManagerClient::OnSetAdapterEnabled,
                     weak_ptr_factory_.GetWeakPtr()),
      command, adapter);
}

void FlossManagerClient::OnSetAdapterEnabled(DBusResult<Void> response) {
  // Only handle error cases since non-error called in OnHciEnabledChange
  if (powered_callback_ && !response.has_value()) {
    powered_callback_->Run(base::unexpected(Error(kErrorNoResponse, "")));
    powered_callback_.reset();
  }
}

void FlossManagerClient::SetLLPrivacy(ResponseCallback<Void> callback,
                                      const bool enable) {
  CallExperimentalMethod<Void>(std::move(callback), experimental::kSetLLPrivacy,
                               enable);
}

void FlossManagerClient::SetDevCoredump(ResponseCallback<Void> callback,
                                        const bool enable) {
  CallExperimentalMethod<Void>(std::move(callback),
                               experimental::kSetDevCoredump, enable);
}

// Register manager client against manager.
void FlossManagerClient::RegisterWithManager() {
  DCHECK(!manager_available_);

  // Get the default adapter.
  CallManagerMethod<int>(
      base::BindOnce(&FlossManagerClient::HandleGetDefaultAdapter,
                     weak_ptr_factory_.GetWeakPtr()),
      manager::kGetDefaultAdapter);

  // Get the list of available adapters.
  CallManagerMethod<std::vector<AdapterWithEnabled>>(
      base::BindOnce(&FlossManagerClient::HandleGetAvailableAdapters,
                     weak_ptr_factory_.GetWeakPtr()),
      manager::kGetAvailableAdapters);

  // Register for callbacks.
  CallManagerMethod<Void>(
      base::BindOnce(&FlossManagerClient::HandleRegisterCallback,
                     weak_ptr_factory_.GetWeakPtr()),
      manager::kRegisterCallback, dbus::ObjectPath(kExportedCallbacksPath));

  manager_available_ = true;
  for (auto& observer : observers_) {
    observer.ManagerPresent(manager_available_);
  }
}

// Remove manager client (no longer available).
void FlossManagerClient::RemoveManager() {
  // Make copy of old adapters and clear existing ones.
  auto previous_adapters = std::move(adapter_to_powered_);
  adapter_to_powered_.clear();

  // All old adapters need to be sent a `present = false` notification.
  for (auto& [adapter, enabled] : previous_adapters) {
    for (auto& observer : observers_) {
      observer.AdapterPresent(adapter, false);
    }
  }

  manager_available_ = false;
  for (auto& observer : observers_) {
    observer.ManagerPresent(manager_available_);
  }
}

// The manager can manage multiple adapters so ignore the adapter index given
// here. It is unused.
void FlossManagerClient::Init(dbus::Bus* bus,
                              const std::string& service_name,
                              const int adapter_index,
                              base::OnceClosure on_ready) {
  bus_ = bus;
  service_name_ = service_name;

  // We should always have object proxy since the client initialization is
  // gated on ObjectManager marking the manager interface as available.
  if (!bus_->GetObjectProxy(service_name_, dbus::ObjectPath(kManagerObject))) {
    LOG(ERROR) << "FlossManagerClient couldn't init. Object proxy was null.";
    return;
  }

  DVLOG(1) << __func__;

  exported_callback_manager_.Init(bus_.get());
  exported_callback_manager_.AddMethod(
      manager::kOnHciDeviceChanged,
      &FlossManagerClientCallbacks::OnHciDeviceChanged);
  exported_callback_manager_.AddMethod(
      manager::kOnHciEnabledChanged,
      &FlossManagerClientCallbacks::OnHciEnabledChanged);
  exported_callback_manager_.AddMethod(
      manager::kOnDefaultAdapterChanged,
      &FlossManagerClientCallbacks::OnDefaultAdapterChanged);
  if (!exported_callback_manager_.ExportCallback(
          dbus::ObjectPath(kExportedCallbacksPath),
          weak_ptr_factory_.GetWeakPtr(),
          base::BindOnce(&FlossManagerClient::RegisterWithManager,
                         weak_ptr_factory_.GetWeakPtr()))) {
    LOG(ERROR) << "Unable to successfully export FlossManagerClientCallbacks.";
    return;
  }

  // Register object manager for Manager.
  object_manager_ = bus_->GetObjectManager(
      service_name, dbus::ObjectPath(kObjectManagerPath));
  object_manager_->RegisterInterface(kManagerInterface, this);

  // Enable Floss and retry a few times until it is set.
  SetFlossEnabled(floss::features::IsFlossEnabled(), kSetFlossRetryCount,
                  kSetFlossRetryDelayMs,
                  base::BindOnce(&FlossManagerClient::CompleteSetFlossEnabled,
                                 weak_ptr_factory_.GetWeakPtr()));

#if BUILDFLAG(IS_CHROMEOS)
  SetDevCoredump(base::BindOnce([](DBusResult<Void> ret) {
                   if (!ret.has_value()) {
                     LOG(ERROR) << "Fail to set devcoredump.\n";
                   }
                 }),
                 base::FeatureList::IsEnabled(
                     chromeos::bluetooth::features::kBluetoothFlossCoredump));
#endif  // BUILDFLAG(IS_CHROMEOS)

  SetLLPrivacy(
      base::BindOnce([](DBusResult<Void> ret) {
        if (!ret.has_value())
          LOG(ERROR) << "Fail to set LL privacy.\n";
      }),
      base::FeatureList::IsEnabled(bluez::features::kLinkLayerPrivacy));

  on_ready_ = std::move(on_ready);
}

void FlossManagerClient::HandleGetDefaultAdapter(DBusResult<int32_t> response) {
  if (!response.has_value()) {
    LOG(ERROR) << "GetDefaultAdapter responded with error: "
               << response.error();
    return;
  }

  OnDefaultAdapterChanged(response.value());
}

void FlossManagerClient::HandleGetAvailableAdapters(
    DBusResult<std::vector<AdapterWithEnabled>> adapters) {
  if (!adapters.has_value()) {
    LOG(WARNING) << "GetAvailableAdapters return error " << adapters.error();
    return;
  }

  auto previous_adapters = std::move(adapter_to_powered_);

  // Clear existing adapters.
  adapter_to_powered_.clear();
  for (auto v : adapters.value()) {
    adapter_to_powered_.insert({v.adapter, v.enabled});
  }

  // Trigger the observers for adapter present on any new ones we listed.
  for (auto& observer : observers_) {
    // Emit present for new adapters that weren't in old list. Also emit the
    // powered changed for them.
    for (auto& [adapter, enabled] : adapter_to_powered_) {
      if (!base::Contains(previous_adapters, adapter)) {
        observer.AdapterPresent(adapter, true);
        observer.AdapterEnabledChanged(adapter, enabled);
      }
    }

    // Emit not present for adapters that aren't in new list.
    // We don't need to emit AdapterEnabledChanged since we emit
    // AdapterPresent is false
    for (auto& [adapter, enabled] : previous_adapters) {
      if (!base::Contains(adapter_to_powered_, adapter))
        observer.AdapterPresent(adapter, false);
    }
  }
}

void FlossManagerClient::HandleRegisterCallback(DBusResult<Void> result) {
  if (!result.has_value()) {
    LOG(ERROR) << "Floss manager RegisterCallback returned error: "
               << result.error();
    return;
  }

  if (on_ready_) {
    std::move(on_ready_).Run();
  }
}

void FlossManagerClient::OnHciDeviceChanged(int32_t adapter, bool present) {
  for (auto& observer : observers_) {
    observer.AdapterPresent(adapter, present);
  }

  // Update the cached list of available adapters.
  auto iter = adapter_to_powered_.find(adapter);
  if (present && iter == adapter_to_powered_.end()) {
    adapter_to_powered_.insert({adapter, false});
  } else if (!present && iter != adapter_to_powered_.end()) {
    adapter_to_powered_.erase(iter);
  }
}

void FlossManagerClient::OnHciEnabledChanged(int32_t adapter, bool enabled) {
  if (adapter == GetDefaultAdapter() && powered_callback_) {
    powered_callback_->Run(Void{});
    powered_callback_.reset();
  }

  adapter_to_powered_[adapter] = enabled;

  for (auto& observer : observers_) {
    observer.AdapterEnabledChanged(adapter, enabled);
  }
}

void FlossManagerClient::OnDefaultAdapterChanged(int32_t adapter) {
  int32_t previous_default = default_adapter_;
  default_adapter_ = adapter;

  for (auto& observer : observers_) {
    observer.DefaultAdapterChanged(previous_default, adapter);
  }
}

void FlossManagerClient::HandleSetFlossEnabled(bool target,
                                               int retry,
                                               int retry_wait_ms,
                                               DBusResult<Void> response) {
  // Failed to call |SetFlossEnabled| so first log the error and post a delayed
  // set if there are retries left.
  if (!response.has_value()) {
    LOG(ERROR) << response.error();
    if (retry > 0) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&FlossManagerClient::SetFlossEnabled,
                         weak_ptr_factory_.GetWeakPtr(), target, retry - 1,
                         retry_wait_ms, absl::nullopt),
          base::Milliseconds(retry_wait_ms));
    } else if (set_floss_enabled_callback_) {
      set_floss_enabled_callback_->Run(base::unexpected(response.error()));
      set_floss_enabled_callback_.reset();
    }

    return;
  }

  GetFlossEnabledWithTarget(target, retry, retry_wait_ms);
}

void FlossManagerClient::HandleGetFlossEnabled(bool target,
                                               int retry,
                                               int retry_wait_ms,
                                               DBusResult<bool> response) {
  if (!response.has_value()) {
    LOG(ERROR) << response.error();
    if (retry > 0) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&FlossManagerClient::GetFlossEnabledWithTarget,
                         weak_ptr_factory_.GetWeakPtr(), target, retry - 1,
                         retry_wait_ms),
          base::Milliseconds(retry_wait_ms));
    } else if (set_floss_enabled_callback_) {
      set_floss_enabled_callback_->Run(base::unexpected(response.error()));
      set_floss_enabled_callback_.reset();
    }

    return;
  }

  bool floss_enabled = response.value();

  // Target doesn't match reality. Retry |SetFlossEnabled|.
  if (floss_enabled != target && retry > 0) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&FlossManagerClient::SetFlossEnabled,
                       weak_ptr_factory_.GetWeakPtr(), target, retry - 1,
                       retry_wait_ms, absl::nullopt),
        base::Milliseconds(kSetFlossRetryDelayMs));
  } else {
    DVLOG(1) << "Floss is currently "
             << (floss_enabled ? "enabled" : "disabled") << " and target was "
             << (target ? "enabled" : "disabled");
    if (set_floss_enabled_callback_) {
      set_floss_enabled_callback_->Run(floss_enabled);
      set_floss_enabled_callback_.reset();
    }
  }
}

void FlossManagerClient::CompleteSetFlossEnabled(DBusResult<bool> ret) {
  if (!ret.has_value()) {
    LOG(ERROR) << "Floss couldn't be enabled. Error=" << ret.error();
  } else {
    DVLOG(1) << "Completed SetFlossEnabled with value " << *ret;
  }
}

dbus::PropertySet* FlossManagerClient::CreateProperties(
    dbus::ObjectProxy* object_proxy,
    const dbus::ObjectPath& object_path,
    const std::string& interface_name) {
  return new dbus::PropertySet(object_proxy, interface_name, base::DoNothing());
}

// Manager interface is available.
void FlossManagerClient::ObjectAdded(const dbus::ObjectPath& object_path,
                                     const std::string& interface_name) {
  // TODO(b/193839304) - When manager exits, we're not getting the
  //                     ObjectRemoved notification. So remove the manager
  //                     before re-adding it here.
  if (manager_available_) {
    RemoveManager();
  }

  DVLOG(0) << __func__ << ": " << object_path.value() << ", " << interface_name;

  RegisterWithManager();
}

// Manager interface is gone (no longer present).
void FlossManagerClient::ObjectRemoved(const dbus::ObjectPath& object_path,
                                       const std::string& interface_name) {
  if (!manager_available_)
    return;

  DVLOG(0) << __func__ << ": " << object_path.value() << ", " << interface_name;

  RemoveManager();
}

// static
std::unique_ptr<FlossManagerClient> FlossManagerClient::Create() {
  return std::make_unique<FlossManagerClient>();
}
}  // namespace floss