/*
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.yc.netlib.stetho;



import androidx.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;

/**
 * Interface that callers must invoke in order to supply data to the Network tab in
 * the WebKit Inspector.  For HTTP specific traffic, the following call flow must be met:
 *
 * <pre>
 * requestWillBeSent +---> responseHeadersReceived +---> interpretResponseStream
 *                   |           |                 |
 *                   |           `---> dataSent    |
 *                   |                             |
 *                   `-----------------------------`--------> httpExchangeFailed
 * </pre>
 *
 * <p>Note that {@link #interpretResponseStream} combined with {@link DefaultResponseHandler}
 * will automatically invoke {@link #dataReceived}, {@link #responseReadFailed} and
 * {@link #responseReadFinished}.  If you use your own custom {@link ResponseHandler} you
 * must be sure to invoke these methods manually.</p>
 *
 * <p>For arbitrary sockets or explicitly for WebSockets, the following call flow must be met:</p>
 *
 * <pre>
 * webSocketCreated +---> webSocketWillSendHandshakeRequest ----> webSocketHandshakeResponseReceived
 *                  |                                                              |
 *                  |                     ,----------------------+-----------------+--------,
 *                  |                     v                      v                          |
 *                  +---------> [ webSocketFrameSent | webSocketFrameReceived ] ---,        |
 *                  |                     ^                      ^                 |        |
 *                  |                     `----------------------+-----------------+        |
 *                  |                                                              |        |
 *                  `---------> webSocketClosed <----------------------------------'        |
 *                                     ^                                                    |
 *                                     `----------------------------------------------------'
 * </pre>
 *
 * <p>Explicitly worth nothing is that regular sockets in an Android app can be treated as
 * WebSockets for the purpose of arbitrary socket inspection and can skip
 * {@link #webSocketWillSendHandshakeRequest} and {@link #webSocketHandshakeResponseReceived}
 * which are only used for the WebSocket-specific HTTP upgrade.</p>
 */
public interface NetworkEventReporter {
  /**
   * Returns true if there is at least one peer listening for network events; false otherwise.
   * This value is provided as an optimization to avoid expensive work when the WebKit Inspector is
   * not being used.  It is otherwise safe to invoke methods defined in this interface when
   * the value is false.
   */
  boolean isEnabled();

  /**
   * Indicates that a request is about to be sent, but has not yet been delivered over the wire.
   *
   * @param request Request descriptor.
   */
  void requestWillBeSent(InspectorRequest request);

  /**
   * Indicates that a response message was just received from the network, but the body
   * has not yet been read.
   *
   * @param response Response descriptor.
   */
  void responseHeadersReceived(InspectorResponse response);

 /**
  * Indicates that communication with the server has failed. You are expected to call this for any
  * exception before you call {@link #interpretResponseStream}. After
  * {@link #interpretResponseStream} is called we will reporting any
  * {@link IOException} during reading from the {@link InputStream}.
  *
  * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}
  * @param errorText Text to report for the error; using {@link IOException#toString()} is
  *     recommended.
   */
  void httpExchangeFailed(String requestId, String errorText);

  /**
   * Intercept the stream as given by the underlying HTTP library that contains the body of the
   * response. In order to have the response show up in inspector (and to have the request be
   * completed successfully) you need to call this AND read until exhaustion/EOF of the returned
   * stream.
   *
   * <p>
   * We will internally signal a failure if there is an {@link IOException} received while reading
   * from the stream.
   *
   * <p>
   * Do not invoke {@link #httpExchangeFailed(String, String)} after calling this method.
   *
   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}
   * @param contentType The {@code Content-Type} header value that was specified in
   *     {@link InspectorResponse}.  This header is used to determine the appropriate
   *     storage format for the body.  For instance, {@code image/*} is necessary to cause
   *     images to appear in the Inspector UI.
   * @param contentEncoding The {@code Content-Encoding} header value that was specified in
   *     {@link InspectorResponse}.  This header is used to determine what type of decompression
   *     is to be applied when delivering the raw response stream to the debugging interface.
   *     If null, no decompression will be used.
   * @param inputStream Response stream if applicable ("HEAD" for instance does not have a body).
   *     {@code null} otherwise.
   * @param responseHandler Callback to forward stream events back to the relevant event reporter
   *     methods.  Recommend using {@link DefaultResponseHandler} for most callers.
   *
   * @return {@link InputStream} that has been intercepted if WebkitInspector is active and enabled
   *     otherwise it will return {@code inputStream}
   */
  @Nullable
  InputStream interpretResponseStream(
          String requestId,
          @Nullable String contentType,
          @Nullable String contentEncoding,
          @Nullable InputStream inputStream,
          ResponseHandler responseHandler);

  /**
   * Indicates that there was a failure while reading from response stream.  If you use
   * {@link #interpretResponseStream} with {@link DefaultResponseHandler} (as is recommended),
   * this method will be invoked automatically for you.
   *
   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}
   * @param errorText Text to report for the error; using {@link IOException#toString()} is
   *     recommended.
   */
  void responseReadFailed(String requestId, String errorText);

  /**
   * Indicates that the response stream has been fully exhausted and the request is now
   * complete.  If you use {@link #interpretResponseStream} with {@link DefaultResponseHandler}
   * (as is recommended), this method will be invoked automatically for you.
   *
   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}
   */
  void responseReadFinished(String requestId);

