
#include <stdio.h>
#include <curl/curl.h>
#include <vector>

#ifndef WIN32
#define max(a,b) (a > b ? a : b)
#endif

const char* testURL = "http://grin.hq.nasa.gov/IMAGES/SMALL/GPN-2000-000215.jpg";
const curl_off_t maxBytesPerSec = 20000;

static CURLM* multiHandle_;
static CURL* easyHandle_;

struct SockInfo {
    curl_socket_t sock;
    int sock_what;
};

static std::vector<SockInfo> sockInfo;

static curl_socket_t sock = 0;
static int sock_what = CURL_POLL_NONE;
static long timeout_ms = 0;

/**
* Called by curl to add/remove/change mask for socket
* associate with a curl easy handle.
*/
static int socketCB(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) {

    printf("socketCB: 0x%x %d %d\n", e, s, what);

    // find this socket in the list
    std::vector<SockInfo>::iterator i = sockInfo.begin();
    while (i != sockInfo.end() && i->sock != s) {
        ;
    }

    if (i == sockInfo.end()) {
        // new socket
        if (what != CURL_POLL_REMOVE) {
            SockInfo info;
            info.sock = s;
            info.sock_what = what;
            sockInfo.push_back(info);
            printf("socketCB: add new socket %d\n", s);
        }
    } else {
        // existing socket
        if (what == CURL_POLL_REMOVE) {
            sockInfo.erase(i);
            printf("socketCB: remove socket %d\n", s);
        } else {
            i->sock_what = what;
        }
    }

    return 0;

}

/**
* Called by curl to set timeout interval for multihandle.
*/
static int timeoutCB(CURLM *multi, long ms, void *g) {
    timeout_ms = ms;
    printf("timeoutCB: %d\n", ms);
    return 0;
}

static void checkForComplete() {

    CURLMsg* msg;
    int numMsgs;

    while ((msg = curl_multi_info_read(multiHandle_, &numMsgs)) != NULL) {
        if (msg->msg == CURLMSG_DONE) {
            // Request completed.
            if (msg->data.result == CURLE_OK) {
                printf("Requested completed ok!!\n");
            } else {
                const char* errmsg = curl_easy_strerror(msg->data.result);

                long int responseCode;
                curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &responseCode);

                printf("Error: %s (resp code %d)\n", errmsg, responseCode);

            }
        }
    }
}

static size_t curlWriteCB(void* data, size_t size, size_t nmemb, void* cookie) {
    int bytesWritten = size * nmemb;
    printf("curlWriteCB: got %d bytes\n", size * nmemb);
    return bytesWritten;
}

int test_multi_socket(void)
{

    // setup curl multihandle
    multiHandle_ = curl_multi_init();
    curl_multi_setopt(multiHandle_, CURLMOPT_SOCKETFUNCTION, socketCB);
    curl_multi_setopt(multiHandle_, CURLMOPT_TIMERFUNCTION, timeoutCB);

    // setup curl easy handle
    easyHandle_ = curl_easy_init();

    curl_easy_setopt(easyHandle_, CURLOPT_URL, testURL);

    curl_easy_setopt(easyHandle_, CURLOPT_WRITEFUNCTION, curlWriteCB);
    curl_easy_setopt(easyHandle_, CURLOPT_MAX_RECV_SPEED_LARGE, maxBytesPerSec);

    curl_multi_add_handle(multiHandle_, easyHandle_);

    // prime curl - this should give us first socket callback
    // and get things started
    int stillRunning = 1;
    CURLMcode rc;
    do {
        rc = curl_multi_socket_all(multiHandle_, &stillRunning);
    } while (CURLM_CALL_MULTI_PERFORM == rc);


    while(stillRunning) {

        if (sockInfo.size() == 0) {
            // all sockets removed, try to jumpstart again
            // if I don't do this the request never completes
            int stillRunning = 1;
            CURLMcode rc;
            do {
                //printf("curl_multi_socket_all\n");
                rc = curl_multi_socket_all(multiHandle_, &stillRunning);
            } while (CURLM_CALL_MULTI_PERFORM == rc);
            continue;
        }

        // build fd sets for select call
        struct timeval timeout;
        fd_set fdread;
        fd_set fdwrite;
        fd_set fdexcep;
        unsigned int maxfd;
        int ev_bitmask = 0;

        FD_ZERO(&fdread);
        FD_ZERO(&fdwrite);
        FD_ZERO(&fdexcep);

        maxfd = 0;

        for (unsigned int i = 0; i < sockInfo.size();++i) {

            switch (sockInfo[i].sock_what) {
case CURL_POLL_NONE:
    break;
case CURL_POLL_IN:
    maxfd = max(maxfd, sockInfo[i].sock);
    FD_SET(sockInfo[i].sock, &fdread);
    FD_SET(sockInfo[i].sock, &fdexcep);
    break;
case CURL_POLL_OUT:
    maxfd = max(maxfd, sockInfo[i].sock);
    FD_SET(sockInfo[i].sock, &fdwrite);
    FD_SET(sockInfo[i].sock, &fdexcep);
    break;
case CURL_POLL_INOUT:
    maxfd = max(maxfd, sockInfo[i].sock);
    FD_SET(sockInfo[i].sock, &fdread);
    FD_SET(sockInfo[i].sock, &fdwrite);
    FD_SET(sockInfo[i].sock, &fdexcep);
    break;
            }
        }

        /* set a suitable timeout to play around with */
        timeout.tv_sec = timeout_ms/1000;
        timeout.tv_usec = (timeout_ms % 1000) * 1000;


        printf("Call select: timeout = %d\n", timeout_ms);
        int rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

        if (rc <= 0) {
            // timeout
            do {
                printf("Call multi_socket_action with timeout\n");
                rc = curl_multi_socket_action(multiHandle_, CURL_SOCKET_TIMEOUT,
                    0, &stillRunning);
            } while (rc == CURLM_CALL_MULTI_PERFORM);

        } else {

            // call multi_socket_action for any fds that were selected
            for (unsigned int i = 0; i < sockInfo.size();++i) {
                ev_bitmask = 0;
                if (FD_ISSET(sockInfo[i].sock, &fdread)) {
                    ev_bitmask |= CURL_CSELECT_IN;
                }
                if (FD_ISSET(sockInfo[i].sock, &fdwrite)) {
                    ev_bitmask |= CURL_CSELECT_OUT;
                }
                if (FD_ISSET(sockInfo[i].sock, &fdexcep)) {
                    ev_bitmask |= CURL_CSELECT_ERR;
                }

                do {
                    printf("Call multi_socket_action with sock=%d, ev=%d\n", sockInfo[i].sock, ev_bitmask);
                    rc = curl_multi_socket_action(multiHandle_, sockInfo[i].sock,
                        ev_bitmask, &stillRunning);
                } while (rc == CURLM_CALL_MULTI_PERFORM);
            }
        }

        checkForComplete();
    }

    curl_multi_cleanup(multiHandle_);
    curl_easy_cleanup(easyHandle_);

    return 0;
}

