/*******************************************************************************
 * Copyright Tecnotree Oy 2010. All rights reserved. No part of this document
 * may be reproduced, distributed, stored in a retrieval system or translated
 * into any language, in any form or by any means, electronic, mechanical,
 * magnetic, optical, photocopying, manual or otherwise, without the prior
 * written permission of Tecnomen Oy. For additional copies of the document,
 * please contact Tecnotree Oy.
 ******************************************************************************/

/**
 ******************************************************************************
 * @file http_client.c
 * @ingroup HTTP_CLIENT
 *      HTTP client library implementation.
 ******************************************************************************/

/*******************************************************************************
* Include public/global header files
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>

/*******************************************************************************
* Include private header files
*******************************************************************************/
#include <includes.h>
#include SMILEDEF_H
#include SMILE_H
#include COMMON_DEFS_H
#include "http_client.h"

/*******************************************************************************
* Define Constants and Macros
*******************************************************************************/
#define MAXCLIENTCONNECT        51       /* Max simultaneous connections*/
#define IDLE                    FALSE
#define BUSY                    TRUE
//#define NDEBUG

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Stores the verbose mode for this process. 
*******************************************************************************/
static unsigned short verbose=0;

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*    Mutexes used for synchronisation.
*******************************************************************************/
#ifndef NDEBUG
static pthread_mutex_t write_response_synch;
#endif

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Used in HTTP Put requests.
*******************************************************************************/
struct WriteThis 
{
    char *readptr;
    int read_sizeleft;
};

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*    Used to process HTTP responses.
*******************************************************************************/
struct ReadThis
{    
    char *writeptr;
    int write_size;
};

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Easy Handles accomodating MAXCLIENTCONNECT concurrent client connections 
*   and the state of the handle is either IDLE(0) or BUSY(1).
*******************************************************************************/
struct client_connection
{
    CURL *easy_handle;
    unsigned short state;
    struct http_client_config config;
    struct WriteThis write_payload;
    struct ReadThis read_response;
};
struct client_connection *client_connection_array=NULL;

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Multi stack Handles accomodating concurrency of connections associated with 
*   easy handles.
*******************************************************************************/
CURLM *multi_handle=NULL;

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   How many concurrent connections are actually configured for the HTTP client.
*******************************************************************************/
static unsigned int configured_number_of_connections; 

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Pointer to the headers for modification / customisation.
*******************************************************************************/
//~ static struct curl_slist *headers = NULL;

/**
********************************************************************************
* @ingroup HTTP_CLIENT
* @description
*   Http client stats counters.
*******************************************************************************/
static struct http_client_stats http_stats = {0}, *stats=&http_stats;

/**
 * read_data
 */
size_t read_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
    struct WriteThis *write_test = (struct WriteThis *)userp;
      
    if(write_test->read_sizeleft) 
    {  
        *(char *) buffer = write_test->readptr[0];  /* copy one single byte */
        write_test->readptr++;                      /* advance pointer */
        write_test->read_sizeleft--;                /* less data left */
        return 1;                                   /* we return 1 byte at a time */
    }
    return 0;
}

/**
 * write_data
 */
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
    register int realsize = size*nmemb;
    struct ReadThis *read_test = (struct ReadThis *)userp;
    
    read_test->writeptr = (char *)realloc(  read_test->writeptr, 
                                            read_test->write_size + realsize + 1);
    if(read_test->writeptr) 
    {
        memcpy(&(read_test->writeptr[read_test->write_size]), buffer, realsize);
        read_test->write_size += realsize;
        read_test->writeptr[read_test->write_size] = 0;
    }
    return realsize;
}

/**
 * http_client_build_request
 */
