#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libuv_dbus.h>
#include <curl/curl.h>
#include <uthash/uthash.h>
#include <uthash/utstring.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <quickmail.h>
#include <uthash/utlist.h>

#include "log.h"
#include "inet_api.h"

#define MAX_TIMEOUT_VALUE			(10)
#define SSL_CA_FILE                 ("/etc/ssl/certs/ca-certificates.crt")

typedef enum
{
	INET_HTTP_DOWNLOAD_FILE		= 0,
	INET_HTTP_WEBSERVICE_POST,
} INET_ACCESS_TYPE;

typedef struct
{
	uv_poll_t		uvPool;
	curl_socket_t	sock;
} CURL_CONTEXT_DATA, *PCURL_CONTEXT_DATA;

typedef struct
{
	char* 				pReqUrl;
	char				sPath[MAX_PATH];
	char				sDlPath[MAX_PATH];
	char*				pTaskUuid;
	INET_ACCESS_TYPE	type;
	unsigned int		dlSize;
	unsigned int		lastTm;
	unsigned int		createTm;
	uv_fs_t				uvFsOpen;
	uv_fs_t				uvFsWrite;
	uv_fs_t				uvFsDataSync;
	uv_fs_t				uvFsClose;
	uv_buf_t			uvFsBuf;
	OnProgressNotify	onPrgCb;
	OnHttpResponse		onRspCb;
	int					isCancel;
	CURL*				pCurl;
	void*				pData;
	int					errCode;
} HTTP_REQ_PARAMS, *PHTTP_REQ_PARAMS;

typedef struct
{
	char 				*pTaskUuid;
	unsigned int		uRetryTimes;
	PHTTP_REQ_PARAMS	pCurlItem;

	UT_hash_handle  hh;         ///< UT Hash handle
} CURL_HANDLE_TBL, *PCURL_HANDLE_TBL;

static uv_timer_t   g_uvCurlTm;
static uv_timer_t	g_uvDlTm;
static CURLM*       g_pCurl = NULL;
static uv_loop_t*	g_pMainLoop = NULL;
static PCURL_HANDLE_TBL	g_ReqHandleTbl	 = NULL;
static uv_rwlock_t      g_uvHashRwLock;
static unsigned		g_TotalDownloads = 0;

static void __addReqIdToTable(const char* pTaskUuid, PHTTP_REQ_PARAMS pParams)
{
	PCURL_HANDLE_TBL	pItem = NULL;
	
	HASH_FIND_STR(g_ReqHandleTbl, pTaskUuid, pItem);
	
	if(pItem == NULL)
	{
		pItem 			= (PCURL_HANDLE_TBL)malloc(sizeof(CURL_HANDLE_TBL));

		memset(pItem, 0, sizeof(CURL_HANDLE_TBL));
		pItem->pTaskUuid	= (char*)pTaskUuid;

		uv_rwlock_wrlock(&g_uvHashRwLock);
		HASH_ADD_STR(g_ReqHandleTbl, pTaskUuid, pItem);
		uv_rwlock_wrunlock(&g_uvHashRwLock);
	}
	
	pItem->pCurlItem	= pParams;
	pItem->uRetryTimes++;
}

static void __removeReqIdFromTable(const char* pTaskUuid)
{
	PCURL_HANDLE_TBL	pItem = NULL;

	uv_rwlock_wrlock(&g_uvHashRwLock);
	HASH_FIND_STR(g_ReqHandleTbl, pTaskUuid, pItem);
	if(pItem != NULL)
	{
		HASH_DEL(g_ReqHandleTbl, pItem);

		if(pItem->pTaskUuid)
		{
			free(pItem->pTaskUuid);
		}

		free(pItem);
	}
	uv_rwlock_wrunlock(&g_uvHashRwLock);
}

static void __cancelDownloadTask(PHTTP_REQ_PARAMS pItem)
{
	if(pItem)
	{
		pItem->isCancel = TRUE;
	}
}

static void __uvFsCloseCb(uv_fs_t *puvFs)
{
	PHTTP_REQ_PARAMS pParams = (PHTTP_REQ_PARAMS)puvFs->data;

    if(puvFs->result < 0)
    {
        LOG_EX(LOG_Error, "[%s] Error: %d\n", __FUNCTION__, puvFs->result);
    }

	uv_fs_req_cleanup(puvFs);

	if(pParams->type == INET_HTTP_DOWNLOAD_FILE)
	{
		if(strcmp(pParams->sDlPath, pParams->sPath) != 0)
		{
			CopyFile(pParams->sDlPath, pParams->sPath);
			unlink(pParams->sDlPath);
		}

		if(pParams->errCode == CURLE_ABORTED_BY_CALLBACK)
		{
			pParams->errCode = CURLE_OPERATION_TIMEDOUT;
		}

		if(pParams->onRspCb && pParams->isCancel == FALSE)
		{
			pParams->onRspCb(NULL, pParams->dlSize, pParams->pReqUrl, pParams->sPath,
							 pParams->pTaskUuid, -pParams->errCode, pParams->pData);
		}
	}
	
	__removeReqIdFromTable(pParams->pTaskUuid);

	if(pParams->pReqUrl)
	{       
		free(pParams->pReqUrl);
		pParams->pReqUrl = NULL;
	}

	free(pParams);
	pParams = NULL;
}

static void __uvFsDataSyncCb(uv_fs_t *puvFs)
{
	PHTTP_REQ_PARAMS pParams = (PHTTP_REQ_PARAMS)puvFs->data;

    if(puvFs->result < 0)
    {
        LOG_EX(LOG_Error, "[%s] Error: %d\n", __FUNCTION__, puvFs->result);
    }

    uv_fs_req_cleanup(puvFs);
	
	uv_fs_close(g_pMainLoop, &pParams->uvFsClose, pParams->uvFsOpen.result, __uvFsCloseCb);
}

