/*
 *
 * Copyright 2016 gRPC authors.
 *
 * 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 GRPCPP_IMPL_CODEGEN_RPC_SERVICE_METHOD_H
#define GRPCPP_IMPL_CODEGEN_RPC_SERVICE_METHOD_H

#include <climits>
#include <functional>
#include <map>
#include <memory>
#include <vector>

#include <grpc/impl/codegen/log.h>
#include <grpcpp/impl/codegen/byte_buffer.h>
#include <grpcpp/impl/codegen/config.h>
#include <grpcpp/impl/codegen/rpc_method.h>
#include <grpcpp/impl/codegen/status.h>

namespace grpc_impl {
class ServerContextBase;
}  // namespace grpc_impl

namespace grpc {
namespace internal {
/// Base class for running an RPC handler.
class MethodHandler {
 public:
  virtual ~MethodHandler() {}
  struct HandlerParameter {
    /// Constructor for HandlerParameter
    ///
    /// \param c : the gRPC Call structure for this server call
    /// \param context : the ServerContext structure for this server call
    /// \param req : the request payload, if appropriate for this RPC
    /// \param req_status : the request status after any interceptors have run
    /// \param handler_data: internal data for the handler.
    /// \param requester : used only by the callback API. It is a function
    ///        called by the RPC Controller to request another RPC (and also
    ///        to set up the state required to make that request possible)
    HandlerParameter(Call* c, ::grpc_impl::ServerContextBase* context,
                     void* req, Status req_status, void* handler_data,
                     std::function<void()> requester)
        : call(c),
          server_context(context),
          request(req),
          status(req_status),
          internal_data(handler_data),
          call_requester(std::move(requester)) {}
    ~HandlerParameter() {}
    Call* const call;
    ::grpc_impl::ServerContextBase* const server_context;
    void* const request;
    const Status status;
    void* const internal_data;
    const std::function<void()> call_requester;
  };
  virtual void RunHandler(const HandlerParameter& param) = 0;

  /* Returns a pointer to the deserialized request. \a status reflects the
     result of deserialization. This pointer and the status should be filled in
     a HandlerParameter and passed to RunHandler. It is illegal to access the
     pointer after calling RunHandler. Ownership of the deserialized request is
     retained by the handler. Returns nullptr if deserialization failed. */
  virtual void* Deserialize(grpc_call* /*call*/, grpc_byte_buffer* req,
                            Status* /*status*/, void** /*handler_data*/) {
    GPR_CODEGEN_ASSERT(req == nullptr);
    return nullptr;
  }
};

/// Server side rpc method class
class RpcServiceMethod : public RpcMethod {
 public:
  /// Takes ownership of the handler
  RpcServiceMethod(const char* name, RpcMethod::RpcType type,
                   MethodHandler* handler)
      : RpcMethod(name, type),
        server_tag_(nullptr),
        api_type_(ApiType::SYNC),
        handler_(handler) {}

  enum class ApiType {
    SYNC,
    ASYNC,
    RAW,
    CALL_BACK,  // not CALLBACK because that is reserved in Windows
    RAW_CALL_BACK,
  };

  void set_server_tag(void* tag) { server_tag_ = tag; }
  void* server_tag() const { return server_tag_; }
  /// if MethodHandler is nullptr, then this is an async method
  MethodHandler* handler() const { return handler_.get(); }
  ApiType api_type() const { return api_type_; }
  void SetHandler(MethodHandler* handler) { handler_.reset(handler); }
  void SetServerApiType(RpcServiceMethod::ApiType type) {
    if ((api_type_ == ApiType::SYNC) &&
        (type == ApiType::ASYNC || type == ApiType::RAW)) {
      // this marks this method as async
      handler_.reset();
    } else if (api_type_ != ApiType::SYNC) {
      // this is not an error condition, as it allows users to declare a server
      // like WithRawMethod_foo<AsyncService>. However since it
      // overwrites behavior, it should be logged.
      gpr_log(
          GPR_INFO,
          "You are marking method %s as '%s', even though it was "
          "previously marked '%s'. This behavior will overwrite the original "
          "behavior. If you expected this then ignore this message.",
          name(), TypeToString(api_type_), TypeToString(type));
    }
    api_type_ = type;
  }

 private:
  void* server_tag_;
  ApiType api_type_;
  std::unique_ptr<MethodHandler> handler_;

  const char* TypeToString(RpcServiceMethod::ApiType type) {
    switch (type) {
      case ApiType::SYNC:
        return "sync";
      case ApiType::ASYNC:
        return "async";
      case ApiType::RAW:
        return "raw";
      case ApiType::CALL_BACK:
        return "callback";
      case ApiType::RAW_CALL_BACK:
        return "raw_callback";
      default:
        GPR_UNREACHABLE_CODE(return "unknown");
    }
  }
};
}  // namespace internal

}  // namespace grpc

#endif  // GRPCPP_IMPL_CODEGEN_RPC_SERVICE_METHOD_H
