#include <stdio.h>
#include <memory.h>

#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/select.h>
#endif

#include <curl/curl.h>

// Use pause routines
#define USE_PAUSE

// How many times curl_multi_perform should be called before hitting of CURLPAUSE_CONT.
// (including curl_multi_perform that causes WriteFunction to pause writes,
// i.e. 1 means that CURLPAUSE_CONT will be performed immediately after pause.)
#define WAIT_COUNT 32

// Small resource (~10kb)
char * RESOURCE_URL = "http://www.google.com/accounts/TOS";

// Large resource (~300kb) on the server, which allows deflate or gzip encodings.
//char * RESOURCE_URL = "http://www.issart.com/themes/default/flash/eng/menu.swf";

// Easy handle for connection
CURL * easy;

typedef enum
{
	STATE_PAUSE = 0,   // write function should return CURL_WRITEFUNC_PAUSE
	STATE_WAIT = 1,    // waiting for CURLPAUSE_CONT
	STATE_WRITE = 2,   // write function should perform write
	STATE_WRITTEN = 3  // write function have performed write
} State;

// Current state
State state = STATE_PAUSE;

// Countdown to continue writes
int waitCount = 0;

// Received data size
size_t datasize = 0;
// Received data
char * data;

size_t WriteFunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
	size_t rs;
#ifdef USE_PAUSE
	switch (state)
	{
		case STATE_PAUSE:
		{
			state = STATE_WAIT;
			waitCount = WAIT_COUNT;
			return CURL_WRITEFUNC_PAUSE;
		}
		case STATE_WAIT:
		{
			// callback shouldn't be called in this state
			printf("WARNING: write-callback called in STATE_WAIT\n");
			//return CURLE_WRITE_ERROR;
			return CURL_WRITEFUNC_PAUSE;
		}
		case STATE_WRITE:
		{
			state = STATE_WRITTEN; // signaling that write is performed
			break; 
		}
	}
#endif
	rs = size * nmemb;
	memcpy(data + datasize, ptr, rs);
	datasize += rs;
	return nmemb;
}

int perform(CURLM * multi)
{
	int handles;
	CURLMcode code;
    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd;
	CURLMsg * msg;
	int msgcount;

	for (;;)
	{
		do {
			code = curl_multi_perform(multi, &handles);
		} while (CURLM_CALL_MULTI_PERFORM == code);
#ifdef USE_PAUSE
		switch (state)
		{
			case STATE_WAIT:
			{
				if (--waitCount == 0)
				{
					state = STATE_WRITE;
					curl_easy_pause(easy, CURLPAUSE_CONT);
				}
				break;
			}
			case STATE_WRITTEN:
			{
				state = STATE_PAUSE;
				break;
			}
		}
#endif
		if (0 == handles)
		{
			msg = curl_multi_info_read(multi, &msgcount);
			if (msg && (CURLMSG_DONE == msg->msg))
			{
				return CURLE_OK == msg->data.result;
			}
			return 0;
		}
		switch (code)
		{
			case CURLM_OK: break;
			case CURLM_CALL_MULTI_PERFORM: continue;
			default: return 0;
		}
		FD_ZERO(&fdread);
		FD_ZERO(&fdwrite);
		FD_ZERO(&fdexcep);
		curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd);
		if (maxfd >= 0)
		{
			select(maxfd + 1, &fdread, &fdwrite, &fdexcep, 0);
		}
	}
}

int main(int argc, char **argv)
{
	FILE * file;
	CURLM * multi;
	struct curl_slist * headers = NULL;

	data = malloc(0x300000);

	curl_global_init(CURL_GLOBAL_ALL);
	multi = curl_multi_init();
	easy = curl_easy_init();

	curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, WriteFunction);
	curl_easy_setopt(easy, CURLOPT_URL, RESOURCE_URL);
//	curl_easy_setopt(easy, CURLOPT_ENCODING, "gzip,deflate");

	curl_multi_add_handle(multi, easy);
	if (perform(multi))
		printf("OK: data retrieved successfully\n");
	else
		printf("ERROR: data retrieve failed\n");

	curl_multi_remove_handle(multi, easy);
	curl_easy_cleanup(easy);
	curl_multi_cleanup(multi);

	curl_slist_free_all(headers);

	curl_global_cleanup();

	file = fopen("resource.bin", "wb");
	fwrite(data, 1, datasize, file);
	fclose(file);

	free(data);

	return 0;
}

