// Copyright 2014 The Chromium OS 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 <brillo/dbus/dbus_object.h>

#include <vector>

#include <base/bind.h>
#include <base/logging.h>
#include <brillo/dbus/async_event_sequencer.h>
#include <brillo/dbus/exported_object_manager.h>
#include <brillo/dbus/exported_property_set.h>
#include <dbus/property.h>

namespace brillo {
namespace dbus_utils {

namespace {

void SetupDefaultPropertyHandlers(DBusInterface* prop_interface,
                                  ExportedPropertySet* property_set) {
  prop_interface->AddSimpleMethodHandler(dbus::kPropertiesGetAll,
                                         base::Unretained(property_set),
                                         &ExportedPropertySet::HandleGetAll);
  prop_interface->AddSimpleMethodHandlerWithError(
      dbus::kPropertiesGet, base::Unretained(property_set),
      &ExportedPropertySet::HandleGet);
  prop_interface->AddSimpleMethodHandlerWithError(
      dbus::kPropertiesSet, base::Unretained(property_set),
      &ExportedPropertySet::HandleSet);
}

}  // namespace

//////////////////////////////////////////////////////////////////////////////

DBusInterface::DBusInterface(DBusObject* dbus_object,
                             const std::string& interface_name)
    : dbus_object_(dbus_object), interface_name_(interface_name) {
}

void DBusInterface::AddProperty(const std::string& property_name,
                                ExportedPropertyBase* prop_base) {
  dbus_object_->property_set_.RegisterProperty(
      interface_name_, property_name, prop_base);
}

void DBusInterface::RemoveProperty(const std::string& property_name) {
  dbus_object_->property_set_.UnregisterProperty(interface_name_,
                                                 property_name);
}

void DBusInterface::ExportAsync(
    ExportedObjectManager* object_manager,
    dbus::Bus* /* bus */,
    dbus::ExportedObject* exported_object,
    const dbus::ObjectPath& object_path,
    const AsyncEventSequencer::CompletionAction& completion_callback) {
  VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '"
          << object_path.value() << "'";
  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
  for (const auto& pair : handlers_) {
    std::string method_name = pair.first;
    VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name;
    std::string export_error = "Failed exporting " + method_name + " method";
    auto export_handler = sequencer->GetExportHandler(
        interface_name_, method_name, export_error, true);
    auto method_handler =
        base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this));
    exported_object->ExportMethod(
        interface_name_, method_name, method_handler, export_handler);
  }

  std::vector<AsyncEventSequencer::CompletionAction> actions;
  if (object_manager) {
    auto property_writer_callback =
        dbus_object_->property_set_.GetPropertyWriter(interface_name_);
    actions.push_back(
        base::Bind(&DBusInterface::ClaimInterface,
                   weak_factory_.GetWeakPtr(),
                   object_manager->AsWeakPtr(),
                   object_path,
                   property_writer_callback));
  }
  actions.push_back(completion_callback);
  sequencer->OnAllTasksCompletedCall(actions);
}

void DBusInterface::ExportAndBlock(
    ExportedObjectManager* object_manager,
    dbus::Bus* /* bus */,
    dbus::ExportedObject* exported_object,
    const dbus::ObjectPath& object_path) {
  VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '"
          << object_path.value() << "'";
  for (const auto& pair : handlers_) {
    std::string method_name = pair.first;
    VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name;
    auto method_handler =
        base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this));
    if (!exported_object->ExportMethodAndBlock(
            interface_name_, method_name, method_handler)) {
        LOG(FATAL) << "Failed exporting " << method_name << " method";
    }
  }

  if (object_manager) {
    auto property_writer_callback =
        dbus_object_->property_set_.GetPropertyWriter(interface_name_);
    ClaimInterface(object_manager->AsWeakPtr(),
                   object_path,
                   property_writer_callback,
                   true);
  }
}

void DBusInterface::ClaimInterface(
      base::WeakPtr<ExportedObjectManager> object_manager,
      const dbus::ObjectPath& object_path,
      const ExportedPropertySet::PropertyWriter& writer,
      bool all_succeeded) {
  if (!all_succeeded || !object_manager) {
    LOG(ERROR) << "Skipping claiming interface: " << interface_name_;
    return;
  }
  object_manager->ClaimInterface(object_path, interface_name_, writer);
  release_interface_cb_.ReplaceClosure(
      base::Bind(&ExportedObjectManager::ReleaseInterface,
                 object_manager, object_path, interface_name_));
}

void DBusInterface::HandleMethodCall(dbus::MethodCall* method_call,
                                     ResponseSender sender) {
  std::string method_name = method_call->GetMember();
  // Make a local copy of |interface_name_| because calling HandleMethod()
  // can potentially kill this interface object...
  std::string interface_name = interface_name_;
  VLOG(1) << "Received method call request: " << interface_name << "."
          << method_name << "(" << method_call->GetSignature() << ")";
  auto pair = handlers_.find(method_name);
  if (pair == handlers_.end()) {
    auto response =
        dbus::ErrorResponse::FromMethodCall(method_call,
                                            DBUS_ERROR_UNKNOWN_METHOD,
                                            "Unknown method: " + method_name);
    sender.Run(std::move(response));
    return;
  }
  VLOG(1) << "Dispatching DBus method call: " << method_name;
  pair->second->HandleMethod(method_call, sender);
}

