// 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.

package io.flutter.plugin.common;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import java.nio.ByteBuffer;

/**
 * Facility for communicating with Flutter using asynchronous message passing with binary messages.
 * The Flutter Dart code should use <a
 * href="https://api.flutter.dev/flutter/services/BinaryMessages-class.html">BinaryMessages</a> to
 * participate.
 *
 * <p>{@code BinaryMessenger} is expected to be utilized from a single thread throughout the
 * duration of its existence. If created on the main thread, then all invocations should take place
 * on the main thread. If created on a background thread, then all invocations should take place on
 * that background thread.
 *
 * @see BasicMessageChannel , which supports message passing with Strings and semi-structured
 *     messages.
 * @see MethodChannel , which supports communication using asynchronous method invocation.
 * @see EventChannel , which supports communication using event streams.
 */
public interface BinaryMessenger {
  /**
   * An abstraction over the threading policy used to invoke message handlers.
   *
   * <p>These are generated by calling methods like {@link
   * BinaryMessenger#makeBackgroundTaskQueue(TaskQueueOptions)} and can be passed into platform
   * channels' constructors to control the threading policy for handling platform channels'
   * messages.
   */
  public interface TaskQueue {}

  /** Options that control how a TaskQueue should operate and be created. */
  public static class TaskQueueOptions {
    private boolean isSerial = true;

    public boolean getIsSerial() {
      return isSerial;
    }

    /**
     * Setter for `isSerial` property.
     *
     * <p>When this is true all tasks performed by the TaskQueue will be forced to happen serially
     * (one completes before the other begins).
     */
    public TaskQueueOptions setIsSerial(boolean isSerial) {
      this.isSerial = isSerial;
      return this;
    }
  }

  /**
   * Creates a TaskQueue that executes the tasks serially on a background thread.
   *
   * <p>There is no guarantee that the tasks will execute on the same thread, just that execution is
   * serial. This is could be problematic if your code relies on ThreadLocal storage or
   * introspection about what thread is actually executing.
   */
  @UiThread
  default TaskQueue makeBackgroundTaskQueue() {
    return makeBackgroundTaskQueue(new TaskQueueOptions());
  }

  /**
   * Creates a TaskQueue that executes the tasks serially on a background thread.
   *
   * <p>{@link TaskQueueOptions} can be used to configure the task queue to execute tasks
   * concurrently. Doing so can be more performant, though users need to ensure that the task
   * handlers are thread-safe.
   */
  @UiThread
  default TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) {
    // TODO(92582): Remove default implementation when it is safe for Google Flutter users.
    throw new UnsupportedOperationException("makeBackgroundTaskQueue not implemented.");
  }

  /**
   * Sends a binary message to the Flutter application.
   *
   * @param channel the name {@link String} of the logical channel used for the message.
   * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message
   *     bytes between position zero and current position, or null.
   */
  @UiThread
  void send(@NonNull String channel, @Nullable ByteBuffer message);

  /**
   * Sends a binary message to the Flutter application, optionally expecting a reply.
   *
   * <p>Any uncaught exception thrown by the reply callback will be caught and logged.
   *
   * @param channel the name {@link String} of the logical channel used for the message.
   * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message
   *     bytes between position zero and current position, or null.
   * @param callback a {@link BinaryReply} callback invoked when the Flutter application responds to
   *     the message, possibly null.
   */
  @UiThread
  void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback);

  /**
   * Registers a handler to be invoked when the Flutter application sends a message to its host
   * platform.
   *
   * <p>Registration overwrites any previous registration for the same channel name. Use a null
   * handler to deregister.
   *
   * <p>If no handler has been registered for a particular channel, any incoming message on that
   * channel will be handled silently by sending a null reply.
   *
   * @param channel the name {@link String} of the channel.
   * @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null.
   */
  @UiThread
  void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler);

  /**
   * Registers a handler to be invoked when the Flutter application sends a message to its host
   * platform.
   *
   * <p>Registration overwrites any previous registration for the same channel name. Use a null
   * handler to deregister.
   *
   * <p>If no handler has been registered for a particular channel, any incoming message on that
   * channel will be handled silently by sending a null reply.
   *
   * @param channel the name {@link String} of the channel.
   * @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null.
   * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute
   *     the handler. Specifying null means execute on the platform thread.
   */
  @UiThread
  default void setMessageHandler(
      @NonNull String channel,
      @Nullable BinaryMessageHandler handler,
      @Nullable TaskQueue taskQueue) {
    // TODO(92582): Remove default implementation when it is safe for Google Flutter users.
    if (taskQueue != null) {
      throw new UnsupportedOperationException(
          "setMessageHandler called with nonnull taskQueue is not supported.");
    }
    setMessageHandler(channel, handler);
  }

  /**
   * Enables the ability to queue messages received from Dart.
   *
   * <p>This is useful when there are pending channel handler registrations. For example, Dart may
   * be initialized concurrently, and prior to the registration of the channel handlers. This
   * implies that Dart may start sending messages while plugins are being registered.
   */
  default void enableBufferingIncomingMessages() {
    throw new UnsupportedOperationException("enableBufferingIncomingMessages not implemented.");
  }

  /**
   * Disables the ability to queue messages received from Dart.
   *
   * <p>This can be used after all pending channel handlers have been registered.
   */
  default void disableBufferingIncomingMessages() {
    throw new UnsupportedOperationException("disableBufferingIncomingMessages not implemented.");
  }

  /** Handler for incoming binary messages from Flutter. */
  interface BinaryMessageHandler {
    /**
     * Handles the specified message.
     *
     * <p>Handler implementations must reply to all incoming messages, by submitting a single reply
     * message to the given {@link BinaryReply}. Failure to do so will result in lingering Flutter
     * reply handlers. The reply may be submitted asynchronously.
     *
     * <p>Any uncaught exception thrown by this method will be caught by the messenger
     * implementation and logged, and a null reply message will be sent back to Flutter.
     *
     * @param message the message {@link ByteBuffer} payload, possibly null.
     * @param reply A {@link BinaryReply} used for submitting a reply back to Flutter.
     */
    @UiThread
    void onMessage(@Nullable ByteBuffer message, @NonNull BinaryReply reply);
  }

  /**
   * Binary message reply callback. Used to submit a reply to an incoming message from Flutter. Also
   * used in the dual capacity to handle a reply received from Flutter after sending a message.
   */
  interface BinaryReply {
    /**
     * Handles the specified reply.
     *
     * @param reply the reply payload, a direct-allocated {@link ByteBuffer} or null. Senders of
     *     outgoing replies must place the reply bytes between position zero and current position.
     *     Reply receivers can read from the buffer directly.
     */
    void reply(@Nullable ByteBuffer reply);
  }
}