static PCURL_CONTEXT_DATA __createCurlContext(curl_socket_t sock)
{
	PCURL_CONTEXT_DATA pContext = (PCURL_CONTEXT_DATA)malloc(sizeof(CURL_CONTEXT_DATA));

	pContext->sock	= sock;

	if(uv_poll_init_socket(g_pMainLoop, &pContext->uvPool, sock) != 0)
	{
		LOG_EX(LOG_Error, "uv_poll_init_socket Error\n");
	}

	pContext->uvPool.data	= pContext;

	return (pContext);
}

static void __uvCloseCb(uv_handle_t *puvPoll)
{
	PCURL_CONTEXT_DATA pContext = (PCURL_CONTEXT_DATA)puvPoll->data;
	free(pContext);
}

static void __destoryCurlContext(PCURL_CONTEXT_DATA pContext)
{
	uv_close((uv_handle_t *)&pContext->uvPool, __uvCloseCb);
}

static void __checkMultiInfoTimeout(void)
{
    PHTTP_REQ_PARAMS	pReq;
	CURLMsg *pMsg	= NULL;
	int iPending;

    while((pMsg = curl_multi_info_read(g_pCurl, &iPending)))
	{
        switch(pMsg->msg)
		{
			case CURLMSG_DONE:
				curl_easy_getinfo(pMsg->easy_handle, CURLINFO_PRIVATE, (void*)&pReq);
	
				LOG_EX(LOG_Debug, "Cleanup CURL: %p\n", pMsg->easy_handle);

				curl_multi_remove_handle(g_pCurl, pMsg->easy_handle);
				curl_easy_cleanup(pMsg->easy_handle);
				
				if(pReq)
				{
					if(pReq->type == INET_HTTP_DOWNLOAD_FILE)
					{
						uv_fs_close(g_pMainLoop, &pReq->uvFsDataSync, pReq->uvFsOpen.result, NULL);
					}
					
					if(pReq->onRspCb && pReq->isCancel == FALSE)
					{
						pReq->onRspCb(NULL, 0, pReq->pReqUrl, pReq->sPath, pReq->pTaskUuid,
										  -CURLE_OPERATION_TIMEDOUT, pReq->pData);
					}

                    if(pReq->pReqUrl)
                    {
                        free(pReq->pReqUrl);
                        pReq->pReqUrl = NULL;
                    }

                    __removeReqIdFromTable(pReq->pTaskUuid);

                    free(pReq);
					pReq = NULL;
				}
		
				break;

		   default:
			    LOG_EX(LOG_Error, "pMsg->msg(%d) != CURLMSG_DONE\n", pMsg->msg);
				return;
		}
    }
}

static void __checkMultiInfo(void)
{
	PHTTP_REQ_PARAMS	pReq;
	CURLMsg *pMsg	= NULL;
	int iPending;
	
	while((pMsg = curl_multi_info_read(g_pCurl, &iPending)))
	{
		switch(pMsg->msg)
		{
			case CURLMSG_DONE:
				curl_easy_getinfo(pMsg->easy_handle, CURLINFO_PRIVATE, (void*)&pReq);
	
				curl_multi_remove_handle(g_pCurl, pMsg->easy_handle);
				LOG_EX(LOG_Debug, "Cleanup CURL: %p\n", pMsg->easy_handle);
				curl_easy_cleanup(pMsg->easy_handle);
				
				if(pReq)
				{
					if(pReq->type == INET_HTTP_DOWNLOAD_FILE)
					{
						if(pMsg->data.result != CURLE_OK)
						{
							pReq->errCode = pMsg->data.result;
						}
						else
						{
							pReq->errCode = 0;
						}

						uv_fs_fdatasync(g_pMainLoop, &pReq->uvFsDataSync, pReq->uvFsOpen.result, __uvFsDataSyncCb);
					}
					else if(pReq->type == INET_HTTP_WEBSERVICE_POST)
					{
						if(pMsg->data.result != CURLE_OK)
						{
							if(pReq->onRspCb && pReq->isCancel == FALSE)
							{
								pReq->onRspCb(pReq->uvFsBuf.base, pReq->dlSize, pReq->pReqUrl, pReq->sPath,
											  pReq->pTaskUuid, -pMsg->data.result, pReq->pData);
							}
						}
						else
						{
							if(pReq->onRspCb && pReq->isCancel == FALSE)
							{
								pReq->onRspCb(pReq->uvFsBuf.base, pReq->dlSize, pReq->pReqUrl,  pReq->sPath,
											  pReq->pTaskUuid, 0, pReq->pData);
							}
						}

						if(pReq->uvFsBuf.base)
						{
							free(pReq->uvFsBuf.base);
						}

						if(pReq->pReqUrl)
						{
							free(pReq->pReqUrl);
							pReq->pReqUrl = NULL;
						}

						__removeReqIdFromTable(pReq->pTaskUuid);

						free(pReq);
						pReq = NULL;
					}
					else
					{
						if(pMsg->data.result != CURLE_OK)
						{
							if(pReq->onRspCb && pReq->isCancel == FALSE){
								pReq->onRspCb(NULL, 0, pReq->pReqUrl, pReq->sPath, pReq->pTaskUuid, -pMsg->data.result, pReq->pData);
							}
						}
						else
						{
							if(pReq->onRspCb && pReq->isCancel == FALSE)
							{
								pReq->onRspCb(NULL, 0, pReq->pReqUrl, pReq->sPath, pReq->pTaskUuid, 0, pReq->pData);
							}
						}

						if(pReq->pReqUrl)
						{
							free(pReq->pReqUrl);
							pReq->pReqUrl = NULL;
						}

						__removeReqIdFromTable(pReq->pTaskUuid);

						free(pReq);
						pReq = NULL;
					}
				}
		
				break;

		    default:
			    LOG_EX(LOG_Error, "pMsg->msg(%d) != CURLMSG_DONE\n", pMsg->msg);
				return;
		}
	}
}