int test_multi(void)
{

    // setup curl multihandle
    multiHandle_ = curl_multi_init();
    curl_multi_setopt(multiHandle_, CURLMOPT_TIMERFUNCTION, timeoutCB);

    // setup curl easy handle
    easyHandle_ = curl_easy_init();

    curl_easy_setopt(easyHandle_, CURLOPT_URL, testURL);

    curl_easy_setopt(easyHandle_, CURLOPT_WRITEFUNCTION, curlWriteCB);
    curl_easy_setopt(easyHandle_, CURLOPT_MAX_RECV_SPEED_LARGE, maxBytesPerSec);

    curl_multi_add_handle(multiHandle_, easyHandle_);

    int stillRunning = 1;

    while(CURLM_CALL_MULTI_PERFORM ==
        curl_multi_perform(multiHandle_, &stillRunning));

    while(stillRunning) {

        struct timeval timeout;
        fd_set fdread;
        fd_set fdwrite;
        fd_set fdexcep;
        int maxfd;

        FD_ZERO(&fdread);
        FD_ZERO(&fdwrite);
        FD_ZERO(&fdexcep);

        curl_multi_fdset(multiHandle_, &fdread, &fdwrite, &fdexcep, &maxfd);

        /* set a suitable timeout to play around with */
        timeout.tv_sec = timeout_ms/1000;
        timeout.tv_usec = (timeout_ms % 1000) * 1000;

        printf("Call select: timeout = %d\n", timeout_ms);

        if (maxfd == -1) {
            select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
        }

        /* if I move this call into the if above, i.e. I skip this when
        curl_multi_fdset doesn't give me any sockets then the request does not
        complete, if I leave it here, I end up calling curl_multi_perform in a tight
        loop instead of using the timeout (i.e. busywait) */
        while(CURLM_CALL_MULTI_PERFORM ==
            curl_multi_perform(multiHandle_, &stillRunning));

        checkForComplete();
    }

    curl_multi_cleanup(multiHandle_);
    curl_easy_cleanup(easyHandle_);

    return 0;
}

int test_easy(void)
{
    // setup curl easy handle
    easyHandle_ = curl_easy_init();

    curl_easy_setopt(easyHandle_, CURLOPT_URL, testURL);

    curl_easy_setopt(easyHandle_, CURLOPT_WRITEFUNCTION, curlWriteCB);
    curl_easy_setopt(easyHandle_, CURLOPT_MAX_RECV_SPEED_LARGE, maxBytesPerSec);

    CURLcode rc = curl_easy_perform(easyHandle_);

    if (rc == CURLE_OK) {
        printf("Requested completed ok!!\n");
    } else {
        const char* errmsg = curl_easy_strerror(rc);

        long int responseCode;
        curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &responseCode);

        printf("Error: %s (resp code %d)\n", errmsg, responseCode);

    }

    curl_easy_cleanup(easyHandle_);

    return 0;
}

int main(void) {

    test_multi_socket();
    //test_multi();
    //test_easy();
}
