#include <stdlib.h>
#include <curl/curl.h>
#include <event.h>

/*	I have libcurl4 in /usr/local/lib and libcurl3 in /usr/local, hence:
	$ gcc -g -levent `curl-config --cflags --libs` -Wl,-rpath -Wl,/usr/local/lib -o load load.c */

static const char url[] = "http://www.ebay.de/";
static const size_t num = 100; /* number of transfers */
static const size_t ccy = 1; /* concurrency */

static struct event timeout;
static int U;
static size_t C, F;

static int dummy(CURL *easy, size_t n, size_t s, void *data)
{
	return n*s;
}

static void requesthandler(CURLM *cm)
{
	CURL *ch = curl_easy_init();
	size_t *P = malloc(sizeof(size_t));
	
	++U;
	*P = ++C;
	
	curl_easy_setopt(ch, CURLOPT_PRIVATE, P);
	curl_easy_setopt(ch, CURLOPT_URL, url);
	curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, dummy);
	curl_multi_add_handle(cm, ch);

	while ( CURLM_CALL_MULTI_PERFORM == curl_multi_socket_all(cm, &U) );
	
	fprintf(stderr, "%s:%i Added request     %6zu\n",__FILE__, __LINE__, C);
	
}

static void responsehandler(CURLM *cm)
{
	CURLMsg *msg;
	int remaining = 0;
	
	do {
		msg = curl_multi_info_read(cm, &remaining);
		if (msg && CURLMSG_DONE == msg->msg) {
			int *P;
			
			if (CURLE_OK != msg->data.result) {
				fprintf(stderr, "E:%s:%i %s\n", __FILE__, __LINE__, curl_easy_strerror(msg->data.result));
			}
			
			curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &P);
			
			fprintf(stderr, "%s:%i Finished request  %6zu\n", __FILE__, __LINE__, *P);
			free(P);
			
			if (C < num) {
				requesthandler(cm);
			}
			
			CURL *curl = msg->easy_handle;			
			curl_multi_remove_handle(cm, msg->easy_handle);
			curl_easy_cleanup(curl);
			++F;
		}
	} while (remaining);
}

static void timeout_callback(int socket, short action, void *event_data)
{
	CURLMcode rc;
	CURLM *cm = event_data;
	
	fprintf(stderr, "%s:%i Timeout occurred\n",__FILE__, __LINE__);
	
	while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket(cm, CURL_SOCKET_TIMEOUT, &U)));
	
	if (CURLM_OK != rc) {
		fprintf(stderr, "E: %s:%i %s\n", __FILE__, __LINE__, curl_multi_strerror(rc));
	}
}

static void event_callback(int socket, short action, void *event_data)
{
	CURLMcode rc = CURLE_OK;
	CURLM *cm = event_data;
	
	static const char event_strings[][20] = {"NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"};
	fprintf(stderr, "%s:%i Event on socket %d (%s) \n", __FILE__, __LINE__, socket, event_strings[action]);
	
	do {
		fprintf(stderr, "%s:%i looping in %s \n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
		switch (action & (EV_READ|EV_WRITE)) {
			case EV_READ:
				rc = curl_multi_socket_action(cm, socket, CURL_CSELECT_IN, &U);
				break;
			case EV_WRITE:
				rc = curl_multi_socket_action(cm, socket, CURL_CSELECT_OUT, &U);
				break;
			case EV_READ|EV_WRITE:
				rc = curl_multi_socket_action(cm, socket, CURL_CSELECT_IN|CURL_CSELECT_OUT, &U);
				break;
			default:
				fprintf(stderr, "Unknown event %d\n", (int) action);
				return;
		}
		fprintf(stderr, "%s:%i rc is %i \n", __FILE__, __LINE__, rc);
	} while (CURLM_CALL_MULTI_PERFORM == rc);
	
	if (CURLM_OK != rc) {
		fprintf(stderr, "E: %s:%i %s (%d)\n", __FILE__, __LINE__, curl_multi_strerror(rc), (int) socket);
	}
	
	responsehandler(cm);
	
	/* remove timeout if there are no transfers left */
	if (!U && event_initialized(&timeout) && event_pending(&timeout, EV_TIMEOUT, NULL)) {
		event_del(&timeout);
		fprintf(stderr, "%s:%i Removed timeout\n", __FILE__, __LINE__);
	}
}

static int socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
{
	int events = EV_PERSIST;
	CURLM *cm = socket_data;
	struct event *ev = assign_data;
	
	if (!ev) {
		ev = calloc(1, sizeof(struct event));
		curl_multi_assign(cm, sock, ev);
	} else {

	}
	
	{
		static const char action_strings[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
		fprintf(stderr, "%s:%i Callback on socket %d (%s)\n", __FILE__, __LINE__, (int) sock, action_strings[action]);
	}
	
	switch (action) {
		case CURL_POLL_IN:
			events |= EV_READ;
			break;
		case CURL_POLL_OUT:
			events |= EV_WRITE;
			break;
		case CURL_POLL_INOUT:
			events |= EV_READ|EV_WRITE;
			break;
			
		case CURL_POLL_REMOVE:
			event_del(ev);
			free(ev);
		case CURL_POLL_NONE:
			return 0;
			
		default:
			fprintf(stderr, "%s:%i Unknown socket action %d\n",__FILE__, __LINE__, action);
			return -1;
	}
	
	event_set(ev, sock, events, event_callback, cm);
	event_add(ev, NULL);
	
	return 0;
}

static void timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
{
	if (timeout_ms) {
		struct timeval tv = {10, (timeout_ms % 1000) * 1000};
		
		if (event_initialized(&timeout) && event_pending(&timeout, EV_TIMEOUT, NULL)) {
			event_del(&timeout);
		}
		
		event_set(&timeout, -1, 0, timeout_callback, multi);
		event_add(&timeout, &tv);
		
		fprintf(stderr, "%s:%i Updating timeout (%lu, %lu)\n", __FILE__, __LINE__, (ulong) tv.tv_sec, (ulong) tv.tv_usec);
	}
}

void load(void)
{
	int i;
	struct timeval start_time, end_time;
	CURLM *cm = curl_multi_init();
	
	curl_multi_setopt(cm, CURLMOPT_SOCKETDATA, cm);
	curl_multi_setopt(cm, CURLMOPT_SOCKETFUNCTION, socket_callback);
	curl_multi_setopt(cm, CURLMOPT_TIMERDATA, cm);
	curl_multi_setopt(cm, CURLMOPT_TIMERFUNCTION, timer_callback);
	
	gettimeofday(&start_time, NULL);
	for (i = 0; i < ccy; ++i) {
		requesthandler(cm);
	}
	while (CURLM_CALL_MULTI_PERFORM == curl_multi_socket_all(cm, &U));
	event_dispatch();
	gettimeofday(&end_time, NULL);
	
	curl_multi_cleanup(cm);
	
	fprintf(stdout, "Added: %zu, Finished: %zu, Time: %lums\n", C, F, (end_time.tv_sec*1000+end_time.tv_usec/1000)-(start_time.tv_sec*1000+start_time.tv_usec/1000));
}

int main(int argc, char *argv[])
{
	event_init();
	curl_global_init(CURL_GLOBAL_ALL);
	
	load();
	
	curl_global_cleanup();
	
	return 0;
}