static void __onDlTmoutCb(uv_timer_t *pufTimer)
{
	PCURL_HANDLE_TBL pItem = NULL, pTemp = NULL;
	unsigned int curTm = (unsigned int)LIBUV_CURRENT_TIME_S();

	HASH_ITER(hh, g_ReqHandleTbl, pItem, pTemp)
	{
		int dlTime;

		if(pItem->pCurlItem->isCancel)
		{
			continue;
		}

		dlTime = curTm - pItem->pCurlItem->createTm;

		// 下载时间大于10s且平均下载速度小于10K/s超时
		if((dlTime * 10000 > pItem->pCurlItem->dlSize) && dlTime > 10)
		{
			LOG_EX(LOG_Error, "Download Speed less than 10k/s: %s (%uK/%ds)\n", 
				   pItem->pTaskUuid, pItem->pCurlItem->dlSize / 1000, dlTime);

			__cancelDownloadTask(pItem->pCurlItem);
			if(pItem->pCurlItem->onRspCb)
			{
				pItem->pCurlItem->onRspCb(NULL, 
										  pItem->pCurlItem->dlSize, 
										  pItem->pCurlItem->pReqUrl, 
										  pItem->pCurlItem->sPath, 
										  pItem->pCurlItem->pTaskUuid, 
										  -CURLE_OPERATION_TIMEDOUT, 
										  pItem->pCurlItem->pData);
			}
			break;
		}

		// 10秒内没有下载任何数据超时
		if(pItem->pCurlItem->lastTm > 0)
		{
			if(curTm >  pItem->pCurlItem->lastTm + MAX_TIMEOUT_VALUE)
			{
				LOG_EX(LOG_Error, "Download Timeout: %s\n", pItem->pTaskUuid);
				__cancelDownloadTask(pItem->pCurlItem);
				if(pItem->pCurlItem->onRspCb)
				{
					pItem->pCurlItem->onRspCb(NULL, 
											  pItem->pCurlItem->dlSize, 
											  pItem->pCurlItem->pReqUrl, 
											  pItem->pCurlItem->sPath, 
											  pItem->pCurlItem->pTaskUuid, 
											  -CURLE_OPERATION_TIMEDOUT, 
											  pItem->pCurlItem->pData);
				}
				break;
			}
		}

		// 下载最长时间设置为1800秒(30分钟)
		if(dlTime > 1800)
		{
			LOG_EX(LOG_Error, "Download More than 1800 seconds: %s (%uK/%ds)\n", 
				   pItem->pTaskUuid, pItem->pCurlItem->dlSize/1000, dlTime);
			__cancelDownloadTask(pItem->pCurlItem);
			if(pItem->pCurlItem->onRspCb)
			{
				pItem->pCurlItem->onRspCb(NULL, 
										  pItem->pCurlItem->dlSize, 
										  pItem->pCurlItem->pReqUrl, 
										  pItem->pCurlItem->sPath, 
										  pItem->pCurlItem->pTaskUuid, 
										  -CURLE_OPERATION_TIMEDOUT, 
										  pItem->pCurlItem->pData);
			}
			break;
		}
	}
}

static void __onTimeoutCb(uv_timer_t *pufTimer)
{
	int iRun;

	curl_multi_socket_action(g_pCurl, CURL_SOCKET_TIMEOUT, 0, &iRun);

	__checkMultiInfoTimeout();
}

static int __curlTimerCb(CURLM *multi,    /* multi handle */
						 long timeout_ms, /* see above */
						 void *userp)    /* private callback pointer */
{
	if(timeout_ms <= 0)
	{
		timeout_ms = 1;
	}

	uv_timer_start(&g_uvCurlTm, __onTimeoutCb, timeout_ms, 0);

	return 0;
}

static void __curlPollCb(uv_poll_t *pPoll, int status, int events)
{
	int 		iRun;
	int 		flags;
	PCURL_CONTEXT_DATA	pContext = NULL;

	uv_timer_stop(&g_uvCurlTm);

	if(events & UV_READABLE)
	{
		flags	= CURL_CSELECT_IN;
	}
	else if(events & UV_WRITABLE)
	{
		flags	= CURL_CSELECT_OUT;
	}

	pContext = (PCURL_CONTEXT_DATA)pPoll;
	curl_multi_socket_action(g_pCurl, pContext->sock, flags, &iRun);
	__checkMultiInfo();
}

static int __curlSockCb(CURL *easy,      /* easy handle */
                    curl_socket_t s, /* socket */
                    int what,        /* describes the socket */
                    void *userp,     /* private callback pointer */
                    void *socketp)   /* private socket pointer */
{
	PCURL_CONTEXT_DATA pContext 	= NULL;

	if(what == CURL_POLL_IN || what == CURL_POLL_OUT)
	{
		if(socketp)
		{
			pContext	= (PCURL_CONTEXT_DATA)socketp;
		}
		else
		{
			pContext	= __createCurlContext(s);
		}

		curl_multi_assign(g_pCurl, s, (void *)pContext);
	}

	switch(what)
	{
		case CURL_POLL_IN:
			uv_poll_start(&pContext->uvPool, UV_READABLE, __curlPollCb);
			break;

		case CURL_POLL_OUT:
			uv_poll_start(&pContext->uvPool, UV_WRITABLE, __curlPollCb);
			break;

		case CURL_POLL_REMOVE:
			if(socketp)
			{
				uv_poll_stop(&((PCURL_CONTEXT_DATA)socketp)->uvPool);
				__destoryCurlContext((PCURL_CONTEXT_DATA)socketp);
				curl_multi_assign(g_pCurl, s, NULL);
			}
			break;

		default:
			return (0);
	}

	return (0);
}

