#define CARES_STATICLIB
#include "ares.h"
#include "async-wrap.h"
#include "async-wrap-inl.h"
#include "env.h"
#include "env-inl.h"
#include "node.h"
#include "req-wrap.h"
#include "req-wrap-inl.h"
#include "tree.h"
#include "util.h"
#include "uv.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#if defined(__ANDROID__) || \
    defined(__MINGW32__) || \
    defined(__OpenBSD__) || \
    defined(_MSC_VER)

# include <nameser.h>
#else
# include <arpa/nameser.h>
#endif

#if defined(__OpenBSD__)
# define AI_V4MAPPED 0
#endif

namespace node {
namespace cares_wrap {

using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;


class GetAddrInfoReqWrap : public ReqWrap<uv_getaddrinfo_t> {
 public:
  GetAddrInfoReqWrap(Environment* env, Local<Object> req_wrap_obj);

  size_t self_size() const override { return sizeof(*this); }
};

GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env,
                                       Local<Object> req_wrap_obj)
    : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETADDRINFOREQWRAP) {
  Wrap(req_wrap_obj, this);
}


static void NewGetAddrInfoReqWrap(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
}


class GetNameInfoReqWrap : public ReqWrap<uv_getnameinfo_t> {
 public:
  GetNameInfoReqWrap(Environment* env, Local<Object> req_wrap_obj);

  size_t self_size() const override { return sizeof(*this); }
};

GetNameInfoReqWrap::GetNameInfoReqWrap(Environment* env,
                                       Local<Object> req_wrap_obj)
    : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETNAMEINFOREQWRAP) {
  Wrap(req_wrap_obj, this);
}


static void NewGetNameInfoReqWrap(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
}


static int cmp_ares_tasks(const ares_task_t* a, const ares_task_t* b) {
  if (a->sock < b->sock)
    return -1;
  if (a->sock > b->sock)
    return 1;
  return 0;
}


RB_GENERATE_STATIC(ares_task_list, ares_task_t, node, cmp_ares_tasks)



/* This is called once per second by loop->timer. It is used to constantly */
/* call back into c-ares for possibly processing timeouts. */
static void ares_timeout(uv_timer_t* handle) {
  Environment* env = Environment::from_cares_timer_handle(handle);
  CHECK_EQ(false, RB_EMPTY(env->cares_task_list()));
  ares_process_fd(env->cares_channel(), ARES_SOCKET_BAD, ARES_SOCKET_BAD);
}


static void ares_poll_cb(uv_poll_t* watcher, int status, int events) {
  ares_task_t* task = ContainerOf(&ares_task_t::poll_watcher, watcher);
  Environment* env = task->env;

  /* Reset the idle timer */
  uv_timer_again(env->cares_timer_handle());

  if (status < 0) {
    /* An error happened. Just pretend that the socket is both readable and */
    /* writable. */
    ares_process_fd(env->cares_channel(), task->sock, task->sock);
    return;
  }

  /* Process DNS responses */
  ares_process_fd(env->cares_channel(),
                  events & UV_READABLE ? task->sock : ARES_SOCKET_BAD,
                  events & UV_WRITABLE ? task->sock : ARES_SOCKET_BAD);
}


static void ares_poll_close_cb(uv_handle_t* watcher) {
  ares_task_t* task = ContainerOf(&ares_task_t::poll_watcher,
                                  reinterpret_cast<uv_poll_t*>(watcher));
  free(task);
}


/* Allocates and returns a new ares_task_t */
static ares_task_t* ares_task_create(Environment* env, ares_socket_t sock) {
  ares_task_t* task = static_cast<ares_task_t*>(malloc(sizeof(*task)));

  if (task == nullptr) {
    /* Out of memory. */
    return nullptr;
  }

  task->env = env;
  task->sock = sock;

  if (uv_poll_init_socket(env->event_loop(), &task->poll_watcher, sock) < 0) {
    /* This should never happen. */
    free(task);
    return nullptr;
  }

  return task;
}


