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

#pragma once

#include <map>

#include "swoc/IntrusiveDList.h"

#include "iocore/eventsystem/EventSystem.h"
#include "iocore/eventsystem/Event.h"
#include "iocore/eventsystem/IOBuffer.h"
#include "tscore/Arena.h"
#include "proxy/hdrs/MIME.h"
#include "proxy/hdrs/HTTP.h"
#include "proxy/hdrs/XPACK.h"
#include "iocore/net/quic/QUICApplication.h"
#include "iocore/net/quic/QUICStreamVCAdapter.h"
#include "iocore/net/quic/QUICConnection.h"

class HTTPHdr;

enum {
  QPACK_EVENT_DECODE_COMPLETE = QPACK_EVENT_EVENTS_START,
  QPACK_EVENT_DECODE_FAILED,
};

class QPACK : public QUICApplication
{
public:
  QPACK(QUICConnection *qc, uint32_t max_field_section_size, uint16_t max_table_size, uint16_t max_blocking_streams);
  virtual ~QPACK();

  void on_stream_open(QUICStream &stream) override;
  void on_stream_close(QUICStream &stream) override;

  int event_handler(int event, Event *data);

  /*
   * header_block must have enough size to store all headers in header_set.
   * The maximum size can be estimated with QPACK::estimate_header_block_size().
   */
  int encode(uint64_t stream_id, HTTPHdr &header_set, MIOBuffer *header_block, uint64_t &header_block_len);

  /*
   * This will emit either of two events below:
   * - QPACK_EVENT_DECODE_COMPLETE (Data: *HTTPHdr)
   * - QPACK_EVENT_DECODE_FAILED (Data: nullptr)
   */
  int decode(uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr, Continuation *cont,
             EThread *thread = this_ethread());

  int cancel(uint64_t stream_id);

  void set_encoder_stream(QUICStreamId id);
  void set_decoder_stream(QUICStreamId id);

  void update_max_field_section_size(uint32_t max_field_section_size);
  void update_max_table_size(uint16_t max_table_size);
  void update_max_blocking_streams(uint16_t max_blocking_streams);

  static size_t estimate_header_block_size(const HTTPHdr &header_set);

private:
  struct Header {
    Header(const char *n, const char *v) : name(n), value(v), name_len(strlen(name)), value_len(strlen(value)) {}
    const char  *name;
    const char  *value;
    const size_t name_len;
    const size_t value_len;
  };

  class StaticTable
  {
  public:
    static const XpackLookupResult lookup(uint16_t index, const char **name, size_t *name_len, const char **value,
                                          size_t *value_len);
    static const XpackLookupResult lookup(const char *name, size_t name_len, const char *value, size_t value_len);

  private:
    static const Header STATIC_HEADER_FIELDS[];
  };

  class DecodeRequest
  {
  public:
    DecodeRequest(uint16_t largest_reference, EThread *thread, Continuation *continuation, uint64_t stream_id,
                  const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr)
      : _largest_reference(largest_reference),
        _thread(thread),
        _continuation(continuation),
        _stream_id(stream_id),
        _header_block(header_block),
        _header_block_len(header_block_len),
        _hdr(hdr)
    {
    }

    uint16_t
    largest_reference() const
    {
      return this->_largest_reference;
    }

    EThread *
    thread()
    {
      return this->_thread;
    }

    Continuation *
    continuation()
    {
      return this->_continuation;
    }

    uint64_t
    stream_id() const
    {
      return this->_stream_id;
    }

    const uint8_t *
    header_block() const
    {
      return this->_header_block;
    }

    size_t
    header_block_len() const
    {
      return this->_header_block_len;
    }

    HTTPHdr &
    hdr()
    {
      return this->_hdr;
    }

    class Linkage
    {
    public:
      static DecodeRequest *&
      next_ptr(DecodeRequest *t)
      {
        return *reinterpret_cast<DecodeRequest **>(&t->_next);
      }
      static DecodeRequest *&
      prev_ptr(DecodeRequest *t)
      {
        return *reinterpret_cast<DecodeRequest **>(&t->_prev);
      }
    };

  private:
    uint16_t       _largest_reference;
    EThread       *_thread;
    Continuation  *_continuation;
    uint64_t       _stream_id;
    const uint8_t *_header_block;
    size_t         _header_block_len;
    HTTPHdr       &_hdr;

    // For IntrusiveDList support
    DecodeRequest *_next = nullptr;
    DecodeRequest *_prev = nullptr;
  };

  struct EntryReference {
    uint16_t smallest;
    uint16_t largest;
  };

  XpackDynamicTable                         _dynamic_table;
  std::map<uint64_t, struct EntryReference> _references;
  uint32_t                                  _max_field_section_size = 0;
  uint16_t                                  _max_table_size         = 0;
  uint16_t                                  _max_blocking_streams   = 0;

  Continuation *_event_handler = nullptr;
  void          _resume_decode();
  void          _abort_decode();

