#include <curl/curl.h>
#include <uv.h>
#include <unistd.h>

#include <iostream>
#include <thread>

/*
 * This example tries to employ CURL multi interface with multiple threads working on the easy handles
 * that are part of the multi handle. 
 * It uses libuv for socket event handling.
 */

CURLM* gMultiHandle;
uv_loop_t* gLoop;
uv_timer_t gTimer;
bool gThreadStart = false;

void TermHandler(int signum)
{
   gThreadStart = false;
   uv_stop(gLoop);
}

typedef struct curl_context_s 
{
   uv_poll_t poll_handle;
   curl_socket_t sockfd;
} curl_context_t;
 
/* CURLOPT_WRITEFUNCTION for the easy handle */
static std::size_t OnReceiveReply(void* buffer, std::size_t size, std::size_t count, void* userdata)
{
   std::size_t byteCount = size * count;
   std::cout << " - Inside OnReceiveReply - " << byteCount << std::endl;
   return byteCount;
}

/* CURLMOPT_TIMERFUNCTION for multi handle*/
void on_timeout(uv_timer_t *req, int status)
{
  int running_handles;
  curl_multi_socket_action(gMultiHandle, CURL_SOCKET_TIMEOUT, 0, &running_handles);
}

void start_timeout(CURLM *multi, long timeout_ms, void *userp)
{
   if (timeout_ms <= 0)
   {
      timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in
                       a bit */
   }
   uv_timer_start(&gTimer, on_timeout, timeout_ms, 0);
}

/* Thread function */
void http_get()
{
   CURL* easyHandle = curl_easy_init();
   curl_easy_setopt(easyHandle, CURLOPT_WRITEFUNCTION, OnReceiveReply);
   curl_multi_add_handle(gMultiHandle, easyHandle);
   std::cout << "Added easy handle to multi handle" << std::endl;
   while (gThreadStart)
   {
      // In the real application, this URL will have different query parameters in each request.
      // Hence this call to set CURLOPT_URL within the loop.
      curl_easy_setopt(easyHandle, CURLOPT_URL, "http://10.91.31.152:8080/index.html");
      sleep(2);
   }
   curl_multi_remove_handle(gMultiHandle, easyHandle);
   curl_easy_cleanup(easyHandle);
}

curl_context_t* create_curl_context(curl_socket_t sockfd)
{
   curl_context_t* context = (curl_context_t *) malloc(sizeof *context);
   context->sockfd = sockfd;
 
   uv_poll_init_socket(gLoop, &context->poll_handle, sockfd);
   context->poll_handle.data = context;
 
   return context;
}
 
void curl_close_cb(uv_handle_t *handle)
{
   curl_context_t *context = (curl_context_t *) handle->data;
   free(context);
}
 
void destroy_curl_context(curl_context_t *context)
{
   uv_close((uv_handle_t *) &context->poll_handle, curl_close_cb);
}

void curl_perform(uv_poll_t *req, int status, int events)
{
   int flags = 0;
   uv_timer_stop(&gTimer);
 
   if (events & UV_READABLE)
      flags |= CURL_CSELECT_IN;
   if (events & UV_WRITABLE)
      flags |= CURL_CSELECT_OUT;
 
   curl_context_t* context = (curl_context_t *) req;
 
   int running_handles;
   curl_multi_socket_action(gMultiHandle, context->sockfd, flags, &running_handles);

   CURLMsg *message = NULL;
   int pending = 0;
   while (message = curl_multi_info_read(gMultiHandle, &pending))
   {
      if (message->msg == CURLMSG_DONE)
      {
         std::cout << "Finished receiving message" << std::endl; 
      } 
   }
}

int handle_socket(CURL *easy, curl_socket_t s, int action, void *userp, void* socketp)
{
   curl_context_t *curl_context = NULL;
   if (action == CURL_POLL_IN || action == CURL_POLL_OUT) 
   {
      if (socketp) 
      {
         curl_context = (curl_context_t *) socketp;
      }
      else 
      {
         curl_context = create_curl_context(s);
      }
      curl_multi_assign(gMultiHandle, s, (void *) curl_context);
   } 

   switch (action) 
   {
      case CURL_POLL_IN:
         std::cout << "handle_socket - CURL_POLL_IN " << std::endl;
         uv_poll_start(&curl_context->poll_handle, UV_READABLE, curl_perform);
         break;
      case CURL_POLL_OUT:
         std::cout << "handle_socket - CURL_POLL_OUT " << std::endl;
         uv_poll_start(&curl_context->poll_handle, UV_WRITABLE, curl_perform);
         break;
      case CURL_POLL_REMOVE:
         std::cout << "handle_socket - CURL_POLL_REMOVE " << std::endl;
/*         if (socketp) 
         {
            uv_poll_stop(&((curl_context_t*)socketp)->poll_handle);
            destroy_curl_context((curl_context_t*) socketp);
            curl_multi_assign(gMultiHandle, s, NULL);
         }  */
         break;
      default:
         std::cout << "handle_socket - default " << std::endl;
         abort();
   }
   return 0;
}

int main()
{
   signal(SIGTERM, TermHandler);

   gLoop = uv_default_loop();
   uv_timer_init(gLoop, &gTimer);
 
   if (curl_global_init(CURL_GLOBAL_ALL))
   {
      std::cout << "curl_global_init failed";
      return 1;
   };

   gMultiHandle = curl_multi_init();
   curl_multi_setopt(gMultiHandle, CURLMOPT_SOCKETFUNCTION, handle_socket);
   curl_multi_setopt(gMultiHandle, CURLMOPT_TIMERFUNCTION, start_timeout);

   std::thread t1(http_get);
   std::thread t2(http_get);

   gThreadStart = true;

   std::cout << "Starting uv loop" << std::endl;
   uv_run(gLoop, UV_RUN_DEFAULT);

   t1.join();
   t2.join();

   curl_multi_cleanup(gMultiHandle);
   return 0;
}