/* Callback from ares when socket operation is started */
static void ares_sockstate_cb(void* data,
                              ares_socket_t sock,
                              int read,
                              int write) {
  Environment* env = static_cast<Environment*>(data);
  ares_task_t* task;

  ares_task_t lookup_task;
  lookup_task.sock = sock;
  task = RB_FIND(ares_task_list, env->cares_task_list(), &lookup_task);

  if (read || write) {
    if (!task) {
      /* New socket */

      /* If this is the first socket then start the timer. */
      uv_timer_t* timer_handle = env->cares_timer_handle();
      if (!uv_is_active(reinterpret_cast<uv_handle_t*>(timer_handle))) {
        CHECK(RB_EMPTY(env->cares_task_list()));
        uv_timer_start(timer_handle, ares_timeout, 1000, 1000);
      }

      task = ares_task_create(env, sock);
      if (task == nullptr) {
        /* This should never happen unless we're out of memory or something */
        /* is seriously wrong. The socket won't be polled, but the the query */
        /* will eventually time out. */
        return;
      }

      RB_INSERT(ares_task_list, env->cares_task_list(), task);
    }

    /* This should never fail. If it fails anyway, the query will eventually */
    /* time out. */
    uv_poll_start(&task->poll_watcher,
                  (read ? UV_READABLE : 0) | (write ? UV_WRITABLE : 0),
                  ares_poll_cb);

  } else {
    /* read == 0 and write == 0 this is c-ares's way of notifying us that */
    /* the socket is now closed. We must free the data associated with */
    /* socket. */
    CHECK(task &&
          "When an ares socket is closed we should have a handle for it");

    RB_REMOVE(ares_task_list, env->cares_task_list(), task);
    uv_close(reinterpret_cast<uv_handle_t*>(&task->poll_watcher),
             ares_poll_close_cb);

    if (RB_EMPTY(env->cares_task_list())) {
      uv_timer_stop(env->cares_timer_handle());
    }
  }
}


static Local<Array> HostentToAddresses(Environment* env, struct hostent* host) {
  EscapableHandleScope scope(env->isolate());
  Local<Array> addresses = Array::New(env->isolate());

  char ip[INET6_ADDRSTRLEN];
  for (uint32_t i = 0; host->h_addr_list[i] != nullptr; ++i) {
    uv_inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
    Local<String> address = OneByteString(env->isolate(), ip);
    addresses->Set(i, address);
  }

  return scope.Escape(addresses);
}


static Local<Array> HostentToNames(Environment* env, struct hostent* host) {
  EscapableHandleScope scope(env->isolate());
  Local<Array> names = Array::New(env->isolate());

  for (uint32_t i = 0; host->h_aliases[i] != nullptr; ++i) {
    Local<String> address = OneByteString(env->isolate(), host->h_aliases[i]);
    names->Set(i, address);
  }

  return scope.Escape(names);
}