  bool _invalid = false;

  swoc::IntrusiveDList<DecodeRequest::Linkage> _blocked_list;
  bool                                         _add_to_blocked_list(DecodeRequest *decode_request);

  uint16_t _largest_known_received_index = 0;
  void     _update_largest_known_received_index_by_insert_count(uint16_t insert_count);
  void     _update_largest_known_received_index_by_stream_id(uint64_t stream_id);

  void _update_reference_counts(uint64_t stream_id);

  // Encoder Stream
  int _read_insert_with_name_ref(IOBufferReader &reader, bool &is_static, uint16_t &index, Arena &arena, char **value,
                                 size_t &value_len);
  int _read_insert_without_name_ref(IOBufferReader &reader, Arena &arena, char **name, size_t &name_len, char **value,
                                    size_t &value_len);
  int _read_duplicate(IOBufferReader &reader, uint16_t &index);
  int _read_dynamic_table_size_update(IOBufferReader &reader, uint16_t &max_size);
  int _write_insert_with_name_ref(uint16_t index, bool dynamic, const char *value, uint16_t value_len);
  int _write_insert_without_name_ref(const char *name, int name_len, const char *value, uint16_t value_len);
  int _write_duplicate(uint16_t index);
  int _write_dynamic_table_size_update(uint16_t max_size);

  // Decoder Stream
  int _read_table_state_synchronize(IOBufferReader &reader, uint16_t &insert_count);
  int _read_header_acknowledgement(IOBufferReader &reader, uint64_t &stream_id);
  int _read_stream_cancellation(IOBufferReader &reader, uint64_t &stream_id);
  int _write_table_state_synchronize(uint16_t insert_count);
  int _write_header_acknowledgement(uint64_t stream_id);
  int _write_stream_cancellation(uint64_t stream_id);

  // Request and Push Streams
  int _encode_prefix(uint16_t largest_reference, uint16_t base_index, IOBufferBlock *prefix);
  int _encode_header(const MIMEField &field, uint16_t base_index, IOBufferBlock *compressed_header, uint16_t &referred_index);
  int _encode_indexed_header_field(uint16_t index, uint16_t base_index, bool dynamic_table, IOBufferBlock *compressed_header);
  int _encode_indexed_header_field_with_postbase_index(uint16_t index, uint16_t base_index, bool never_index,
                                                       IOBufferBlock *compressed_header);
  int _encode_literal_header_field_with_name_ref(uint16_t index, bool dynamic_table, uint16_t base_index, const char *value,
                                                 int value_len, bool never_index, IOBufferBlock *compressed_header);
  int _encode_literal_header_field_without_name_ref(const char *name, int name_len, const char *value, int value_len,
                                                    bool never_index, IOBufferBlock *compressed_header);
  int _encode_literal_header_field_with_postbase_name_ref(uint16_t index, uint16_t base_index, const char *value, int value_len,
                                                          bool never_index, IOBufferBlock *compressed_header);

  void _decode(EThread *ethread, Continuation *cont, uint64_t stream_id, const uint8_t *header_block, size_t header_block_len,
               HTTPHdr &hdr);
  int  _decode_header(const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr);
  int  _decode_indexed_header_field(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len);
  int  _decode_indexed_header_field_with_postbase_index(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr,
                                                        uint32_t &header_len);
  int  _decode_literal_header_field_with_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr,
                                                  uint32_t &header_len);
  int  _decode_literal_header_field_without_name_ref(const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len);
  int  _decode_literal_header_field_with_postbase_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr,
                                                           uint32_t &header_len);

  // Utilities
  uint16_t _calc_absolute_index_from_relative_index(uint16_t base_index, uint16_t relative_index);
  uint16_t _calc_absolute_index_from_postbase_index(uint16_t base_index, uint16_t postbase_index);
  uint16_t _calc_relative_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index);
  uint16_t _calc_postbase_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index);
  void     _attach_header(HTTPHdr &hdr, const char *name, int name_len, const char *value, int value_len, bool never_index);

  int _on_read_ready(VIO *vio);
  int _on_decoder_stream_read_ready(IOBufferReader &reader);
  int _on_encoder_stream_read_ready(IOBufferReader &reader);

  int _on_write_ready(VIO *vio);
  int _on_decoder_write_ready(MIOBuffer &writer);
  int _on_encoder_write_ready(MIOBuffer &writer);

  // Stream numbers
  // FIXME How are these stream ids negotiated? In interop, encoder stream id have to be 0 and decoder stream id must not be used.
  uint64_t _encoder_stream_id = 0;
  uint64_t _decoder_stream_id = 9999;

  // Chain of sending instructions
  MIOBuffer      *_encoder_stream_sending_instructions;
  MIOBuffer      *_decoder_stream_sending_instructions;
  IOBufferReader *_encoder_stream_sending_instructions_reader;
  IOBufferReader *_decoder_stream_sending_instructions_reader;

  // Temporal buffer
  Arena _arena;
};
