#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <curl/curl.h> 
#include <linux/types.h> 
#include <linux/socket.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/socket.h> /* socket definitions */ 
#include <sys/types.h> /* socket types */ 
#include <arpa/inet.h> /* inet (3) funtions */ 
#include <unistd.h> /* misc. UNIX functions */ 
#include <errno.h> 
#include <pthread.h>


struct sockaddr_in servaddr; /* socket address structure */ 
#define IP_ADDR "172.16.4.6"
#define PORT 80

#define NO_SOCKET -1

#define NUMT 20
#define CNT 2

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

int g_ms_connection_timeout = 30;
int g_ms_timeout = 85;
struct curl_slist* curl_http_headers;

CURL *curl[CNT]; 
int sockfd[CNT]; 

typedef struct threadInfo {
	int thread_id;
	char URL[256];
}threadInfo_t;


int perform_task(CURLM *master_handle)
{
	CURLMcode retval = CURLM_OK;
	int still_running = 0;
	struct timeval timeout;
	int rc; /* select() return code */
	fd_set fdread;
	fd_set fdwrite;
	fd_set fdexcep;
	int maxfd;
	int repeat = 0;
	//struct timeval start, end, tmp;
	//Curl multi-perform call

	while((retval=curl_multi_perform(master_handle, &still_running)) == CURLM_CALL_MULTI_PERFORM ) {
		printf("in while %d\n", repeat++);
	}
	if ( retval != CURLM_OK) {
		return (-1);
	}
	while(still_running) {

		FD_ZERO(&fdread);
		FD_ZERO(&fdwrite);
		FD_ZERO(&fdexcep);

		timeout.tv_sec =  g_ms_timeout/1000;
		timeout.tv_usec = (g_ms_timeout%1000)*1000; /*conversion factor */

		/* get file descriptors from the transfers */
		retval = curl_multi_fdset(master_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
		if(retval != CURLM_OK){
			return(-1);
		}

		if(maxfd == -1){
			break;
		}
		else{
			if(timeout.tv_sec == 0 && timeout.tv_usec == 0){
				still_running = 0;
				printf("TIMED OUT\n");
				rc = -1;
			}
			else{
				rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
			}
		}

		switch(rc) {
			case -1:
				/* select error */
				//
				/* select error */
				//
				fprintf(stderr,"ERROR:RTB select returned error %s:%d\n",__FILE__,__LINE__);
				still_running = 0;
				//~
				break;
			case 0:
				//Timeout case. setting still_running to 0 essentially breaks the loop
				//
				//fprintf(stderr,"ERROR:RTB select timed out %s:%d\n",__FILE__,__LINE__);
				//~
				still_running = 0;
				break;
			default:
				/* one or more of curl's file descriptors say there's data to read
				   or write */
				while((retval=curl_multi_perform(master_handle, &still_running)) ==
						CURLM_CALL_MULTI_PERFORM );
				printf("in while\n");
				if (retval != CURLM_OK) {
					return (-1);
				}
				break;
		}
	}
	return 0;
}


static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
        int written = fwrite(ptr, size, nmemb, (FILE *)stream);
        return written;
}

curl_socket_t opensocket (void *clientp, curlsocktype purpose, struct curl_sockaddr *address) { 
	int opt_val,*ptr;
	opt_val= 1;
	ptr=&opt_val;
	curl_socket_t sockfd = *(curl_socket_t *)clientp;
	if(sockfd == -1) {
		printf("Inside %s:: creating socket, ",__FUNCTION__);
		if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { 
			printf("Error: socket: %s\n",strerror(errno)); 
			return -1;
		}
		if(setsockopt(sockfd, IPPROTO_TCP, SO_KEEPALIVE, (const void *)ptr, sizeof(int)) == -1) {
			close(sockfd);
			printf("Error: setsockopt: %s\n", strerror(errno));
		}
		printf( "sockfd=%d\n",sockfd); 
		//getchar();
	}
	*(curl_socket_t *)clientp = sockfd;
	return sockfd; 
}

