/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>

#include "uv.h"
#include "../uv-common.h"
#include "internal.h"


/*
 * Subclass of uv_handle_t. Used for integration of c-ares.
 */
struct uv_ares_action_s {
  UV_HANDLE_FIELDS
  struct uv_req_s ares_req;
  SOCKET sock;
  int read;
  int write;
};


/* memory used per ares_channel */
typedef struct uv_ares_channel_s {
  ares_channel channel;
  int activesockets;
  uv_timer_t pollingtimer;
} uv_ares_channel_t;


/* static data to hold single ares_channel */
static uv_ares_channel_t uv_ares_data = { NULL, 0 };


/* default timeout per socket request if ares does not specify value */
/* use 20 sec */
#define ARES_TIMEOUT_MS            20000


/* thread pool callback when socket is signalled */
static void CALLBACK uv_ares_socksignal_tp(void* parameter, BOOLEAN timerfired) {
  WSANETWORKEVENTS network_events;
  uv_ares_task_t* sockhandle;
  uv_ares_action_t* selhandle;
  uv_req_t* uv_ares_req;

  assert(parameter != NULL);

  if (parameter != NULL) {
    sockhandle = (uv_ares_task_t*)parameter;

    /* clear socket status for this event */
    /* do not fail if error, thread may run after socket close */
    /* The code assumes that c-ares will write all pending data in the callback,
       unless the socket would block. We can clear the state here to avoid unecessary
       signals. */
    WSAEnumNetworkEvents(sockhandle->sock, sockhandle->h_event, &network_events);

    /* setup new handle */
    selhandle = (uv_ares_action_t*)malloc(sizeof(uv_ares_action_t));
    if (selhandle == NULL) {
      uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
    }
    selhandle->type = UV_ARES_EVENT;
    selhandle->close_cb = NULL;
    selhandle->data = sockhandle->data;
    selhandle->sock = sockhandle->sock;
    selhandle->read = (network_events.lNetworkEvents & (FD_READ | FD_CONNECT)) ? 1 : 0;
    selhandle->write = (network_events.lNetworkEvents & (FD_WRITE | FD_CONNECT)) ? 1 : 0;

    uv_ares_req = &selhandle->ares_req;
    uv_req_init(uv_ares_req);
    uv_ares_req->type = UV_ARES_EVENT_REQ;
    uv_ares_req->data = selhandle;

    /* post ares needs to called */
    if (!PostQueuedCompletionStatus(LOOP->iocp,
                                    0,
                                    0,
                                    &uv_ares_req->overlapped)) {
      uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus");
    }
  }
}


