// $Id: Internet.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Deadly Rooms of Death.
*
* The Initial Developer of the Original Code is
* Caravel Software.
* Portions created by the Initial Developer are Copyright (C) 2002 
* Caravel Software. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** */

#include "Assert.h"
#include "Files.h"
#include "Internet.h"

#ifdef DISABLE_INTERNET
#warning Internet disabled
#endif

CURL* CInternet::pHandle = NULL;
string CInternet::strCookies = "";
int CInternet::nHandles = 0;
bool CInternet::bInit = false;
std::map<UINT, CInternet_Thread_Info*> CInternet::threadInfo;
curl_httppost* CInternet::pFormPostBegin = NULL;
curl_httppost* CInternet::pFormPostEnd = NULL;
string CInternet::userAgent;

char errorBuffer[CURL_ERROR_SIZE] = {0};
bool bQuitNow = false;

using namespace std;

//
//Public methods.
//

//*****************************************************************************
size_t CInternet_HandleDataStretchyBuffer(
//Handler for getting a URL into a stretchy buffer.  Used as a callback
// in CInternet_HTTP_Get_Worker().
//
//Params:
	void* pPtr,    //(in) Data to be appended to buffer
	size_t size,   //(in) size of each element
	size_t num,    //(in) number of elements
	void* pStream) //(in/out) The CStretchyBuffer to append to
//
//Returns: The size of the appended data
{
#ifdef DISABLE_INTERNET
	return 0;
#else
   CInternet_Thread_Info* pInfo = static_cast<CInternet_Thread_Info*>(pStream);

	pInfo->pBuffer->Append(static_cast<const BYTE*>(pPtr), size*num);
   pInfo->bytesComplete = pInfo->pBuffer->Size();

	return size*num;
#endif
}

//****************************************
int CInternet_HTTP_Get_Worker(void* pPtr)
{
#ifdef DISABLE_INTERNET
	return CURLE_UNSUPPORTED_PROTOCOL;
#else
	CInternet_Thread_Info* pInfo = (CInternet_Thread_Info *)pPtr;
	curl_httppost* pPost = CInternet::pFormPostBegin;
   curl_httppost* pEnd = CInternet::pFormPostEnd;

   CInternet::pFormPostBegin = NULL;
   CInternet::pFormPostEnd = NULL;

	static const UINT MAX_ATTEMPTS = 2;
	UINT wAttempts = 0;
	CURLcode code;	//error code
	do
	{
		//Send HTTP post.
		CURL* pHandle = curl_easy_init();

		curl_easy_setopt(pHandle, CURLOPT_WRITEFUNCTION, CInternet_HandleDataStretchyBuffer);
		curl_easy_setopt(pHandle, CURLOPT_WRITEDATA, pInfo);
		curl_easy_setopt(pHandle, CURLOPT_URL, pInfo->strUrl.c_str());
		curl_easy_setopt(pHandle, CURLOPT_NOPROGRESS, 1L);
		curl_easy_setopt(pHandle, CURLOPT_NOSIGNAL, 1L);
		curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT, 5L);  //fail if connect takes longer than this (s)
		curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_LIMIT, 1L); //the client must receive at least 1 byte...
		curl_easy_setopt(pHandle, CURLOPT_LOW_SPEED_TIME, 10L); //...every 10 seconds, otherwise fail
		curl_easy_setopt(pHandle, CURLOPT_DNS_CACHE_TIMEOUT, -1L);	//preserve name resolves forever
		curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, errorBuffer);
		curl_easy_setopt(pHandle, CURLOPT_USERAGENT, CInternet::userAgent.c_str());

		string cookies = CInternet::GetCookies();
		if (!cookies.empty())
			curl_easy_setopt(pHandle, CURLOPT_COOKIE, cookies.c_str());

		if (pPost)
			curl_easy_setopt(pHandle, CURLOPT_HTTPPOST, pPost);

		code = curl_easy_perform(pHandle);
		if (code == CURLE_OK)
			curl_easy_getinfo(pHandle, CURLINFO_RESPONSE_CODE, &pInfo->responseCode);
		curl_easy_cleanup(pHandle);

		//Try sending a few times on timeout.
	} while (code == CURLE_OPERATION_TIMEDOUT && ++wAttempts < MAX_ATTEMPTS && !bQuitNow);
	if (pPost)
		curl_formfree(pPost);

	pInfo->eRetVal = code;

	return (int)code;
#endif
}

//*****************************************************************************
void CInternet::Deinit()
//Deinitializes the curl library.
{
#ifdef DISABLE_INTERNET
	return;
#else
	if (!CInternet::bInit) return;

	bQuitNow = true;

	//Wait for active threads to finish.
	for (std::map<UINT, CInternet_Thread_Info*>::const_iterator iter = CInternet::threadInfo.begin();
			iter != CInternet::threadInfo.end(); ++iter)
	{
		CInternet_Thread_Info* pInfo = iter->second;
		ASSERT(pInfo->pThread);
		SDL_WaitThread(pInfo->pThread, NULL);
		delete pInfo->pBuffer;
		delete pInfo;
	}
	CInternet::threadInfo.clear();

	curl_global_cleanup();

	CInternet::bInit = false;
#endif
}

