
#define HAVE_BOOL_T

#ifndef __CURL_INCLUDED__
#define __CURL_INCLUDED__
#include <curl/curl.h>
#include <hash.h>
#include <multihandle.h>
#endif

#define PROXY_URL			"YOUR PROXY URL:PORT GOES HERE"
#define PROXY_CREDENTIALS	"YOUR PROXY USER:PASS GOES HERE"

#define CERT_PATH			"YOUR CERTIFICATE PATH GOES HERE"
#define CERT_KEYPATH		"YOUR CERTIFICATE KEY PATH GOES HERE"
#define CERT_CAPATH			"YOUR CERTIFICATE CA PATH GOES HERE"
#define CERT_PASSWORD		"YOUR CERTIFICATE PASSWORD GOES HERE"

#define APNS_TOPIC			"YOUR APNS TOPIC GOES HERE"

#include <Windows.h>

class CTransfers
{
public:
	CTransfers(void) : lTransfers(0)
	{
		hEvent = CreateEvent(0, TRUE, FALSE, "__12345_PUSHTRANSFER_67890__");
	}

	~CTransfers(void)
	{
		SetEvent(hEvent);
		WaitForSingleObject(hEvent, 10);
		CloseHandle(hEvent);
	}

	long operator++(int)
	{
		InterlockedIncrement(&lTransfers);
		return Get();
	}

	long operator--(int)
	{
		InterlockedDecrement(&lTransfers);
		return Get();
	}

	long operator=(long lValue)
	{
		InterlockedExchange(&lTransfers, lValue);
		return Get();
	}

	long Get(void)
	{
		long lResult = InterlockedExchangeAdd(&lTransfers, 0);
		if (lResult == 0)
		{
			ResetEvent(hEvent);
		}
		else if (lResult == 1)
		{
			SetEvent(hEvent);
		}
		return lResult;
	}

	void Pulse(void)
	{
		SetEvent(hEvent);
	}

	void WaitInput(void)
	{
		WaitForSingleObject(hEvent, INFINITE);
	}
private:
	HANDLE hEvent;
	long lTransfers;
};

typedef long (__cdecl* RESPONSECALLBACK)(long lResponseCode, char* szDeviceToken);

class CPushResponse
{
public:
	CPushResponse(long lResponseCode, char* szDeviceToken)
	{
		ResponseCode = lResponseCode;
		char* pszDeviceToken = strrchr(szDeviceToken, '/');
		if (pszDeviceToken)
		{
			strcpy(DeviceToken, ++pszDeviceToken);
		}
		else
		{
			strcpy(DeviceToken, szDeviceToken);
		}
	}
public:
	long ResponseCode;
	char DeviceToken[128];
};

