/*
	get.dll - DLL for simple http requests via libcurl
		- Is threadsafe, supports only one get at a time though
	Jason A. Petrasko, 2007, Public Domain
*/

#include "get.h"
#include <curl/curl.h>

// simple portable locking code
#ifdef WIN32
	#include <windows.h>
	// assume we want to use critical sections
	typedef LPCRITICAL_SECTION thread_lock;
	thread_lock thread_lock_new(void) { thread_lock ret = (thread_lock)malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection(ret); return ret; }
	void thread_lock_free(thread_lock fl) { DeleteCriticalSection(fl); free(fl); }
	void thread_enter(thread_lock fl) { EnterCriticalSection(fl); }
	int thread_try(thread_lock fl) { return TryEnterCriticalSection(fl) == TRUE; }
	void thread_leave(thread_lock fl) { LeaveCriticalSection(fl); }
#else
	#include <pthread.h>
	// assume we want to use posix thread mutexes (untested)
	typedef pthread_mutex_t* thread_lock;
	thread_lock thread_lock_new() { thread_lock ret = malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(ret,NULL); return ret; }
	void thread_lock_free(thread_lock fl) { pthread_mutex_destroy(fl); free(fl); }
	void thread_enter(thread_lock fl) { pthread_mutex_lock(fl); }
	int thread_try(thead_lock fl) { return pthread_mutex_trylock(fl) == 0; }
	void thread_leave(thread_lock fl) { pthread_mutex_unlock(fl); }	
#endif

int GetValid = 0;
thread_lock GetLocker = NULL;
CURL* GetCurler =  NULL;
char *GetClientS = NULL;
char *GetProxy = NULL;
curl_proxytype GetProxyType = CURLPROXY_HTTP;
char *GetInterface = NULL;
char *GetPup = NULL;
char *GetRup = NULL;
void *DefUserPointer = NULL;
getUserTrackerFunc DefUserTracker = NULL;
getObject *GetCurrent = NULL;

void getSetup()
{
	GetLocker = thread_lock_new();
	GetCurler = curl_easy_init();
	GetValid = 1;
	if (!GetLocker) GetValid = 0;
	if (!GetCurler) GetValid = 0;
	
	getSetClient(0);
}

void getCleanup()
{
	if (!GetValid) return;
	if (GetLocker) thread_lock_free(GetLocker);
	if (GetCurler) curl_easy_cleanup(GetCurler);
}

void getSetClient(const char *name)
{
	if (!GetValid) return; thread_enter(GetLocker);
	
	if (GetClientS) free(GetClientS);
	if (name) GetClientS = strdup(name);
	 else GetClientS = strdup("unknown");
	
	thread_leave(GetLocker);
}

void getSetProxy(const char *proxy)
{
	if (!GetValid) return; thread_enter(GetLocker);
	
	if (GetProxy) free(GetProxy);
	if (proxy) GetClientS = strdup(proxy);
	 else GetClientS = NULL;
	
	thread_leave(GetLocker);	
}

void getSetInterface(const char *addr)
{
	if (!GetValid) return; thread_enter(GetLocker);
	
	if (GetInterface) free(GetInterface);
	if (addr) GetInterface = strdup(addr);
	 else GetInterface = NULL;
	
	thread_leave(GetLocker);	
}

void getSetProxyUP(const char *up)
{
	if (!GetValid) return; thread_enter(GetLocker);
	
	if (GetPup) free(GetPup);
	if (up) GetPup = strdup(up);
	 else GetPup = NULL;
	
	thread_leave(GetLocker);	
}

void getSetRequestUP(const char *up)
{
	if (!GetValid) return; thread_enter(GetLocker);
	
	if (GetRup) free(GetRup);
	if (up) GetRup = strdup(up);
	 else GetRup = NULL;
	
	thread_leave(GetLocker);	
}

// very rough, but how much do we care?
inline float getCurrentTime() { return ((float)clock() / (float)CLOCKS_PER_SEC); }

getObject *getFileObject(const char *fname)
{
	getObject *ret = (getObject*)calloc(1,sizeof(getObject));
	
	ret->UserPointer = DefUserPointer;
	ret->StateUpdate = DefUserTracker;
	ret->GetType = GET_FILE;
	ret->FileNameOrData = strdup(fname);
	
	ret->TimeStarted = getCurrentTime();
	ret->TimeTaken = 0.001f;
	
	return ret;
}

