// Copyright 2015 The Chromium 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 COMPONENTS_ARC_CONNECTION_HOLDER_H_
#define COMPONENTS_ARC_CONNECTION_HOLDER_H_

#include <memory>
#include <string>
#include <type_traits>
#include <utility>

#include "base/bind.h"
#include "base/macros.h"
#include "base/observer_list.h"
#include "base/threading/thread_checker.h"
#include "components/arc/connection_notifier.h"
#include "components/arc/connection_observer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_request.h"

// A macro to call
// ConnectionHolder<T>::GetInstanceForVersionDoNotCallDirectly(). In order to
// avoid exposing method names from within the Mojo bindings, we will rely on
// stringification and the fact that the method min versions have a consistent
// naming scheme.
#define ARC_GET_INSTANCE_FOR_METHOD(holder, method_name)        \
  (holder)->GetInstanceForVersionDoNotCallDirectly(             \
      std::remove_pointer<decltype(                             \
          holder)>::type::Instance::k##method_name##MinVersion, \
      #method_name)

namespace arc {

namespace internal {

// Small helper to implement HasInit<> trait below.
// Note that only Check() declaration is needed.
// - If InstanceType::Init exists, Check(InstanceType()) would return
//   std::true_type, because 1) the template param is successfully substituted,
//   and 2) Check(...) is weaker than the template's so there is no overload
//   conflict.
// - If not, Check(InstanceType()) would return std::false_type, because
//   template param substitution is failed, but it won't be compile error
//   thanks to SFINAE, thus Check(...) is the only candidate.
struct HasInitImpl {
  template <typename InstanceType>
  static auto Check(InstanceType* v)
      -> decltype(&InstanceType::Init, std::true_type());
  static std::false_type Check(...);
};

// Type trait to return whether InstanceType has Init() or not.
template <typename InstanceType>
using HasInit =
    decltype(HasInitImpl::Check(static_cast<InstanceType*>(nullptr)));

// Same as above, but for InstanceType::InitDeprecated.
struct HasInitDeprecatedImpl {
  template <typename InstanceType>
  static auto Check(InstanceType* v)
      -> decltype(&InstanceType::InitDeprecated, std::true_type());
  static std::false_type Check(...);
};

// Type trait to return whether InstanceType has InitDeprecated() or not.
template <typename InstanceType>
using HasInitDeprecated =
    decltype(HasInitDeprecatedImpl::Check(static_cast<InstanceType*>(nullptr)));

// Templates to count the number of arguments in a function. This is used to
// distinguish between interfaces that do not declare an Init() method, a
// one-argument (single-duplex) Init() and the two-argument (full-duplex)
// Init().
// TODO(crbug.com/750563): Simplify the templates once InitDeprecated() is
// removed.
template <typename Signature>
struct CountInitArgsImpl;

template <typename R, typename Receiver, typename... Args>
struct CountInitArgsImpl<R (Receiver::*)(Args...)> {
  static constexpr size_t value = sizeof...(Args);
};

template <class T>
struct Void {
  typedef void type;
};

template <typename T, typename U = void>
struct CountInitArgs {
  static constexpr size_t value = 0;
};

template <typename T>
struct CountInitArgs<T, typename Void<decltype(&T::Init)>::type> {
  static constexpr size_t value = CountInitArgsImpl<decltype(&T::Init)>::value;
};

// Full duplex Mojo connection holder implementation.
// InstanceType and HostType are Mojo interface types (arc::mojom::XxxInstance,
// and arc::mojom::XxxHost respectively).
template <typename InstanceType, typename HostType>
class ConnectionHolderImpl {
 public:
  explicit ConnectionHolderImpl(ConnectionNotifier* connection_notifier)
      : connection_notifier_(connection_notifier), weak_ptr_factory_(this) {}

  InstanceType* instance() { return IsConnected() ? instance_ : nullptr; }
  uint32_t instance_version() const {
    return IsConnected() ? instance_version_ : 0;
  }

  // Returns true if |binding_| is set.
  bool IsConnected() const { return binding_.get(); }

  // Sets (or resets if |host| is nullptr) the host.
  void SetHost(HostType* host) {
    // Some tests overwrite host, now. It is safe iff the |instance_| is
    // not yet set.
    // TODO(hidehiko): Make check more strict.
    DCHECK(host == nullptr || host_ == nullptr || instance_ == nullptr);

    if (host_ == host)
      return;

    host_ = host;
    OnChanged();
  }

  // Sets the instance.
  void SetInstance(InstanceType* instance,
                   uint32_t version = InstanceType::version_) {
    DCHECK(instance);
    DCHECK(instance_ != instance);

    instance_ = instance;
    instance_version_ = version;
    OnChanged();
  }

  // Resets the instance if it matches |instance|.
  void CloseInstance(InstanceType* instance) {
    DCHECK(instance);

    if (instance != instance_) {
      DVLOG(1) << "Dropping request to close a stale instance";
      return;
    }

    instance_ = nullptr;
    instance_version_ = 0;
    OnChanged();
  }

 private:
  // Called when |instance_| or |host_| are updated.
  void OnChanged() {
    // Cancel any in-flight connection requests. This also prevents Observers
    // from being notified of a spurious OnConnectionClosed() before an
    // OnConnectionReady() event.
    weak_ptr_factory_.InvalidateWeakPtrs();
    if (binding_.get()) {
      // Regardless of what has changed, the old connection is now stale. Reset
      // the current binding and notify any listeners. Since |binding_| is set
      // just before the OnConnectionReady() event, we never notify observers of
      // OnConnectionClosed() without seeing the former event first.
      if (instance_ && host_)
        LOG(ERROR) << "Unbinding instance of a stale connection";
      OnConnectionClosed();
    }
    if (!instance_ || !host_)
      return;
    // When both the instance and host are ready, start connection.
    // TODO(crbug.com/750563): Fix the race issue.
    auto binding = std::make_unique<mojo::Binding<HostType>>(host_);
    mojo::InterfacePtr<HostType> host_proxy;
    binding->Bind(mojo::MakeRequest(&host_proxy));
    // Call the appropriate version of Init().
    CallInstanceInit<InstanceType>(std::move(host_proxy), std::move(binding),
                                   HasInitDeprecated<InstanceType>());
  }

  // Dispatches the correct version of Init(). The template type is needed
  // because std::enable_if<> needs to depend on a template parameter in order
  // for SFINAE to work. The second parameter (std::true_type or
  // std::false_type) refers to whether InstanceType::DeprecatedInit() exists.
  template <class T>
  typename std::enable_if<CountInitArgs<T>::value == 2, void>::type
  CallInstanceInit(mojo::InterfacePtr<HostType> host_proxy,
                   std::unique_ptr<mojo::Binding<HostType>> binding,
                   std::true_type) {
    if (instance_version_ < InstanceType::kInitMinVersion) {
      // The instance is too old to know about the new Init() version. For now,
      // call the deprecated version for backwards-compatibility.
      // TODO(crbug.com/750563): Deprecate this version.
      CallInstanceInitDeprecated(std::move(host_proxy), std::move(binding),
                                 HasInitDeprecated<InstanceType>());
      return;
    }

    instance_->Init(
        std::move(host_proxy),
        base::BindOnce(&ConnectionHolderImpl::OnConnectionReady,
                       weak_ptr_factory_.GetWeakPtr(), std::move(binding)));
  }

  template <class T>
  typename std::enable_if<CountInitArgs<T>::value == 2, void>::type
  CallInstanceInit(mojo::InterfacePtr<HostType> host_proxy,
                   std::unique_ptr<mojo::Binding<HostType>> binding,
                   std::false_type) {
    instance_->Init(
        std::move(host_proxy),
        base::BindOnce(&ConnectionHolderImpl::OnConnectionReady,
                       weak_ptr_factory_.GetWeakPtr(), std::move(binding)));
  }

  // TODO(crbug.com/750563): Deprecate this version.
  template <class T>
  typename std::enable_if<CountInitArgs<T>::value == 1, void>::type
  CallInstanceInit(mojo::InterfacePtr<HostType> host_proxy,
                   std::unique_ptr<mojo::Binding<HostType>> binding,
                   ...) {
    instance_->Init(std::move(host_proxy));
    OnConnectionReady(std::move(binding));
  }

  void CallInstanceInitDeprecated(
      mojo::InterfacePtr<HostType> host_proxy,
      std::unique_ptr<mojo::Binding<HostType>> binding,
      std::true_type) {
    instance_->InitDeprecated(std::move(host_proxy));
    OnConnectionReady(std::move(binding));
  }

  void CallInstanceInitDeprecated(
      mojo::InterfacePtr<HostType> host_proxy,
      std::unique_ptr<mojo::Binding<HostType>> binding,
      std::false_type) {
    // If InitDeprecated does not exists, ARC container must support
    // Init() with callback, already. Thus, this should not be called.
    NOTREACHED();
  }

  // Resets the binding and notifies all the observers that the connection is
  // closed.
  void OnConnectionClosed() {
    DCHECK(binding_);
    binding_.reset();
    connection_notifier_->NotifyConnectionClosed();
  }

  // Notifies all the observers that the connection is ready.
  void OnConnectionReady(std::unique_ptr<mojo::Binding<HostType>> binding) {
    DCHECK(!binding_);
    // Now that we can finally commit to this connection and will deliver the
    // OnConnectionReady() event, set the connection error handler to notify
    // Observers of connection closures.
    // Note: because the callback will be destroyed with |binding_|,
    // base::Unretained() can be safely used.
    binding->set_connection_error_handler(base::BindOnce(
        &ConnectionHolderImpl::OnConnectionClosed, base::Unretained(this)));

    binding_ = std::move(binding);
    connection_notifier_->NotifyConnectionReady();
  }

  // This class does not have ownership. The pointers should be managed by the
  // caller.
  ConnectionNotifier* const connection_notifier_;
  InstanceType* instance_ = nullptr;
  uint32_t instance_version_ = 0;
  HostType* host_ = nullptr;

  // Created when both |instance_| and |host_| ptr are set.
  std::unique_ptr<mojo::Binding<HostType>> binding_;

  base::WeakPtrFactory<ConnectionHolderImpl> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(ConnectionHolderImpl);
};

// Single direction Mojo connection holder implementation.
// (HostType == void means single direction).
// InstanceType should be Mojo interface type (arc::mojom::XxxInstance).
template <typename InstanceType>
class ConnectionHolderImpl<InstanceType, void> {
 public:
  // InstanceType must not have Init() method, which should be for a
  // full-duplex connection.
  static_assert(!HasInit<InstanceType>::value,
                "Full duplex ConnectionHolderImpl should be used instead");

  explicit ConnectionHolderImpl(ConnectionNotifier* connection_notifier)
      : connection_notifier_(connection_notifier) {}

  InstanceType* instance() { return instance_; }
  uint32_t instance_version() const { return instance_version_; }

  // For single direction connection, when Instance proxy is obtained,
  // it means connected.
  bool IsConnected() const { return instance_; }

  void SetHost(void* unused) {
    static_assert(!sizeof(*this),
                  "ConnectionHolder::SetHost for single direction connection "
                  "is called unexpectedly.");
    NOTREACHED();
  }

  // Sets the instance.
  void SetInstance(InstanceType* instance,
                   uint32_t version = InstanceType::version_) {
    DCHECK(instance);
    DCHECK(instance_ != instance);

    instance_ = instance;
    instance_version_ = version;

    // There is nothing more than setting in this case. Notify observers.
    connection_notifier_->NotifyConnectionReady();
  }

  // Resets the instance if it matches |instance|.
  void CloseInstance(InstanceType* instance) {
    DCHECK(instance);

    if (instance != instance_) {
      DVLOG(1) << "Dropping request to close a stale instance";
      return;
    }

    instance_ = nullptr;
    instance_version_ = 0;
    connection_notifier_->NotifyConnectionClosed();
  }

 private:
  // This class does not have ownership. The pointers should be managed by the
  // caller.
  ConnectionNotifier* const connection_notifier_;
  InstanceType* instance_ = nullptr;
  uint32_t instance_version_ = 0;

  DISALLOW_COPY_AND_ASSIGN(ConnectionHolderImpl);
};

}  // namespace internal

// Holds a Mojo connection. This also allows for listening for state changes
// for the particular connection.
// InstanceType and HostType should be Mojo interface type
// (arc::mojom::XxxInstance and arc::mojom::XxxHost respectively).
// If HostType is void, it is single direction Mojo connection, so it only
// looks at Instance pointer.
// Otherwise, it is full duplex Mojo connection. When both Instance and Host
// pointers are set, it calls Instance::Init() method to pass Host pointer
// to the ARC container.
template <typename InstanceType, typename HostType = void>
class ConnectionHolder {
 public:
  using Observer = ConnectionObserver<InstanceType>;
  using Instance = InstanceType;

  ConnectionHolder() = default;

  // Returns true if the Mojo interface is ready at least for its version 0
  // interface. Use an Observer if you want to be notified when this is ready.
  // This can only be called on the thread that this class was created on.
  bool IsConnected() const { return impl_.IsConnected(); }

  // Gets the Mojo interface that's intended to call for
  // |method_name_for_logging|, but only if its reported version is at least
  // |min_version|. Returns nullptr if the connection is either not ready or
  // the instance does not have the requested version, and logs appropriately.
  // This function should not be called directly. Instead, use the
  // ARC_GET_INSTANCE_FOR_METHOD() macro.
  InstanceType* GetInstanceForVersionDoNotCallDirectly(
      uint32_t min_version,
      const char method_name_for_logging[]) {
    if (!impl_.IsConnected()) {
      VLOG(1) << "Instance " << InstanceType::Name_ << " not available.";
      return nullptr;
    }
    if (impl_.instance_version() < min_version) {
      LOG(ERROR) << "Instance for " << InstanceType::Name_
                 << "::" << method_name_for_logging
                 << " version mismatch. Expected " << min_version << " got "
                 << impl_.instance_version();
      return nullptr;
    }
    return impl_.instance();
  }

  // Adds or removes observers. This can only be called on the thread that this
  // class was created on. RemoveObserver does nothing if |observer| is not in
  // the list.
  void AddObserver(Observer* observer) {
    connection_notifier_.AddObserver(observer);
    if (impl_.IsConnected())
      connection_notifier_.NotifyConnectionReady();
  }

  void RemoveObserver(Observer* observer) {
    connection_notifier_.RemoveObserver(observer);
  }

  // Sets |host|. This can be called in both cases; on ready, or on closed.
  // Passing nullptr to |host| means closing.
  // This must not be called if HostType is void (i.e. single direciton
  // connection).
  void SetHost(HostType* host) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    impl_.SetHost(host);
  }

  // Sets |instance| with |version|.
  void SetInstance(InstanceType* instance,
                   uint32_t version = InstanceType::Version_) {
    DCHECK(instance);
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    impl_.SetInstance(instance, version);
  }

  // Closes |instance|, if it matches against the current instance. Otherwise,
  // it is a no-op.
  void CloseInstance(InstanceType* instance) {
    DCHECK(instance);
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    impl_.CloseInstance(instance);
  }

 private:
  THREAD_CHECKER(thread_checker_);
  internal::ConnectionNotifier connection_notifier_;
  internal::ConnectionHolderImpl<InstanceType, HostType> impl_{
      &connection_notifier_};

  DISALLOW_COPY_AND_ASSIGN(ConnectionHolder);
};

}  // namespace arc

#endif  // COMPONENTS_ARC_CONNECTION_HOLDER_H_