//*****************************************************************************
bool CInternet::Init(
//Initializes the curl library.
	const char* pUserAgent,
	const bool bSSL) //[default=false]
{
#ifdef DISABLE_INTERNET
	return false;
#else
	if (CInternet::bInit) return true;

	bQuitNow = false;

	ASSERT(pUserAgent);
	CInternet::userAgent = pUserAgent;

	CInternet::bInit =
		curl_global_init(bSSL ? CURL_GLOBAL_ALL : CURL_GLOBAL_WIN32) == CURLE_OK;

	return CInternet::bInit;
#endif
}

//*****************************************************************************
int CInternet::GetStatus(const UINT handle)
//Returns: Error code returned if handle response received (0 indicating success), else -1
{
#ifdef DISABLE_INTERNET
	return CURLE_UNSUPPORTED_PROTOCOL;
#else
	map<UINT, CInternet_Thread_Info*>::iterator found = CInternet::threadInfo.find(handle);
	if (found == CInternet::threadInfo.end())
		return -1;	//result not received yet

	return (int)found->second->eRetVal;
#endif
}

//*****************************************************************************
UINT CInternet::GetBytesCompleted(const UINT handle)
{
#ifdef DISABLE_INTERNET
	return 0;
#else
   map<UINT, CInternet_Thread_Info*>::iterator found = CInternet::threadInfo.find(handle);
   if (found == CInternet::threadInfo.end())
		return 0;

	return (UINT)found->second->bytesComplete;
#endif
}

//*****************************************************************************
CStretchyBuffer* CInternet::GetResults(const UINT handle)
//Query a handle.  If it's found, return the data member and free container.
{
#ifdef DISABLE_INTERNET
	return NULL;
#else
	map<UINT, CInternet_Thread_Info*>::iterator found = CInternet::threadInfo.find(handle);
	if (found == CInternet::threadInfo.end())
		return NULL;

	CInternet_Thread_Info* pInfo = found->second;
	const bool bSuccess = pInfo->eRetVal == CURLE_OK && pInfo->responseCode == 200;
	CStretchyBuffer* pBuffer = pInfo->pBuffer;
	int status;
	ASSERT(pInfo->pThread);
	SDL_WaitThread(pInfo->pThread, &status); //SDL 1.2.7 bug: must call this after thread procedure completes to avoid memory leaks/hangs
	delete pInfo;
	CInternet::threadInfo.erase(found);

	if (bSuccess)
		return pBuffer;

	//Error occurred -- return nothing.
	delete pBuffer;
	return NULL;
#endif
}

//*****************************************************************************
void CInternet::OutputError(const int nRes)
//Output error if some problem occurred in CInternet_HTTP_Get_Worker.
{
#ifdef DISABLE_INTERNET
	return;
#else
	if (nRes > 0)
	{
		char errorStr[256];
		sprintf(errorStr,	"Internet warning %d: %s\n", nRes, errorBuffer);
		CFiles Files;
		Files.AppendErrorLog((char *)errorStr);
	}
#endif
}

//*****************************************************************************
void CInternet::AddCookie(
//Adds a cookie to all HTTP requests
//
//Params:
	const string& strName,     //(in) Cookie name
	const string& strValue)    //(in) Cookie value
{
#ifdef DISABLE_INTERNET
	return;
#else
	if (CInternet::strCookies != "") CInternet::strCookies += "; ";
	CInternet::strCookies += strName;
	CInternet::strCookies += "=";
	CInternet::strCookies += strValue;
#endif
}

//*****************************************************************************
UINT CInternet::HttpGet(
//Gets a file via HTTP
//
//Params:
	const string& strUrl)     //(in) URL to retrieve
//Returns:
//True if the operation has been initialized.
{
#ifdef DISABLE_INTERNET
	return 0;
#else
	if (!CInternet::bInit) return 0;

	CInternet_Thread_Info* pInfo = new CInternet_Thread_Info;
	pInfo->pBuffer = new CStretchyBuffer;
   pInfo->pBuffer->Alloc(1000);
	pInfo->strUrl = strUrl;

	SDL_Thread* pThread = SDL_CreateThread(CInternet_HTTP_Get_Worker, pInfo);
	if (!pThread) {delete pInfo->pBuffer; delete pInfo; return 0;} //couldn't create thread

	pInfo->pThread = pThread;

	const UINT handle = SDL_GetThreadID(pThread);
	CInternet::threadInfo[handle] = pInfo;

	return handle;
#endif
}

//***************************
void CInternet::AddPostData(
//Adds a name/value pair to the POST data of the next request
//
//Params:
	const string& strName,          //(in) Name
	const CStretchyBuffer& value)   //(in) Value
{
#ifdef DISABLE_INTERNET
	return;
#else
	curl_formadd(&CInternet::pFormPostBegin, &CInternet::pFormPostEnd,
	             CURLFORM_COPYNAME, strName.c_str(),
	             CURLFORM_COPYCONTENTS, (char*)(BYTE*)value,
                CURLFORM_CONTENTSLENGTH, long(value.Size()),
	             CURLFORM_CONTENTTYPE, "application/binary", 
                CURLFORM_END);
#endif
}

void CInternet::AddPostData(
//Adds a name/value pair to the POST data of the next request
//
//Params:
	const string& strName,     //(in) Name
	const string& value)       //(in) Value
{
#ifdef DISABLE_INTERNET
	return;
#else
	curl_formadd(&CInternet::pFormPostBegin, &CInternet::pFormPostEnd,
		         CURLFORM_COPYNAME, strName.c_str(),
               CURLFORM_COPYCONTENTS, value.c_str(),
               CURLFORM_CONTENTSLENGTH, long(value.length()),
		         CURLFORM_END);
#endif
}
