/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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.
 */

/***************************************************************************
 *
 *  FetchSM.h header file for Fetch Page
 *
 ****************************************************************************/

#pragma once

#include "proxy/http/HttpSM.h"
#include "proxy/http/HttpTunnel.h"

class PluginVC;

class FetchSM : public Continuation
{
public:
  FetchSM() {}

  /** Indicate whether FetchSM dependencies have been initialized by ATS.
   * @return True if FetchSM dependencies have been initialized, false otherwise.
   */
  static bool is_initialized();

  void
  init_comm()
  {
    cont_mutex.clear();
    req_buffer  = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX);
    req_reader  = req_buffer->alloc_reader();
    resp_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_32K);
    resp_reader = resp_buffer->alloc_reader();
    http_parser_init(&http_parser);
    client_response_hdr.create(HTTPType::RESPONSE);
    SET_HANDLER(&FetchSM::fetch_handler);
  }

  void
  init(Continuation *cont, TSFetchWakeUpOptions options, TSFetchEvent events, const char *headers, int length, sockaddr const *addr)
  {
    static DbgCtl dbg_ctl{"FetchSM"};
    Dbg(dbg_ctl, "[%s] FetchSM initialized for request with headers\n--\n%.*s\n--", __FUNCTION__, length, headers);
    init_comm();
    contp            = cont;
    callback_events  = events;
    callback_options = options;
    _addr.assign(addr);
    writeRequest(headers, length);
    mutex = new_ProxyMutex();

    //
    // We had dropped response_buffer/response_reader to avoid unnecessary
    // memory copying. But for the original TSFetchURL() API, PluginVC may
    // stop adding data to resp_buffer when the pending data in resp_buffer
    // reach its water_mark.
    //
    // So we should set the water_mark of resp_buffer with a large value,
    // INT64_MAX would be reasonable.
    resp_buffer->water_mark = INT64_MAX;
  }

  void
  set_fetch_flags(int flags)
  {
    fetch_flags = flags;
  }

  int   fetch_handler(int event, void *data);
  void  process_fetch_read(int event);
  void  process_fetch_write(int event);
  void  httpConnect();
  void  cleanUp();
  void  get_info_from_buffer(IOBufferReader *reader);
  char *resp_get(int *length);

  TSMBuffer resp_hdr_bufp();
  TSMLoc    resp_hdr_mloc();

  //
  // Extended APIs for FetchSM
  //
  // *flags* can be bitwise OR of several TSFetchFlags
  //
  void    ext_init(Continuation *cont, const char *method, const char *url, const char *version, const sockaddr *client_addr,
                   int flags);
  void    ext_add_header(const char *name, int name_len, const char *value, int value_len);
  void    ext_launch();
  void    ext_destroy();
  ssize_t ext_read_data(char *buf, size_t len);
  void    ext_write_data(const void *data, size_t len);
  void    ext_set_user_data(void *data);
  void   *ext_get_user_data();
  bool
  get_internal_request()
  {
    return is_internal_request;
  }
  void
  set_internal_request(bool val)
  {
    is_internal_request = val;
  }

private:
  int  InvokePlugin(int event, void *data);
  void InvokePluginExt(int error_event = 0);

  void
  writeRequest(const char *headers, int length)
  {
    if (length == -1)
      req_buffer->write(headers, strlen(headers));
    else
      req_buffer->write(headers, length);
  }

  int64_t
  getReqLen() const
  {
    return req_reader->read_avail();
  }
  /// Check if the comma supporting MIME field @a name has @a value in it.
  bool check_for_field_value(const char *name, size_t name_len, char const *value, size_t value_len);

  bool has_body();
  bool check_body_done();
  bool check_chunked();
  bool check_connection_close();
  int  dechunk_body();

  int                  recursion       = 0;
  PluginVC            *http_vc         = nullptr;
  VIO                 *read_vio        = nullptr;
  VIO                 *write_vio       = nullptr;
  MIOBuffer           *req_buffer      = nullptr;
  IOBufferReader      *req_reader      = nullptr;
  char                *client_response = nullptr;
  int                  client_bytes    = 0;
  MIOBuffer           *resp_buffer     = nullptr; // response to HttpConnect Call
  IOBufferReader      *resp_reader     = nullptr;
  Continuation        *contp           = nullptr;
  Ptr<ProxyMutex>      cont_mutex;
  HTTPParser           http_parser;
  HTTPHdr              client_response_hdr;
  ChunkedHandler       chunked_handler;
  TSFetchEvent         callback_events{};
  TSFetchWakeUpOptions callback_options    = NO_CALLBACK;
  bool                 req_finished        = false;
  bool                 header_done         = false;
  bool                 is_method_head      = false;
  bool                 is_internal_request = true;
  bool                 destroyed           = false;
  IpEndpoint           _addr;
  int                  resp_is_chunked        = -1;
  int                  resp_received_close    = -1;
  int                  fetch_flags            = 0;
  void                *user_data              = nullptr;
  bool                 has_sent_header        = false;
  int64_t              req_content_length     = 0;
  int64_t              resp_content_length    = -1;
  int64_t              resp_received_body_len = 0;
};