class QueryWrap : public AsyncWrap {
 public:
  QueryWrap(Environment* env, Local<Object> req_wrap_obj)
      : AsyncWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP) {
    if (env->in_domain())
      req_wrap_obj->Set(env->domain_string(), env->domain_array()->Get(0));
  }

  virtual ~QueryWrap() override {
    CHECK_EQ(false, persistent().IsEmpty());
    persistent().Reset();
  }

  // Subclasses should implement the appropriate Send method.
  virtual int Send(const char* name) {
    UNREACHABLE();
    return 0;
  }

  virtual int Send(const char* name, int family) {
    UNREACHABLE();
    return 0;
  }

 protected:
  void* GetQueryArg() {
    return static_cast<void*>(this);
  }

  static void Callback(void *arg, int status, int timeouts,
      unsigned char* answer_buf, int answer_len) {
    QueryWrap* wrap = static_cast<QueryWrap*>(arg);

    if (status != ARES_SUCCESS) {
      wrap->ParseError(status);
    } else {
      wrap->Parse(answer_buf, answer_len);
    }

    delete wrap;
  }

  static void Callback(void *arg, int status, int timeouts,
      struct hostent* host) {
    QueryWrap* wrap = static_cast<QueryWrap*>(arg);

    if (status != ARES_SUCCESS) {
      wrap->ParseError(status);
    } else {
      wrap->Parse(host);
    }

    delete wrap;
  }

  void CallOnComplete(Local<Value> answer) {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    Local<Value> argv[] = {
      Integer::New(env()->isolate(), 0),
      answer
    };
    MakeCallback(env()->oncomplete_string(), ARRAY_SIZE(argv), argv);
  }

  void CallOnComplete(Local<Value> answer, Local<Value> family) {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    Local<Value> argv[] = {
      Integer::New(env()->isolate(), 0),
      answer,
      family
    };
    MakeCallback(env()->oncomplete_string(), ARRAY_SIZE(argv), argv);
  }

  void ParseError(int status) {
    CHECK_NE(status, ARES_SUCCESS);
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    Local<Value> arg;
    switch (status) {
#define V(code)                                                               \
      case ARES_ ## code:                                                     \
        arg = FIXED_ONE_BYTE_STRING(env()->isolate(), #code);                 \
        break;
      V(ENODATA)
      V(EFORMERR)
      V(ESERVFAIL)
      V(ENOTFOUND)
      V(ENOTIMP)
      V(EREFUSED)
      V(EBADQUERY)
      V(EBADNAME)
      V(EBADFAMILY)
      V(EBADRESP)
      V(ECONNREFUSED)
      V(ETIMEOUT)
      V(EOF)
      V(EFILE)
      V(ENOMEM)
      V(EDESTRUCTION)
      V(EBADSTR)
      V(EBADFLAGS)
      V(ENONAME)
      V(EBADHINTS)
      V(ENOTINITIALIZED)
      V(ELOADIPHLPAPI)
      V(EADDRGETNETWORKPARAMS)
      V(ECANCELLED)
#undef V
      default:
        arg = FIXED_ONE_BYTE_STRING(env()->isolate(), "UNKNOWN_ARES_ERROR");
        break;
    }
    MakeCallback(env()->oncomplete_string(), 1, &arg);
  }

  // Subclasses should implement the appropriate Parse method.
  virtual void Parse(unsigned char* buf, int len) {
    UNREACHABLE();
  };

  virtual void Parse(struct hostent* host) {
    UNREACHABLE();
  };
};


class QueryAWrap: public QueryWrap {
 public:
  QueryAWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_a,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    struct hostent* host;

    int status = ares_parse_a_reply(buf, len, &host, nullptr, nullptr);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> addresses = HostentToAddresses(env(), host);
    ares_free_hostent(host);

    this->CallOnComplete(addresses);
  }
};


class QueryAaaaWrap: public QueryWrap {
 public:
  QueryAaaaWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_aaaa,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    struct hostent* host;

    int status = ares_parse_aaaa_reply(buf, len, &host, nullptr, nullptr);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> addresses = HostentToAddresses(env(), host);
    ares_free_hostent(host);

    this->CallOnComplete(addresses);
  }
};


class QueryCnameWrap: public QueryWrap {
 public:
  QueryCnameWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_cname,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    struct hostent* host;

    int status = ares_parse_a_reply(buf, len, &host, nullptr, nullptr);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    // A cname lookup always returns a single record but we follow the
    // common API here.
    Local<Array> result = Array::New(env()->isolate(), 1);
    result->Set(0, OneByteString(env()->isolate(), host->h_name));
    ares_free_hostent(host);

    this->CallOnComplete(result);
  }
};


class QueryMxWrap: public QueryWrap {
 public:
  QueryMxWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_mx,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    struct ares_mx_reply* mx_start;
    int status = ares_parse_mx_reply(buf, len, &mx_start);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> mx_records = Array::New(env()->isolate());
    Local<String> exchange_symbol = env()->exchange_string();
    Local<String> priority_symbol = env()->priority_string();

