/*
 * Copyright (C) 2017 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.
 */

#ifndef INCLUDE_PERFETTO_EXT_IPC_DEFERRED_H_
#define INCLUDE_PERFETTO_EXT_IPC_DEFERRED_H_

#include <functional>
#include <memory>
#include <utility>

#include "perfetto/ext/ipc/async_result.h"
#include "perfetto/ext/ipc/basic_types.h"

namespace perfetto {
namespace ipc {

// This class is a wrapper for a callback handling async results.
// The problem this is solving is the following: For each result argument of the
// methods generated from the .proto file:
// - The client wants to see something on which it can Bind() a callback, which
//   is invoked asynchronously once reply is received from the host.
// - The host wants to expose something to user code that implements the IPC
//   methods to allow them to provide an asynchronous reply back to the client.
//   Eventually even more than once, for the case streaming replies.
//
// In both cases we want to make sure that callbacks don't get lost along the
// way. To address this, this class will automatically reject the callbacks
// if they are not resolved at destructor time (or the object is std::move()'d).
//
// The client is supposed to use this class as follows:
//   class GreeterProxy {
//      void SayHello(const HelloRequest&, Deferred<HelloReply> reply)
//   }
//  ...
//  Deferred<HelloReply> reply;
//  reply.Bind([] (AsyncResult<HelloReply> reply) {
//    std::cout << reply.success() ? reply->message : "failure";
//  });
//  host_proxy_instance.SayHello(req, std::move(reply));
//
// The host instead is supposed to use this as follows:
//   class GreeterImpl : public Greeter {
//     void SayHello(const HelloRequest& req, Deferred<HelloReply> reply) {
//        AsyncResult<HelloReply> reply = AsyncResult<HelloReply>::Create();
//        reply->set_greeting("Hello " + req.name)
//        reply.Resolve(std::move(reply));
//     }
//   }
// Or for more complex cases, the deferred object can be std::move()'d outside
// and the reply can continue asynchronously later.

template <typename T>
class Deferred;

class DeferredBase {
 public:
  explicit DeferredBase(
      std::function<void(AsyncResult<ProtoMessage>)> callback = nullptr);

  ~DeferredBase();
  DeferredBase(DeferredBase&&) noexcept;
  DeferredBase& operator=(DeferredBase&&);
  void Bind(std::function<void(AsyncResult<ProtoMessage>)> callback);
  bool IsBound() const;
  void Resolve(AsyncResult<ProtoMessage>);
  void Reject();

 protected:
  template <typename T>
  friend class Deferred;
  void Move(DeferredBase&);

  std::function<void(AsyncResult<ProtoMessage>)> callback_;
};

template <typename T>  // T : ProtoMessage subclass
class Deferred : public DeferredBase {
 public:
  explicit Deferred(std::function<void(AsyncResult<T>)> callback = nullptr) {
    Bind(std::move(callback));
  }

  // This move constructor (and the similar one in DeferredBase) is meant to be
  // called only by the autogenerated code. The caller has to guarantee that the
  // moved-from and moved-to types match. The behavior is otherwise undefined.
  explicit Deferred(DeferredBase&& other) {
    callback_ = std::move(other.callback_);
    other.callback_ = nullptr;
  }

  void Bind(std::function<void(AsyncResult<T>)> callback) {
    if (!callback)
      return;

    // Here we need a callback adapter to downcast the callback to a generic
    // callback that takes an AsyncResult<ProtoMessage>, so that it can be
    // stored in the base class |callback_|.
    auto callback_adapter = [callback](
                                AsyncResult<ProtoMessage> async_result_base) {
      // Upcast the async_result from <ProtoMessage> -> <T : ProtoMessage>.
      static_assert(std::is_base_of<ProtoMessage, T>::value, "T:ProtoMessage");
      AsyncResult<T> async_result(
          std::unique_ptr<T>(static_cast<T*>(async_result_base.release_msg())),
          async_result_base.has_more(), async_result_base.fd());
      callback(std::move(async_result));
    };
    DeferredBase::Bind(callback_adapter);
  }

  // If no more messages are expected, |callback_| is released.
  void Resolve(AsyncResult<T> async_result) {
    // Convert the |async_result| to the generic base one (T -> ProtoMessage).
    AsyncResult<ProtoMessage> async_result_base(
        std::unique_ptr<ProtoMessage>(async_result.release_msg()),
        async_result.has_more(), async_result.fd());
    DeferredBase::Resolve(std::move(async_result_base));
  }
};

}  // namespace ipc
}  // namespace perfetto

#endif  // INCLUDE_PERFETTO_EXT_IPC_DEFERRED_H_
