/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/*!
 * \file rpc_endpoint.h
 * \brief Communication endpoints to connect local and remote RPC sessions.
 */
#ifndef TVM_RUNTIME_RPC_RPC_ENDPOINT_H_
#define TVM_RUNTIME_RPC_RPC_ENDPOINT_H_

#include <tvm/ffi/function.h>

#include <memory>
#include <mutex>
#include <string>
#include <utility>

#include "../../support/ring_buffer.h"
#include "minrpc/rpc_reference.h"
#include "rpc_channel.h"
#include "rpc_session.h"

namespace tvm {
namespace runtime {

// Magic header for RPC data plane
const int kRPCMagic = 0xff271;
// magic header for RPC tracker(control plane)
const int kRPCTrackerMagic = 0x2f271;
// sucess response
const int kRPCSuccess = kRPCMagic + 0;
// cannot found matched key in server
const int kRPCMismatch = kRPCMagic + 2;

/*! \brief Enumeration code for the RPC tracker */
enum class TrackerCode : int {
  kFail = -1,
  kSuccess = 0,
  kPing = 1,
  kStop = 2,
  kPut = 3,
  kRequest = 4,
  kUpdateInfo = 5,
  kSummary = 6,
  kGetPendingMatchKeys = 7
};

/*!
 * \brief Communication endpoints to connect local and remote RPC sessions.
 *        An endpoint can either be a client or a server.
 */
class RPCEndpoint {
 public:
  /*! \brief virtual destructor
   * Closes the connection if the connection hasn't already been closed.
   */
  ~RPCEndpoint();

  /*!
   *  \brief Shutdown RPC connection.
   *
   *  Shutdown has no effect if the connection has already been shut down.
   *  Shutdown will wait for all output currently queued from the RPC connection (i.e. The user
   * doesn't need to wait for completion before calling Shutdown.) Any further use of objects that
   * depended on the endpoint (e.g. A tvm.runtime.tensor allocated on the remote RPC session) may
   * throw an exception when used.
   */
  void Shutdown();

  /*!
   *  \brief The server loop that server runs to handle RPC calls.
   */
  void ServerLoop();
  /*!
   * \brief Message handling function for an async IO event driven server.
   *
   *  Called when the server receives a message or an IO event update.
   *  Event driven handler will never call recv on the channel
   *  and always relies on the ServerIOEventHandler to receive the data.
   *
   * \param in_bytes The incoming bytes.
   * \param event_flag  1: read_available, 2: write_avaiable.
   * \return State flag.
   *     1: continue running, no need to write,
   *     2: need to write
   *     0: shutdown
   */
  int ServerAsyncIOEventHandler(const std::string& in_bytes, int event_flag);

  /*!
   * \brief Initalize the session on the remote that will be used to back all the RPC requests.
   *
   *  If no session constructor arguments is passed, LocalSession will be used in the remote.
   *  Otherwise the remote serving session will be constructed using the arguments
   *  specified in the session_constructor_args.
   *
   *  The construction rule can be summarized as follows:
   *
   * \code
   *
   *  auto args = session_constructor_args;
   *  int n = args.size();
   *  if (n != 0) {
   *    auto constructor = args[0].cast<std::string>();
   *    server.serving_session_ = GetGlobalFunc(constructor)(
   *        args[1], args[2] ... args[n - 1])
   *  } else {
   *    server.serving_session_ = LocalSession();
   *  }
   * \endcode
   *
   * \param session_constructor_args Optional sequence of the remote sesssion constructor.
   */
  void InitRemoteSession(ffi::PackedArgs session_constructor_args);

  /*!
   * \brief Call into remote function
   * \param handle The function handle
   * \param args The argument values.
   * \param fencode_return The function to receive return value encodings.
   */
  void CallFunc(RPCSession::PackedFuncHandle handle, ffi::PackedArgs args,
                RPCSession::FEncodeReturn encode_return);
  /*!
   * \brief Copy bytes into remote array content.
   * \param from The source host data.
   * \param from_offset The byte offeset in the from.
   * \param to The target array.
   * \param to_offset The byte offset in the to.
   * \param nbytes The size of the memory in bytes.
   * \param dev_to The target device.
   * \param type_hint Hint of content data type.
   */
  void CopyToRemote(void* from_bytes, DLTensor* to, uint64_t nbytes);
  /*!
   * \brief Copy bytes from remote array content.
   * \param from The source host data.
   * \param from_offset The byte offeset in the from.
   * \param to The target array.
   * \param to_offset The byte offset in the to.
   * \param nbytes The size of the memory in bytes.
   * \param dev_from The source device.
   * \param type_hint Hint of content data type.
   */
  void CopyFromRemote(DLTensor* from, void* to_bytes, uint64_t nbytes);

  /*!
   * \brief Call a remote defined system function with arguments.
   * \param fcode The function code.
   * \param args The arguments
   * \return The returned remote value.
   */
  template <typename... Args>
  inline ffi::Any SysCallRemote(RPCCode fcode, Args&&... args);
  /*!
   * \brief Create a RPC session with given channel.
   * \param channel The communication channel.
   * \param name The local name of the session, used for debug
   * \param remote_key The remote key of the session
   *   if remote_key equals "%toinit", we need to re-intialize
   *   it by event handler.
   * \param fcleanup The cleanup Packed function.
   */
  static std::shared_ptr<RPCEndpoint> Create(std::unique_ptr<RPCChannel> channel, std::string name,
                                             std::string remote_key,
                                             ffi::TypedFunction<void()> fcleanup = nullptr);

 private:
  class EventHandler;
  // Handle events until receives a return
  // Also flushes channels so that the function advances.
  RPCCode HandleUntilReturnEvent(bool client_mode, RPCSession::FEncodeReturn setreturn);
  // Initalization
  void Init();
  // Internal channel.
  std::unique_ptr<RPCChannel> channel_;

  // Internal mutex
  std::mutex mutex_;
  // Internal ring buffer.
  support::RingBuffer reader_, writer_;
  // Event handler.
  std::shared_ptr<EventHandler> handler_;
  // syscall remote with specified function code.
  ffi::Function syscall_remote_;
  // The name of the session.
  std::string name_;
  // The remote key
  std::string remote_key_;
  // Invoked when the RPC session is terminated
  ffi::TypedFunction<void()> fcleanup_;
};

/*!
 * \brief Create an RPC client session from an RPC client endpoint.
 * \param endpoint The endpoint.
 * \return The created session.
 */
std::shared_ptr<RPCSession> CreateClientSession(std::shared_ptr<RPCEndpoint> endpoint);

// implementation of inline functions
template <typename... Args>
inline ffi::Any RPCEndpoint::SysCallRemote(RPCCode code, Args&&... args) {
  return syscall_remote_(static_cast<int>(code), std::forward<Args>(args)...);
}

/*!
 * \brief Calculates overhead size of a CopyToRemote packet.
 * \param to DLTensor to copy.
 * \param code RPCCode for this transfer.
 * \param nbytes Number of bytes to transfer.
 * \return The remote-copy packet overhead size.
 */
uint64_t RemoteCopyCalculatePacketOverheadSize(DLTensor* tensor, RPCCode code, uint64_t nbytes);

}  // namespace runtime
}  // namespace tvm
#endif  // TVM_RUNTIME_RPC_RPC_ENDPOINT_H_