    ares_mx_reply* current = mx_start;
    for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
      Local<Object> mx_record = Object::New(env()->isolate());
      mx_record->Set(exchange_symbol,
                     OneByteString(env()->isolate(), current->host));
      mx_record->Set(priority_symbol,
                     Integer::New(env()->isolate(), current->priority));
      mx_records->Set(i, mx_record);
    }

    ares_free_data(mx_start);

    this->CallOnComplete(mx_records);
  }
};


class QueryNsWrap: public QueryWrap {
 public:
  QueryNsWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_ns,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    struct hostent* host;

    int status = ares_parse_ns_reply(buf, len, &host);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> names = HostentToNames(env(), host);
    ares_free_hostent(host);

    this->CallOnComplete(names);
  }
};


class QueryTxtWrap: public QueryWrap {
 public:
  QueryTxtWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_txt,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    struct ares_txt_reply* txt_out;

    int status = ares_parse_txt_reply(buf, len, &txt_out);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> txt_records = Array::New(env()->isolate());
    Local<Array> txt_chunk;

    ares_txt_reply* current = txt_out;
    uint32_t i = 0;
    for (uint32_t j = 0; current != nullptr; current = current->next) {
      Local<String> txt = OneByteString(env()->isolate(), current->txt);
      // New record found - write out the current chunk
      if (current->record_start) {
        if (!txt_chunk.IsEmpty())
          txt_records->Set(i++, txt_chunk);
        txt_chunk = Array::New(env()->isolate());
        j = 0;
      }
      txt_chunk->Set(j++, txt);
    }
    // Push last chunk if it isn't empty
    if (!txt_chunk.IsEmpty())
      txt_records->Set(i, txt_chunk);

    ares_free_data(txt_out);

    this->CallOnComplete(txt_records);
  }
};


class QuerySrvWrap: public QueryWrap {
 public:
  explicit QuerySrvWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_srv,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    struct ares_srv_reply* srv_start;
    int status = ares_parse_srv_reply(buf, len, &srv_start);
    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> srv_records = Array::New(env()->isolate());
    Local<String> name_symbol = env()->name_string();
    Local<String> port_symbol = env()->port_string();
    Local<String> priority_symbol = env()->priority_string();
    Local<String> weight_symbol = env()->weight_string();

    ares_srv_reply* current = srv_start;
    for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
      Local<Object> srv_record = Object::New(env()->isolate());
      srv_record->Set(name_symbol,
                      OneByteString(env()->isolate(), current->host));
      srv_record->Set(port_symbol,
                      Integer::New(env()->isolate(), current->port));
      srv_record->Set(priority_symbol,
                      Integer::New(env()->isolate(), current->priority));
      srv_record->Set(weight_symbol,
                      Integer::New(env()->isolate(), current->weight));
      srv_records->Set(i, srv_record);
    }

    ares_free_data(srv_start);

    this->CallOnComplete(srv_records);
  }
};

class QueryNaptrWrap: public QueryWrap {
 public:
  explicit QueryNaptrWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_naptr,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    ares_naptr_reply* naptr_start;
    int status = ares_parse_naptr_reply(buf, len, &naptr_start);

    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Array> naptr_records = Array::New(env()->isolate());
    Local<String> flags_symbol = env()->flags_string();
    Local<String> service_symbol = env()->service_string();
    Local<String> regexp_symbol = env()->regexp_string();
    Local<String> replacement_symbol = env()->replacement_string();
    Local<String> order_symbol = env()->order_string();
    Local<String> preference_symbol = env()->preference_string();

    ares_naptr_reply* current = naptr_start;
    for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
      Local<Object> naptr_record = Object::New(env()->isolate());
      naptr_record->Set(flags_symbol,
                        OneByteString(env()->isolate(), current->flags));
      naptr_record->Set(service_symbol,
                        OneByteString(env()->isolate(), current->service));
      naptr_record->Set(regexp_symbol,
                        OneByteString(env()->isolate(), current->regexp));
      naptr_record->Set(replacement_symbol,
                        OneByteString(env()->isolate(), current->replacement));
      naptr_record->Set(order_symbol,
                        Integer::New(env()->isolate(), current->order));
      naptr_record->Set(preference_symbol,
                        Integer::New(env()->isolate(), current->preference));
      naptr_records->Set(i, naptr_record);
    }

    ares_free_data(naptr_start);

    this->CallOnComplete(naptr_records);
  }
};