static size_t __writeDataCb(void *pData, size_t size, size_t nmemb, void *pParams)
{
	PHTTP_REQ_PARAMS	pReq = (PHTTP_REQ_PARAMS)pParams;
	int 				iMemSize = size * nmemb;

	//print_hex_dump_bytes("OTA", DUMP_PREFIX_ADDRESS, pData, size * nmemb);

	if(pReq->isCancel)
	{
		return 0;
	}

	pReq->lastTm	= LIBUV_CURRENT_TIME_S();

	if(pReq->type == INET_HTTP_DOWNLOAD_FILE)
	{
		int wr = 0;

		pReq->uvFsBuf = uv_buf_init(pData, iMemSize);
		
		wr = uv_fs_write(g_pMainLoop, &pReq->uvFsWrite, pReq->uvFsOpen.result, &pReq->uvFsBuf, 1, -1, NULL);

		if(wr > 0)
		{
			pReq->dlSize += wr;
		}
	}
	else if(pReq->type == INET_HTTP_WEBSERVICE_POST)
	{
		int newSize = 0;

		if(pReq->uvFsBuf.base == NULL && pReq->uvFsBuf.len == 0)
		{
			newSize = iMemSize + 1;
			//fprintf(stdout, "size = %d, newsize = %d, dlsize = %d\n", iMemSize, newSize, pReq->dlSize);
			pReq->uvFsBuf.base = malloc(newSize);
			memcpy(pReq->uvFsBuf.base, pData, iMemSize);
		}
		else
		{
			newSize = pReq->dlSize + iMemSize + 1;
			//fprintf(stdout, "::size = %d, newsize = %d, dlsize = %d\n", iMemSize, newSize, pReq->dlSize);
			pReq->uvFsBuf.base = realloc(pReq->uvFsBuf.base, newSize);
			memcpy(pReq->uvFsBuf.base + pReq->dlSize, pData, iMemSize);
		}
		
		pReq->uvFsBuf.base[pReq->dlSize] = 0;
		pReq->dlSize += iMemSize;
	}

	return (size * nmemb);
}

static int __progressCb(void*	pData,
					 double total,
					 double now,
					 double ultotal,
					 double ulnow)
{
	PHTTP_REQ_PARAMS pParams = (PHTTP_REQ_PARAMS)pData;

	if(pParams->onPrgCb)
	{
		if(pParams->type == INET_HTTP_DOWNLOAD_FILE)
		{
			pParams->onPrgCb(pParams->pReqUrl, pParams->pTaskUuid, (unsigned char)(now * 100.0 / total), pParams->pData);
		}
	}

	if(pParams->isCancel)
	{
		LOG_EX(LOG_Debug, "Cancel Download: %s\n", pParams->pTaskUuid);
		return (-CURLE_OPERATION_TIMEDOUT);
	}

	return (0);
}

static size_t __getRemoteSizeCb(void *pData, size_t size, size_t nmemb, void *pParams)
{
	return (size * nmemb);
}

static int __iNetGetRemoteSize(const char* pURL, unsigned int reqId)
{
	double size 	= 0.0;
	CURL *pCurl 	=  curl_easy_init();
	CURLcode res;

	curl_easy_setopt(pCurl, CURLOPT_URL, pURL);  
    curl_easy_setopt(pCurl, CURLOPT_NOBODY, 1L);  
    curl_easy_setopt(pCurl, CURLOPT_HEADERFUNCTION, __getRemoteSizeCb);  
    curl_easy_setopt(pCurl, CURLOPT_FOLLOWLOCATION, 1L);  
	
	curl_easy_perform(pCurl);

	res = curl_easy_getinfo(pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);

	if(res != CURLE_OK)
	{
		return (-1);
	}
	
	curl_easy_cleanup(pCurl);

	return (int)(size);
}

