#include "StdAfx.h"
#include "ping.h"
#include "emule.h"
#include "preferences.h"
#include "math.h"

IMPLEMENT_DYNCREATE(CPingThread, CWinThread)
CPingThread::CPingThread(void): isDynup(false), librariesloaded(false), upload(1)
{
	m_bAutoDelete = false; // Must be cleaned manually -do not forget it!

	// by Mighty Knife: better to fill the ping array with zeros at startup
	for (int i=0; i < m_ping_values; i++) ping [i] = 0;

	// by Mighty Knife: initialize handle to NULL
	hndlFile = NULL;
}

CPingThread::~CPingThread(void)
{
	// Close the echo request file handle
	if(hndlFile != NULL) pIcmpCloseHandle(hndlFile);
	if(hndlIcmp != NULL) FreeLibrary(hndlIcmp);
	WSACleanup();
	librariesloaded = false;
}

void CPingThread::Init(void)
{
	if(librariesloaded) return;
	
	// Load libraries and all we need for the Amuc
	hndlIcmp = LoadLibrary("ICMP.DLL");
	if (hndlIcmp == NULL) {
		theApp.emuledlg->AddLogLine(false, "\nCould not load ICMP.DLL\n");
		isDynup = false;
		return;
	}

	// Retrieve ICMP function pointers
	pIcmpCreateFile = (HMODULE (WINAPI*)(void))GetProcAddress(hndlIcmp, "IcmpCreateFile");
	pIcmpCloseHandle = (BOOL (WINAPI*)(HMODULE))GetProcAddress(hndlIcmp, "IcmpCloseHandle");
	pIcmpSendEcho = (DWORD (WINAPI*)(HMODULE, DWORD, LPVOID, WORD, PIPINFO, LPVOID, DWORD, DWORD))GetProcAddress(hndlIcmp, "IcmpSendEcho");

	// Check all the function pointers
	if (pIcmpCreateFile == NULL || pIcmpCloseHandle == NULL || pIcmpSendEcho == NULL) {
		theApp.emuledlg->AddLogLine(false, "\nError getting ICMP proc address\n");
		return;
	}

	// Init WinSock
	nRet = WSAStartup(0x0101, &wsaData);
	if (nRet) {
		theApp.emuledlg->AddLogLine(false, "\nWSAStartup() error: %d\n", nRet);
		return;
	}

	// Check WinSock version
	if (0x0101 != wsaData.wVersion) {
		theApp.emuledlg->AddLogLine(false, "\nWinSock version 1.1 not supported\n");
		return;
	}

	// Set some reasonable default values
	ipInfo.Ttl = 255;
	ipInfo.Tos = 0;
	ipInfo.IPFlags = 0;
	ipInfo.OptSize = 0;
	ipInfo.Options = NULL;

	theApp.emuledlg->AddLogLine(false, "Dynamic Upload ready");
	librariesloaded = true;
}