class QuerySoaWrap: public QueryWrap {
 public:
  QuerySoaWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    ares_query(env()->cares_channel(),
               name,
               ns_c_in,
               ns_t_soa,
               Callback,
               GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(unsigned char* buf, int len) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());

    ares_soa_reply* soa_out;
    int status = ares_parse_soa_reply(buf, len, &soa_out);

    if (status != ARES_SUCCESS) {
      ParseError(status);
      return;
    }

    Local<Object> soa_record = Object::New(env()->isolate());

    soa_record->Set(env()->nsname_string(),
                    OneByteString(env()->isolate(), soa_out->nsname));
    soa_record->Set(env()->hostmaster_string(),
                    OneByteString(env()->isolate(), soa_out->hostmaster));
    soa_record->Set(env()->serial_string(),
                    Integer::New(env()->isolate(), soa_out->serial));
    soa_record->Set(env()->refresh_string(),
                    Integer::New(env()->isolate(), soa_out->refresh));
    soa_record->Set(env()->retry_string(),
                    Integer::New(env()->isolate(), soa_out->retry));
    soa_record->Set(env()->expire_string(),
                    Integer::New(env()->isolate(), soa_out->expire));
    soa_record->Set(env()->minttl_string(),
                    Integer::New(env()->isolate(), soa_out->minttl));

    ares_free_data(soa_out);

    this->CallOnComplete(soa_record);
  }
};


class GetHostByAddrWrap: public QueryWrap {
 public:
  explicit GetHostByAddrWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name) override {
    int length, family;
    char address_buffer[sizeof(struct in6_addr)];

    if (uv_inet_pton(AF_INET, name, &address_buffer) == 0) {
      length = sizeof(struct in_addr);
      family = AF_INET;
    } else if (uv_inet_pton(AF_INET6, name, &address_buffer) == 0) {
      length = sizeof(struct in6_addr);
      family = AF_INET6;
    } else {
      return UV_EINVAL;  // So errnoException() reports a proper error.
    }

    ares_gethostbyaddr(env()->cares_channel(),
                       address_buffer,
                       length,
                       family,
                       Callback,
                       GetQueryArg());
    return 0;
  }

  size_t self_size() const override { return sizeof(*this); }

 protected:
  void Parse(struct hostent* host) override {
    HandleScope handle_scope(env()->isolate());
    Context::Scope context_scope(env()->context());
    this->CallOnComplete(HostentToNames(env(), host));
  }
};


class GetHostByNameWrap: public QueryWrap {
 public:
  explicit GetHostByNameWrap(Environment* env, Local<Object> req_wrap_obj)
      : QueryWrap(env, req_wrap_obj) {
  }

  int Send(const char* name, int family) override {
    ares_gethostbyname(env()->cares_channel(),
                       name,
                       family,
                       Callback,
                       GetQueryArg());
    return 0;
  }

 protected:
  void Parse(struct hostent* host) override {
    HandleScope scope(env()->isolate());

    Local<Array> addresses = HostentToAddresses(env(), host);
    Local<Integer> family = Integer::New(env()->isolate(), host->h_addrtype);

    this->CallOnComplete(addresses, family);
  }
};


template <class Wrap>
static void Query(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK_EQ(false, args.IsConstructCall());
  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());

  Local<Object> req_wrap_obj = args[0].As<Object>();
  Local<String> string = args[1].As<String>();
  Wrap* wrap = new Wrap(env, req_wrap_obj);

  node::Utf8Value name(env->isolate(), string);
  int err = wrap->Send(*name);
  if (err)
    delete wrap;

  args.GetReturnValue().Set(err);
}


