/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.haxx.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ***************************************************************************/
/* <DESC>
 * A multi-threaded example that uses pthreads to fetch several files at once
 * </DESC>
 */

#include <stdio.h>
#include <pthread.h>

#include <curl/curl.h>
#include <openssl/crypto.h>
#include <list>


#define NUMT 8
#define MAXHANDLECOUNT 6  /* Number of simultaneous transfers */
#define URLSIZE 5

#define MUTEX_TYPE       pthread_mutex_t
#define MUTEX_SETUP(x)   pthread_mutex_init(&(x), NULL)
#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
#define MUTEX_LOCK(x)    pthread_mutex_lock(&(x))
#define MUTEX_UNLOCK(x)  pthread_mutex_unlock(&(x))


static pthread_mutex_t * mutex_buf = NULL;


std::list<CURL*> mCurlHandles;
int currentHandleCount = 0;
pthread_mutex_t handleLock;

//Never Called
static void lock_callback(int mode, int type, const char *file, int line)
{
    if(mode & CRYPTO_LOCK) {
        MUTEX_LOCK(mutex_buf[type]);
    }
    else {
        MUTEX_UNLOCK(mutex_buf[type]);
    }
}

static unsigned long thread_id(void)
{
    unsigned long ret;
    
    ret=(unsigned long)pthread_self();
    return ret;
}

static void initLock()
{
    pthread_mutex_init(&handleLock , NULL);
    int i;
    mutex_buf = (pthread_mutex_t*) malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
    
    for(i = 0;  i < CRYPTO_num_locks();  i++){
        
         MUTEX_SETUP(mutex_buf[i]);
    }
    
    
    CRYPTO_set_id_callback((unsigned long (*)())thread_id);
    CRYPTO_set_locking_callback(lock_callback);
}

static void removeLock()
{
    pthread_mutex_destroy(&handleLock);
}

static CURL* grabHandle(bool refresh)
{
    pthread_mutex_lock(&handleLock);
    CURL* handle = NULL;
    if (refresh == true)
    {
        handle = curl_easy_init();
        currentHandleCount++;
        //Some error checking
        return handle;
    }
    
    
    if (!mCurlHandles.empty())
    {
        handle = mCurlHandles.front();
        mCurlHandles.pop_front();
    }
    else
    {
        if (currentHandleCount < MAXHANDLECOUNT || MAXHANDLECOUNT ==  -1)
        {
            handle = curl_easy_init();
            currentHandleCount++;
        }
    }

    pthread_mutex_unlock(&handleLock);
    return handle;
}

static CURL* cleanHandle(CURL* handle)
{
    if (handle != NULL){
        curl_easy_reset(handle);
    }
    return handle;
}
static void returnHandle(CURL* handle)
{
    pthread_mutex_lock(&handleLock);
    handle = cleanHandle(handle);
    mCurlHandles.push_back(handle);
    
    pthread_mutex_unlock(&handleLock);
}

static void destoryHandles()
{
    pthread_mutex_lock(&handleLock);
    
    while(!mCurlHandles.empty())
    {
        curl_easy_cleanup(mCurlHandles.front());
        mCurlHandles.pop_front();
    }
    printf("Destorying all handles\n");
    pthread_mutex_unlock(&handleLock);
}


static void perform(CURLM *handle)
{
    int still_running;
    
    CURLM *multi_handle;
    
    multi_handle = curl_multi_init();
    
    curl_multi_add_handle(multi_handle, handle);

    curl_multi_perform(multi_handle, &still_running);
    
    
    do {
        struct timeval timeout;
        int rc; /* select() return code */
        CURLMcode mc; /* curl_multi_fdset() 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 = 0;
        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 */
        mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
        
        if(mc != CURLM_OK) {
           // fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
            break;
        }
        
        /* On success the value of maxfd is guaranteed to be >= -1. We call
         select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
         no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
         to sleep 100ms, which is the minimum suggested value in the
         curl_multi_fdset() doc. */
        
        if(maxfd == -1) {
#ifdef _WIN32
            Sleep(100);
            rc = 0;
#else
            /* Portable sleep for platforms other than Windows. */
            struct timeval wait = { 0, 1 * 10 }; /* 100ms */
            rc = select(0, NULL, NULL, NULL, &wait);
#endif
        }
        else {
            /* Note that on some platforms 'timeout' may be modified by select().
             If you need access to the original value save a copy beforehand. */
            rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
        }
        
        switch(rc) {
            case -1:
            /* select error */
            break;
            case 0: /* timeout */
            default: /* action */
            curl_multi_perform(multi_handle, &still_running);
            
            break;
        }
    } while(still_running);
    
     curl_multi_cleanup(multi_handle);
}

static void *doMoreWork(void *arg)
{

    while(true)
    {
        CURL *handle = NULL;
        
        handle = grabHandle(false);

        if (handle != NULL){
            
            
            curl_easy_setopt(handle , CURLOPT_URL, "https://curl.haxx.se/" );
                       
            perform(handle);
            
            returnHandle(handle);
        }
    }
    return NULL;
}


int main(int argc, char **argv)
{
   // printf("Program starting\n");
    pthread_t tid[NUMT];
    int i;
    int error;
    
    /* Must initialize libcurl before any threads are started */
    curl_global_init(CURL_GLOBAL_ALL);
    
    initLock();
    
    for(i = 0; i< NUMT; i++) {
        error = pthread_create(&tid[i],
                               NULL, /* default attributes please */
                               doMoreWork,
                               NULL);
    }
    
    /* now wait for all threads to terminate */
    for(i = 0; i< NUMT; i++) {
        error = pthread_join(tid[i], NULL);
    }
    
    destoryHandles();
    removeLock();
    
    return 0;
}