int CPingThread::Run(void)
{
	// by Mighty Knife: We count the timeouts for the pings. If there are too many
	// timeouts, we again resolve the DNS of host we are pinging
	int remainingMaxPings = 3*m_ping_values;

	// By Mighty Knife: The main dialog seems to run at another processor priority.
	// If the main dialog calls SetIP to get a handle for the TCP stack,
	// the TCP stack seems to run with the priority of the main dialog. 
	// Because this is too low the calculated ping timings are too high. 
	// We restart the ping thread to ensure that the TCP stack is 
	// running with the processor priority of our thread so that ping timings
	// are calculated correctly.
	if (isDynup) {
		theApp.Dynup->SetIP(1, theApp.glob_prefs->GetMaxGraphUploadRate(), theApp.glob_prefs->GetURLPing());
		theApp.Dynup->SetInitialPing();
		// Ensure that the thread is running even if this ping didn't work.
		// We know that the host exists because otherwise isDynup would have
		// been false !
		isDynup = true;
	}

	// maxUpload als ms base !!!
	check_trigger = ::GetTickCount();
	while (isDynup) {
		soll_ping = (double)theApp.glob_prefs->GetBasePing();

		raw_ping = Ping() ;
		if (initial_ping > raw_ping) {
			initial_ping = raw_ping;
		}
		normalized_ping = raw_ping - initial_ping;

		// Shift values and add new value
		for (int i = (m_ping_values - 1); i > 0; i--) {
			ping[i] = ping[i - 1];
			// average_ping += ping[i]; // by Mighty Knife: line irrelevant
		}
		ping[0] = normalized_ping;

		// Calculate average
		average_ping = 0;
		for (int i = 0; i < m_ping_values; i++) { // Possible value range from 1 to 20
			average_ping += ping[i];   // by Mighty Knife: "ping [0]" is wrong here
		}
		average_ping /= m_ping_values;

		// Calculate Waiting Time
		hping = (soll_ping - average_ping);
		
		upload += (double)hping / 1000.0f;

		if (upload < 1) {
			upload = 1;
		} else if (upload > maxUpload) {
			upload = maxUpload;
		}

		//if ((::GetTickCount() - check_trigger) > 1000) {
		//	check_trigger = ::GetTickCount();
		//	theApp.emuledlg->AddLogLine(false, "Dynup: upload=%.1f, initial_ping=%i, raw_ping=%i, soll_ping=%i, average_ping=%i", upload, initial_ping, raw_ping, soll_ping, average_ping);
		//	theApp.emuledlg->AddLogLine(false, "Dynup: hping=%i, normalized_ping=%i", hping, normalized_ping);
		//}

		if ((uint16)upload != theApp.glob_prefs->GetMaxUpload()) {
			theApp.glob_prefs->SetMaxUpload((uint16)upload);
		}

		// by Mighty Knife: handle disconnection after 24 hours:

		// Use a counter to detect diconnection
		if (raw_ping==1100) {
			if (remainingMaxPings > 0) remainingMaxPings--;
		} else remainingMaxPings = 3*m_ping_values; // decrement if timeout, reset if ok

		if (remainingMaxPings <= 0) { // there was a disconnection
			// We have to reinitialize the IP of the server we are pinging.
			// There was an unrecoverable socket error because of a phone line break 
			// (i.e. with DSL users after 24 hours or sometimes 
			// because of hitting the disconnect button).
			// The ping packets go out but come never back again.
			// Retrieving a new IP of the host reinitializes WinSOCK. 
			// Afterwards packets come back in again.
			//theApp.emuledlg->AddLogLine(false,"WinSOCK seems to be confused. Restarting Dynamic Upload.");
			if (SetIP(1, theApp.glob_prefs->GetMaxGraphUploadRate(), theApp.glob_prefs->GetURLPing())) {
				SetInitialPing ();
				remainingMaxPings = 3*m_ping_values;  // Reset counter of timeouts
			} else {
				theApp.emuledlg->AddLogLine(false,"Couldn't resolve DNS hostname for Dynamic Upload. Trying again.");
				isDynup = true; // We have to make sure that this thread doesn't terminate
								// even if the DNS wasn't found; we know that it exist
								// (because otherwise this thread would not have been started
								// at all), but eventually there's still no connection
								// so that the DNS couldn't be resolved yet.
			}
			::Sleep (500);  // Wait a moment
		}
		else {
			// normal ping handling
			m_wait_ms = soll_ping - raw_ping;
			if (m_wait_ms < 0) m_wait_ms = 0;
			::Sleep((DWORD)m_wait_ms);
		}
	}

	AfxEndThread(0, true);

	return 0;
}

//int CPingThread::Run(void)
//{
//	while (isDynup) {
//		soll_ping = (double)theApp.glob_prefs->GetBasePing();
//
//		raw_ping = Ping() ;
//		if (initial_ping > raw_ping) {
//			initial_ping = raw_ping;
//		}
//		normalized_ping = raw_ping - initial_ping;
//
//		// Shift values and add new value
//		for (int i = (m_ping_values - 1); i > 0; i--) {
//			ping[i] = ping[i - 1];
//			average_ping += ping[i];
//		}
//		ping[0] = normalized_ping;
//
//		// Calculate average
//		average_ping = 0 ;
//		for (int i = 0; i < m_ping_values; i++) { // Possible value range from 1 to 20
//			average_ping += ping[0];
//		}
//		average_ping /= m_ping_values;
//
//		// Calculate Waiting Time
//		hping = soll_ping - average_ping;
//		if (raw_ping >= 1100) {		// Needed emergency countermesure
//			upload -= (double)1.5;
//		} else if (hping > 100) {	// No emergency countermesures needed everything look nice
//			upload += (double)0.5;
//		} else if (hping < -200) {
//			upload -= (double)1;
//		} else if (hping < -100) {
//			upload -= (double)0.5;
//		}
//
//		if (upload < 1) {
//			upload = 1;
//		} else if (upload > maxUpload) {
//			upload = maxUpload;
//		}
//
//		//theApp.emuledlg->AddLogLine(false, "Dynup: upload=%.1f, initial_ping=%i, raw_ping=%i, soll_ping=%i, average_ping=%i", upload, initial_ping, raw_ping, soll_ping, average_ping);
//		//theApp.emuledlg->AddLogLine(false, "Dynup: hping=%i, normalized_ping=%i", hping, normalized_ping);
//		theApp.glob_prefs->SetMaxUpload((uint16)upload);
//
//		m_wait_ms = 1000 - raw_ping;
//		if (m_wait_ms < 0) m_wait_ms = 0;
//		::Sleep((DWORD)m_wait_ms);
//	}
//
//	AfxEndThread(0, true);
//
//	return 0;
//}