void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
  GetAddrInfoReqWrap* req_wrap = static_cast<GetAddrInfoReqWrap*>(req->data);
  Environment* env = req_wrap->env();

  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Value> argv[] = {
    Integer::New(env->isolate(), status),
    Null(env->isolate())
  };

  if (status == 0) {
    // Success
    struct addrinfo *address;
    int n = 0;

    // Count the number of responses.
    for (address = res; address; address = address->ai_next) {
      n++;
    }

    // Create the response array.
    Local<Array> results = Array::New(env->isolate(), n);

    char ip[INET6_ADDRSTRLEN];
    const char *addr;

    n = 0;

    // Iterate over the IPv4 responses again this time creating javascript
    // strings for each IP and filling the results array.
    address = res;
    while (address) {
      CHECK_EQ(address->ai_socktype, SOCK_STREAM);

      // Ignore random ai_family types.
      if (address->ai_family == AF_INET) {
        // Juggle pointers
        addr = reinterpret_cast<char*>(&(reinterpret_cast<struct sockaddr_in*>(
            address->ai_addr)->sin_addr));
        int err = uv_inet_ntop(address->ai_family,
                               addr,
                               ip,
                               INET6_ADDRSTRLEN);
        if (err)
          continue;

        // Create JavaScript string
        Local<String> s = OneByteString(env->isolate(), ip);
        results->Set(n, s);
        n++;
      }

      // Increment
      address = address->ai_next;
    }

    // Iterate over the IPv6 responses putting them in the array.
    address = res;
    while (address) {
      CHECK_EQ(address->ai_socktype, SOCK_STREAM);

      // Ignore random ai_family types.
      if (address->ai_family == AF_INET6) {
        // Juggle pointers
        addr = reinterpret_cast<char*>(&(reinterpret_cast<struct sockaddr_in6*>(
            address->ai_addr)->sin6_addr));
        int err = uv_inet_ntop(address->ai_family,
                               addr,
                               ip,
                               INET6_ADDRSTRLEN);
        if (err)
          continue;

        // Create JavaScript string
        Local<String> s = OneByteString(env->isolate(), ip);
        results->Set(n, s);
        n++;
      }

      // Increment
      address = address->ai_next;
    }


    argv[1] = results;
  }

  uv_freeaddrinfo(res);

  // Make the callback into JavaScript
  req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv);

  delete req_wrap;
}


void AfterGetNameInfo(uv_getnameinfo_t* req,
                      int status,
                      const char* hostname,
                      const char* service) {
  GetNameInfoReqWrap* req_wrap = static_cast<GetNameInfoReqWrap*>(req->data);
  Environment* env = req_wrap->env();

  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Value> argv[] = {
    Integer::New(env->isolate(), status),
    Null(env->isolate()),
    Null(env->isolate())
  };

  if (status == 0) {
    // Success
    Local<String> js_hostname = OneByteString(env->isolate(), hostname);
    Local<String> js_service = OneByteString(env->isolate(), service);
    argv[1] = js_hostname;
    argv[2] = js_service;
  }

  // Make the callback into JavaScript
  req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv);

  delete req_wrap;
}


static void IsIP(const FunctionCallbackInfo<Value>& args) {
  node::Utf8Value ip(args.GetIsolate(), args[0]);
  char address_buffer[sizeof(struct in6_addr)];

  int rc = 0;
  if (uv_inet_pton(AF_INET, *ip, &address_buffer) == 0)
    rc = 4;
  else if (uv_inet_pton(AF_INET6, *ip, &address_buffer) == 0)
    rc = 6;

  args.GetReturnValue().Set(rc);
}


