// Copyright 2021 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 GRPC_SRC_CORE_EXT_TRANSPORT_BINDER_UTILS_TRANSPORT_STREAM_RECEIVER_IMPL_H
#define GRPC_SRC_CORE_EXT_TRANSPORT_BINDER_UTILS_TRANSPORT_STREAM_RECEIVER_IMPL_H

#include <functional>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>

#include <grpc/support/port_platform.h>

#include "src/core/ext/transport/binder/utils/transport_stream_receiver.h"
#include "src/core/lib/gprpp/sync.h"

namespace grpc_binder {

// Routes the data received from transport to corresponding streams
class TransportStreamReceiverImpl : public TransportStreamReceiver {
 public:
  explicit TransportStreamReceiverImpl(
      bool is_client, std::function<void()> accept_stream_callback = nullptr)
      : is_client_(is_client),
        accept_stream_callback_(accept_stream_callback) {}
  void RegisterRecvInitialMetadata(StreamIdentifier id,
                                   InitialMetadataCallbackType cb) override;
  void RegisterRecvMessage(StreamIdentifier id,
                           MessageDataCallbackType cb) override;
  void RegisterRecvTrailingMetadata(StreamIdentifier id,
                                    TrailingMetadataCallbackType cb) override;
  void NotifyRecvInitialMetadata(
      StreamIdentifier id, absl::StatusOr<Metadata> initial_metadata) override;
  void NotifyRecvMessage(StreamIdentifier id,
                         absl::StatusOr<std::string> message) override;
  void NotifyRecvTrailingMetadata(StreamIdentifier id,
                                  absl::StatusOr<Metadata> trailing_metadata,
                                  int status) override;

  void CancelStream(StreamIdentifier id) override;

 private:
  // Trailing metadata marks the end of one-side of the stream. Thus, after
  // receiving trailing metadata from the other-end, we know that there will
  // never be in-coming message data anymore, and all recv_message callbacks
  // (as well as recv_initial_metadata callback, if there's any) registered will
  // never be satisfied. This function cancels all such callbacks gracefully
  // (with absl::OkStatus()) to avoid being blocked waiting for them.
  void OnRecvTrailingMetadata(StreamIdentifier id);

  void CancelInitialMetadataCallback(StreamIdentifier id, absl::Status error);
  void CancelMessageCallback(StreamIdentifier id, absl::Status error);
  void CancelTrailingMetadataCallback(StreamIdentifier id, absl::Status error);

  std::map<StreamIdentifier, InitialMetadataCallbackType> initial_metadata_cbs_;
  std::map<StreamIdentifier, MessageDataCallbackType> message_cbs_;
  std::map<StreamIdentifier, TrailingMetadataCallbackType>
      trailing_metadata_cbs_;
  // TODO(waynetu): Better thread safety design. For example, use separate
  // mutexes for different type of messages.
  grpc_core::Mutex m_;
  // TODO(waynetu): gRPC surface layer will not wait for the current message to
  // be delivered before sending the next message. The following implementation
  // is still buggy with the current implementation of wire writer if
  // transaction issued first completes after the one issued later does. This is
  // because we just take the first element out of the queue and assume it's the
  // one issued first without further checking, which results in callbacks being
  // invoked with incorrect data.
  //
  // This should be fixed in the wire writer level and make sure out-of-order
  // messages will be re-ordered by it. In such case, the queueing approach will
  // work fine. Refer to the TODO in WireWriterImpl::ProcessTransaction() at
  // wire_reader_impl.cc for detecting and resolving out-of-order transactions.
  //
  // TODO(waynetu): Use absl::flat_hash_map.
  std::map<StreamIdentifier, std::queue<absl::StatusOr<Metadata>>>
      pending_initial_metadata_ ABSL_GUARDED_BY(m_);
  std::map<StreamIdentifier, std::queue<absl::StatusOr<std::string>>>
      pending_message_ ABSL_GUARDED_BY(m_);
  std::map<StreamIdentifier,
           std::queue<std::pair<absl::StatusOr<Metadata>, int>>>
      pending_trailing_metadata_ ABSL_GUARDED_BY(m_);
  // Record whether or not the recv_message callbacks of a given stream is
  // cancelled. Although we explicitly cancel the registered recv_message() in
  // CancelRecvMessageCallbacksDueToTrailingMetadata(), there are chances that
  // the registration comes "after" we receive trailing metadata. Therefore,
  // when RegisterRecvMessage() gets called, we should check whether
  // recv_message_cancelled_ contains the corresponding stream ID, and if so,
  // directly cancel the callback gracefully without pending it.
  std::set<StreamIdentifier> trailing_metadata_recvd_ ABSL_GUARDED_BY(m_);

  bool is_client_;
  // Called when receiving initial metadata to inform the server about a new
  // stream.
  std::function<void()> accept_stream_callback_;
};
}  // namespace grpc_binder

#endif  // GRPC_SRC_CORE_EXT_TRANSPORT_BINDER_UTILS_TRANSPORT_STREAM_RECEIVER_IMPL_H