int close_socket (void *clientp, curl_socket_t sockfd) {

	printf("Inside %s:: closing socket, o-%d : c-%d\n",__FUNCTION__, *(curl_socket_t *)clientp, sockfd);
	getchar();

	if( close(sockfd) == -1) {
		fprintf(stderr, "Error: close: %s\n", strerror(errno));
		//getchar();
		return 1;
	} 
	*(curl_socket_t *)clientp = NO_SOCKET;
	return 0;
}


int sockopt_callback(void *clientp, curl_socket_t sockfd, curlsocktype purpose) 
{
	socklen_t *len_ptr;
	socklen_t len = sizeof(servaddr); 
	len_ptr = &len;
	//curl_socket_t sockfd = curlfd;
	printf( "Inside %s::curl_sockfd%d",__FUNCTION__, sockfd);
	//getchar();
	if(getpeername(sockfd,(struct sockaddr *)&servaddr, len_ptr) == -1) { 
		printf( "Before connect:: sockfd=%d\n",sockfd); 
		if(connect(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr)) == -1) { 
			close(sockfd); 
			printf("Error: connect: %s\n", strerror(errno)); 
			return CURLE_COULDNT_CONNECT;
		}
	} 
	return CURL_SOCKOPT_ALREADY_CONNECTED;

}

static void *pull_one_url(void *thd)
{
	int i, *sock_ptr;
	CURLM *master_handle;
	CURLcode res; 
	long sockextr; 
	size_t iolen; 
	int int_res,opt_val,*ptr;
	opt_val= 1;
	ptr=&opt_val;
	CURLMcode curlm_code = CURLM_OK;

	master_handle = curl_multi_init();
	if (master_handle == NULL) {
		printf("curl_multi_init  failed\n");
		return NULL;
	}
	res = curl_multi_setopt(master_handle, CURLMOPT_MAXCONNECTS, 0);
	printf("\nOption CURLMOPT_MAXCONNECTS Set with status: %s\n",curl_multi_strerror(res));
	//getchar();
	//curl_easy_setopt(curl, CURLOPT_URL, "http://172.16.4.6"); //please add ur URL here 
	/* Create the listening socket */ 
	/*if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { 
	  printf("Error: socket: %s\n",strerror(errno)); 
	  return -1; 
	  } */

	 

	/*if(connect(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr)) == -1) { 
	  close(sockfd); 
	  printf("Error: connect: %s\n", strerror(errno)); 
	  } */

	/*if(setsockopt(sockfd, IPPROTO_TCP, SO_KEEPALIVE, (const void *)ptr, sizeof(int)) == -1) {
	  close(sockfd);
	  printf("Error: setsockopt: %s\n", strerror(errno));		
	  }*/

	while(1) {
	
		//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); 
		//res = curl_easy_perform(curl); 
		/*if(CURLE_OK != res) 
		{ 
			printf("Error: %s\n", strerror(res)); 
			return -1; 
			}*/
			
		pthread_mutex_lock( &mutex1 );
		for(i=0; i<CNT; i++) {
			res = curl_easy_setopt(curl[i], CURLOPT_NOPROGRESS, 1L); 
			//printf("\nOption CURLOPT_NOPROGRESS Set with status: %s\n",curl_easy_strerror(res));

			res = curl_easy_setopt(curl[i], CURLOPT_WRITEFUNCTION, write_data); 
			//printf("\nOption CURLOPT_WRITEFUNCTION Set with status: %s\n",curl_easy_strerror(res));

			res = curl_easy_setopt(curl[i], CURLOPT_URL, "http://172.16.4.6"); //please add ur URL here 
			//printf("\nOption CURLOPT_URL Set with status: %s\n",curl_easy_strerror(res));

			res = curl_easy_setopt(curl[i], CURLOPT_OPENSOCKETDATA, &sockfd[i]);
			//printf("\nOption CURLOPT_OPENSOCKETDATA Set with status: %s\n",curl_easy_strerror(res));

			res = curl_easy_setopt(curl[i], CURLOPT_OPENSOCKETFUNCTION, opensocket);
			//printf("\nOption CURLOPT_OPENSOCKETFUNCTION Set with status: %s\n",curl_easy_strerror(res));
		
			res = curl_easy_setopt(curl[i], CURLOPT_SOCKOPTFUNCTION, sockopt_callback); 
			//printf("\nOption CURLOPT_SOCKOPTFUNCTION Set with status:%s\n",curl_easy_strerror(res));

			res = curl_easy_setopt(curl[i], CURLOPT_CLOSESOCKETDATA, &sockfd[i]);
			//printf("\nOption CURLOPT_CLOSESOCKETDATA Set with status: %s\n",curl_easy_strerror(res));
		
			res = curl_easy_setopt(curl[i], CURLOPT_CLOSESOCKETFUNCTION, close_socket);
			//printf("\nOption CURLOPT_CLOSESOCKETFUNCTION Set with status: %s\n",curl_easy_strerror(res));
			
			curlm_code = curl_multi_add_handle(master_handle, curl[i]);
			if (curlm_code != CURLM_OK) {
				printf("curl_multi_add_handle failed\n");
				return NULL;
			}
		}

		//pthread_mutex_lock( &mutex1 );
		perform_task(master_handle);
		//pthread_mutex_unlock( &mutex1 );
 		for(i=0; i<CNT; i++) {
			curl_multi_remove_handle(master_handle, curl[i]);
		}
		pthread_mutex_unlock( &mutex1 );
	}
	return NULL; 
}