#if 0
static const char* __restartDlFileAsync(PHTTP_REQ_PARAMS pParams)
{
	CURL *pCurl = curl_easy_init();
	
	pParams->type		= INET_HTTP_DOWNLOAD_FILE;
	pParams->dlSize		= 0;
	pParams->pCurl		= pCurl;
	pParams->lastTm		= 0;

	uv_fs_open(g_pMainLoop, 
			   &pParams->uvFsOpen, 
			   pParams->sDlPath, 
			   O_RDWR | O_CREAT | O_TRUNC, 
			   S_IRUSR | S_IWUSR,
			   NULL);

	curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, __writeDataCb);
	curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, pParams);
	curl_easy_setopt(pCurl, CURLOPT_PRIVATE, pParams);
	curl_easy_setopt(pCurl, CURLOPT_URL, pParams->pReqUrl);
	
	curl_easy_setopt(pCurl, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(pCurl, CURLOPT_PROGRESSFUNCTION, __progressCb);
	curl_easy_setopt(pCurl, CURLOPT_PROGRESSDATA, pParams);
	
	curl_easy_setopt(pCurl, CURLOPT_LOW_SPEED_LIMIT, 10000L); 	// 10K bytes
    curl_easy_setopt(pCurl, CURLOPT_LOW_SPEED_TIME, 10L);		// 30 seconds

    curl_easy_setopt(pCurl, CURLOPT_CONNECTTIMEOUT, 10L);

#ifdef SKIP_PEER_VERIFICATION
    /*
     * If you want to connect to a site who isn't using a certificate that is
     * signed by one of the certs in the CA bundle you have, you can skip the
     * verification of the server's certificate. This makes the connection
     * A LOT LESS SECURE.
     *
     * If you have a CA cert for the server stored someplace else than in the
     * default bundle, then the CURLOPT_CAPATH option might come handy for
     * you.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
#else    
    curl_easy_setopt(pCurl, CURLOPT_CAINFO, SSL_CA_FILE);
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
#endif

#ifdef SKIP_HOSTNAME_VERIFICATION
    /*
     * If the site you're connecting to uses a different host name that what
     * they have mentioned in their server certificate's commonName (or
     * subjectAltName) fields, libcurl will refuse to connect. You can skip
     * this check, but this will make the connection less secure.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
	
	//LOG_EX(LOG_Debug, "Total Size = %d\n", __iNetGetRemoteSize(pURL, 0));

	curl_multi_add_handle(g_pCurl, pCurl);
	
	__addReqIdToTable(pParams->pTaskUuid, pCurl);

	return (pParams->pTaskUuid);
}
#endif

const char* InetHttpDlFileAsync(const char *pURL, 
						 const char *pPath, 
						 OnHttpResponse onRespCb, 
						 OnProgressNotify onProgressCb, 
						 void* pData)
{
	CURLMcode   ret;
	uuid_t		msgId;
	char		strMsgId[64];
	PHTTP_REQ_PARAMS pParams = NULL;
	CURL *pCurl = NULL;
	unsigned long long uMemFreeSize = GetPartitionFreeSize("/tmp/");

	if(pURL == NULL || strlen(pURL) == 0 || onRespCb == NULL)
	{
		free(pParams);
		return (NULL);
	}

	LOG_EX(LOG_Debug, "Begin Download: %s --> %s\n", pURL, pPath);

	pParams = (PHTTP_REQ_PARAMS)malloc(sizeof(HTTP_REQ_PARAMS));

	memset(pParams, 0, sizeof(HTTP_REQ_PARAMS));

	pCurl = curl_easy_init();

	pParams->onRspCb	= onRespCb;
	pParams->pReqUrl	= (char *)malloc(strlen(pURL) + 1);
	pParams->type		= INET_HTTP_DOWNLOAD_FILE;
	pParams->dlSize		= 0;
	pParams->onPrgCb	= onProgressCb;
	pParams->pData		= pData;
	pParams->pCurl		= pCurl;
	pParams->lastTm		= 0;
	pParams->isCancel	= FALSE;
	pParams->createTm	= (unsigned int)LIBUV_CURRENT_TIME_S();

	memset(pParams->pReqUrl, 0, strlen(pURL) + 1);
	strcpy(pParams->pReqUrl, pURL);
	
	uuid_generate_random(msgId);
	memset(strMsgId, 0, 64);
	uuid_unparse_lower(msgId, strMsgId);
	pParams->pTaskUuid	= strdup(strMsgId);

	if(pPath == NULL)
	{
		sprintf(pParams->sPath, "./%s", basename_v2(pURL));
	}
	else
	{
		strcpy(pParams->sPath, pPath);
	}

	// Memory Free More Than 100M, Download Temp File To Memory
	if(uMemFreeSize >= 100 * 1024 * 1024 &&
		strncmp(pParams->sPath, "/tmp/", 5) != 0)
	{
		int ret = system("mkdir /tmp/dl -p");
		sprintf(pParams->sDlPath, "/tmp/dl/%s_%s.dl", basename_v2(pParams->sPath), pParams->pTaskUuid);
	}
	else
	{
		strcpy(pParams->sDlPath, pParams->sPath);
	}
	
	pParams->uvFsDataSync.data 	= pParams;
	pParams->uvFsClose.data		= pParams;

	uv_fs_open(g_pMainLoop, 
			   &pParams->uvFsOpen, 
			   pParams->sDlPath, 
			   O_RDWR | O_CREAT | O_TRUNC, 
			   S_IRUSR | S_IWUSR,
			   NULL);

	curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, __writeDataCb);
	curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, pParams);
	curl_easy_setopt(pCurl, CURLOPT_PRIVATE, pParams);
	curl_easy_setopt(pCurl, CURLOPT_URL, pURL);
	
	curl_easy_setopt(pCurl, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(pCurl, CURLOPT_PROGRESSFUNCTION, __progressCb);
	curl_easy_setopt(pCurl, CURLOPT_PROGRESSDATA, pParams);

	//curl_easy_setopt(pCurl, CURLOPT_TIMEOUT,		1800L); 		// Max download times (30 minutes)1800s
    curl_easy_setopt(pCurl, CURLOPT_LOW_SPEED_LIMIT, 10000L); 	// 10K bytes
    curl_easy_setopt(pCurl, CURLOPT_LOW_SPEED_TIME, 10L);		// 30 seconds

    curl_easy_setopt(pCurl, CURLOPT_CONNECTTIMEOUT, 10L);
    //curl_easy_setopt(pCurl, CURLOPT_CONNECTTIMEOUT_MS, 10L);


	//curl_easy_setopt(pCurl, CURLOPT_VERBOSE, 1L);
	
#ifdef SKIP_PEER_VERIFICATION
    /*
     * If you want to connect to a site who isn't using a certificate that is
     * signed by one of the certs in the CA bundle you have, you can skip the
     * verification of the server's certificate. This makes the connection
     * A LOT LESS SECURE.
     *
     * If you have a CA cert for the server stored someplace else than in the
     * default bundle, then the CURLOPT_CAPATH option might come handy for
     * you.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
#else    
    curl_easy_setopt(pCurl, CURLOPT_CAINFO, SSL_CA_FILE);
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
#endif

#ifdef SKIP_HOSTNAME_VERIFICATION
    /*
     * If the site you're connecting to uses a different host name that what
     * they have mentioned in their server certificate's commonName (or
     * subjectAltName) fields, libcurl will refuse to connect. You can skip
     * this check, but this will make the connection less secure.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
	
	LOG_EX(LOG_Debug, "Download(%u): %s --> %p\n", g_TotalDownloads++, pParams->pTaskUuid, pCurl);
	ret = curl_multi_add_handle(g_pCurl, pCurl);
	if(ret == CURLE_OK)
	{
		__addReqIdToTable(pParams->pTaskUuid, pParams);
		return (pParams->pTaskUuid);
	}
	else
	{
		free(pParams->pTaskUuid);
		LOG_EX(LOG_Error, "Add Handle Error: %d\n", ret);
		return NULL;
	}
}

int InetCancelDownload(const char *pTaskUuid)
{
	if(pTaskUuid && strlen(pTaskUuid) > 0)
	{
		PCURL_HANDLE_TBL	pItem = NULL;
		
		HASH_FIND_STR(g_ReqHandleTbl, pTaskUuid, pItem);
        
		if(pItem != NULL && pItem->pCurlItem->isCancel != TRUE)
		{
			__cancelDownloadTask(pItem->pCurlItem);
			if(pItem->pCurlItem->onRspCb)
			{
				pItem->pCurlItem->onRspCb(NULL, 
										  pItem->pCurlItem->dlSize, 
										  pItem->pCurlItem->pReqUrl, 
										  pItem->pCurlItem->sPath, 
										  pItem->pCurlItem->pTaskUuid, 
										  -CURLE_OPERATION_TIMEDOUT, 
										  pItem->pCurlItem->pData);
			}
		}
	}

	return (0);
}

static size_t __uploadCb(char *d, size_t n, size_t l, void *p)  
{  
	return n*l;  
} 

#ifdef LIBCURL_DEBUG
struct data {
  char trace_ascii; /* 1 or 0 */ 
};
 
