#include <stdio.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

struct data {
  char trace_ascii; /* 1 or 0 */
};

static void dump(const char *text, FILE *stream, unsigned char *ptr, size_t size, char nohex)
{
  size_t i;
  size_t c;

  unsigned int width=0x10;

  if(nohex)
    /* without the hex output, we can fit more on screen */
    width = 0x40;

  fprintf(stream, "%s, %010.10ld bytes (0x%08.8lx)\n",
          text, (long)size, (long)size);

  for(i=0; i<size; i+= width) {

    fprintf(stream, "%04.4lx: ", (long)i);

    if(!nohex) {
      /* hex not disabled, show it */
      for(c = 0; c < width; c++)
        if(i+c < size)
          fprintf(stream, "%02x ", ptr[i+c]);
        else
          fputs("   ", stream);
    }

    for(c = 0; (c < width) && (i+c < size); c++) {
      /* check for 0D0A; if found, skip past and start a new line of output */
      if (nohex && (i+c+1 < size) && ptr[i+c]==0x0D && ptr[i+c+1]==0x0A) {
        i+=(c+2-width);
        break;
      }
      fprintf(stream, "%c",
              (ptr[i+c]>=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.');
      /* check again for 0D0A, to avoid an extra \n if it's at width */
      if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) {
        i+=(c+3-width);
        break;
      }
    }
    fputc('\n', stream); /* newline */
  }
  fflush(stream);
}

static int my_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp)
{
  char * dataToDump = data;
  struct data *config = (struct data *)userp;
  const char *text;
  (void)handle; /* prevent compiler warning */

  switch (type) {
  case CURLINFO_TEXT:
    fprintf(stderr, "== Info: %s", data);
  default: /* in case a new one is introduced to shock us */
    return 0;

  case CURLINFO_HEADER_OUT:
    text = "=> Send header";
    break;
  case CURLINFO_DATA_OUT:
    text = "=> Send data";
    break;
  case CURLINFO_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
	dataToDump = "lotsa data";
	size = 10;
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
	dataToDump = "lotsa SSL data";
	size = 14;
    text = "<= Recv SSL data";
    break;
  }

  dump(text, stderr, (unsigned char *)dataToDump, size, config->trace_ascii);
  return 0;
}

static int abortTransfer = 0;

int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
	return abortTransfer;
}

//no-op write callback to reduce screen clutter
size_t writeCallback(char * buf, size_t size, size_t nmemb, void * data) {
	return (nmemb * size);
}

void checkTransfers(CURLM * multi_handle) {
	CURLMsg * msg;
	int msgs_left;
	CURL * easy;
	while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
        if (msg->msg == CURLMSG_DONE) {
            easy = msg->easy_handle;

            //transfer done - remove easy from multi and cleanup easy
            curl_multi_remove_handle(multi_handle, easy);
			curl_easy_cleanup(easy);
        }
    }
}

void runMulti(CURLM * multi_handle, CURL * easy_handle) {
  int still_running; /* keep number of running handles */

  curl_multi_add_handle(multi_handle, easy_handle);

  /* we start some action by calling perform right away */
  
  curl_multi_perform(multi_handle, &still_running);
  checkTransfers(multi_handle);

  while(still_running) {
    struct timeval timeout;
    int rc; /* select() return code */

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = -1;

    long curl_timeo = -1;

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    /* set a suitable timeout to play around with */
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    curl_multi_timeout(multi_handle, &curl_timeo);
    if(curl_timeo >= 0) {
      timeout.tv_sec = curl_timeo / 1000;
      if(timeout.tv_sec > 1)
        timeout.tv_sec = 1;
      else
        timeout.tv_usec = (curl_timeo % 1000) * 1000;
    }

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    /* In a real-world program you OF COURSE check the return code of the
       function calls.  On success, the value of maxfd is guaranteed to be
       greater or equal than -1.  We call select(maxfd + 1, ...), specially in
       case of (maxfd == -1), we call select(0, ...), which is basically equal
       to sleep. */

    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

    switch(rc) {
    case -1:
      /* select error */
      break;
    case 0:
    default:
      /* timeout or readable/writable sockets */
      curl_multi_perform(multi_handle, &still_running);
      break;
    }

	checkTransfers(multi_handle);
  }
}

int main(int argc, char *argv[])
{
  struct data config;

  CURL *http_handle;
  CURL *http_handle2;
  CURL *http_handle3;
  CURLM *multi_handle;

  http_handle = curl_easy_init();
  http_handle2 = curl_easy_init();
  http_handle3 = curl_easy_init();

  /* set options 1*/
  curl_easy_setopt(http_handle, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle, CURLOPT_DEBUGDATA, &config);

  /* set options 2*/
  curl_easy_setopt(http_handle2, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle2, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle2, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle2, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle2, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle2, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle2, CURLOPT_DEBUGDATA, &config);

  /* set options 3*/
  curl_easy_setopt(http_handle3, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle3, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle3, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle3, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle3, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle3, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle3, CURLOPT_DEBUGDATA, &config);

  /* init a multi stack */
  multi_handle = curl_multi_init();

  //let first handle run to completion - this should leave connection intact
  abortTransfer = 0;
  runMulti(multi_handle, http_handle);

  //***ISSUE: cancel early - libcurl will send the headers out, stop but leave the
  //connection available for reuse after both runs
  abortTransfer = 1;
  runMulti(multi_handle, http_handle2);
  runMulti(multi_handle, http_handle3);

  //done with multi
  curl_multi_cleanup(multi_handle);

  return 0;
}
