/** @file

  PROXY Protocol

  See:  https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

  @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 <tscore/ink_inet.h>
#include <swoc/TextView.h>
#include <unordered_map>
#include <cstdlib>
#include <optional>

enum class ProxyProtocolVersion {
  UNDEFINED,
  V1,
  V2,
};

enum class ProxyProtocolData {
  UNDEFINED,
  SRC,
  DST,
};

constexpr uint8_t PP2_TYPE_ALPN           = 0x01;
constexpr uint8_t PP2_TYPE_AUTHORITY      = 0x02;
constexpr uint8_t PP2_TYPE_CRC32C         = 0x03;
constexpr uint8_t PP2_TYPE_NOOP           = 0x04;
constexpr uint8_t PP2_TYPE_UNIQUE_ID      = 0x05;
constexpr uint8_t PP2_TYPE_SSL            = 0x20;
constexpr uint8_t PP2_SUBTYPE_SSL_VERSION = 0x21;
constexpr uint8_t PP2_SUBTYPE_SSL_CN      = 0x22;
constexpr uint8_t PP2_SUBTYPE_SSL_CIPHER  = 0x23;
constexpr uint8_t PP2_SUBTYPE_SSL_SIG_ALG = 0x24;
constexpr uint8_t PP2_SUBTYPE_SSL_KEY_ALG = 0x25;
constexpr uint8_t PP2_TYPE_NETNS          = 0x30;

class ProxyProtocol
{
public:
  ProxyProtocol() {}
  ProxyProtocol(ProxyProtocolVersion pp_ver, uint16_t family, IpEndpoint src, IpEndpoint dst)
    : version(pp_ver), ip_family(family), src_addr(src), dst_addr(dst)
  {
  }
  ProxyProtocol(const ProxyProtocol &other)
    : version(other.version), ip_family(other.ip_family), src_addr(other.src_addr), dst_addr(other.dst_addr)
  {
    if (!other.additional_data.empty()) {
      set_additional_data(other.additional_data);
    }
  }
  ProxyProtocol(ProxyProtocol &&other)
    : version(other.version), ip_family(other.ip_family), src_addr(other.src_addr), dst_addr(other.dst_addr)
  {
    if (!other.additional_data.empty()) {
      set_additional_data(other.additional_data);
    }
    other.additional_data.clear();
    other.tlv.clear();
  }
  ~ProxyProtocol() = default;

  /** Release owned heap and reset to the default-constructed state.
   *
   * Swap rather than clear() the containers: clear() retains capacity, which would be abandoned
   * (leaked) when the slot is reused, since the NetVConnection allocators are Destruct_on_free=false
   * and so never run ~ProxyProtocol.
   */
  void
  reset()
  {
    std::string{}.swap(additional_data);
    std::unordered_map<uint8_t, std::string_view>{}.swap(tlv);
    version   = ProxyProtocolVersion::UNDEFINED;
    ip_family = AF_UNSPEC;
    type      = 0;
    src_addr  = {};
    dst_addr  = {};
  }

  int  set_additional_data(std::string_view data);
  void set_ipv4_addrs(in_addr_t src_addr, uint16_t src_port, in_addr_t dst_addr, uint16_t dst_port);
  void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port);

  std::optional<std::string_view> get_tlv(const uint8_t tlvCode) const;

  ProxyProtocolVersion                          version   = ProxyProtocolVersion::UNDEFINED;
  uint16_t                                      ip_family = AF_UNSPEC;
  int                                           type      = 0;
  IpEndpoint                                    src_addr  = {};
  IpEndpoint                                    dst_addr  = {};
  std::unordered_map<uint8_t, std::string_view> tlv;

  ProxyProtocol &
  operator=(const ProxyProtocol &other)
  {
    if (&other == this) {
      return *this;
    }
    version   = other.version;
    ip_family = other.ip_family;
    src_addr  = other.src_addr;
    dst_addr  = other.dst_addr;
    if (!other.additional_data.empty()) {
      set_additional_data(other.additional_data);
    } else {
      additional_data.clear();
      tlv.clear();
    }
    return *this;
  }

  ProxyProtocol &
  operator=(ProxyProtocol &&other)
  {
    version   = other.version;
    ip_family = other.ip_family;
    src_addr  = other.src_addr;
    dst_addr  = other.dst_addr;

    additional_data.clear();
    tlv.clear();

    if (!other.additional_data.empty()) {
      set_additional_data(other.additional_data);
    }
    other.additional_data.clear();
    other.tlv.clear();
    return *this;
  }

private:
  std::string additional_data;
};

const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108;
const size_t PPv2_CONNECTION_HEADER_LEN     = 16;

extern size_t               proxy_protocol_parse(ProxyProtocol *pp_info, swoc::TextView tv);
extern size_t               proxy_protocol_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &pp_info,
                                                 ProxyProtocolVersion force_version = ProxyProtocolVersion::UNDEFINED);
extern ProxyProtocolVersion proxy_protocol_version_cast(int i);