//int CPingThread::Run(void)
//{ 
//    double  dlCurrentUpload = (double)theApp.glob_prefs->GetMaxUpload(); 
//
//	while (isDynup) {  
//		soll_ping = (double)theApp.glob_prefs->GetBasePing();  
//		raw_ping = Ping() ;  
//		if (initial_ping > raw_ping) {
//			initial_ping = raw_ping;  
//		}  
//		normalized_ping = raw_ping - initial_ping;  
//
//		// Shift values and add new value  
//		for (int i = (m_ping_values - 1); i > 0; i--) {
//			ping[i] = ping[i - 1];  
//		}  
//		ping[0] = normalized_ping;  
//
//		// Calculate average  
//		average_ping = 0 ;  
//		for (int i = 0; i < m_ping_values; i++) { 
//			// Possible value range from 1 to 20 
//			average_ping += ping[i];  
//		}
//		average_ping /= m_ping_values;    
//
//		// Calculate upload
//		dlCurrentUpload = (double)theApp.uploadqueue->GetDatarate() / 1024.0f;
//		hping = soll_ping - average_ping;
//		upload = dlCurrentUpload + (double)hping / 100.0f;
//		if (upload < 1) {
//			upload = 1;
//		} else if (upload > maxUpload) {
//			upload = maxUpload;
//		}
//		if (theApp.glob_prefs->GetMaxUpload() != (uint16)upload) {
//			theApp.glob_prefs->SetMaxUpload((uint16)upload);
//		}
//		//theApp.emuledlg->AddLogLine(false, "Dynup:  upload set to: %i, %f" ,  (uint16)upload,         //upload);  
//		
//		m_wait_ms = 1000 - raw_ping;
//		if (m_wait_ms < 0) m_wait_ms = 0 ;
//
//		::Sleep(m_wait_ms);
//	}
//
//	AfxEndThread(0, true);
//	return 0;
//}


bool CPingThread::SetIP(uint16 minup, uint16 maxup, CString DynupNic)
{	
	maxUpload = (double)maxup * 0.96;
	minUpload = (double)minup;
	if (minUpload < 1.0) minUpload = 1.0;
	//theApp.emuledlg->AddLogLine(false,"Dynamic Upload System: min.:%.1f, max.:%.1f pinging %s", minUpload, maxUpload, DynupNic);

	// Lookup destination
    // Use inet_addr() to determine if we're dealing with a name
    // or an address
	iaDest.s_addr = inet_addr(DynupNic);
	if (iaDest.s_addr == INADDR_NONE) {
		pHost = gethostbyname(DynupNic);
	} else {
        pHost = gethostbyaddr((const char*)&iaDest, sizeof(struct in_addr), AF_INET);
	}
	if (pHost == NULL) {
		theApp.emuledlg->AddLogLine(false, "\n%s not found\n", DynupNic);
		isDynup = false;
		return false;
	}
	dwAddress = (DWORD*)(*pHost->h_addr_list);

	// by Mighty Knife: Close handle if it is already open
	// Reopening another handle restarts the TCP stack !
	if(hndlFile != NULL) pIcmpCloseHandle(hndlFile);

	// Get an ICMP echo request handle        
	hndlFile = pIcmpCreateFile();

	return true;
}

int CPingThread::Ping(void)
{
	//icmpEcho.ipInfo.Ttl = 256;

	// Reqest an ICMP echo
	dwRet = pIcmpSendEcho(hndlFile,		// Handle from IcmpCreateFile()
						  *dwAddress,	// Destination IP address
						  NULL,			// Pointer to buffer to send
						  0,			// Size of buffer in bytes
						  &ipInfo,		// Request options
						  &icmpEcho,	// Reply buffer
						  sizeof(struct tagICMPECHO),
						  1000);		// Time to wait in milliseconds
	
	// Print the results
	iaDest.s_addr = icmpEcho.Source;
	//theApp.emuledlg->AddLogLine(false,"\nReply from %s  Time=%ldms  TTL=%d",inet_ntoa(iaDest),	icmpEcho.RTTime,icmpEcho.ipInfo.Ttl);
		
	// Timeout
	if (icmpEcho.Status) return 1100;

	return icmpEcho.RTTime;
}

// Stop thread (can be called more than one time)
void CPingThread::StopDynup(void)
{
	if (isDynup) {
		SuspendThread();
		isDynup = false; 
	}
}

// Resumes thread (can be called more than one time)
void CPingThread::StartDynup(void)
{
	if (!isDynup) {
		isDynup = true;
		ResumeThread();
	}
}

// Finish thread
void CPingThread::EndDynup(void)
{
	isDynup = false;
	ResumeThread();
	::Sleep(1000); // Added by Tarod let it finish before close
}
