// 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.

#ifndef LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_
#define LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_

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

#include <base/callback.h>
#include <base/memory/weak_ptr.h>
#include <brillo/brillo_export.h>
#include <brillo/dbus/dbus_object.h>
#include <brillo/dbus/exported_property_set.h>
#include <brillo/variant_dictionary.h>
#include <dbus/bus.h>
#include <dbus/exported_object.h>
#include <dbus/message.h>
#include <dbus/object_path.h>

namespace brillo {

namespace dbus_utils {

// ExportedObjectManager is a delegate that implements the
// org.freedesktop.DBus.ObjectManager interface on behalf of another
// object. It handles sending signals when new interfaces are added.
//
// This class is very similar to the ExportedPropertySet class, except that
// it allows objects to expose an object manager interface rather than the
// properties interface.
//
//  Example usage:
//
//   class ExampleObjectManager {
//    public:
//     ExampleObjectManager(dbus::Bus* bus)
//         : object_manager_(bus, "/my/objects/path") { }
//
//     void RegisterAsync(const CompletionAction& cb) {
//          object_manager_.RegisterAsync(cb);
//     }
//     void ClaimInterface(const dbus::ObjectPath& path,
//                         const std::string& interface_name,
//                         const ExportedPropertySet::PropertyWriter& writer) {
//       object_manager_->ClaimInterface(...);
//     }
//     void ReleaseInterface(const dbus::ObjectPath& path,
//                           const std::string& interface_name) {
//       object_manager_->ReleaseInterface(...);
//     }
//
//    private:
//     ExportedObjectManager object_manager_;
//   };
//
//   class MyObjectClaimingAnInterface {
//    public:
//     MyObjectClaimingAnInterface(ExampleObjectManager* object_manager)
//       : object_manager_(object_manager) {}
//
//     void OnInitFinish(bool success) {
//       if (!success) { /* handle that */ }
//       object_manager_->ClaimInterface(
//           my_path_, my_interface_, my_properties_.GetWriter());
//     }
//
//    private:
//     struct Properties : public ExportedPropertySet {
//      public:
//       /* Lots of interesting properties. */
//     };
//
//     Properties my_properties_;
//     ExampleObjectManager* object_manager_;
//   };
class BRILLO_EXPORT ExportedObjectManager
    : public base::SupportsWeakPtr<ExportedObjectManager> {
 public:
  using ObjectMap =
      std::map<dbus::ObjectPath, std::map<std::string, VariantDictionary>>;
  using InterfaceProperties =
      std::map<std::string, ExportedPropertySet::PropertyWriter>;

  ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
                        const dbus::ObjectPath& path);
  virtual ~ExportedObjectManager() = default;

  // Registers methods implementing the ObjectManager interface on the object
  // exported on the path given in the constructor. Must be called on the
  // origin thread.
  virtual void RegisterAsync(
      const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
          completion_callback);

  // Trigger a signal that |path| has added an interface |interface_name|
  // with properties as given by |writer|.
  virtual void ClaimInterface(
      const dbus::ObjectPath& path,
      const std::string& interface_name,
      const ExportedPropertySet::PropertyWriter& writer);

  // Trigger a signal that |path| has removed an interface |interface_name|.
  virtual void ReleaseInterface(const dbus::ObjectPath& path,
                                const std::string& interface_name);

  const scoped_refptr<dbus::Bus>& GetBus() const { return bus_; }

  // Due to D-Bus forwarding, clients may need to access the underlying
  // DBusObject to handle signals/methods.
  // TODO(sonnysasaka): Refactor this accessor into a stricter API once we know
  // what D-Bus forwarding needs when it's completed, without exposing
  // DBusObject directly.
  brillo::dbus_utils::DBusObject* dbus_object() { return &dbus_object_; };

 private:
  BRILLO_PRIVATE ObjectMap HandleGetManagedObjects();

  scoped_refptr<dbus::Bus> bus_;
  brillo::dbus_utils::DBusObject dbus_object_;
  // Tracks all objects currently known to the ExportedObjectManager.
  std::map<dbus::ObjectPath, InterfaceProperties> registered_objects_;

  using SignalInterfacesAdded =
      DBusSignal<dbus::ObjectPath, std::map<std::string, VariantDictionary>>;
  using SignalInterfacesRemoved =
      DBusSignal<dbus::ObjectPath, std::vector<std::string>>;

  std::weak_ptr<SignalInterfacesAdded> signal_itf_added_;
  std::weak_ptr<SignalInterfacesRemoved> signal_itf_removed_;

  friend class ExportedObjectManagerTest;
  DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager);
};

}  // namespace dbus_utils

}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_