static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());
  CHECK(args[2]->IsInt32());
  Local<Object> req_wrap_obj = args[0].As<Object>();
  node::Utf8Value hostname(env->isolate(), args[1]);

  int32_t flags = (args[3]->IsInt32()) ? args[3]->Int32Value() : 0;
  int family;

  switch (args[2]->Int32Value()) {
  case 0:
    family = AF_UNSPEC;
    break;
  case 4:
    family = AF_INET;
    break;
  case 6:
    family = AF_INET6;
    break;
  default:
    CHECK(0 && "bad address family");
    abort();
  }

  GetAddrInfoReqWrap* req_wrap = new GetAddrInfoReqWrap(env, req_wrap_obj);

  struct addrinfo hints;
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = family;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = flags;

  int err = uv_getaddrinfo(env->event_loop(),
                           &req_wrap->req_,
                           AfterGetAddrInfo,
                           *hostname,
                           nullptr,
                           &hints);
  req_wrap->Dispatched();
  if (err)
    delete req_wrap;

  args.GetReturnValue().Set(err);
}


static void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());
  CHECK(args[2]->IsUint32());
  Local<Object> req_wrap_obj = args[0].As<Object>();
  node::Utf8Value ip(env->isolate(), args[1]);
  const unsigned port = args[2]->Uint32Value();
  struct sockaddr_storage addr;

  CHECK(uv_ip4_addr(*ip, port, reinterpret_cast<sockaddr_in*>(&addr)) == 0 ||
        uv_ip6_addr(*ip, port, reinterpret_cast<sockaddr_in6*>(&addr)) == 0);

  GetNameInfoReqWrap* req_wrap = new GetNameInfoReqWrap(env, req_wrap_obj);

  int err = uv_getnameinfo(env->event_loop(),
                           &req_wrap->req_,
                           AfterGetNameInfo,
                           (struct sockaddr*)&addr,
                           NI_NAMEREQD);
  req_wrap->Dispatched();
  if (err)
    delete req_wrap;

  args.GetReturnValue().Set(err);
}


static void GetServers(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  Local<Array> server_array = Array::New(env->isolate());

  ares_addr_node* servers;

  int r = ares_get_servers(env->cares_channel(), &servers);
  CHECK_EQ(r, ARES_SUCCESS);

  ares_addr_node* cur = servers;

  for (uint32_t i = 0; cur != nullptr; ++i, cur = cur->next) {
    char ip[INET6_ADDRSTRLEN];

    const void* caddr = static_cast<const void*>(&cur->addr);
    int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip));
    CHECK_EQ(err, 0);

    Local<String> addr = OneByteString(env->isolate(), ip);
    server_array->Set(i, addr);
  }

  ares_free_data(servers);

  args.GetReturnValue().Set(server_array);
}


static void SetServers(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsArray());

  Local<Array> arr = Local<Array>::Cast(args[0]);

  uint32_t len = arr->Length();

  if (len == 0) {
    int rv = ares_set_servers(env->cares_channel(), nullptr);
    return args.GetReturnValue().Set(rv);
  }

  ares_addr_node* servers = new ares_addr_node[len];
  ares_addr_node* last = nullptr;

  int err;

  for (uint32_t i = 0; i < len; i++) {
    CHECK(arr->Get(i)->IsArray());

    Local<Array> elm = Local<Array>::Cast(arr->Get(i));

    CHECK(elm->Get(0)->Int32Value());
    CHECK(elm->Get(1)->IsString());

    int fam = elm->Get(0)->Int32Value();
    node::Utf8Value ip(env->isolate(), elm->Get(1));

    ares_addr_node* cur = &servers[i];

    switch (fam) {
      case 4:
        cur->family = AF_INET;
        err = uv_inet_pton(AF_INET, *ip, &cur->addr);
        break;
      case 6:
        cur->family = AF_INET6;
        err = uv_inet_pton(AF_INET6, *ip, &cur->addr);
        break;
      default:
        CHECK(0 && "Bad address family.");
        abort();
    }

    if (err)
      break;

    cur->next = nullptr;

    if (last != nullptr)
      last->next = cur;

    last = cur;
  }

  if (err == 0)
    err = ares_set_servers(env->cares_channel(), &servers[0]);
  else
    err = ARES_EBADSTR;

  delete[] servers;

  args.GetReturnValue().Set(err);
}


static void StrError(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  const char* errmsg = ares_strerror(args[0]->Int32Value());
  args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg));
}