static
void dump(const char *text,
          FILE *stream, unsigned char *ptr, size_t size,
          char nohex)
{
  size_t i;
  size_t c;
 
  unsigned int width = 0x10;
 
  if(nohex)
    /* without the hex output, we can fit more on screen */ 
    width = 0x40;
 
  fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n",
          text, (long)size, (long)size);
 
  for(i = 0; i<size; i += width) {
 
    fprintf(stream, "%4.4lx: ", (long)i);
 
    if(!nohex) {
      /* hex not disabled, show it */ 
      for(c = 0; c < width; c++)
        if(i + c < size)
          fprintf(stream, "%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;
      }
      fprintf(stream, "%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;
      }
    }
    fputc('\n', stream); /* newline */ 
  }
  fflush(stream);
}
 
static
int my_trace(CURL *handle, curl_infotype type,
             char *data, size_t size,
             void *userp)
{
  struct data *config = (struct data *)userp;
  const char *text;
  (void)handle; /* prevent compiler warning */ 
 
  switch(type) {
  case CURLINFO_TEXT:
    fprintf(stderr, "== Info: %s", data);
    /* FALLTHROUGH */ 
  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_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
    text = "<= Recv SSL data";
    break;
  }
 
  dump(text, stderr, (unsigned char *)data, size, config->trace_ascii);
  return 0;
}
#endif

