//
// 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_supplicant_interface_proxy.h"

#include <string>

#include <base/bind.h>

#include "shill/logging.h"
#include "shill/supplicant/supplicant_event_delegate_interface.h"
#include "shill/supplicant/wpa_supplicant.h"

using std::string;

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kDBus;
static string ObjectID(const dbus::ObjectPath* p) { return p->value(); }
}

const char ChromeosSupplicantInterfaceProxy::kInterfaceName[] =
    "fi.w1.wpa_supplicant1.Interface";
const char ChromeosSupplicantInterfaceProxy::kPropertyDisableHighBitrates[] =
    "DisableHighBitrates";
const char ChromeosSupplicantInterfaceProxy::kPropertyFastReauth[] =
    "FastReauth";
const char ChromeosSupplicantInterfaceProxy::kPropertyRoamThreshold[] =
    "RoamThreshold";
const char ChromeosSupplicantInterfaceProxy::kPropertyScan[] = "Scan";
const char ChromeosSupplicantInterfaceProxy::kPropertyScanInterval[] =
    "ScanInterval";
const char ChromeosSupplicantInterfaceProxy::kPropertySchedScan[] = "SchedScan";

ChromeosSupplicantInterfaceProxy::PropertySet::PropertySet(
    dbus::ObjectProxy* object_proxy,
    const std::string& interface_name,
    const PropertyChangedCallback& callback)
    : dbus::PropertySet(object_proxy, interface_name, callback) {
  RegisterProperty(kPropertyDisableHighBitrates, &disable_high_bitrates);
  RegisterProperty(kPropertyFastReauth, &fast_reauth);
  RegisterProperty(kPropertyRoamThreshold, &roam_threshold);
  RegisterProperty(kPropertyScan, &scan);
  RegisterProperty(kPropertyScanInterval, &scan_interval);
  RegisterProperty(kPropertySchedScan, &sched_scan);
}

ChromeosSupplicantInterfaceProxy::ChromeosSupplicantInterfaceProxy(
    const scoped_refptr<dbus::Bus>& bus,
    const std::string& object_path,
    SupplicantEventDelegateInterface* delegate)
    : interface_proxy_(
          new fi::w1::wpa_supplicant1::InterfaceProxy(
              bus,
              WPASupplicant::kDBusAddr,
              dbus::ObjectPath(object_path))),
      delegate_(delegate) {
  // Register properites.
  properties_.reset(
      new PropertySet(
          interface_proxy_->GetObjectProxy(),
          kInterfaceName,
          base::Bind(&ChromeosSupplicantInterfaceProxy::OnPropertyChanged,
                     weak_factory_.GetWeakPtr())));

  // Register signal handlers.
  dbus::ObjectProxy::OnConnectedCallback on_connected_callback =
      base::Bind(&ChromeosSupplicantInterfaceProxy::OnSignalConnected,
                 weak_factory_.GetWeakPtr());
  interface_proxy_->RegisterScanDoneSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::ScanDone,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterBSSAddedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::BSSAdded,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterBSSRemovedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::BSSRemoved,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterBlobAddedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::BlobAdded,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterBlobRemovedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::BlobRemoved,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterCertificationSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::Certification,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterEAPSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::EAP,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterNetworkAddedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::NetworkAdded,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterNetworkRemovedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::NetworkRemoved,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterNetworkSelectedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::NetworkSelected,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterPropertiesChangedSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::PropertiesChanged,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);
  interface_proxy_->RegisterTDLSDiscoverResponseSignalHandler(
      base::Bind(&ChromeosSupplicantInterfaceProxy::TDLSDiscoverResponse,
                 weak_factory_.GetWeakPtr()),
      on_connected_callback);

  // Connect property signals and initialize cached values. Based on
  // recommendations from src/dbus/property.h.
  properties_->ConnectSignals();
  properties_->GetAll();
}

ChromeosSupplicantInterfaceProxy::~ChromeosSupplicantInterfaceProxy() {
  interface_proxy_->ReleaseObjectProxy(base::Bind(&base::DoNothing));
}

bool ChromeosSupplicantInterfaceProxy::AddNetwork(const KeyValueStore& args,
                                                  string* network) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::VariantDictionary dict;
  KeyValueStore::ConvertToVariantDictionary(args, &dict);
  dbus::ObjectPath path;
  brillo::ErrorPtr error;
  if (!interface_proxy_->AddNetwork(dict, &path, &error)) {
    LOG(ERROR) << "Failed to add network: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  *network = path.value();
  return true;
}