int main(int argc, char **argv)
{
	pthread_t tid[NUMT];
	threadInfo_t thread_data[NUMT];
	int i, *sock_ptr;
	int error;
	CURLcode res;
	
	//getchar();
	printf("START %s\n",__FUNCTION__);
	/* Must initialize libcurl before any threads are started */
	curl_global_init(CURL_GLOBAL_ALL);

	memset(&servaddr, 0, sizeof(servaddr)); 
	servaddr.sin_family = AF_INET; 
	servaddr.sin_port = htons(PORT); 

	if(inet_aton(IP_ADDR, &servaddr.sin_addr) <= 0 ) { 
		printf("Error: Invalid remote IP address.\n"); 
		return -1;
	}

	for(i = 0 ; i < CNT ; i++) {
		sockfd[i] = -1;
		/*if(sockfd[i] == -1) {
			printf("Inside %s:: creating socket, ",__FUNCTION__);
			if((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
				printf("Error: socket: %s\n",strerror(errno));
				return -1;
			}
			printf( "sockfd=%d\n",sockfd[i]);
		}*/

		curl[i] = curl_easy_init(); 
		if(curl[i] == NULL) {
			printf("curl_easy_init failed\n");
			return -1;
		}
		/*res = curl_easy_setopt(curl[i], CURLOPT_SOCKOPTFUNCTION, sockopt_callback); 
		printf("\nOption CURLOPT_SOCKOPTFUNCTION Set with status:	%s\n",curl_easy_strerror(res)); */
	}

	for(i=0; i< NUMT-1; i++) {
		thread_data[i].thread_id = i;
		thread_data[i].URL[0] = '\0';
		//strncpy(thread_data[i].URL,OnlyUrl,256);
		error = pthread_create(&tid[i],
				NULL, /* default attributes please */
				pull_one_url,
				(void *)&thread_data[i]);
		if(0 != error)
			fprintf(stderr, "Couldn't run thread number %d, errno %d\n", i, error);
		else
			fprintf(stderr, "Thread %d\n", i);
	}

	/* now wait for all threads to terminate */
	/*for(i=0; i< NUMT; i++) {
	  error = pthread_join(tid[i], NULL);
	  fprintf(stderr, "Thread %d terminated\n", i);
	  }*/
	pull_one_url((void *)&thread_data[i]);

	return 0;
}