getObject *getDataObject()
{
	getObject *ret = (getObject*)calloc(1,sizeof(getObject));
	
	ret->UserPointer = DefUserPointer;
	ret->StateUpdate = DefUserTracker;
	ret->GetType = GET_DATA;
	ret->TimeStarted = getCurrentTime();
	ret->TimeTaken = 0.001f;
	
	return ret;
}

getObject *getMemObject(int initial_buffer)
{
	getObject *ret = (getObject*)calloc(1,sizeof(getObject));
	
	ret->UserPointer = DefUserPointer;
	ret->StateUpdate = DefUserTracker;
	ret->GetType = GET_MEM;
	ret->TimeStarted = getCurrentTime();
	ret->TimeTaken = 0.001f;
	ret->FileNameOrData = calloc(1,initial_buffer);
	ret->InitialAllocation = initial_buffer;
	ret->AllocatedBytes = initial_buffer;
	
	return ret;
}

getObject *getMemFixObject(int fixed_size)
{
	getObject *ret = (getObject*)calloc(1,sizeof(getObject));
	
	ret->UserPointer = DefUserPointer;
	ret->StateUpdate = DefUserTracker;
	ret->GetType = GET_MEM_FIX;
	ret->TimeStarted = getCurrentTime();
	ret->TimeTaken = 0.001f;
	ret->FileNameOrData = calloc(1,fixed_size);
	ret->InitialAllocation = fixed_size;
	ret->AllocatedBytes = fixed_size;
	
	return ret;	
}

void getSetUserPointer(getObject *p, void* user)
{
	p->UserPointer = user;
}

void getSetTracker(getObject *p, getUserTrackerFunc f)
{
	p->StateUpdate = f;
}

int GetProgressCallback(void *clientp,double dltotal,double dlnow,double ultotal,double ulnow)
{
	GetCurrent->TotalBytes = (unsigned int)dltotal;
	GetCurrent->TimeTaken = getCurrentTime() - GetCurrent->TimeStarted;
	return 0;
}

char getDebugText[1024];
int GetDebugCallback(CURL *c, curl_infotype cit, char *pc, size_t sz, void *unused)
{
	// handle headers internally here
	if (cit == CURLINFO_HEADER_IN)
	{
		return 0;
	} else if (cit == CURLINFO_TEXT)
	{
		if (!GetCurrent) return 0;
		if (sz > 1023) sz = 1023;
		if (sz == 0) return 0;
	
		memset(getDebugText,0,1024);
		memcpy(getDebugText,pc,sz);
		GetCurrent->Info = getDebugText;
		GetCurrent->TimeTaken = getCurrentTime() - GetCurrent->TimeStarted;
		if (GetCurrent->StateUpdate) GetCurrent->StateUpdate((void*)GetCurrent,GET_STATE_INFO);
		return 0;
	} else return 0;
}

size_t GetDataWriteFunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
	size_t ret = 0;
	unsigned int future, newsz;
	switch (GetCurrent->GetType)
	{
		case GET_FILE: 
			ret = fwrite(ptr,size,nmemb,(FILE*)GetCurrent->Internal);
			if (ret >= 0) GetCurrent->CurrentBytes += ret;
		 break;
		case GET_DATA: // don't do anything, just uh, return the proper value  (I don't think this will ever actually get called)
			ret = size * nmemb;
		 break;
		case GET_MEM:
			future = GetCurrent->CurrentBytes + size*nmemb;
			newsz = GetCurrent->AllocatedBytes;
			while (future > newsz) newsz += GetCurrent->InitialAllocation;
			if (newsz != GetCurrent->AllocatedBytes)
			{
				if (newsz > GET_MEM_LIMIT) return 0;
				GetCurrent->FileNameOrData = (char*)realloc((void*)GetCurrent->FileNameOrData,newsz);
				GetCurrent->AllocatedBytes = newsz;
			}
			memcpy(&GetCurrent->FileNameOrData[GetCurrent->CurrentBytes],ptr,size*nmemb);
			ret = size*nmemb;
			GetCurrent->CurrentBytes += ret;
		 break;
		case GET_MEM_FIX:
			future = GetCurrent->CurrentBytes + size*nmemb;
			if (future > GetCurrent->AllocatedBytes) return 0;
			memcpy(&GetCurrent->FileNameOrData[GetCurrent->CurrentBytes],ptr,size*nmemb);
			ret = size*nmemb;
			GetCurrent->CurrentBytes += ret;
		 break;		 
	}
	GetCurrent->TimeTaken = getCurrentTime() - GetCurrent->TimeStarted;
	if (GetCurrent->StateUpdate) GetCurrent->StateUpdate((void*)GetCurrent,GET_STATE_PROGRESS);
	return ret;
}

