// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_

#include <iostream>
#include <memory>
#include <string>

#include "binary_messenger.h"
#include "engine_method_result.h"
#include "event_sink.h"
#include "event_stream_handler.h"

namespace flutter {

class EncodableValue;

// A named channel for communicating with the Flutter application using
// asynchronous event streams. Incoming requests for event stream setup are
// decoded from binary on receipt, and C++ responses and events are encoded into
// binary before being transmitted back to Flutter. The MethodCodec used must be
// compatible with the one used by the Flutter application. This can be achieved
// by creating an EventChannel
// ("https://api.flutter.dev/flutter/services/EventChannel-class.html")
// counterpart of this channel on the Dart side.
// The C++ type of stream configuration arguments, events, and error details are
// templated, but only values supported by the specified MethodCodec can be
// used.
template <typename T = EncodableValue>
class EventChannel {
 public:
  // Creates an instance that sends and receives event handler on the channel
  // named |name|, encoded with |codec| and dispatched via |messenger|.
  EventChannel(BinaryMessenger* messenger,
               const std::string& name,
               const MethodCodec<T>* codec)
      : messenger_(messenger), name_(name), codec_(codec) {}
  ~EventChannel() = default;

  // Prevent copying.
  EventChannel(EventChannel const&) = delete;
  EventChannel& operator=(EventChannel const&) = delete;

  // Registers a stream handler on this channel.
  // If no handler has been registered, any incoming stream setup requests will
  // be handled silently by providing an empty stream.
  void SetStreamHandler(std::unique_ptr<StreamHandler<T>> handler) {
    if (!handler) {
      messenger_->SetMessageHandler(name_, nullptr);
      is_listening_ = false;
      return;
    }

    // std::function requires a copyable lambda, so convert to a shared pointer.
    // This is safe since only one copy of the shared_pointer will ever be
    // accessed.
    std::shared_ptr<StreamHandler<T>> shared_handler(handler.release());
    const MethodCodec<T>* codec = codec_;
    const std::string channel_name = name_;
    const BinaryMessenger* messenger = messenger_;
    BinaryMessageHandler binary_handler = [shared_handler, codec, channel_name,
                                           messenger,
                                           this](const uint8_t* message,
                                                 const size_t message_size,
                                                 BinaryReply reply) {
      constexpr char kOnListenMethod[] = "listen";
      constexpr char kOnCancelMethod[] = "cancel";

      std::unique_ptr<MethodCall<T>> method_call =
          codec->DecodeMethodCall(message, message_size);
      if (!method_call) {
        std::cerr << "Unable to construct method call from message on channel: "
                  << channel_name << std::endl;
        reply(nullptr, 0);
        return;
      }

      const std::string& method = method_call->method_name();
      if (method.compare(kOnListenMethod) == 0) {
        if (is_listening_) {
          std::unique_ptr<StreamHandlerError<T>> error =
              shared_handler->OnCancel(nullptr);
          if (error) {
            std::cerr << "Failed to cancel existing stream: "
                      << (error->error_code) << ", " << (error->error_message)
                      << ", " << (error->error_details);
          }
        }
        is_listening_ = true;

        std::unique_ptr<std::vector<uint8_t>> result;
        auto sink = std::make_unique<EventSinkImplementation>(
            messenger, channel_name, codec);
        std::unique_ptr<StreamHandlerError<T>> error =
            shared_handler->OnListen(method_call->arguments(), std::move(sink));
        if (error) {
          result = codec->EncodeErrorEnvelope(
              error->error_code, error->error_message, error->error_details);
        } else {
          result = codec->EncodeSuccessEnvelope();
        }
        reply(result->data(), result->size());
      } else if (method.compare(kOnCancelMethod) == 0) {
        std::unique_ptr<std::vector<uint8_t>> result;
        if (is_listening_) {
          std::unique_ptr<StreamHandlerError<T>> error =
              shared_handler->OnCancel(method_call->arguments());
          if (error) {
            result = codec->EncodeErrorEnvelope(
                error->error_code, error->error_message, error->error_details);
          } else {
            result = codec->EncodeSuccessEnvelope();
          }
          is_listening_ = false;
        } else {
          result = codec->EncodeErrorEnvelope(
              "error", "No active stream to cancel", nullptr);
        }
        reply(result->data(), result->size());
      } else {
        reply(nullptr, 0);
      }
    };
    messenger_->SetMessageHandler(name_, std::move(binary_handler));
  }

 private:
  class EventSinkImplementation : public EventSink<T> {
   public:
    EventSinkImplementation(const BinaryMessenger* messenger,
                            const std::string& name,
                            const MethodCodec<T>* codec)
        : messenger_(messenger), name_(name), codec_(codec) {}
    ~EventSinkImplementation() = default;

    // Prevent copying.
    EventSinkImplementation(EventSinkImplementation const&) = delete;
    EventSinkImplementation& operator=(EventSinkImplementation const&) = delete;

   private:
    const BinaryMessenger* messenger_;
    const std::string name_;
    const MethodCodec<T>* codec_;

   protected:
    void SuccessInternal(const T* event = nullptr) override {
      auto result = codec_->EncodeSuccessEnvelope(event);
      messenger_->Send(name_, result->data(), result->size());
    }

    void ErrorInternal(const std::string& error_code,
                       const std::string& error_message,
                       const T* error_details) override {
      auto result =
          codec_->EncodeErrorEnvelope(error_code, error_message, error_details);
      messenger_->Send(name_, result->data(), result->size());
    }

    void EndOfStreamInternal() override { messenger_->Send(name_, nullptr, 0); }
  };

  BinaryMessenger* messenger_;
  const std::string name_;
  const MethodCodec<T>* codec_;
  bool is_listening_ = false;
};

}  // namespace flutter

#endif  // FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_