inline int http_client_build_request(   const char *url,
                                        const char *http_method,
                                        const char *request_payload,
                                        unsigned short destination_module,
                                        unsigned short destination_instance,
                                        client_process_response response_callbk,
                                        timeout_client_connection_notify timeout_client_callbk)
{
    int i, result=CURLE_OK, found=FALSE, resm=CURLM_OK;
    
    /* Validation */
    assert(url!=NULL);
    assert(http_method!=NULL);
    assert(response_callbk!=NULL);
    assert(timeout_client_callbk!=NULL);
    assert(client_connection_array!=NULL);
    
    /* Find an available easy handle for the connection */
    for(i=0; i<configured_number_of_connections; i++) 
    {
        if(client_connection_array[i].state == IDLE)
        {
            found=TRUE;
            break;
        }
    }        
    
    if(!found)
    {
        printf("No easy handles available in HTTP client.\nTry again later.\n");
        return -1;
    }   
 
    /*  Copy the per connection configuration as this needs to be preserved 
        for the lifetime of the session */   
    strcpy(client_connection_array[i].config.url, url);
    strcpy(client_connection_array[i].config.http_method, http_method);
    strcpy(client_connection_array[i].config.request_payload, request_payload);    
    client_connection_array[i].config.destination_module=destination_module;
    client_connection_array[i].config.destination_instance=destination_instance;
    client_connection_array[i].config.response_callbk=response_callbk;
    client_connection_array[i].config.timeout_client_callbk=timeout_client_callbk; 
  
    /* Initialise struct for processing the response */    
    client_connection_array[i].read_response.write_size = 0;
    client_connection_array[i].read_response.writeptr = NULL;
            
    /* Set options for this handle and connection */
    if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                    CURLOPT_URL, 
                                    client_connection_array[i].config.url)))
    {
        printf("\nProblem setting option CURLOPT_URL, result %d\n", result);
        return -2;
    }
    
    //~ if((result = curl_easy_setopt(  client_connection_array[i].easy_handle,
                                    //~ CURLOPT_HTTPHEADER, headers)))
    //~ {
        //~ printf("\nProblem setting option CURLOPT_HTTPHEADER, result %d\n", result);
        //~ return -2;
    //~ }
    
    /* Options per http method - GET is default */
    if(0 == strcmp(client_connection_array[i].config.http_method, "POST"))
    {
        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_POST, TRUE)))
        {
            printf("\nProblem setting option CURLOPT_POST, result %d\n", result);
            return -2;            
        }  

        //~ if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        //~ CURLOPT_POSTFIELDSIZE, 
                                        //~ strlen(client_connection_array[i].config.request_payload)+1)))
        //~ {
            //~ printf("\nProblem setting option CURLOPT_POSTFIELDSIZE, result %d\n", 
                //~ result);
            //~ return -2;            
        //~ }          
        
        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_POSTFIELDS, 
                                        client_connection_array[i].config.request_payload)))
        {
            printf("\nProblem setting option CURLOPT_POSTFIELDS, result %d\n", 
                result);
            return -2;            
        }        
    }
    
    if(0 == strcmp(client_connection_array[i].config.http_method, "PUT"))
    {
        client_connection_array[i].write_payload.readptr = 
            client_connection_array[i].config.request_payload;
    
        client_connection_array[i].write_payload.read_sizeleft =
            strlen(client_connection_array[i].config.request_payload)+1;        
        
        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_UPLOAD, TRUE)))
        {
            printf("\nProblem setting option CURLOPT_UPLOAD, result %d\n", result);
            return -2;            
        }  

        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_INFILESIZE, 
                                        client_connection_array[i].write_payload.read_sizeleft)))
        {
            printf("\nProblem setting option CURLOPT_INFILESIZE, result %d\n", result);
            return -2;            
        }  
        
        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_READFUNCTION, 
                                        read_data)))
        {
            printf("\nProblem setting option CURLOPT_READFUNCTION, result %d\n", result);
            return -2;            
        }  
       
        if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                        CURLOPT_READDATA, 
                                        &client_connection_array[i].write_payload)))
        {
            printf("\nProblem setting option CURLOPT_READDATA, result %d\n", result);
            return -2;            
        }                 
    }

    if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                    CURLOPT_WRITEFUNCTION, 
                                    write_data)))
    {
        printf("\nProblem setting option CURLOPT_WRITEFUNCTION, result %d\n", result);
        return -2;            
    } 
    
    if((result = curl_easy_setopt(  client_connection_array[i].easy_handle, 
                                    CURLOPT_WRITEDATA, 
                                    &client_connection_array[i].read_response)))
    {
        printf("\nProblem setting option CURLOPT_WRITEDATA, result %d\n", result);
        return -2;            
    } 
    
    /* Set the easy handle to unavailable for subsequent connection attempts */
    client_connection_array[i].state = BUSY;    
    
    /* Add the easy handle to the multi session */
    if((resm=curl_multi_add_handle(multi_handle, client_connection_array[i].easy_handle)))
    {
        if(resm!=CURLM_CALL_MULTI_PERFORM)
        {
            log_error(LOCAL_EVENT, 
                "Problem adding to multi-stack, returned %d\n",resm);
            printf("Problem adding to multi-stack, returned %d\n",resm);
            return -2;
        }
    }    
    return result;
}

/**
 * http_client_send_request
 */