class CPushService
{
public:
	CPushService(RESPONSECALLBACK fnResponseCallback) : ResponseCallback(fnResponseCallback)
	{
		// 
		SecureZeroMemory(szOutputBuffer, 4096);
		
		// MD5 hash: [__SERVERREADY_20161015180622331__]
		pszReadyEvent = "362c4945d1c91cb8563d7678a5f09930";
		// MD5 hash: [__SERVEREXIT_20161016114452997__]
		pszExitEvent = "fe9ff286fcb8f9c19522b12b6f05677a";
		// MD5 hash: [__DISPATCHEREVENT_20161017041344443__]
		pszDispatcherEvent = "f8eb63cc78ae0fd950021662f5aca3cd";
		
		// 
		InitializeLocks();

		// 
		hMultiHandle = curl_multi_init();
		if (hMultiHandle)
		{
			int iTransfers = 0;
			int iStillRunning = 0;
			hMulti = static_cast<Curl_multi*>(hMultiHandle);
			// 
			curl_multi_setopt(hMultiHandle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
			// 
			curl_multi_setopt(hMultiHandle, CURLMOPT_PUSHDATA, &iTransfers);
			// 
			curl_multi_perform(hMultiHandle, &iStillRunning);
		}

		// 
		hServerReady = CreateEvent(0, TRUE, FALSE, pszReadyEvent);
		//
		hServer = CreateThread(0, 0, ServerProc, this, 0, &dwServerID);
		//
		WaitForSingleObject(hServerReady, 60 * 1000);

		//
		hDispatcherReady = CreateEvent(0, TRUE, FALSE, pszDispatcherEvent);
		// 
		hDispatcher = CreateThread(0, 0, DispatcherProc, this, 0, &dwDispatcherID);
		//
		WaitForSingleObject(hDispatcherReady, 60 * 1000);

		//
		hExit = CreateEvent(0, TRUE, FALSE, pszExitEvent);
	}

	virtual ~CPushService(void)
	{
		//
		transfers.Pulse();

		//
		SetEvent(hExit);
		//
		WaitForSingleObject(hServer, 60 * 1000);
		//
		CloseHandle(hServer);
		//
		CloseHandle(hServerReady);
		//
		CloseHandle(hExit);

		//
		if (hMultiHandle)
		{
			//
			curl_multi_cleanup(hMultiHandle);
		}

		//
		ReleaseLocks();

		//
		PostThreadMessage(dwDispatcherID, MSG_EXITSERVER, 0, 0);
		//
		WaitForSingleObject(hDispatcher, 60 * 1000);
		//
		CloseHandle(hDispatcher);
		// 
		CloseHandle(hDispatcherReady);
	}

	long PushRequest(char* szDeviceToken, char* szContent, bool bVerbose = true)
	{
		CURL* hEasyHandle = 0;
		Lock();
		{
			hEasyHandle = curl_easy_init();
		}
		Unlock();

		if (!hEasyHandle)
		{
			return -1;
		}

		curl_easy_setopt(hEasyHandle, CURLOPT_PROXY, PROXY_URL);
		curl_easy_setopt(hEasyHandle, CURLOPT_PROXYUSERPWD, PROXY_CREDENTIALS);
	
		sprintf(szUrlBuffer, "https://api.push.apple.com/3/device/%s", szDeviceToken);
		curl_easy_setopt(hEasyHandle, CURLOPT_URL, szUrlBuffer);
		if (bVerbose)
		{
			curl_easy_setopt(hEasyHandle, CURLOPT_VERBOSE, 1L);
		}
		curl_easy_setopt(hEasyHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
		
		char* szContentBuffer = new char[4096]; /// FREE MEMORY LATER
		sprintf(szContentBuffer, "{\"aps\":{\"alert\":\"%s\",\"sound\":\"default\"}}", szContent);
		__int64 size = (__int64)strlen(szContentBuffer);
		curl_easy_setopt(hEasyHandle, CURLOPT_POSTFIELDS, szContentBuffer);
		curl_easy_setopt(hEasyHandle, CURLOPT_POSTFIELDSIZE_LARGE, size);

		curl_easy_setopt(hEasyHandle, CURLOPT_SSLCERTTYPE, 0);
		curl_easy_setopt(hEasyHandle, CURLOPT_SSLKEYTYPE, 0);
		
		curl_easy_setopt(hEasyHandle, CURLOPT_SSLCERT, CERT_PATH);
		curl_easy_setopt(hEasyHandle, CURLOPT_SSLKEY, CERT_KEYPATH);
		curl_easy_setopt(hEasyHandle, CURLOPT_KEYPASSWD, CERT_PASSWORD);
		curl_easy_setopt(hEasyHandle, CURLOPT_CAINFO, CERT_CAPATH);
		curl_easy_setopt(hEasyHandle, CURLOPT_SSL_VERIFYPEER, 0L);
		curl_easy_setopt(hEasyHandle, CURLOPT_SSL_VERIFYHOST, 0L);

		curl_easy_setopt(hEasyHandle, CURLOPT_AUTOREFERER, 0L);
		curl_easy_setopt(hEasyHandle, CURLOPT_HEADER, 0L);
		curl_easy_setopt(hEasyHandle, CURLOPT_USERAGENT, "curl/7.51.0-DEV");

		curl_slist *chunk = NULL;
		char szTopicBuffer[1024] = { 0 };
		sprintf(szTopicBuffer, "apns-topic: %s", APNS_TOPIC);
		chunk = curl_slist_append(chunk, szTopicBuffer);
		curl_easy_setopt(hEasyHandle, CURLOPT_HTTPHEADER, chunk);

		curl_easy_setopt(hEasyHandle, CURLOPT_TRANSFERTEXT, 0L);
		curl_easy_setopt(hEasyHandle, CURLOPT_TIMEOUT_MS, 0);
		curl_easy_setopt(hEasyHandle, CURLOPT_MAXREDIRS, 50);
		curl_easy_setopt(hEasyHandle, CURLOPT_POSTREDIR, 0);
		curl_easy_setopt(hEasyHandle, CURLOPT_RESUME_FROM_LARGE, CURL_OFF_T_C(0));
		curl_easy_setopt(hEasyHandle, CURLOPT_TCP_KEEPALIVE, 1L);

	#if (CURLPIPE_MULTIPLEX > 0)
		/* wait for pipe connection to confirm */ 
		curl_easy_setopt(hEasyHandle, CURLOPT_PIPEWAIT, 1L);
	#endif
	
		AddHandle(hMultiHandle, hEasyHandle);
		return 0L;
	}
protected:
	void InitializeLocks(void)
	{
		InitializeCriticalSection(&lock);
	}

	void ReleaseLocks(void)
	{
		DeleteCriticalSection(&lock);
	}

	void Lock(void)
	{
		EnterCriticalSection(&lock);
	}

	void Unlock(void)
	{
		LeaveCriticalSection(&lock);
	}

	CURLMcode AddHandle(CURLM* hMultiHandle, CURL* hEasyHandle)
	{
		CURLMcode result = CURLM_OK;
		size_t uTransfers = 0;
		
		Lock();
		{
			result = curl_multi_add_handle(hMultiHandle, hEasyHandle);
		}
		Unlock();
		
		transfers++;
		return result;
	}

	void RemoveHandle(CURLM* hMultiHandle, CURL* hEasyHandle)
	{
		Lock();
		{
			curl_multi_remove_handle(hMultiHandle, hEasyHandle);
		}
		Unlock();

		transfers--;
	}

	void ReleaseHandle(CURL* hEasyHandle)
	{
		curl_easy_cleanup(hEasyHandle);
	}

	DWORD Running(void)
	{
		return WaitForSingleObject(hExit, 0);
	}

	CURLMcode Perform(void)
	{
		int numfds = 0;
		int iStillRunning = 0;
		CURLMcode result = CURLM_OK;
		
		if ((result = curl_multi_perform(hMultiHandle, &iStillRunning)) == CURLM_OK)
		{
			result = curl_multi_wait(hMultiHandle, NULL, 0, 100, &numfds);
		}

		return result;
	}

	CURLMsg* Read(void)
	{
		int msgq = 0;
		return curl_multi_info_read(hMultiHandle, &msgq);
	}

	static DWORD WINAPI ServerProc(LPVOID hParameter)
	{
		CPushService* pThis = static_cast<CPushService*>(hParameter);
		CURLMsg* hMsg = 0;

		HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, pThis->pszReadyEvent);
		SetEvent(hEvent);
		CloseHandle(hEvent);

		long response = 0;
		char* buffer = 0;
		CURLMcode mcResult = CURLM_OK;
		while (pThis->Running())
		{
			pThis->transfers.WaitInput();
			pThis->Perform();
		
			do
			{
				hMsg = pThis->Read();
				if (hMsg && (hMsg->msg == CURLMSG_DONE))
				{
					curl_easy_getinfo(hMsg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
					curl_easy_getinfo(hMsg->easy_handle, CURLINFO_EFFECTIVE_URL, &buffer);
					
					CPushResponse* reponse = new CPushResponse(response, buffer);
					PostThreadMessage(pThis->dwDispatcherID, MSG_NEWREQUEST, 0, reinterpret_cast<LPARAM>(reponse));
				
					pThis->RemoveHandle(pThis->hMultiHandle, hMsg->easy_handle);
					pThis->ReleaseHandle(hMsg->easy_handle);
				}
			} while(hMsg);
		}
		return 0L;
	}

	static DWORD WINAPI DispatcherProc(LPVOID hParameter)
	{
		CPushService* pThis = static_cast<CPushService*>(hParameter);

		MSG msg = { 0 };
		PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

		HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, pThis->pszDispatcherEvent);
		SetEvent(hEvent);
		CloseHandle(hEvent);

		while (GetMessage(&msg, 0, MSG_EXITSERVER, MSG_NEWREQUEST))
		{
			switch (msg.message)
			{
			case MSG_EXITSERVER:
				return 0L;
			case MSG_NEWREQUEST:
				CPushResponse* response = reinterpret_cast<CPushResponse*>(msg.lParam);
				pThis->ResponseCallback(response->ResponseCode, response->DeviceToken);
				delete response;
				break;
			}
		}

		return 0L;
	}
private:
	CURLM* hMultiHandle;
	Curl_multi* hMulti;
private:
	CRITICAL_SECTION lock;
	CTransfers transfers;
private:
	char* pszExitEvent;
	HANDLE hExit;
private:
	char* pszReadyEvent;
	HANDLE hServerReady;
	HANDLE hServer;
	DWORD dwServerID;
private:
	char* pszDispatcherEvent;
	HANDLE hDispatcherReady;
	HANDLE hDispatcher;
	DWORD dwDispatcherID;
	RESPONSECALLBACK ResponseCallback;
private:
	static const unsigned MSG_EXITSERVER = 0x0400;
	static const unsigned MSG_NEWREQUEST = 0x0401;

private:
	size_t NumberTransfers(void)
	{
		return hMulti->conn_cache.num_connections;
	}	
};

long Callback(long lResponseCode, char* szDeviceToken)
{
	// DO PROCESSING
	// SAVE RESPONSE CODE, ...
	// 
	switch (lResponseCode)
	{
	case 200:
		// success
		break;
	case 400:
		// bad request
		break;
	/* ... */
	}
	return 0L;
}

CPushService* g_PushService = 0;

DWORD WINAPI ThreadStart(LPVOID hParameter)
{
	char* szDeviceToken = static_cast<char*>(hParameter);
	g_PushService->PushRequest(sDeviceToken, "Hello");
	return 0L;
}

#define NUM_TOKENS	SET_THE_NUMBER_OF_TOKENS_HERE
#define NUM_THREADS	100

int main(void)
{
	char* pszDeviceTokens[NUM_TOKENS] = {
		"place some tokens here",
		"place some tokens here",
		/* ... */
	};
	g_PushService = new CPushService(Callback);

	HANDLE hThreads[NUM_THREADS] = { 0 };
	for (int i = 0; i < NUM_THREADS; i++)
	{
		hThreads[i] = CreateThread(0, 0, ThreadStart, pszDeviceTokens[rand() % NUM_TOKENS], 0, 0);
	}

	DWORD dwStatus = WaitForMultipleObjects(NUM_THREADS, hThreads, TRUE, 1000 * 60 * 10);
	if (dwStatus != WAIT_OBJECT_0)
	{
		// something went wrong
	}

	// Give it some time to finish
	Sleep(120 * 1000);

	delete g_PushService;
	return 0;
}