/** @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 "iocore/dns/SRV.h"

#include "iocore/eventsystem/Action.h"
#include "iocore/eventsystem/Continuation.h"
#include "iocore/eventsystem/EThread.h"
#include "iocore/eventsystem/Event.h"
#include "iocore/eventsystem/Processor.h"

#include "tscore/ink_config.h"
#include "tscore/ink_inet.h"
#include "tscore/ink_platform.h"
#include "tscore/ink_resolver.h"
#include "tscore/Version.h"

#include <cstdint>
#include <string_view>

// Events
#define DNS_EVENT_LOOKUP DNS_EVENT_EVENTS_START

const int DOMAIN_SERVICE_PORT = NAMESERVER_PORT;

const int MAX_DNS_REQUEST_LEN  = NS_PACKETSZ;
const int MAX_DNS_RESPONSE_LEN = 65536;
const int DNS_RR_MAX_COUNT     = (MAX_DNS_RESPONSE_LEN - HFIXEDSZ + RRFIXEDSZ - 1) / RRFIXEDSZ;
const int DNS_MAX_ALIASES      = DNS_RR_MAX_COUNT;
const int DNS_MAX_ADDRS        = DNS_RR_MAX_COUNT;
const int DNS_HOSTBUF_SIZE     = MAX_DNS_RESPONSE_LEN;

/**
  All buffering required to handle a DNS receipt. For asynchronous DNS,
  only one HostEntBuf will exist in the system. For synchronous DNS,
  one will exist per call until the user deletes them.

*/
struct HostEnt : RefCountObj {
  struct hostent ent         = {.h_name = nullptr, .h_aliases = nullptr, .h_addrtype = 0, .h_length = 0, .h_addr_list = nullptr};
  uint32_t       ttl         = 0;
  int            packet_size = 0;
  char           buf[MAX_DNS_RESPONSE_LEN]      = {0};
  u_char        *host_aliases[DNS_MAX_ALIASES]  = {nullptr};
  u_char        *h_addr_ptrs[DNS_MAX_ADDRS + 1] = {nullptr};
  u_char         hostbuf[DNS_HOSTBUF_SIZE]      = {0};
  SRVHosts       srv_hosts;
  bool           good = true;
  bool           isNameError();
  void           free() override;
};

extern EventType ET_DNS;

struct DNSHandler;

/** Data for a DNS query.
 * This is either a name for a standard query or an IP address for reverse DNS.
 * Its type should be indicated by other parameters, generally the query type.
 * - T_PTR: IP Address
 * - T_A, T_SRV: Name
 */
union DNSQueryData {
  std::string_view name; ///< Look up a name.
  IpAddr const    *addr; ///< Reverse DNS lookup.

  DNSQueryData(std::string_view tv) : name(tv) {}
  DNSQueryData(IpAddr const *a) : addr(a) {}
};

struct DNSProcessor : public Processor {
  // Public Interface
  //

  /// Options for host name resolution.
  struct Options {
    using self_type = Options; ///< Self reference type.

    /// Query handler to use.
    /// Default: single threaded handler.
    DNSHandler *handler = nullptr;
    /// Query timeout value.
    /// Default: @c DEFAULT_DNS_TIMEOUT (or as set in records.yaml)
    int timeout = 0; ///< Timeout value for request.
    /// Host resolution style.
    /// Default: IPv4, IPv6 ( @c HOST_RES_IPV4 )
    HostResStyle host_res_style = HOST_RES_IPV4;

    /// Default constructor.
    Options();

    /// Set @a handler option.
    /// @return This object.
    self_type &setHandler(DNSHandler *handler);

    /// Set @a timeout option.
    /// @return This object.
    self_type &setTimeout(int timeout);

    /// Set host query @a style option.
    /// @return This object.
    self_type &setHostResStyle(HostResStyle style);

    /// Reset to default constructed values.
    /// @return This object.
    self_type &reset();
  };

  // DNS lookup
  //   calls: cont->handleEvent( DNS_EVENT_LOOKUP, HostEnt *ent) on success
  //          cont->handleEvent( DNS_EVENT_LOOKUP, nullptr) on failure
  // NOTE: the HostEnt *block is freed when the function returns
  //

  // To avoid lifetime issues, the name will be copied out of the view's
  // buffer before the query is scheduled.
  Action *gethostbyname(Continuation *cont, std::string_view name, Options const &opt);
  Action *getSRVbyname(Continuation *cont, std::string_view name, Options const &opt);

  Action *gethostbyaddr(Continuation *cont, IpAddr const *ip, Options const &opt);

  // Processor API
  //
  /* currently dns system uses event threads
   * dont pass any value to the call */
  int start(int no_of_extra_dns_threads = 0, size_t stacksize = DEFAULT_STACKSIZE) override;

  // Open/close a link to a 'named' (done in start())
  //
  void open(sockaddr const *ns = nullptr);

  DNSProcessor();

  // private:
  //
  EThread         *thread  = nullptr;
  DNSHandler      *handler = nullptr;
  ts_imp_res_state l_res;
  IpEndpoint       local_ipv6;
  IpEndpoint       local_ipv4;

  /** Internal implementation for all getXbyY methods.
      For host resolution queries pass @c T_A for @a type. It will be adjusted
      as needed based on @a opt.host_res_style.

      For address resolution ( @a type is @c T_PTR ), @a x should be a
      @c sockaddr cast to  @c char @c const* .
   */
  Action *getby(DNSQueryData x, int type, Continuation *cont, Options const &opt);

  void dns_init();
};

//
// Global data
//
extern DNSProcessor dnsProcessor;

//
// Inline Functions
//

inline Action *
DNSProcessor::getSRVbyname(Continuation *cont, std::string_view name, Options const &opt)
{
  return getby(name, T_SRV, cont, opt);
}

inline Action *
DNSProcessor::gethostbyname(Continuation *cont, std::string_view name, Options const &opt)
{
  return getby(name, T_A, cont, opt);
}

inline Action *
DNSProcessor::gethostbyaddr(Continuation *cont, IpAddr const *addr, Options const &opt)
{
  return getby(addr, T_PTR, cont, opt);
}

inline DNSProcessor::Options::Options() {}

inline DNSProcessor::Options &
DNSProcessor::Options::setHandler(DNSHandler *h)
{
  handler = h;
  return *this;
}

inline DNSProcessor::Options &
DNSProcessor::Options::setTimeout(int t)
{
  timeout = t;
  return *this;
}

inline DNSProcessor::Options &
DNSProcessor::Options::setHostResStyle(HostResStyle style)
{
  host_res_style = style;
  return *this;
}

inline DNSProcessor::Options &
DNSProcessor::Options::reset()
{
  *this = Options();
  return *this;
}

void ink_dns_init(ts::ModuleVersion version);