inline void http_client_send_request(void)
{
    int still_running; /* keep number of running connection handles */
    CURLMsg *msg; /* for picking up messages with the transfer status */
    unsigned int idx, msgs_left, found = 0; 
    int resm=CURLM_OK;
    
    /* Make sure there is at least one connection being processed in multi-stack*/
    for(idx=0; idx<configured_number_of_connections; idx++) 
    {
        if(client_connection_array[idx].state==BUSY)
        {
            found=TRUE;
            break;
        }     
    }
    
    if(!found)
        return;
    
    /* we start some action by calling perform right away */
    while(CURLM_CALL_MULTI_PERFORM == 
        curl_multi_perform(multi_handle, &still_running));
            
    while(still_running) 
    {
        struct timeval timeout;
        int rc; /* select() return code */

        fd_set fdread;
        fd_set fdwrite;
        fd_set fdexcep;
        int maxfd;

        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;

        /* get file descriptors from the transfers */
        curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
        rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

        switch(rc) {
            case -1:
                /* select error */
            break;
            case 0:
                /* timeout, do something else */
            break;
            default:
                /*  One or more of curl's file descriptors say there's 
                    data to read or write */
                while(CURLM_CALL_MULTI_PERFORM ==
                    curl_multi_perform(multi_handle, &still_running));
                break;
        }                              
    } 
    
    while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) 
    {
        if(msg->msg == CURLMSG_DONE) 
        {
            /* Find out which handle this message is about */
            for(idx=0; idx<configured_number_of_connections; idx++) 
            {
                found = 
                    ((msg->easy_handle == client_connection_array[idx].easy_handle)
                    && (client_connection_array[idx].state == BUSY));
                
                if(found)
                    break;
            }
            
            if((msg->data.result == CURLE_OK) && (found))
            {
                stats->http_requests_processed_ok++;
                client_process_response pr = 
                    client_connection_array[idx].config.response_callbk;
                
                (*pr)(  client_connection_array[idx].config.destination_module,
                        client_connection_array[idx].config.destination_instance,
                        client_connection_array[idx].read_response.writeptr,
                        client_connection_array[idx].read_response.write_size);            
                
                if((resm=curl_multi_remove_handle(multi_handle, 
                    client_connection_array[idx].easy_handle)))
                {                    
                    printf("Failed to remove easy handle from multi-stack, ret %d\n",
                        resm);
                }             
                client_connection_array[idx].state = IDLE;                
            }
            else if((msg->data.result == CURLE_OPERATION_TIMEOUTED) && (found))
            {
                stats->http_requests_timed_out++;
                timeout_client_connection_notify tn = 
                    client_connection_array[idx].config.timeout_client_callbk;
                
                stats->http_requests_timed_out++;
                
                (*tn)(  client_connection_array[idx].config.destination_module,
                        client_connection_array[idx].config.destination_instance);
                
                if((resm=curl_multi_remove_handle(multi_handle, 
                    client_connection_array[idx].easy_handle)))
                {                    
                    printf("Failed to remove easy handle from multi-stack, ret %d\n",
                        resm);
                }    
                client_connection_array[idx].state = IDLE;
            }
            else
            {
                stats->http_requests_processed_with_error++;
            }
            
            if(NULL != client_connection_array[idx].read_response.writeptr)
                free(client_connection_array[idx].read_response.writeptr);
        }
    }
    return;  
}

/**
 * get_http_client_configuration
 */
struct http_client_config *get_http_client_configuration(short table_index)
{
    return &client_connection_array[table_index].config;
}

/**
 * get_http_client_stats
 */
struct http_client_stats *get_http_client_stats(void)
{
    return stats;
}

/**
 * client_dump
 */
static void client_dump(const char *text,
                        FILE *stream, 
                        unsigned char *ptr, 
                        size_t size,
                        short nohex)
{
    size_t i, c;
    unsigned int width=0x10;

    if(nohex)
        /* without the hex output, we can fit more on screen */
        width = 0x40;

    printf("%s, %zd bytes (0x%zx)\n", text, size, size);
    for(i=0; i<size; i+= width) 
    {
        printf("%04zx: ", i);

        if(!nohex) {
            /* hex not disabled, show it */
            for(c = 0; c < width; c++)
                if(i+c < size)
                    printf("%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;
            }
            printf("%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;
            }
        }
        printf("\n"); /* newline */
    }
}

/**
 * http_trace
 */
