/*
 *
 * 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_impl {

class Server;
class CompletionQueue;
}  // namespace grpc_impl
namespace grpc {

class ServerInterface;
class ServerContext;

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 (auto it = methods_.begin(); it != methods_.end(); ++it) {
      if (*it && (*it)->handler() == nullptr) {
        return true;
      }
    }
    return false;
  }

  bool has_synchronous_methods() const {
    for (auto it = methods_.begin(); it != methods_.end(); ++it) {
      if (*it &&
          (*it)->api_type() == internal::RpcServiceMethod::ApiType::SYNC) {
        return true;
      }
    }
    return false;
  }

  bool has_callback_methods() const {
    for (auto it = methods_.begin(); it != methods_.end(); ++it) {
      if (*it && ((*it)->api_type() ==
                      internal::RpcServiceMethod::ApiType::CALL_BACK ||
                  (*it)->api_type() ==
                      internal::RpcServiceMethod::ApiType::RAW_CALL_BACK)) {
        return true;
      }
    }
    return false;
  }

  bool has_generic_methods() const {
    for (auto it = methods_.begin(); it != methods_.end(); ++it) {
      if (it->get() == 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) {
      // 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(
          service_->methods_[idx].get() != nullptr &&
          "Cannot mark the method as 'callback' because it has already been "
          "marked as 'generic'.");
      service_->methods_[idx]->SetHandler(handler);
      service_->methods_[idx]->SetServerApiType(
          internal::RpcServiceMethod::ApiType::CALL_BACK);
    }

    void MarkMethodRawCallback(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(
          service_->methods_[idx].get() != nullptr &&
          "Cannot mark the method as 'raw callback' because it has already "
          "been marked as 'generic'.");
      service_->methods_[idx]->SetHandler(handler);
      service_->methods_[idx]->SetServerApiType(
          internal::RpcServiceMethod::ApiType::RAW_CALL_BACK);
    }

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

   private:
    Service* service_;
  };

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

  template <class Message>
  void RequestAsyncUnary(int index, ServerContext* context, Message* request,
                         internal::ServerAsyncStreamingInterface* stream,
                         CompletionQueue* call_cq,
                         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, ServerContext* context,
      internal::ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq,
      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, ServerContext* context, Message* request,
      internal::ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq,
      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, ServerContext* context,
      internal::ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq,
      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);
  }

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

}  // namespace grpc

#endif  // GRPCPP_IMPL_CODEGEN_SERVICE_TYPE_H