/* periodically call ares to check for timeouts */
static void uv_ares_poll(uv_timer_t* handle, int status) {
  if (uv_ares_data.channel != NULL && uv_ares_data.activesockets > 0) {
    ares_process_fd(uv_ares_data.channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
  }
}


/* callback from ares when socket operation is started */
static void uv_ares_sockstate_cb(void *data, ares_socket_t sock, int read, int write) {
  /* look to see if we have a handle for this socket in our list */
  uv_ares_task_t* uv_handle_ares = uv_find_ares_handle(sock);
  uv_ares_channel_t* uv_ares_data_ptr = (uv_ares_channel_t*)data;

  int timeoutms = 0;

  if (read == 0 && write == 0) {
    /* if read and write are 0, cleanup existing data */
    /* The code assumes that c-ares does a callback with read = 0 and write = 0
       when the socket is closed. After we recieve this we stop monitoring the socket. */
    if (uv_handle_ares != NULL) {
      uv_req_t* uv_ares_req;

      uv_handle_ares->h_close_event = CreateEvent(NULL, FALSE, FALSE, NULL);
      /* remove Wait */
      if (uv_handle_ares->h_wait) {
        UnregisterWaitEx(uv_handle_ares->h_wait, uv_handle_ares->h_close_event);
        uv_handle_ares->h_wait = NULL;
      }

      /* detach socket from the event */
      WSAEventSelect(sock, NULL, 0);
      if (uv_handle_ares->h_event != WSA_INVALID_EVENT) {
        WSACloseEvent(uv_handle_ares->h_event);
        uv_handle_ares->h_event = WSA_INVALID_EVENT;
      }
      /* remove handle from list */
      uv_remove_ares_handle(uv_handle_ares);

      /* Post request to cleanup the Task */
      uv_ares_req = &uv_handle_ares->ares_req;
      uv_req_init(uv_ares_req);
      uv_ares_req->type = UV_ARES_CLEANUP_REQ;
      uv_ares_req->data = uv_handle_ares;

      /* post ares done with socket - finish cleanup when all threads done. */
      if (!PostQueuedCompletionStatus(LOOP->iocp,
                                      0,
                                      0,
                                      &uv_ares_req->overlapped)) {
        uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus");
      }
    } else {
      assert(0);
      uv_fatal_error(ERROR_INVALID_DATA, "ares_SockStateCB");
    }
  } else {
    if (uv_handle_ares == NULL) {
      /* setup new handle */
      /* The code assumes that c-ares will call us when it has an open socket.
        We need to call into c-ares when there is something to read,
        or when it becomes writable. */
      uv_handle_ares = (uv_ares_task_t*)malloc(sizeof(uv_ares_task_t));
      if (uv_handle_ares == NULL) {
        uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
      }
      uv_handle_ares->type = UV_ARES_TASK;
      uv_handle_ares->close_cb = NULL;
      uv_handle_ares->data = uv_ares_data_ptr;
      uv_handle_ares->sock = sock;
      uv_handle_ares->h_wait = NULL;
      uv_handle_ares->flags = 0;

      /* create an event to wait on socket signal */
      uv_handle_ares->h_event = WSACreateEvent();
      if (uv_handle_ares->h_event == WSA_INVALID_EVENT) {
        uv_fatal_error(WSAGetLastError(), "WSACreateEvent");
      }

      /* tie event to socket */
      if (SOCKET_ERROR == WSAEventSelect(sock, uv_handle_ares->h_event, FD_READ | FD_WRITE | FD_CONNECT)) {
        uv_fatal_error(WSAGetLastError(), "WSAEventSelect");
      }

      /* add handle to list */
      uv_add_ares_handle(uv_handle_ares);
      uv_ref();

      /*
       * we have a single polling timer for all ares sockets.
       * This is preferred to using ares_timeout. See ares_timeout.c warning.
       * if timer is not running start it, and keep socket count
       */
      if (uv_ares_data_ptr->activesockets == 0) {
        uv_timer_init(&uv_ares_data_ptr->pollingtimer);
        uv_timer_start(&uv_ares_data_ptr->pollingtimer, uv_ares_poll, 1000L, 1000L);
      }
      uv_ares_data_ptr->activesockets++;

      /* specify thread pool function to call when event is signaled */
      if (RegisterWaitForSingleObject(&uv_handle_ares->h_wait,
                                  uv_handle_ares->h_event,
                                  uv_ares_socksignal_tp,
                                  (void*)uv_handle_ares,
                                  INFINITE,
                                  WT_EXECUTEINWAITTHREAD) == 0) {
        uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject");
      }
    } else {
      /* found existing handle.  */
      assert(uv_handle_ares->type == UV_ARES_TASK);
      assert(uv_handle_ares->data != NULL);
      assert(uv_handle_ares->h_event != WSA_INVALID_EVENT);
    }
  }
}


/* called via uv_poll when ares completion port signaled */
void uv_process_ares_event_req(uv_ares_action_t* handle, uv_req_t* req) {
  uv_ares_channel_t* uv_ares_data_ptr = (uv_ares_channel_t*)handle->data;

  ares_process_fd(uv_ares_data_ptr->channel,
                    handle->read ? handle->sock : INVALID_SOCKET,
                    handle->write ?  handle->sock : INVALID_SOCKET);

  /* release handle for select here  */
  free(handle);
}


/* called via uv_poll when ares is finished with socket */
void uv_process_ares_cleanup_req(uv_ares_task_t* handle, uv_req_t* req) {
  /* check for event complete without waiting */
  unsigned int signaled = WaitForSingleObject(handle->h_close_event, 0);

  if (signaled != WAIT_TIMEOUT) {
    uv_ares_channel_t* uv_ares_data_ptr = (uv_ares_channel_t*)handle->data;

    uv_unref();

    /* close event handle and free uv handle memory */
    CloseHandle(handle->h_close_event);
    free(handle);

    /* decrement active count. if it becomes 0 stop polling */
    if (uv_ares_data_ptr->activesockets > 0) {
      uv_ares_data_ptr->activesockets--;
      if (uv_ares_data_ptr->activesockets == 0) {
        uv_close((uv_handle_t*)&uv_ares_data_ptr->pollingtimer, NULL);
      }
    }
  } else {
    /* stil busy - repost and try again */
    if (!PostQueuedCompletionStatus(LOOP->iocp,
                                    0,
                                    0,
                                    &req->overlapped)) {
      uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus");
    }
  }
}


/* set ares SOCK_STATE callback to our handler */
int uv_ares_init_options(ares_channel *channelptr,
                        struct ares_options *options,
                        int optmask) {
  int rc;

  /* only allow single init at a time */
  if (uv_ares_data.channel != NULL) {
    return UV_EALREADY;
  }

  /* set our callback as an option */
  options->sock_state_cb = uv_ares_sockstate_cb;
  options->sock_state_cb_data = &uv_ares_data;
  optmask |= ARES_OPT_SOCK_STATE_CB;

  /* We do the call to ares_init_option for caller. */
  rc = ares_init_options(channelptr, options, optmask);

  /* if success, save channel */
  if (rc == ARES_SUCCESS) {
    uv_ares_data.channel = *channelptr;
  }

  return rc;
}


/* release memory */
void uv_ares_destroy(ares_channel channel) {
  /* only allow destroy if did init */
  if (uv_ares_data.channel != NULL) {
    ares_destroy(channel);
    uv_ares_data.channel = NULL;
  }
}