int InetHttpUploadFileSync(const char *pURL, const char* pPath, void* pAttachInfo)
{
	CURL *pCurl = NULL;
	int rc = 0;
	CURLcode ret;
	struct curl_httppost *pPost = NULL, *pLastPtr = NULL;

#ifdef LIBCURL_DEBUG
	struct data config;
	config.trace_ascii = 1; /* enable ascii tracing */ 
#endif

	if(pURL == NULL || strlen(pURL) == 0)
	{
		LOG_EX(LOG_Error, "Url: %s(%p)\n", SAFE_STRING_VALUE(pURL), pURL);
		return -ERR_INPUT_PARAMS;
	}

	if(pPath == NULL || strlen(pPath) == 0)
	{
		LOG_EX(LOG_Error, "Url: %s(%p)\n", SAFE_STRING_VALUE(pPath), pPath);
		return -ERR_INPUT_PARAMS;
	}

	curl_formadd(&pPost, &pLastPtr,
				 CURLFORM_COPYNAME, "file",
				 CURLFORM_FILE, pPath,
				 CURLFORM_END);

	if(pAttachInfo)
	{
		PHTTP_POST_ATTACH   pDevInfoArray   = (PHTTP_POST_ATTACH)pAttachInfo;
		PHTTP_POST_ATTACH	pItem = NULL, pTmp = NULL;

		LL_FOREACH_SAFE(pDevInfoArray, pItem, pTmp)
		{
			curl_formadd(&pPost, &pLastPtr,
						CURLFORM_COPYNAME, 		pItem->keyName,
						CURLFORM_COPYCONTENTS, 	pItem->keyValue,
						CURLFORM_END);
		}
	}

	pCurl = curl_easy_init();

	if(pCurl == NULL)
	{
		LOG_EX(LOG_Error, "curl_easy_init() Error\n");
		return -ERR_MALLOC_MEMORY;
	}
	
	curl_easy_setopt(pCurl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
	curl_easy_setopt(pCurl, CURLOPT_POST, 1L);
	curl_easy_setopt(pCurl, CURLOPT_URL, pURL);
	curl_easy_setopt(pCurl, CURLOPT_HTTPPOST, pPost);
	curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, __uploadCb);
	curl_easy_setopt(pCurl, CURLOPT_CONNECTTIMEOUT, 10L);
#ifdef LIBCURL_DEBUG
	curl_easy_setopt(pCurl, CURLOPT_DEBUGFUNCTION, my_trace);
    curl_easy_setopt(pCurl, CURLOPT_DEBUGDATA, &config);
#endif

#ifdef SKIP_PEER_VERIFICATION
    /*
     * If you want to connect to a site who isn't using a certificate that is
     * signed by one of the certs in the CA bundle you have, you can skip the
     * verification of the server's certificate. This makes the connection
     * A LOT LESS SECURE.
     *
     * If you have a CA cert for the server stored someplace else than in the
     * default bundle, then the CURLOPT_CAPATH option might come handy for
     * you.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
#else    
    curl_easy_setopt(pCurl, CURLOPT_CAINFO, SSL_CA_FILE);
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
#endif

#ifdef SKIP_HOSTNAME_VERIFICATION
    /*
     * If the site you're connecting to uses a different host name that what
     * they have mentioned in their server certificate's commonName (or
     * subjectAltName) fields, libcurl will refuse to connect. You can skip
     * this check, but this will make the connection less secure.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif

	
#ifdef LIBCURL_DEBUG	
	curl_easy_setopt(pCurl, CURLOPT_VERBOSE, 1L);
#else
	curl_easy_setopt(pCurl, CURLOPT_VERBOSE, 0L);
#endif

	ret = curl_easy_perform(pCurl);

	if(ret != CURLE_OK)
	{
		LOG_EX(LOG_Error, "Upload %s File %s Error %s(%d)\n", pURL, pPath, curl_easy_strerror(ret), ret);
		rc = -ERR_NETWORK_SEND;
	}
	
	curl_easy_cleanup(pCurl);
	curl_formfree(pPost);

	return rc;
}

const char* InetHttpWebServicePostAsync(const char *pURL, const char* pPost, OnHttpResponse onRespCb, void* pData)
{
	uuid_t		msgId;
	char		strMsgId[64];
	PHTTP_REQ_PARAMS pParams = (PHTTP_REQ_PARAMS)malloc(sizeof(HTTP_REQ_PARAMS));
	CURL *pCurl = NULL;

	if(pURL == NULL || strlen(pURL) == 0 || onRespCb == NULL)
	{
		free(pParams);
		return (NULL);
	}

	memset(pParams, 0, sizeof(HTTP_REQ_PARAMS));

	pCurl = curl_easy_init();

	pParams->onRspCb	= onRespCb;
	pParams->pReqUrl	= (char *)malloc(strlen(pURL) + 1);
	pParams->type		= INET_HTTP_WEBSERVICE_POST;
	pParams->dlSize		= 0;
	pParams->pData		= pData;
	pParams->pCurl		= pCurl;
	pParams->lastTm		= 0;
	pParams->isCancel	= FALSE;

	memset(pParams->pReqUrl, 0, strlen(pURL) + 1);
	strcpy(pParams->pReqUrl, pURL);

	uuid_generate_random(msgId);
	memset(strMsgId, 0, 64);
	uuid_unparse_lower(msgId, strMsgId);
	pParams->pTaskUuid	= strdup(strMsgId);

	curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, __writeDataCb);
	curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, pParams);
	curl_easy_setopt(pCurl, CURLOPT_PRIVATE, pParams);
	curl_easy_setopt(pCurl, CURLOPT_URL, pURL);
	curl_easy_setopt(pCurl, CURLOPT_NOPROGRESS, 1L);
	curl_easy_setopt(pCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
	
	if(pPost != NULL && strlen(pPost) > 0)
	{
		curl_easy_setopt(pCurl, CURLOPT_POSTFIELDS, pPost);
		curl_easy_setopt(pCurl, CURLOPT_POSTFIELDSIZE, (long)strlen(pPost));
	}

#ifdef SKIP_PEER_VERIFICATION
    /*
     * If you want to connect to a site who isn't using a certificate that is
     * signed by one of the certs in the CA bundle you have, you can skip the
     * verification of the server's certificate. This makes the connection
     * A LOT LESS SECURE.
     *
     * If you have a CA cert for the server stored someplace else than in the
     * default bundle, then the CURLOPT_CAPATH option might come handy for
     * you.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
#else    
    curl_easy_setopt(pCurl, CURLOPT_CAINFO, SSL_CA_FILE);
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
#endif

#ifdef SKIP_HOSTNAME_VERIFICATION
    /*
     * If the site you're connecting to uses a different host name that what
     * they have mentioned in their server certificate's commonName (or
     * subjectAltName) fields, libcurl will refuse to connect. You can skip
     * this check, but this will make the connection less secure.
     */
    curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
	
	curl_multi_add_handle(g_pCurl, pCurl);

	__addReqIdToTable(pParams->pTaskUuid, pParams);

	return (pParams->pTaskUuid);
}

#if 0
static void __curlTaskRuntimeCb(void *pParams)
{
	PCURL_HANDLE_TBL	pItem = NULL, pTmpItem = NULL;

	while(TRUE)
	{
		uv_rwlock_rdlock(&g_uvHashRwLock);
		
		HASH_ITER(hh, g_ReqHandleTbl, pItem, pTmpItem)
		{
			if(pItem->pCurlItem->type == INET_HTTP_DOWNLOAD_FILE
				&& pItem->pCurlItem->lastTm > 0)
			{
				//unsigned int tmNow = LIBUV_CURRENT_TIME_S();
				
				if(pItem->pCurlItem->lastTm > 0)
				{
					//curl_multi_cleanup(pItem->pCurlItem->pCurl);
					curl_multi_remove_handle(g_pCurl, pItem->pCurlItem->pCurl);

					if(pItem->uRetryTimes >= 3)
					{
						if(pItem->pCurlItem->onRspCb)
						{
							if(strcmp(pItem->pCurlItem->sPath, pItem->pCurlItem->sDlPath) != 0)
							{
								unlink(pItem->pCurlItem->sDlPath);
							}

							pItem->pCurlItem->onRspCb(NULL,
											pItem->pCurlItem->dlSize,
											pItem->pCurlItem->pReqUrl,
											pItem->pCurlItem->sPath,
											pItem->pCurlItem->pTaskUuid,
											TRUE,
											pItem->pCurlItem->pData);
						}

						if(pItem->pCurlItem->pReqUrl)
						{
							free(pItem->pCurlItem->pReqUrl);
						}
					}
					else
					{
						__restartDlFileAsync(pItem->pCurlItem);
					}
				}
			}
		}
		uv_rwlock_rdunlock(&g_uvHashRwLock);
		usleep(100000);
	}

	pthread_detach(pthread_self());
}
#endif

