//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "shill/dbus/chromeos_manager_dbus_adaptor.h"

#include <map>
#include <string>
#include <vector>

#include "shill/callbacks.h"
#include "shill/dbus/dbus_service_watcher_factory.h"
#include "shill/device.h"
#include "shill/error.h"
#include "shill/geolocation_info.h"
#include "shill/key_value_store.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/property_store.h"

using base::Unretained;
using std::map;
using std::string;
using std::vector;

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kDBus;
static string ObjectID(ChromeosManagerDBusAdaptor* m) {
  return m->GetRpcIdentifier();
}
}

// static
const char ChromeosManagerDBusAdaptor::kPath[] = "/";

ChromeosManagerDBusAdaptor::ChromeosManagerDBusAdaptor(
    const scoped_refptr<dbus::Bus>& adaptor_bus,
    const scoped_refptr<dbus::Bus> proxy_bus,
    Manager* manager)
    : org::chromium::flimflam::ManagerAdaptor(this),
      ChromeosDBusAdaptor(adaptor_bus, kPath),
      manager_(manager),
      proxy_bus_(proxy_bus),
      dbus_service_watcher_factory_(DBusServiceWatcherFactory::GetInstance()) {}

ChromeosManagerDBusAdaptor::~ChromeosManagerDBusAdaptor() {
  manager_ = nullptr;
}

void ChromeosManagerDBusAdaptor::RegisterAsync(
    const base::Callback<void(bool)>& completion_callback) {
  RegisterWithDBusObject(dbus_object());
  dbus_object()->RegisterAsync(completion_callback);
}

void ChromeosManagerDBusAdaptor::EmitBoolChanged(const string& name,
                                                 bool value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(value));
}

void ChromeosManagerDBusAdaptor::EmitUintChanged(const string& name,
                                         uint32_t value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(value));
}

void ChromeosManagerDBusAdaptor::EmitIntChanged(const string& name, int value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(value));
}

void ChromeosManagerDBusAdaptor::EmitStringChanged(const string& name,
                                           const string& value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(value));
}

void ChromeosManagerDBusAdaptor::EmitStringsChanged(const string& name,
                                            const vector<string>& value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(value));
}

void ChromeosManagerDBusAdaptor::EmitRpcIdentifierChanged(
    const string& name,
    const string& value) {
  SLOG(this, 2) << __func__ << ": " << name;
  SendPropertyChangedSignal(name, brillo::Any(dbus::ObjectPath(value)));
}

void ChromeosManagerDBusAdaptor::EmitRpcIdentifierArrayChanged(
    const string& name,
    const vector<string>& value) {
  SLOG(this, 2) << __func__ << ": " << name;
  vector<dbus::ObjectPath> paths;
  for (const auto& element : value) {
    paths.push_back(dbus::ObjectPath(element));
  }

  SendPropertyChangedSignal(name, brillo::Any(paths));
}

bool ChromeosManagerDBusAdaptor::GetProperties(
    brillo::ErrorPtr* error, brillo::VariantDictionary* properties) {
  SLOG(this, 2) << __func__;
  return ChromeosDBusAdaptor::GetProperties(manager_->store(),
                                            properties,
                                            error);
}

bool ChromeosManagerDBusAdaptor::SetProperty(brillo::ErrorPtr* error,
                                             const string& name,
                                             const brillo::Any& value) {
  SLOG(this, 2) << __func__ << ": " << name;
  return ChromeosDBusAdaptor::SetProperty(manager_->mutable_store(),
                                          name,
                                          value,
                                          error);
}

bool ChromeosManagerDBusAdaptor::GetState(brillo::ErrorPtr* /*error*/,
                                          string* state) {
  SLOG(this, 2) << __func__;
  *state = manager_->CalculateState(nullptr);
  return true;
}

bool ChromeosManagerDBusAdaptor::CreateProfile(brillo::ErrorPtr* error,
                                               const string& name,
                                               dbus::ObjectPath* profile_path) {
  SLOG(this, 2) << __func__ << ": " << name;
  Error e;
  string path;
  manager_->CreateProfile(name, &path, &e);
  if (e.ToChromeosError(error)) {
    return false;
  }
  *profile_path = dbus::ObjectPath(path);
  return true;
}