int getRequest(getObject *p, const char *url)
{
	if (!GetValid) return -1; 
	thread_enter(GetLocker);
	
	int ret = -3;
	if (GetCurrent)
	{
		// we already are working with an object, WTF!?
		thread_leave(GetLocker);
		return -2;
	}
	
	// let do it!
	GetCurrent = p;
	// configure CURL
	curl_easy_reset(GetCurler);
	curl_easy_setopt(GetCurler,CURLOPT_NOPROGRESS,0);
	curl_easy_setopt(GetCurler,CURLOPT_PROGRESSFUNCTION,GetProgressCallback);
	curl_easy_setopt(GetCurler,CURLOPT_VERBOSE,1);							// not sure if I want this or not
	curl_easy_setopt(GetCurler,CURLOPT_DEBUGFUNCTION,GetDebugCallback);
	curl_easy_setopt(GetCurler,CURLOPT_FAILONERROR,1);						// actually fail on HTTP error codes
	curl_easy_setopt(GetCurler,CURLOPT_FOLLOWLOCATION,1);
	curl_easy_setopt(GetCurler,CURLOPT_AUTOREFERER,1);
	if (GetClientS) curl_easy_setopt(GetCurler,CURLOPT_USERAGENT,GetClientS);
	if (GetProxy) 
	{
		curl_easy_setopt(GetCurler,CURLOPT_PROXY,GetProxy);
		curl_easy_setopt(GetCurler,CURLOPT_PROXYTYPE,GetProxyType);
	}
	if (GetInterface) curl_easy_setopt(GetCurler,CURLOPT_INTERFACE,GetInterface);
	if (GetPup) curl_easy_setopt(GetCurler,CURLOPT_PROXYUSERPWD,GetPup);
	if (GetRup) curl_easy_setopt(GetCurler,CURLOPT_USERPWD,GetRup);
	
	if (GetCurrent->GetType == GET_DATA) curl_easy_setopt(GetCurler,CURLOPT_NOBODY,1);
	switch (GetCurrent->GetType)
	{
		case GET_FILE:
			GetCurrent->Internal = fopen(GetCurrent->FileNameOrData,"wb");
			if (!GetCurrent->Internal) goto qout;
		case GET_DATA:
		case GET_MEM:
		case GET_MEM_FIX:
			curl_easy_setopt(GetCurler,CURLOPT_WRITEFUNCTION,GetDataWriteFunction);
			curl_easy_setopt(GetCurler,CURLOPT_WRITEDATA,GetCurrent);
		 break;
	}
	
	// make the request
	char *url_pass = strdup(url);
	GetCurrent->Url = url_pass;
	curl_easy_setopt(GetCurler,CURLOPT_URL,url_pass);
	ret = curl_easy_perform(GetCurler);
	if (GetCurrent->GetType == GET_MEM)
	{
		GetDataWriteFunction("", 1, 1, NULL);   // append a NULL for safety
		GetCurrent->CurrentBytes -= 1;			// don't count it though
	} else if (GetCurrent->GetType == GET_FILE)
	{
		fclose((FILE*)GetCurrent->Internal);
		GetCurrent->Internal = NULL;
	}
	if (ret)
	{
		// we have an error!
		GetCurrent->Info = curl_easy_strerror(ret);
		if (GetCurrent->StateUpdate) GetCurrent->StateUpdate((void*)GetCurrent,GET_STATE_ERROR);
	} else 
	{
		GetCurrent->Info = NULL;
		
		if (GetCurrent->StateUpdate) GetCurrent->StateUpdate((void*)GetCurrent,GET_STATE_OK);		
	}
	free (url_pass);
	
qout:
	thread_leave(GetLocker);
	GetCurrent = NULL;
	return ret;
}

void getFree(getObject *p)
{
	if (p)
	{
		if (p->FileNameOrData) free(p->FileNameOrData);
		free(p);
	}
}

void getSetDefaultUserPointer(void *user)
{
	if (!GetValid) return; 	
	thread_enter(GetLocker);
	DefUserPointer = user;
	thread_leave(GetLocker);
}

void getSetDefaultTracker(getUserTrackerFunc f)
{
	if (!GetValid) return; 	
	thread_enter(GetLocker);
	DefUserTracker = f;
	thread_leave(GetLocker);
}