bool ChromeosSupplicantInterfaceProxy::EnableHighBitrates() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
#if !defined(__ANDROID__)
  brillo::ErrorPtr error;
  if (!interface_proxy_->EnableHighBitrates(&error)) {
    LOG(ERROR) << "Failed to enable high bitrates: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::EAPLogoff() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->EAPLogoff(&error)) {
    LOG(ERROR) << "Failed to EPA logoff "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::EAPLogon() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->EAPLogon(&error)) {
    LOG(ERROR) << "Failed to EAP logon: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::Disconnect() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->Disconnect(&error)) {
    LOG(ERROR) << "Failed to disconnect: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::FlushBSS(const uint32_t& age) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->FlushBSS(age, &error)) {
    LOG(ERROR) << "Failed to flush BSS: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::NetworkReply(const string& network,
                                                    const string& field,
                                                    const string& value) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__
      << " network: " << network << " field: " << field << " value: " << value;
  brillo::ErrorPtr error;
  if (!interface_proxy_->NetworkReply(dbus::ObjectPath(network),
                                      field,
                                      value,
                                      &error)) {
    LOG(ERROR) << "Failed to network reply: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::Roam(const string& addr) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
#if !defined(__ANDROID__)
  brillo::ErrorPtr error;
  if (!interface_proxy_->Roam(addr, &error)) {
    LOG(ERROR) << "Failed to Roam: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::Reassociate() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->Reassociate(&error)) {
    LOG(ERROR) << "Failed to reassociate: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::Reattach() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->Reattach(&error)) {
    LOG(ERROR) << "Failed to reattach: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::RemoveAllNetworks() {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::ErrorPtr error;
  if (!interface_proxy_->RemoveAllNetworks(&error)) {
    LOG(ERROR) << "Failed to remove all networks: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::RemoveNetwork(const string& network) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << network;
  brillo::ErrorPtr error;
  if (!interface_proxy_->RemoveNetwork(dbus::ObjectPath(network),
                                       &error)) {
    LOG(ERROR) << "Failed to remove network: "
               << error->GetCode() << " " << error->GetMessage();
    // RemoveNetwork can fail with three different errors.
    //
    // If RemoveNetwork fails with a NetworkUnknown error, supplicant has
    // already removed the network object, so return true as if
    // RemoveNetwork removes the network object successfully.
    //
    // As shill always passes a valid network object path, RemoveNetwork
    // should not fail with an InvalidArgs error. Return false in such case
    // as something weird may have happened. Similarly, return false in case
    // of an UnknownError.
    if (error->GetCode() != WPASupplicant::kErrorNetworkUnknown) {
      return false;
    }
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::Scan(const KeyValueStore& args) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  brillo::VariantDictionary dict;
  KeyValueStore::ConvertToVariantDictionary(args, &dict);
  brillo::ErrorPtr error;
  if (!interface_proxy_->Scan(dict, &error)) {
    LOG(ERROR) << "Failed to scan: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SelectNetwork(const string& network) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << network;
  brillo::ErrorPtr error;
  if (!interface_proxy_->SelectNetwork(dbus::ObjectPath(network), &error)) {
    LOG(ERROR) << "Failed to select network: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetHT40Enable(const string& network,
                                                     bool enable) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__
      << " network: " << network << " enable: " << enable;
#if defined(__ANDROID__)
  brillo::ErrorPtr error;
  if (!interface_proxy_->SetHT40Enable(dbus::ObjectPath(network),
                                       enable,
                                       &error)) {
    LOG(ERROR) << "Failed to set HT40 enable: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::TDLSDiscover(const string& peer) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << peer;
  brillo::ErrorPtr error;
  if (!interface_proxy_->TDLSDiscover(peer, &error)) {
    LOG(ERROR) << "Failed to perform TDLS discover: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::TDLSSetup(const string& peer) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << peer;
  brillo::ErrorPtr error;
  if (!interface_proxy_->TDLSSetup(peer, &error)) {
    LOG(ERROR) << "Failed to perform TDLS setup: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::TDLSStatus(const string& peer,
                                                  string* status) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << peer;
  brillo::ErrorPtr error;
  if (!interface_proxy_->TDLSStatus(peer, status, &error)) {
    LOG(ERROR) << "Failed to retrieve TDLS status: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::TDLSTeardown(const string& peer) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << peer;
  brillo::ErrorPtr error;
  if (!interface_proxy_->TDLSTeardown(peer, &error)) {
    LOG(ERROR) << "Failed to perform TDLS teardown: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetFastReauth(bool enabled) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << enabled;
#if !defined(__ANDROID__)
  if (!properties_->fast_reauth.SetAndBlock(enabled)) {
    LOG(ERROR) << __func__ << " failed: " << enabled;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetRoamThreshold(uint16_t threshold) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << threshold;
#if !defined(__ANDROID__)
  if (!properties_->roam_threshold.SetAndBlock(threshold)) {
    LOG(ERROR) << __func__ << " failed: " << threshold;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetScanInterval(int32_t scan_interval) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": "
      << scan_interval;
#if !defined(__ANDROID__)
  if (!properties_->scan_interval.SetAndBlock(scan_interval)) {
    LOG(ERROR) << __func__ << " failed: " << scan_interval;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetDisableHighBitrates(
    bool disable_high_bitrates) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": "
      << disable_high_bitrates;
#if !defined(__ANDROID__)
  if (!properties_->disable_high_bitrates.SetAndBlock(disable_high_bitrates)) {
    LOG(ERROR) << __func__ << " failed: " << disable_high_bitrates;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetSchedScan(bool enable) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << enable;
#if !defined(__ANDROID__)
  if (!properties_->sched_scan.SetAndBlock(enable)) {
    LOG(ERROR) << __func__ << " failed: " << enable;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

bool ChromeosSupplicantInterfaceProxy::SetScan(bool enable) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << enable;
#if !defined(__ANDROID__)
  if (!properties_->scan.SetAndBlock(enable)) {
    LOG(ERROR) << __func__ << " failed: " << enable;
    return false;
  }
#endif  // __ANDROID__
  return true;
}

void ChromeosSupplicantInterfaceProxy::BlobAdded(const string& /*blobname*/) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  // XXX
}

void ChromeosSupplicantInterfaceProxy::BlobRemoved(const string& /*blobname*/) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  // XXX
}

void ChromeosSupplicantInterfaceProxy::BSSAdded(
    const dbus::ObjectPath& BSS,
    const brillo::VariantDictionary& properties) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  KeyValueStore store;
  KeyValueStore::ConvertFromVariantDictionary(properties, &store);
  delegate_->BSSAdded(BSS.value(), store);
}

void ChromeosSupplicantInterfaceProxy::Certification(
    const brillo::VariantDictionary& properties) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  KeyValueStore store;
  KeyValueStore::ConvertFromVariantDictionary(properties, &store);
  delegate_->Certification(store);
}

void ChromeosSupplicantInterfaceProxy::EAP(
    const string& status, const string& parameter) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": status "
      << status << ", parameter " << parameter;
  delegate_->EAPEvent(status, parameter);
}

void ChromeosSupplicantInterfaceProxy::BSSRemoved(const dbus::ObjectPath& BSS) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  delegate_->BSSRemoved(BSS.value());
}

void ChromeosSupplicantInterfaceProxy::NetworkAdded(
    const dbus::ObjectPath& /*network*/,
    const brillo::VariantDictionary& /*properties*/) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  // XXX
}

void ChromeosSupplicantInterfaceProxy::NetworkRemoved(
    const dbus::ObjectPath& /*network*/) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  // TODO(quiche): Pass this up to the delegate, so that it can clean its
  // rpcid_by_service_ map. crbug.com/207648
}

void ChromeosSupplicantInterfaceProxy::NetworkSelected(
    const dbus::ObjectPath& /*network*/) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  // XXX
}

void ChromeosSupplicantInterfaceProxy::PropertiesChanged(
    const brillo::VariantDictionary& properties) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__;
  KeyValueStore store;
  KeyValueStore::ConvertFromVariantDictionary(properties, &store);
  delegate_->PropertiesChanged(store);
}

void ChromeosSupplicantInterfaceProxy::ScanDone(bool success) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": " << success;
  delegate_->ScanDone(success);
}

void ChromeosSupplicantInterfaceProxy::TDLSDiscoverResponse(
    const std::string& peer_address) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": "
      << peer_address;
  delegate_->TDLSDiscoverResponse(peer_address);
}

void ChromeosSupplicantInterfaceProxy::OnPropertyChanged(
    const std::string& property_name) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__ << ": "
      << property_name;
}

void ChromeosSupplicantInterfaceProxy::OnSignalConnected(
    const string& interface_name, const string& signal_name, bool success) {
  SLOG(&interface_proxy_->GetObjectPath(), 2) << __func__
      << "interface: " << interface_name << " signal: " << signal_name
      << "success: " << success;
  if (!success) {
    LOG(ERROR) << "Failed to connect signal " << signal_name
        << " to interface " << interface_name;
  }
}

}  // namespace shill