void DBusInterface::AddHandlerImpl(
    const std::string& method_name,
    std::unique_ptr<DBusInterfaceMethodHandlerInterface> handler) {
  VLOG(1) << "Declaring method handler: " << interface_name_ << "."
          << method_name;
  auto res = handlers_.insert(std::make_pair(method_name, std::move(handler)));
  CHECK(res.second) << "Method '" << method_name << "' already exists";
}

void DBusInterface::AddSignalImpl(
    const std::string& signal_name,
    const std::shared_ptr<DBusSignalBase>& signal) {
  VLOG(1) << "Declaring a signal sink: " << interface_name_ << "."
          << signal_name;
  CHECK(signals_.insert(std::make_pair(signal_name, signal)).second)
      << "The signal '" << signal_name << "' is already registered";
}

///////////////////////////////////////////////////////////////////////////////

DBusObject::DBusObject(ExportedObjectManager* object_manager,
                       const scoped_refptr<dbus::Bus>& bus,
                       const dbus::ObjectPath& object_path)
    : DBusObject::DBusObject(object_manager,
                             bus,
                             object_path,
                             base::Bind(&SetupDefaultPropertyHandlers)) {}

DBusObject::DBusObject(
    ExportedObjectManager* object_manager,
    const scoped_refptr<dbus::Bus>& bus,
    const dbus::ObjectPath& object_path,
    PropertyHandlerSetupCallback property_handler_setup_callback)
    : property_set_(bus.get()),
      bus_(bus),
      object_path_(object_path),
      property_handler_setup_callback_(
          std::move(property_handler_setup_callback)) {
  if (object_manager)
    object_manager_ = object_manager->AsWeakPtr();
}

DBusObject::~DBusObject() {
  if (exported_object_)
    exported_object_->Unregister();
}

DBusInterface* DBusObject::AddOrGetInterface(
    const std::string& interface_name) {
  auto iter = interfaces_.find(interface_name);
  if (iter == interfaces_.end()) {
    VLOG(1) << "Adding an interface '" << interface_name << "' to object '"
            << object_path_.value() << "'.";
    // Interface doesn't exist yet. Create one...
    std::unique_ptr<DBusInterface> new_itf(
        new DBusInterface(this, interface_name));
    iter = interfaces_.insert(std::make_pair(interface_name,
                                             std::move(new_itf))).first;
  }
  return iter->second.get();
}

DBusInterface* DBusObject::FindInterface(
    const std::string& interface_name) const {
  auto itf_iter = interfaces_.find(interface_name);
  return (itf_iter == interfaces_.end()) ? nullptr : itf_iter->second.get();
}

void DBusObject::RemoveInterface(const std::string& interface_name) {
  auto itf_iter = interfaces_.find(interface_name);
  CHECK(itf_iter != interfaces_.end())
      << "Interface " << interface_name << " has not been added.";
  interfaces_.erase(itf_iter);
}

void DBusObject::ExportInterfaceAsync(
    const std::string& interface_name,
    const AsyncEventSequencer::CompletionAction& completion_callback) {
  AddOrGetInterface(interface_name)
      ->ExportAsync(object_manager_.get(), bus_.get(), exported_object_,
                    object_path_, completion_callback);
}

void DBusObject::RegisterAsync(
    const AsyncEventSequencer::CompletionAction& completion_callback) {
  VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'.";
  CHECK(exported_object_ == nullptr) << "Object already registered.";
  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
  exported_object_ = bus_->GetExportedObject(object_path_);

  RegisterPropertiesInterface();

  // Export interface methods
  for (const auto& pair : interfaces_) {
    pair.second->ExportAsync(
        object_manager_.get(),
        bus_.get(),
        exported_object_,
        object_path_,
        sequencer->GetHandler("Failed to export interface " + pair.first,
                              false));
  }

  sequencer->OnAllTasksCompletedCall({completion_callback});
}

void DBusObject::RegisterAndBlock() {
  VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'.";
  CHECK(exported_object_ == nullptr) << "Object already registered.";
  exported_object_ = bus_->GetExportedObject(object_path_);

  RegisterPropertiesInterface();

  // Export interface methods
  for (const auto& pair : interfaces_) {
    pair.second->ExportAndBlock(
        object_manager_.get(),
        bus_.get(),
        exported_object_,
        object_path_);
  }
}

void DBusObject::UnregisterAsync() {
  VLOG(1) << "Unregistering D-Bus object '" << object_path_.value() << "'.";
  CHECK(exported_object_ != nullptr) << "Object not registered.";

  // This will unregister the object path from the bus.
  exported_object_->Unregister();
  // This will remove |exported_object_| from bus's object table. This function
  // will also post a task to unregister |exported_object_| (same as the call
  // above), which will be a no-op since it is already done by then.
  // By doing both in here, the object path is guarantee to be reusable upon
  // return from this function.
  bus_->UnregisterExportedObject(object_path_);
  exported_object_ = nullptr;
}

bool DBusObject::SendSignal(dbus::Signal* signal) {
  if (exported_object_) {
    exported_object_->SendSignal(signal);
    return true;
  }
  LOG(ERROR) << "Trying to send a signal from an object that is not exported";
  return false;
}

void DBusObject::RegisterPropertiesInterface() {
  DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface);
  property_handler_setup_callback_.Run(prop_interface, &property_set_);
  property_set_.OnPropertiesInterfaceExported(prop_interface);
}

}  // namespace dbus_utils
}  // namespace brillo