// 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_DBUS_SIGNAL_HANDLER_H_
#define LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_

#include <string>

#include <brillo/bind_lambda.h>
#include <brillo/dbus/dbus_param_reader.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>

namespace brillo {
namespace dbus_utils {

// brillo::dbus_utils::ConnectToSignal() is a helper function similar to
// dbus::ObjectProxy::ConnectToSignal() but the |signal_callback| is an actual
// C++ signal handler with expected signal parameters as native method args.
//
// brillo::dbus_utils::ConnectToSignal() actually registers a stub signal
// handler with D-Bus which has a standard signature that matches
// dbus::ObjectProxy::SignalCallback.
//
// When a D-Bus signal is emitted, the stub handler is invoked, which unpacks
// the expected parameters from dbus::Signal message and then calls
// |signal_callback| with unpacked arguments.
//
// If the signal message doesn't contain correct number or types of arguments,
// an error message is logged to the system log and the signal is ignored
// (|signal_callback| is not invoked).
template<typename... Args>
void ConnectToSignal(
    dbus::ObjectProxy* object_proxy,
    const std::string& interface_name,
    const std::string& signal_name,
    base::Callback<void(Args...)> signal_callback,
    dbus::ObjectProxy::OnConnectedCallback on_connected_callback) {
  // Raw signal handler stub method. When called, unpacks the signal arguments
  // from |signal| message buffer and redirects the call to
  // |signal_callback_wrapper| which, in turn, would call the user-provided
  // |signal_callback|.
  auto dbus_signal_callback = [](
      const base::Callback<void(Args...)>& signal_callback,
      dbus::Signal* signal) {
    // DBusParamReader::Invoke() needs a functor object, not a base::Callback.
    // Wrap the callback with lambda so we can redirect the call.
    auto signal_callback_wrapper = [signal_callback](const Args&... args) {
      if (!signal_callback.is_null()) {
        signal_callback.Run(args...);
      }
    };

    dbus::MessageReader reader(signal);
    DBusParamReader<false, Args...>::Invoke(
        signal_callback_wrapper, &reader, nullptr);
  };

  // Register our stub handler with D-Bus ObjectProxy.
  object_proxy->ConnectToSignal(interface_name,
                                signal_name,
                                base::Bind(dbus_signal_callback, signal_callback),
                                on_connected_callback);
}

}  // namespace dbus_utils
}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_