/*
 *
 * Copyright 2015 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_SERVICE_TYPE_H
#define GRPCPP_IMPL_CODEGEN_SERVICE_TYPE_H

#include <grpcpp/impl/codegen/config.h>
#include <grpcpp/impl/codegen/core_codegen_interface.h>
#include <grpcpp/impl/codegen/rpc_service_method.h>
#include <grpcpp/impl/codegen/serialization_traits.h>
#include <grpcpp/impl/codegen/server_interface.h>
#include <grpcpp/impl/codegen/status.h>

namespace grpc {

class CompletionQueue;
class ServerContext;
class ServerInterface;

namespace internal {
class Call;
class ServerAsyncStreamingInterface {
 public:
  virtual ~ServerAsyncStreamingInterface() {}

  /// Request notification of the sending of initial metadata to the client.
  /// Completion will be notified by \a tag on the associated completion
  /// queue. This call is optional, but if it is used, it cannot be used
  /// concurrently with or after the \a Finish method.
  ///
  /// \param[in] tag Tag identifying this request.
  virtual void SendInitialMetadata(void* tag) = 0;

 private:
  friend class ::grpc::ServerInterface;
  virtual void BindCall(Call* call) = 0;
};
}  // namespace internal

/// Desriptor of an RPC service and its various RPC methods
class Service {
 public:
  Service() : server_(nullptr) {}
  virtual ~Service() {}

  bool has_async_methods() const {
    for (const auto& method : methods_) {
      if (method && method->handler() == nullptr) {
        return true;
      }
    }
    return false;
  }

  bool has_synchronous_methods() const {
    for (const auto& method : methods_) {
      if (method &&
          method->api_type() == internal::RpcServiceMethod::ApiType::SYNC) {
        return true;
      }
    }
    return false;
  }

  bool has_callback_methods() const {
    for (const auto& method : methods_) {
      if (method && (method->api_type() ==
                         internal::RpcServiceMethod::ApiType::CALL_BACK ||
                     method->api_type() ==
                         internal::RpcServiceMethod::ApiType::RAW_CALL_BACK)) {
        return true;
      }
    }
    return false;
  }

  bool has_generic_methods() const {
    for (const auto& method : methods_) {
      if (method == nullptr) {
        return true;
      }
    }
    return false;
  }

 protected:
  // TODO(vjpai): Promote experimental contents once callback API is accepted
  class experimental_type {
   public:
    explicit experimental_type(Service* service) : service_(service) {}

    void MarkMethodCallback(int index, internal::MethodHandler* handler) {
      service_->MarkMethodCallbackInternal(index, handler);
    }

    void MarkMethodRawCallback(int index, internal::MethodHandler* handler) {
      service_->MarkMethodRawCallbackInternal(index, handler);
    }

    internal::MethodHandler* GetHandler(int index) {
      return service_->GetHandlerInternal(index);
    }

   private:
    Service* service_;
  };

  experimental_type experimental() { return experimental_type(this); }

  template <class Message>
  void RequestAsyncUnary(int index, ::grpc::ServerContext* context,
                         Message* request,
                         internal::ServerAsyncStreamingInterface* stream,
                         ::grpc::CompletionQueue* call_cq,
                         ::grpc::ServerCompletionQueue* notification_cq,
                         void* tag) {
    // Typecast the index to size_t for indexing into a vector
    // while preserving the API that existed before a compiler
    // warning was first seen (grpc/grpc#11664)
    size_t idx = static_cast<size_t>(index);
    server_->RequestAsyncCall(methods_[idx].get(), context, stream, call_cq,
                              notification_cq, tag, request);
  }
  void RequestAsyncClientStreaming(
      int index, ::grpc::ServerContext* context,
      internal::ServerAsyncStreamingInterface* stream,
      ::grpc::CompletionQueue* call_cq,
      ::grpc::ServerCompletionQueue* notification_cq, void* tag) {
    size_t idx = static_cast<size_t>(index);
    server_->RequestAsyncCall(methods_[idx].get(), context, stream, call_cq,
                              notification_cq, tag);
  }
  template <class Message>
  void RequestAsyncServerStreaming(
      int index, ::grpc::ServerContext* context, Message* request,
      internal::ServerAsyncStreamingInterface* stream,
      ::grpc::CompletionQueue* call_cq,
      ::grpc::ServerCompletionQueue* notification_cq, void* tag) {
    size_t idx = static_cast<size_t>(index);
    server_->RequestAsyncCall(methods_[idx].get(), context, stream, call_cq,
                              notification_cq, tag, request);
  }
  void RequestAsyncBidiStreaming(
      int index, ::grpc::ServerContext* context,
      internal::ServerAsyncStreamingInterface* stream,
      ::grpc::CompletionQueue* call_cq,
      ::grpc::ServerCompletionQueue* notification_cq, void* tag) {
    size_t idx = static_cast<size_t>(index);
    server_->RequestAsyncCall(methods_[idx].get(), context, stream, call_cq,
                              notification_cq, tag);
  }

  void AddMethod(internal::RpcServiceMethod* method) {
    methods_.emplace_back(method);
  }

  void MarkMethodAsync(int index) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(
        methods_[idx].get() != nullptr &&
        "Cannot mark the method as 'async' because it has already been "
        "marked as 'generic'.");
    methods_[idx]->SetServerApiType(internal::RpcServiceMethod::ApiType::ASYNC);
  }

  void MarkMethodRaw(int index) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(methods_[idx].get() != nullptr &&
                       "Cannot mark the method as 'raw' because it has already "
                       "been marked as 'generic'.");
    methods_[idx]->SetServerApiType(internal::RpcServiceMethod::ApiType::RAW);
  }

  void MarkMethodGeneric(int index) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(
        methods_[idx]->handler() != nullptr &&
        "Cannot mark the method as 'generic' because it has already been "
        "marked as 'async' or 'raw'.");
    methods_[idx].reset();
  }

  void MarkMethodStreamed(int index, internal::MethodHandler* streamed_method) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(methods_[idx] && methods_[idx]->handler() &&
                       "Cannot mark an async or generic method Streamed");
    methods_[idx]->SetHandler(streamed_method);

    // From the server's point of view, streamed unary is a special
    // case of BIDI_STREAMING that has 1 read and 1 write, in that order,
    // and split server-side streaming is BIDI_STREAMING with 1 read and
    // any number of writes, in that order.
    methods_[idx]->SetMethodType(internal::RpcMethod::BIDI_STREAMING);
  }

#ifdef GRPC_CALLBACK_API_NONEXPERIMENTAL
  void MarkMethodCallback(int index, internal::MethodHandler* handler) {
    MarkMethodCallbackInternal(index, handler);
  }

  void MarkMethodRawCallback(int index, internal::MethodHandler* handler) {
    MarkMethodRawCallbackInternal(index, handler);
  }

  internal::MethodHandler* GetHandler(int index) {
    return GetHandlerInternal(index);
  }
#endif
 private:
  // TODO(vjpai): migrate the Internal functions to mainline functions once
  //              callback API is fully de-experimental
  void MarkMethodCallbackInternal(int index, internal::MethodHandler* handler) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(
        methods_[idx].get() != nullptr &&
        "Cannot mark the method as 'callback' because it has already been "
        "marked as 'generic'.");
    methods_[idx]->SetHandler(handler);
    methods_[idx]->SetServerApiType(
        internal::RpcServiceMethod::ApiType::CALL_BACK);
  }

  void MarkMethodRawCallbackInternal(int index,
                                     internal::MethodHandler* handler) {
    // This does not have to be a hard error, however no one has approached us
    // with a use case yet. Please file an issue if you believe you have one.
    size_t idx = static_cast<size_t>(index);
    GPR_CODEGEN_ASSERT(
        methods_[idx].get() != nullptr &&
        "Cannot mark the method as 'raw callback' because it has already "
        "been marked as 'generic'.");
    methods_[idx]->SetHandler(handler);
    methods_[idx]->SetServerApiType(
        internal::RpcServiceMethod::ApiType::RAW_CALL_BACK);
  }

  internal::MethodHandler* GetHandlerInternal(int index) {
    size_t idx = static_cast<size_t>(index);
    return methods_[idx]->handler();
  }

  friend class Server;
  friend class ServerInterface;
  ServerInterface* server_;
  std::vector<std::unique_ptr<internal::RpcServiceMethod>> methods_;
};

}  // namespace grpc

#endif  // GRPCPP_IMPL_CODEGEN_SERVICE_TYPE_H