static int http_trace(  CURL *handle,       /* easy handle */
                        curl_infotype type,
                        unsigned char *data, 
                        size_t size,
                        void *userp)
{
    const char *text;

    (void)handle; /* prevent compiler warning */

    switch(type) 
    {
        case CURLINFO_TEXT:
            printf("== 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_HEADER_IN:
            text = "<= Recv header";
        break;
        case CURLINFO_DATA_IN:
            text = "<= Recv data";
        break;
    }
    client_dump(text, stderr, data, size, TRUE);
    return 0;
}

#ifndef NDEBUG
/**
 * write_response
 */
size_t write_response(void *ptr, size_t size, size_t nmemb, void *data)
{
    pthread_mutex_lock(&write_response_synch);
    int res=0;
    
    FILE *writehere = (FILE *)data;
    res = fwrite(ptr, size, nmemb, writehere);
    pthread_mutex_unlock(&write_response_synch);
    return res;
}
#endif

/**
 * set_http_client_verbose
 * TODO:Unsure if it is possible to disable HEADERFUNCTION and DEBUGFUNCTION 
 *      functionality once activated. Be careful that these features are turned 
 *      off before running performance tests as file io shall have a big 
 *      impact on performance results.
 */
int set_http_client_verbose(unsigned short verbose_value,
                            const char *http_client_log_file)
{
    int i;
    FILE *respheader=NULL;
    verbose=verbose_value;
    
    /* Validation */
    assert(http_client_log_file!=NULL);

    /*  The following statements are only intended to execute in a development 
        deployment and shall be compiled out of production code */
#ifndef NDEBUG       
    /* Open response header logging file with binary mode */
    respheader = fopen(http_client_log_file, "wb");
    if(!respheader)
    {
        log_error(LOCAL_EVENT, "\nCould not http client log file.\n");
        printf("\nCould not http client log file.\n");
        return -1;
    }
   
    if(verbose)
    {
        for(i=0; i<configured_number_of_connections; i++) 
        {
            /*  Write information dump from libcurl to stderr stream */
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_VERBOSE, TRUE);
            
            /* Re-direct verbose from stderr to stream of log file */
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_STDERR, respheader);
            
            /*  Include the header contents in the body so they can be 
                examined by the server side decoding our requests */
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_HEADER, TRUE);

            /*  Specify callback function to process response headers */
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_HEADERFUNCTION, write_response);
            
            /*  Pass file pointer to write_response so write response header 
                information to this file */
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_WRITEHEADER, respheader);
        }            
    }
    else
    {
        for(i=0; i<configured_number_of_connections; i++) 
        {
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_VERBOSE, FALSE);
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_HEADER, FALSE);
        }
    }
#endif

    for(i=0; i<configured_number_of_connections; i++) 
    {    
        if(verbose)
            curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_DEBUGFUNCTION, http_trace);
    }
    return 0;
}

/**
 * start_http_client
 */
