#include <curl/curl.h>
#include <iostream>
#include <sstream>
#include <string>
#include <stdint.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, %10.10ld bytes (0x%8.8lx)\n", text, (long)size, (long)size);

    for(i=0; i<size; i+= width) {
        fprintf(stream, "%4.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);
}

uint64_t toTimestamp(const struct timeval& tv) {
    return (tv.tv_sec * 1000000LL + tv.tv_usec);
}

static uint64_t getTimestampUs() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return toTimestamp(tv);
}

uint64_t start = 0;

static
int my_trace(CURL* /*handle*/, curl_infotype type, char *data, size_t size, void *userp)
{
    struct data *config = (struct data *)userp;
    std::stringstream text;
    text << ((getTimestampUs()-start)/1000000.0) << " ";

    switch (type) {
    case CURLINFO_TEXT:
        fprintf(stderr, "== Info: %s %s", text.str().c_str(), 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:
        text << "<= Recv data";
        size = 0;
        break;
    case CURLINFO_SSL_DATA_IN:
        text << "<= Recv SSL data";
        size = 0;
        break;
    }
    dump(text.str().c_str(), stderr, (unsigned char *)data, size, config->trace_ascii);
    return 0;
}

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
    return size * nmemb;
}

int main(int argc, char* argv[]) {
    CURL *curl_handle;
    struct data config;
    config.trace_ascii = 1; /* enable ascii tracing */

    curl_global_init(CURL_GLOBAL_ALL);
    curl_handle = curl_easy_init();
    curl_easy_setopt(curl_handle, CURLOPT_URL, argv[1]);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, my_trace);
    curl_easy_setopt(curl_handle, CURLOPT_DEBUGDATA, &config);

    start = getTimestampUs();
    curl_easy_perform(curl_handle);

    double dnsTime = 0;
    double pretransfer = 0;
    double appConnect = 0;
    double connectTime = 0;
    double starttransfer = 0;
    double totalTime = 0;

    curl_easy_getinfo(curl_handle, CURLINFO_APPCONNECT_TIME, &appConnect);
    curl_easy_getinfo(curl_handle, CURLINFO_NAMELOOKUP_TIME, &dnsTime);
    curl_easy_getinfo(curl_handle, CURLINFO_CONNECT_TIME, &connectTime);
    curl_easy_getinfo(curl_handle, CURLINFO_PRETRANSFER_TIME, &pretransfer);
    curl_easy_getinfo(curl_handle, CURLINFO_STARTTRANSFER_TIME, &starttransfer);
    curl_easy_getinfo(curl_handle, CURLINFO_TOTAL_TIME, &totalTime);
    std::cout << "timings: " << std::endl;
    std::cout << "CURLINFO_NAMELOOKUP_TIME " << dnsTime << std::endl;
    std::cout << "CURLINFO_CONNECT_TIME " << connectTime << std::endl;
    std::cout << "CURLINFO_APPCONNECT_TIME " << appConnect << std::endl;
    std::cout << "CURLINFO_PRETRANSFER_TIME " << pretransfer << std::endl;
    std::cout << "CURLINFO_STARTTRANSFER_TIME " << starttransfer << std::endl;
    std::cout << "CURLINFO_TOTAL_TIME " << totalTime << std::endl;

    curl_easy_cleanup(curl_handle);
    curl_global_cleanup();
    return 0;
}