  /**
   * Indicates that raw data was sent over the network.  It is permissible to invoke this
   * method just once after the full size of the request is known.
   * <p>
   * Invoking this method is optional and merely provides additional timing metrics and actual
   * payload sizes to the Inspector UI.
   *
   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}
   * @param dataLength Uncompressed data segment length
   * @param encodedDataLength Compressed data segment length
   */
  void dataSent(String requestId, int dataLength, int encodedDataLength);

  /**
   * Indicates that raw data was received from the network.
   *
   * @see #dataSent
   */
  void dataReceived(String requestId, int dataLength, int encodedDataLength);

  /**
   * Provides unique request id for {@link InspectorRequest#id()}.
   */
  String nextRequestId();

  /**
   * Invoked when a socket is created and implicitly being connected (but not necessarily connected
   * yet).  If a websocket is being used, proceed to {@link #webSocketWillSendHandshakeRequest}.
   * Otherwise you may proceed next to {@code webSocketFrame*} methods.
   */
  void webSocketCreated(String requestId, String url);

  /**
   * Socket has been closed for unknown reasons.  Consider first invoking
   * {@link #webSocketFrameError} even for standard sockets to provide context.
   */
  void webSocketClosed(String requestId);

  /**
   * Invoked specifically for websockets to communicate the WebSocket upgrade messages.  Not
   * necessary to call for standard sockets.
   */
  void webSocketWillSendHandshakeRequest(InspectorWebSocketRequest request);

  /**
   * Delivers the reply from the peer in response to the WebSocket upgrade request.
   */
  void webSocketHandshakeResponseReceived(InspectorWebSocketResponse response);

  /**
   * Send a "websocket" frame from our app to the remote peer.  Standard sockets can simply emulate
   * this by capturing each socket send as a frame of either
   * {@link InspectorWebSocketFrame#OPCODE_BINARY} or
   * {@link InspectorWebSocketFrame#OPCODE_TEXT}.  Note that binary
   * payloads are not visualized in Chrome but can be sent by simply assuming the data is UTF-8
   * encoded (yes, really:
   * https://chromium.googlesource.com/chromium/blink/+/master/Source/core/inspector/InspectorResourceAgent.cpp#850).
   */
  void webSocketFrameSent(InspectorWebSocketFrame frame);

  /**
   * The receive side of {@link #webSocketFrameSent}.
   */
  void webSocketFrameReceived(InspectorWebSocketFrame frame);

  /**
   * Indicates a web socket (or standard socket) error has occurred though this doesn't
   * explicitly close the socket (see {@link #webSocketClosed}) but it does let the UI
   * know that it should denote the closure as forceful or as a failure in some way.
   */
  void webSocketFrameError(String requestId, String errorMessage);

  /**
   * Represents the request that will be sent over HTTP.  Note that for many implementations
   * of HTTP the request constructed may differ from the request actually sent over the wire.
   * For instance, additional headers like {@code Host}, {@code User-Agent}, {@code Content-Type},
   * etc may not be part of this request but should be injected if necessary.  Some stacks offer
   * inspection of the raw request about to be sent to the server which is preferable.
   */
  interface InspectorRequest extends InspectorRequestCommon {
    /**
     * Provide an extra integer to decorate the {@link #friendlyName()}.  This shows up next to
     * it in the WebKit Inspector UI and can be used to indicate things like request priority.
     */
    @Nullable
    Integer friendlyNameExtra();

    String url();

    /**
     * HTTP method ("GET", "POST", "DELETE", etc).
     */
    String method();

    /**
     * Provide the body if part of an entity-enclosing request (like "POST" or "PUT").  May
     * return null otherwise.
     */
    @Nullable
    byte[] body() throws IOException;
  }

  interface InspectorResponse extends InspectorResponseCommon {
    String url();

    /**
     * True if the response was furnished on a re-used socket; false otherwise or if unknown.
     */
    boolean connectionReused();

    /**
     * Unique connection identifier representing the socket that was used to furnish the response.
     */
    int connectionId();

    /**
     * True if the response was furnished by disk cache; false otherwise or if unknown.
     */
    boolean fromDiskCache();
  }

  interface InspectorWebSocketRequest extends InspectorRequestCommon {
  }

  interface InspectorWebSocketResponse extends InspectorResponseCommon {
    /**
     * Optional and redundant set of headers from {@link InspectorWebSocketRequest} that are for
     * some mysterious reason are included here in the response by Chrome.
     */
    @Nullable
    InspectorHeaders requestHeaders();
  }

  interface InspectorWebSocketFrame {
    String requestId();

    int OPCODE_CONTINUATION = 0;
    int OPCODE_TEXT = 1;
    int OPCODE_BINARY = 2;
    int OPCODE_CONNECTION_CLOSE = 8;
    int OPCODE_PING = 9;
    int OPCODE_PONG = 10;

    int opcode();
    boolean mask();
    String payloadData();
  }

  interface InspectorRequestCommon extends InspectorHeaders {
    /**
     * Unique identifier for this request.  This identifier must be used in all other network
     * events corresponding to this request.  Identifiers may be re-used for HTTP requests  or
     * WebSockets that have exhuasted the state machine to its final closed/finished state.
     */
    String id();

    /**
     * Arbitrary debug-friendly name of the request.
     */
    String friendlyName();
  }

  interface InspectorResponseCommon extends InspectorHeaders {
    /** @see InspectorRequest#id() */
    String requestId();

    int statusCode();

    String reasonPhrase();
  }

  interface InspectorHeaders {
    int headerCount();
    String headerName(int index);
    String headerValue(int index);

    @Nullable
    String firstHeaderValue(String name);
  }
}