int start_http_client(  unsigned int number_of_connections,
                        unsigned long connection_timeout)
{
    static unsigned short call_only_once=0;
    static unsigned long configured_connection_timeout=0;
    int i, result=CURLE_OK;
    
    /* Validation */
    assert(connection_timeout!=0);
    assert(number_of_connections!=0);
    assert(number_of_connections<MAXCLIENTCONNECT);      
    
    if(!call_only_once)
    {
        configured_number_of_connections = number_of_connections;
        configured_connection_timeout = connection_timeout;
        
        /* Initialise all features in libCURL */
        if(curl_global_init(CURL_GLOBAL_ALL))
        {
            log_error(LOCAL_EVENT, "\nUnable to use any CURL functionality.\n");
            printf("\nUnable to use any CURL functionality.\n");
            return -1;
        }
        
        /*  Initialise a multi stack for multiple asynchronous connections 
            over a single thread */
        if(!(multi_handle = curl_multi_init()))
        {
            log_error(LOCAL_EVENT, 
                "\nUnable to create CURL client multi stack handle.\n");
            printf("\nUnable to create CURL client multi stack handle.\n");
            return -2;
        }    
    
#ifndef NDEBUG        
        /* Mutexes to be initialised just once */
        pthread_mutex_init(&write_response_synch,NULL);
#endif
        /* Allocate heap memory for connection based struct */
        client_connection_array =
            malloc( configured_number_of_connections * 
                    sizeof(struct client_connection));  

        if(NULL == client_connection_array) 
        {
            log_error(LOCAL_EVENT, 
                "\nUnable to allocate client connection structure.\n");
            printf("\nUnable to allocate client connection structure.\n");
            return -2;            
        }            
        
        /*  Allocate one CURL handle per connection. For high throughput http 
            requests we assign a number of easy handles to a multi-stack which 
            facilitates asynchronous concurrency across a number of permanent 
            connections on a single thread */
        for(i=0; i<configured_number_of_connections; i++) 
        {
            if(!(client_connection_array[i].easy_handle = curl_easy_init()))
            {
                log_error(LOCAL_EVENT, 
                    "\nUnable to create CURL client easy handle for connection %d\n", 
                    i);
                
                printf("\nUnable to create CURL client easy handle for connection %d\n",
                    i);
                
                free(client_connection_array);
                return -2;
            }
            client_connection_array[i].state = IDLE;
             
            /*  Necessary option for any multi-threaded process using libcurl. 
                Alternative use for SIGPIPE handler and DNS timeouts */
            if((result=curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_NOSIGNAL, 1L)))
            {
                log_error(LOCAL_EVENT, 
                    "\nProblem setting option CURLOPT_NOSIGNAL with return %d\n", 
                    result);
                
                printf("\nProblem setting option CURLOPT_NOSIGNAL with return %d\n", 
                    result);
                
                free(client_connection_array);
                return -3;                
            }   
        }  
        ++call_only_once;
        //~ headers = curl_slist_append(headers, "Content-Type: application/json");
    }
    else
    {
        /* Re-adjust allocation heap memory for connection based struct */
        client_connection_array =
            realloc(client_connection_array,     
                    number_of_connections * sizeof(struct client_connection)); 

        if(NULL == client_connection_array) 
        {
            log_error(LOCAL_EVENT, 
                "\nUnable to allocate updated client connection structure.\n");
            printf("\nUnable to allocate updated client connection structure.\n");
            return -2;            
        }               
        
        if(number_of_connections > configured_number_of_connections)
        {
            for(i=configured_number_of_connections; i<number_of_connections; i++)
            {
                if(!(client_connection_array[i].easy_handle = curl_easy_init()))
                {
                    log_error(LOCAL_EVENT, 
                        "\nUnable to create CURL client easy handle for connection %d\n", 
                        i);
                    
                    printf("\nUnable to create CURL client easy handle for connection %d\n",
                        i);
                    return -2;
                }
                
                if((result=curl_easy_setopt(client_connection_array[i].easy_handle, 
                    CURLOPT_NOSIGNAL, 1L)))
                {
                    log_error(LOCAL_EVENT, 
                        "\nProblem setting option CURLOPT_NOSIGNAL with return %d\n", 
                        result);
                    
                    printf("\nProblem setting option CURLOPT_NOSIGNAL with return %d\n", 
                        result);
                    return -3;                
                }                
            }          
        }
        else if(number_of_connections < configured_number_of_connections)
        {
            for(i=configured_number_of_connections; i>=number_of_connections; i--)
            {
                if((NULL != client_connection_array[i].easy_handle) &&
                    (client_connection_array[i].state == IDLE))
                {                  
                    curl_easy_cleanup(client_connection_array[i].easy_handle);  
                }
            }
        }
    }
    
    if((configured_connection_timeout != connection_timeout) || 
        (configured_number_of_connections != number_of_connections))
    {
        /*  Dynamically change the connection timeout for all currently 
            used connections */
        for(i=0; i<configured_number_of_connections; i++) 
        {    
            if((result = 
                curl_easy_setopt(client_connection_array[i].easy_handle, 
                CURLOPT_TIMEOUT, connection_timeout)))
            {
                log_error(LOCAL_EVENT, 
                    "\nProblem setting option CURLOPT_TIMEOUT with return %d\n", 
                    result);
                
                printf("\nProblem setting option CURLOPT_TIMEOUT with return %d\n", 
                    result);
                return -3;
            }
        }
    }
    configured_number_of_connections = number_of_connections;
    return 0;
}

/**
 * shutdown_http_client
 */
int shutdown_http_client(void)
{
    static unsigned short call_only_once=0;
    int i, resm=CURLM_OK;
    
    /* Clean-up operations for HTTP Client */ 
    if((!call_only_once) && (NULL != multi_handle))
    {
        ++call_only_once;   
        //~ curl_slist_free_all(headers);      
        for(i=0; i<configured_number_of_connections; i++) 
        {
            if(NULL != client_connection_array[i].easy_handle)
            {
                /*  The next operation may fail if the easy_handle is not 
                    currently included in a multi-stack but this isn't a big
                    deal */
                curl_multi_remove_handle(   multi_handle, 
                                            client_connection_array[i].easy_handle);

                curl_easy_cleanup(client_connection_array[i].easy_handle);  
            }
        }
        
        if((resm=curl_multi_cleanup(multi_handle)))
        {
            log_error(LOCAL_EVENT, "Failed to cleanup multi-stack, ret %d\n", resm);
            printf("Failed to cleanup multi-stack, ret %d\n",resm);
        }
        free(client_connection_array);
        return 0;
    }
    return -1;
}