static int __getUsernameFromMail(const char *pMailAddr, char **pUsername)
{
	char		*pTail;

	if(pMailAddr == NULL || pUsername == NULL || strlen(pMailAddr) == 0)
	{
		LOG_EX(LOG_Error, "Input Params Error: pMailAddr = [%s], pUsername = %p\n", 
			   pMailAddr ? pMailAddr : "NULL", pUsername);
		return (-ERR_INPUT_PARAMS);
	}

	*pUsername	= (char *)malloc(strlen(pMailAddr) + 1);

	if(*pUsername == NULL)
	{
		LOG_EX(LOG_Error, "Error Malloc Memory\n");
		*pUsername	= "";
		return (-ERR_MALLOC_MEMORY);
	}

	memset(*pUsername, 0, strlen(pMailAddr) + 1);
	
	pTail = strchr(pMailAddr, '@');

	if(pTail == NULL)
	{
		strcpy(*pUsername, pMailAddr);
	}
	else
	{
		memcpy(*pUsername, pMailAddr, pTail - pMailAddr);
	}
	
	return (0);
}

int InetSmtpSendEmail(const char* 		pFrom, 
					const char* 		pTo[], 
					const char* 		pCc[], 
					const char* 		pTitle, 
					const char* 		pMessage, 
					const char* 		pAttach[],
					PSMTP_MAIL_CONFIG 	pConfig)
{
	const char *pErrMsg = NULL;
	quickmail	pMail;
	
	if(pConfig == NULL)
	{
		LOG_EX(LOG_Error, "Input Param pConfig = NULL\n");
		return (-ERR_INPUT_PARAMS);
	}

	if(pConfig->pPassword == NULL || strlen(pConfig->pPassword) == 0)
	{
		LOG_EX(LOG_Error, "Input Param Error: pConfig->pPassword = [%s]\n", pConfig->pPassword ? pConfig->pPassword : "NULL");
		return (-ERR_INPUT_PARAMS);
	}

	if(pConfig->pUserName == NULL || strlen(pConfig->pUserName) == 0)
	{
		LOG_EX(LOG_Error, "Input Param Error: pConfig->pUserName = [%s]\n", pConfig->pUserName ? pConfig->pUserName : "NULL");
		return (-ERR_INPUT_PARAMS);
	}

	if(pConfig->pSmtpServer == NULL || strlen(pConfig->pSmtpServer) == 0)
	{
		LOG_EX(LOG_Error, "Input Param Error: pConfig->pUserName = [%s]\n", pConfig->pSmtpServer ? pConfig->pSmtpServer : "NULL");
		return (-ERR_INPUT_PARAMS);
	}
	
	if(pFrom == NULL)
	{
		LOG_EX(LOG_Error, "Input Param pFrom = NULL\n");
		return (-ERR_INPUT_PARAMS);
	}

	if(pTo == NULL && pCc == NULL)
	{
		LOG_EX(LOG_Error, "Input Param pTo = %p, pCc = %p\n", pTo, pCc);
		return (-ERR_INPUT_PARAMS);
	}

	if(pTitle == NULL)
	{
		pTitle = "";
	}

	if(pMessage == NULL)
	{
		pMessage = "";
	}

	quickmail_initialize();

	pMail = quickmail_create(pFrom, pTitle);

	if(pMail == NULL)
	{
		LOG_EX(LOG_Error, "Create Quickmail Object Error\n");
		return (-ERR_MALLOC_MEMORY);
	}

	for(const char **pValue = pTo; pTo && *pValue; pValue++)
	{
		quickmail_add_to(pMail, *pValue);
	}

	for(const char **pValue = pCc; pCc && *pValue; pValue++)
	{
		quickmail_add_cc(pMail, *pValue);
	}

	quickmail_add_header(pMail, "Importance: Low");
	quickmail_add_header(pMail, "X-Priority: 5");
	quickmail_add_header(pMail, "X-MSMail-Priority: Low");
	quickmail_add_body_memory(pMail, "text/html", (char*)pMessage, strlen(pMessage), 0);

	for(const char **pValue = pAttach; pAttach && *pValue; pValue++)
	{
		quickmail_add_attachment_file(pMail, *pValue, NULL);
	}

	//quickmail_set_debug_log(pMail, stderr);
	
	pErrMsg = quickmail_send(pMail, pConfig->pSmtpServer, pConfig->smtpPort, pConfig->pUserName, pConfig->pPassword);

	if(pErrMsg != NULL)
	{
		LOG_EX(LOG_Error, "Send Mail Error: %s\n", pErrMsg);
		return (-ERR_SEND_MAIL);
	}

	return (0);
}

int InetInit(void)
{
	int 		ret = 0;

	ret = curl_global_init(CURL_GLOBAL_ALL);

	if(ret != 0)
	{
		LOG_EX(LOG_Error, "curl init error: %d\n", ret);
		return ret;
	}

	g_pMainLoop = DBusLibuvGetRuntime()->pLoop;

	uv_timer_init(g_pMainLoop, &g_uvCurlTm);
	uv_timer_init(g_pMainLoop, &g_uvDlTm);

	g_pCurl = curl_multi_init();
    
    curl_multi_setopt(g_pCurl, CURLMOPT_SOCKETFUNCTION, __curlSockCb);
    curl_multi_setopt(g_pCurl, CURLMOPT_TIMERFUNCTION, __curlTimerCb);

	uv_rwlock_init(&g_uvHashRwLock);

	uv_timer_start(&g_uvDlTm, __onDlTmoutCb, 1000, 1000);
	
	return (0);
}

void InetUnInit(void)
{
    curl_multi_cleanup(g_pCurl);
	curl_global_cleanup();
}