bool ChromeosManagerDBusAdaptor::RemoveProfile(brillo::ErrorPtr* error,
                                               const string& name) {
  SLOG(this, 2) << __func__ << ": " << name;
  Error e;
  manager_->RemoveProfile(name, &e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::PushProfile(brillo::ErrorPtr* error,
                                             const string& name,
                                             dbus::ObjectPath* profile_path) {
  SLOG(this, 2) << __func__ << ": " << name;
  Error e;
  string path;
  manager_->PushProfile(name, &path, &e);
  if (e.ToChromeosError(error)) {
    return false;
  }
  *profile_path = dbus::ObjectPath(path);
  return true;
}

bool ChromeosManagerDBusAdaptor::InsertUserProfile(
    brillo::ErrorPtr* error,
    const string& name,
    const string& user_hash,
    dbus::ObjectPath* profile_path) {
  SLOG(this, 2) << __func__ << ": " << name;
  Error e;
  string path;
  manager_->InsertUserProfile(name, user_hash, &path, &e);
  if (e.ToChromeosError(error)) {
    return false;
  }
  *profile_path = dbus::ObjectPath(path);
  return true;;
}

bool ChromeosManagerDBusAdaptor::PopProfile(brillo::ErrorPtr* error,
                                            const string& name) {
  SLOG(this, 2) << __func__ << ": " << name;
  Error e;
  manager_->PopProfile(name, &e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::PopAnyProfile(brillo::ErrorPtr* error) {
  SLOG(this, 2) << __func__;
  Error e;
  manager_->PopAnyProfile(&e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::PopAllUserProfiles(brillo::ErrorPtr* error) {
  SLOG(this, 2) << __func__;
  Error e;
  manager_->PopAllUserProfiles(&e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::RecheckPortal(brillo::ErrorPtr* error) {
  SLOG(this, 2) << __func__;
  Error e;
  manager_->RecheckPortal(&e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::RequestScan(brillo::ErrorPtr* error,
                                             const string& technology) {  // NOLINT
  SLOG(this, 2) << __func__ << ": " << technology;
  Error e;
  manager_->RequestScan(Device::kFullScan, technology, &e);
  return !e.ToChromeosError(error);
}

void ChromeosManagerDBusAdaptor::EnableTechnology(
    DBusMethodResponsePtr<> response, const string& technology_name) {
  SLOG(this, 2) << __func__ << ": " << technology_name;
  Error e(Error::kOperationInitiated);
  ResultCallback callback = GetMethodReplyCallback(std::move(response));
  const bool kPersistentSave = true;
  manager_->SetEnabledStateForTechnology(technology_name, true,
                                         kPersistentSave, &e, callback);
  ReturnResultOrDefer(callback, e);
}

void ChromeosManagerDBusAdaptor::DisableTechnology(
    DBusMethodResponsePtr<> response, const string& technology_name) {
  SLOG(this, 2) << __func__ << ": " << technology_name;
  Error e(Error::kOperationInitiated);
  ResultCallback callback = GetMethodReplyCallback(std::move(response));
  const bool kPersistentSave = true;
  manager_->SetEnabledStateForTechnology(technology_name, false,
                                         kPersistentSave, &e, callback);
  ReturnResultOrDefer(callback, e);
}

// Called, e.g., to get WiFiService handle for a hidden SSID.
bool ChromeosManagerDBusAdaptor::GetService(
    brillo::ErrorPtr* error,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {
  SLOG(this, 2) << __func__;
  ServiceRefPtr service;
  KeyValueStore args_store;
  Error e;
  KeyValueStore::ConvertFromVariantDictionary(args, &args_store);
  service = manager_->GetService(args_store, &e);
  if (e.ToChromeosError(error)) {
    return false;
  }
  *service_path = dbus::ObjectPath(service->GetRpcIdentifier());
  return true;
}

// Obsolete, use GetService instead.
bool ChromeosManagerDBusAdaptor::GetVPNService(
    brillo::ErrorPtr* error,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {
  SLOG(this, 2) << __func__;
  return GetService(error, args, service_path);
}

// Obsolete, use GetService instead.
bool ChromeosManagerDBusAdaptor::GetWifiService(
    brillo::ErrorPtr* error,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {
  SLOG(this, 2) << __func__;
  return GetService(error, args, service_path);
}


bool ChromeosManagerDBusAdaptor::ConfigureService(
    brillo::ErrorPtr* error,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {
  SLOG(this, 2) << __func__;
  ServiceRefPtr service;
  KeyValueStore args_store;
  KeyValueStore::ConvertFromVariantDictionary(args, &args_store);
  Error configure_error;
  service = manager_->ConfigureService(args_store, &configure_error);
  if (configure_error.ToChromeosError(error)) {
    return false;
  }
  *service_path = dbus::ObjectPath(service->GetRpcIdentifier());
  return true;
}

bool ChromeosManagerDBusAdaptor::ConfigureServiceForProfile(
    brillo::ErrorPtr* error,
    const dbus::ObjectPath& profile_rpcid,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {
  SLOG(this, 2) << __func__;
  ServiceRefPtr service;
  KeyValueStore args_store;
  KeyValueStore::ConvertFromVariantDictionary(args, &args_store);
  Error configure_error;
  service = manager_->ConfigureServiceForProfile(
      profile_rpcid.value(), args_store, &configure_error);
  if (!service || configure_error.ToChromeosError(error)) {
    return false;
  }
  *service_path = dbus::ObjectPath(service->GetRpcIdentifier());
  return true;
}

bool ChromeosManagerDBusAdaptor::FindMatchingService(
    brillo::ErrorPtr* error,
    const brillo::VariantDictionary& args,
    dbus::ObjectPath* service_path) {  // NOLINT
  SLOG(this, 2) << __func__;
  KeyValueStore args_store;
  KeyValueStore::ConvertFromVariantDictionary(args, &args_store);

  Error find_error;
  ServiceRefPtr service =
      manager_->FindMatchingService(args_store, &find_error);
  if (find_error.ToChromeosError(error)) {
    return false;
  }

  *service_path = dbus::ObjectPath(service->GetRpcIdentifier());
  return true;
}

bool ChromeosManagerDBusAdaptor::GetDebugLevel(brillo::ErrorPtr* /*error*/,
                                       int32_t* level) {
  SLOG(this, 2) << __func__;
  *level = logging::GetMinLogLevel();
  return true;
}

bool ChromeosManagerDBusAdaptor::SetDebugLevel(brillo::ErrorPtr* /*error*/,
                                               int32_t level) {
  SLOG(this, 2) << __func__ << ": " << level;
  if (level < logging::LOG_NUM_SEVERITIES) {
    logging::SetMinLogLevel(level);
    // Like VLOG, SLOG uses negative verbose level.
    ScopeLogger::GetInstance()->set_verbose_level(-level);
  } else {
    LOG(WARNING) << "Ignoring attempt to set log level to " << level;
  }
  return true;
}

bool ChromeosManagerDBusAdaptor::GetServiceOrder(brillo::ErrorPtr* /*error*/,
                                                 string* order) {
  SLOG(this, 2) << __func__;
  *order = manager_->GetTechnologyOrder();
  return true;
}

bool ChromeosManagerDBusAdaptor::SetServiceOrder(brillo::ErrorPtr* error,
                                                 const string& order) {
  SLOG(this, 2) << __func__ << ": " << order;
  Error e;
  manager_->SetTechnologyOrder(order, &e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::GetDebugTags(brillo::ErrorPtr* /*error*/,
                                              string* tags) {
  SLOG(this, 2) << __func__;
  *tags = ScopeLogger::GetInstance()->GetEnabledScopeNames();
  return true;
}

bool ChromeosManagerDBusAdaptor::SetDebugTags(brillo::ErrorPtr* /*error*/,
                                              const string& tags) {
  SLOG(this, 2) << __func__ << ": " << tags;
  ScopeLogger::GetInstance()->EnableScopesByName(tags);
  return true;
}

bool ChromeosManagerDBusAdaptor::ListDebugTags(brillo::ErrorPtr* /*error*/,
                                               string* tags) {
  SLOG(this, 2) << __func__;
  *tags = ScopeLogger::GetInstance()->GetAllScopeNames();
  return true;
}

bool ChromeosManagerDBusAdaptor::GetNetworksForGeolocation(
    brillo::ErrorPtr* /*error*/,
    brillo::VariantDictionary* networks) {
  SLOG(this, 2) << __func__;
  for (const auto& network : manager_->GetNetworksForGeolocation()) {
    Stringmaps value;
    // Convert GeolocationInfos to their Stringmaps equivalent.
    for (const auto& info : network.second) {
      value.push_back(info.properties());
    }
    networks->insert(std::make_pair(network.first, brillo::Any(value)));
  }
  return true;
}

void ChromeosManagerDBusAdaptor::VerifyDestination(
    DBusMethodResponsePtr<bool> response,
    const string& certificate,
    const string& public_key,
    const string& nonce,
    const string& signed_data,
    const string& destination_udn,
    const string& hotspot_ssid,
    const string& hotspot_bssid) {
  SLOG(this, 2) << __func__;
  ResultBoolCallback callback = GetBoolMethodReplyCallback(std::move(response));
#if !defined(DISABLE_WIFI)
  Error e(Error::kOperationInitiated);
  manager_->VerifyDestination(certificate, public_key, nonce,
                              signed_data, destination_udn,
                              hotspot_ssid, hotspot_bssid,
                              callback, &e);
#else
  Error e(Error::kNotImplemented);
#endif  // DISABLE_WIFI
  if (e.IsOngoing()) {
    return;
  }
  // Command failed synchronously.
  CHECK(e.IsFailure()) << __func__ << " should only return directly on error.";
  callback.Run(e, false);
}

void ChromeosManagerDBusAdaptor::VerifyAndEncryptCredentials(
    DBusMethodResponsePtr<string> response,
    const string& certificate,
    const string& public_key,
    const string& nonce,
    const string& signed_data,
    const string& destination_udn,
    const string& hotspot_ssid,
    const string& hotspot_bssid,
    const dbus::ObjectPath& network) {
  SLOG(this, 2) << __func__;
  ResultStringCallback callback =
      GetStringMethodReplyCallback(std::move(response));
#if !defined(DISABLE_WIFI)
  Error e(Error::kOperationInitiated);
  manager_->VerifyAndEncryptCredentials(certificate, public_key, nonce,
                                        signed_data, destination_udn,
                                        hotspot_ssid, hotspot_bssid,
                                        network.value(),
                                        callback,
                                        &e);
#else
  Error e(Error::kNotImplemented);
#endif  // DISABLE_WIFI
  if (e.IsOngoing()) {
    return;
  }
  // Command failed synchronously.
  CHECK(e.IsFailure()) << __func__ << " should only return directly on error.";
  callback.Run(e, "");
}

void ChromeosManagerDBusAdaptor::VerifyAndEncryptData(
    DBusMethodResponsePtr<string> response,
    const string& certificate,
    const string& public_key,
    const string& nonce,
    const string& signed_data,
    const string& destination_udn,
    const string& hotspot_ssid,
    const string& hotspot_bssid,
    const string& data) {
  SLOG(this, 2) << __func__;
  ResultStringCallback callback =
      GetStringMethodReplyCallback(std::move(response));
#if !defined(DISABLE_WIFI)
  Error e(Error::kOperationInitiated);
  manager_->VerifyAndEncryptData(certificate, public_key, nonce,
                                 signed_data, destination_udn,
                                 hotspot_ssid, hotspot_bssid,
                                 data, callback,
                                 &e);
#else
  Error e(Error::kNotImplemented);
#endif  // DISABLE_WIFI
  if (e.IsOngoing()) {
    return;
  }
  // Command failed synchronously.
  CHECK(e.IsFailure()) << __func__ << " should only return directly on error.";
  callback.Run(e, "");
}

bool ChromeosManagerDBusAdaptor::ConnectToBestServices(
    brillo::ErrorPtr* error) {
  SLOG(this, 2) << __func__;
  Error e;
  manager_->ConnectToBestServices(&e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::CreateConnectivityReport(
    brillo::ErrorPtr* error) {
  SLOG(this, 2) << __func__;
  Error e;
  manager_->CreateConnectivityReport(&e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::ClaimInterface(
    brillo::ErrorPtr* error,
    dbus::Message* message,
    const string& claimer_name,
    const string& interface_name) {
  SLOG(this, 2) << __func__;
  Error e;
  // Empty claimer name is used to indicate default claimer.
  // TODO(zqiu): update this API or make a new API to use a flag to indicate
  // default claimer instead.
  string claimer = (claimer_name == "" ? "" : message->GetSender());
  manager_->ClaimDevice(claimer, interface_name, &e);
  if (e.IsSuccess() && claimer_name != "") {
    // Only setup watcher for non-default claimer.
    watcher_for_device_claimer_.reset(
        dbus_service_watcher_factory_->CreateDBusServiceWatcher(
            proxy_bus_, claimer,
            Bind(&ChromeosManagerDBusAdaptor::OnDeviceClaimerVanished,
                 Unretained(this))));
  }
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::ReleaseInterface(
    brillo::ErrorPtr* error,
    dbus::Message* message,
    const string& claimer_name,
    const string& interface_name) {
  SLOG(this, 2) << __func__;
  Error e;
  bool claimer_removed;
  // Empty claimer name is used to indicate default claimer.
  // TODO(zqiu): update this API or make a new API to use a flag to indicate
  // default claimer instead.
  manager_->ReleaseDevice(
      claimer_name == "" ? "" : message->GetSender(),
      interface_name,
      &claimer_removed,
      &e);
  if (claimer_removed) {
    watcher_for_device_claimer_.reset();
  }
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::SetSchedScan(brillo::ErrorPtr* error,
                                              bool enable) {
  SLOG(this, 2) << __func__ << ": " << enable;
  Error e;
  manager_->SetSchedScan(enable, &e);
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::SetupApModeInterface(
    brillo::ErrorPtr* error,
    dbus::Message* message,
    string* out_interface_name) {
  SLOG(this, 2) << __func__;
  Error e;
#if !defined(DISABLE_WIFI) && defined(__BRILLO__)
  manager_->SetupApModeInterface(out_interface_name, &e);
  if (e.IsSuccess()) {
    // Setup a service watcher for the caller. This will restore interface mode
    // back to station mode if the caller vanished.
    watcher_for_ap_mode_setter_.reset(
        dbus_service_watcher_factory_->CreateDBusServiceWatcher(
            proxy_bus_, message->GetSender(),
            Bind(&ChromeosManagerDBusAdaptor::OnApModeSetterVanished,
                 Unretained(this))));
  }
#else
  e.Populate(Error::kNotSupported);
#endif  // !DISABLE_WIFI && __BRILLO__
  return !e.ToChromeosError(error);
}

bool ChromeosManagerDBusAdaptor::SetupStationModeInterface(
    brillo::ErrorPtr* error,
    string* out_interface_name) {
  SLOG(this, 2) << __func__;
  Error e;
#if !defined(DISABLE_WIFI) && defined(__BRILLO__)
  manager_->SetupStationModeInterface(out_interface_name, &e);
  // Remove the service watcher for the AP mode setter.
  watcher_for_ap_mode_setter_.reset();
#else
  e.Populate(Error::kNotSupported);
#endif  // !DISABLE_WIFI && __BRILLO__
  return !e.ToChromeosError(error);
}

void ChromeosManagerDBusAdaptor::OnApModeSetterVanished() {
  SLOG(this, 3) << __func__;
#if !defined(DISABLE_WIFI) && defined(__BRILLO__)
  manager_->OnApModeSetterVanished();
#endif  // !DISABLE_WIFI && __BRILLO__
  watcher_for_ap_mode_setter_.reset();
}

void ChromeosManagerDBusAdaptor::OnDeviceClaimerVanished() {
  SLOG(this, 3) << __func__;
  manager_->OnDeviceClaimerVanished();
  watcher_for_device_claimer_.reset();
}

}  // namespace shill