#include "MultiCurl.h"

typedef struct _MCB
{
	struct curl_slist *slist;
	void *ptr;

	char *buf;
	int total;
	int used;

}MCB;

static size_t CurlWriteCallback( char *ptr, size_t size, size_t nmemb, void *userdata)
{
	if (size * nmemb == 0) return 0;

	int len = (int)(size * nmemb);	

	// debug
	//char debug[1024*16];
	//snprintf(debug, 1024*16, ptr, len);
	//printf("\n###\n%s\n###\n", debug);

	MCB *mcb = (MCB *)userdata;
	if (mcb->ptr == NULL)
	{
		// do nothing
		return len;
	}

	if (len + mcb->used > mcb->total)
	{
		XA_ERROR("buffer is too small: curSize:%d, at least:%d", mcb->total, len + mcb->used);
		return 0; // This will abort the transfer and return CURLE_WRITE_ERROR.
	}

	memcpy(mcb->buf + mcb->used, ptr, len);
	mcb->used += len;
	return len;
}

int CMultiCurl::Init(MultiCurlCallback callback, int bufLen)
{
	m_callback = callback;
	m_mcurl = curl_multi_init();
	if (m_mcurl == NULL)
	{
		XA_ERROR("");
		return -1;
	}
	m_bufLen = bufLen;

	return 0;
}

int CMultiCurl::Poll(int timeout/*msec*/)
{
	struct timeval rTimeo;
	rTimeo.tv_sec = timeout / 1000;
	rTimeo.tv_usec = (timeout % 1000) * 1000;

	long curlTimeo = -1;
	curl_multi_timeout(m_mcurl, &curlTimeo);
	if (curlTimeo >= 0 && curlTimeo < timeout)
	{
		rTimeo.tv_sec = curlTimeo / 1000;
		rTimeo.tv_usec = (curlTimeo % 1000) * 1000;
	}
	//XA_TRACE("multi_curl select timeout: %d, %d", rTimeo.tv_sec, rTimeo.tv_usec);

	fd_set fdread;
	fd_set fdwrite;
	fd_set fdexcep;
	int maxfd = -1;
	FD_ZERO(&fdread);
	FD_ZERO(&fdwrite);
	FD_ZERO(&fdexcep);

	int ret = curl_multi_fdset(m_mcurl, &fdread, &fdwrite, &fdexcep, &maxfd);
	if (ret != 0)
	{
		XA_ERROR("%d", ret);
		return -1;
	}

	ret = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &rTimeo);
	if (ret == -1)
	{
		XA_ERROR("%d: %s", ret, strerror(errno));
		return -1;
	}

	int stillR = 0;
	while (CURLM_CALL_MULTI_PERFORM 
				== (ret = curl_multi_perform(m_mcurl, &stillR))) {}
	if (ret != 0)
	{
		XA_ERROR("%d", ret);
	}

	// check msg
	int rest = 0;
	CURLMsg *msg = NULL;
	while (NULL != (msg = curl_multi_info_read(m_mcurl, &rest)))
	{
		if (msg->msg == CURLMSG_DONE)
		{
			MCB *mcb = (MCB *)GetFromMap(msg->easy_handle);						
			DelFromMap(msg->easy_handle);
			XA_ASSERT(mcb != NULL);
			if (mcb->ptr != NULL)
			{
				m_callback(msg->data.result, mcb->ptr, mcb->buf, mcb->used);
				delete [] mcb->buf;
			}

			if (mcb->slist != NULL)
			{
				curl_slist_free_all(mcb->slist);
			}
			delete mcb;

			curl_multi_remove_handle(m_mcurl, msg->easy_handle);
			curl_easy_cleanup(msg->easy_handle);
		}
	}

	
	return 0;
}

int CMultiCurl::Exit()
{
	return 0;
}

int CMultiCurl::Post(const char *url, const char *param, void *ptr,
			const char *host, int timeout/*msec*/,
			const char *encoding)
{
	CURL *curl = curl_easy_init();	
	XA_ASSERT(curl != NULL);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	//curl_easy_setopt(curl, CURLOPT_HEADER, 1); 
	curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);

	curl_easy_setopt(curl, CURLOPT_POST, 1);
	//curl_easy_setopt(curl, CURLOPT_POSTFIELDS, param); 
	curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, param); 
	curl_easy_setopt(curl, CURLOPT_URL, url);
	struct curl_slist *slist = NULL;
	if (host != NULL)
	{
		char buf[256];
		snprintf(buf, 256, "Host:%s", host);

		slist = curl_slist_append(slist, buf);
		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
	}

	curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
	curl_easy_setopt(curl, CURLOPT_ENCODING , encoding);

	MCB *mcb = new MCB();
	mcb->slist = slist;
	mcb->ptr = ptr;
	mcb->buf = NULL;
	mcb->total = 0;
	mcb->used = 0;

	if (ptr != NULL)// otherwise no callback
	{
		mcb->buf = new char[m_bufLen];
		mcb->total = m_bufLen;
	}
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)mcb); 


	// debug
	//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
	//printf("\n##post\n%s\n##\n", param);

	int ret = curl_multi_add_handle(m_mcurl, curl);
	XA_ASSERT(ret == 0);
	
	AddToMap(curl, (void *)mcb);
	return 0;
}

void CMultiCurl::AddToMap(CURL* key, void *value)
{
	unsigned long k = (unsigned long)key;
	int c = m_mcbMap.count(k);
	XA_ASSERT(c == 0);
	m_mcbMap[k] = value;
	return;
}

void CMultiCurl::DelFromMap(CURL *key)
{
	unsigned long k = (unsigned long)key;
	int c = m_mcbMap.count(k);
	XA_ASSERT(c == 1);
	m_mcbMap.erase(k);
	return;
}

void *CMultiCurl::GetFromMap(CURL *key)
{
	unsigned long k = (unsigned long)key;
	int c = m_mcbMap.count(k);
	XA_ASSERT(c == 1);
	return m_mcbMap[k];
}