static void CaresTimerCloseCb(uv_handle_t* handle) {
  Environment* env = Environment::from_cares_timer_handle(
      reinterpret_cast<uv_timer_t*>(handle));
  env->FinishHandleCleanup(handle);
}


static void CaresTimerClose(Environment* env,
                            uv_handle_t* handle,
                            void* arg) {
  uv_close(handle, CaresTimerCloseCb);
}


static void Initialize(Local<Object> target,
                       Local<Value> unused,
                       Local<Context> context) {
  Environment* env = Environment::GetCurrent(context);

  int r = ares_library_init(ARES_LIB_INIT_ALL);
  CHECK_EQ(r, ARES_SUCCESS);

  struct ares_options options;
  memset(&options, 0, sizeof(options));
  options.flags = ARES_FLAG_NOCHECKRESP;
  options.sock_state_cb = ares_sockstate_cb;
  options.sock_state_cb_data = env;

  /* We do the call to ares_init_option for caller. */
  r = ares_init_options(env->cares_channel_ptr(),
                        &options,
                        ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB);
  CHECK_EQ(r, ARES_SUCCESS);

  /* Initialize the timeout timer. The timer won't be started until the */
  /* first socket is opened. */
  uv_timer_init(env->event_loop(), env->cares_timer_handle());
  env->RegisterHandleCleanup(
      reinterpret_cast<uv_handle_t*>(env->cares_timer_handle()),
      CaresTimerClose,
      nullptr);

  env->SetMethod(target, "queryA", Query<QueryAWrap>);
  env->SetMethod(target, "queryAaaa", Query<QueryAaaaWrap>);
  env->SetMethod(target, "queryCname", Query<QueryCnameWrap>);
  env->SetMethod(target, "queryMx", Query<QueryMxWrap>);
  env->SetMethod(target, "queryNs", Query<QueryNsWrap>);
  env->SetMethod(target, "queryTxt", Query<QueryTxtWrap>);
  env->SetMethod(target, "querySrv", Query<QuerySrvWrap>);
  env->SetMethod(target, "queryNaptr", Query<QueryNaptrWrap>);
  env->SetMethod(target, "querySoa", Query<QuerySoaWrap>);
  env->SetMethod(target, "getHostByAddr", Query<GetHostByAddrWrap>);

  env->SetMethod(target, "getaddrinfo", GetAddrInfo);
  env->SetMethod(target, "getnameinfo", GetNameInfo);
  env->SetMethod(target, "isIP", IsIP);

  env->SetMethod(target, "strerror", StrError);
  env->SetMethod(target, "getServers", GetServers);
  env->SetMethod(target, "setServers", SetServers);

  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_INET"),
              Integer::New(env->isolate(), AF_INET));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_INET6"),
              Integer::New(env->isolate(), AF_INET6));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_UNSPEC"),
              Integer::New(env->isolate(), AF_UNSPEC));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_ADDRCONFIG"),
              Integer::New(env->isolate(), AI_ADDRCONFIG));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_V4MAPPED"),
              Integer::New(env->isolate(), AI_V4MAPPED));

  Local<FunctionTemplate> aiw =
      FunctionTemplate::New(env->isolate(), NewGetAddrInfoReqWrap);
  aiw->InstanceTemplate()->SetInternalFieldCount(1);
  aiw->SetClassName(
      FIXED_ONE_BYTE_STRING(env->isolate(), "GetAddrInfoReqWrap"));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "GetAddrInfoReqWrap"),
              aiw->GetFunction());

  Local<FunctionTemplate> niw =
      FunctionTemplate::New(env->isolate(), NewGetNameInfoReqWrap);
  niw->InstanceTemplate()->SetInternalFieldCount(1);
  niw->SetClassName(
      FIXED_ONE_BYTE_STRING(env->isolate(), "GetNameInfoReqWrap"));
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "GetNameInfoReqWrap"),
              niw->GetFunction());
}

}  // namespace cares_wrap
}  // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(cares_wrap, node::cares_wrap::Initialize)
