//NOTE by WiZaRd: the whole conchecker thing was revamped a bit by myself
//to optimize it a bit and remove unneeded parts... hope it ain't broken :P

#include "StdAfx.h"
#include "emule.h"
#include "emuleDlg.h"
#include "conchecker.h"
#include "otherfunctions.h"
#include "Log.h"
#include "preferences.h"
#include "opcodes.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#endif

#define MINMAX(val, mini, maxi)		{val = (min(max(mini, val), maxi));}
#define CC_INTERVAL		(m_nPingTimeout+500) //was: SEC2MS(1) - no need to check more often than our ping timeout!

const UINT UWM_CONCHECKER = ::RegisterWindowMessage(L"UWM_CONCHECKER_{C44CF9E8-06B0-4ce4-A422-53DAE6802A1E}");
//WPARAM 0 = started
//WPARAM 1 = stopped
//WRAPAM 2 = status		LPARAM (bool)connected
//WRAPAM 3 = new status LPARAM (bool)connected
namespace WombatAgent
{
	CConChecker::CConChecker()
	{
		m_hThread	= NULL;
		m_hEvent[cmd_step]	= ::CreateEvent(NULL, FALSE, FALSE, L"cmd_step");
		m_hEvent[cmd_exit]	= ::CreateEvent(NULL, FALSE, FALSE, L"cmd_exit");
		m_hKilled			= ::CreateEvent(NULL, FALSE, FALSE, L"thread_iskilled");
		m_bRun=false;
		m_dwIP	=0;
//		m_strIP.Empty();
		m_dwLastCheck=0;
		m_dwState		= CONSTATE_NULL;
		m_bSettingsChanged=true;
		m_nCounter=0;
		m_bICMP			= false;
		m_nPingTimeout	= 1000;
		m_nPingTTL		= 10;
	}

	CConChecker::~CConChecker()
	{
		m_bRun = false;
		if (IsActive())
		{
			::TerminateThread(m_hThread,0);
			::CloseHandle(m_hThread);
		}
		for (int i = 0; i < cmd_num; ++i)
		{
			if (m_hEvent[i])
				::CloseHandle(m_hEvent[i]);
		}
		if (m_hKilled)
			::CloseHandle(m_hKilled);
	}

//	bool CConChecker::SpookyAvailable() const
//	{
//		return (thePrefs.GetCheckCon() && m_dwIP != 0);
//	}

	void CConChecker::SetPreferences()
	{
		CSingleLock lock(&m_DataLock);
		lock.Lock();
		const bool bICMP = m_bICMP;
		const uint16 nPingTimeout = m_nPingTimeout;
		const uint8 nPingTTL = m_nPingTTL;

		m_bICMP = thePrefs.GetICMP();
		m_nPingTimeout = thePrefs.GetPingTimeout()*1000;
		MINMAX(m_nPingTimeout, 1000, 20000);
		m_nPingTTL = thePrefs.GetPingTTL();
		MINMAX(m_nPingTTL, 8, 64);

		m_bSettingsChanged = m_bICMP != bICMP || m_nPingTimeout != nPingTimeout || m_nPingTTL != nPingTTL;
		lock.Unlock();
	}

	DWORD CConChecker::GetState()
	{
		DWORD state = 0;
		CSingleLock lock(&m_StateLock);
		lock.Lock();
		state = m_dwState;
		lock.Unlock();
		return state;
	}
	BOOL CConChecker::PassivePing()
	{
		BOOL rc = FALSE;
		CPingReply pr;

// By Taz - fix why ping ourself if NAT ???
		const CString strPing = ipstr(m_dwIP);
/* XXX
		const CString strPing = L"checkip.dyndns.org";
*/
// <------- fix

		for (uint8 i = 0; i < 4 && rc == FALSE; ++i)
		{
			if (theApp.WinSock2() && !m_bICMP)
				rc = ping.Ping2(strPing, pr, m_nPingTTL, m_nPingTimeout); //Ping trough winsock2
			else
				rc = ping.Ping1(strPing, pr, m_nPingTTL, m_nPingTimeout); //Ping trough ICMP
		}
		if (rc == FALSE /*|| m_dwIP == 0*/)
		{
			//rc = FALSE;
			m_dwIP = 0;
		}
		return rc;
	}

	bool CConChecker::ActivePing()
	{
		//First Ping my favorite ISP to check if Internet-connection is available
		//SettingsChanged();

		FindWebAddress("", m_dwIP);
		return m_dwIP != 0;
	}

	BOOL CConChecker::Check(const bool bInitial)
	{
		BOOL rc = FALSE;
		BOOL bBlocked = FALSE;
		if (bInitial || m_dwIP == 0 || m_bSettingsChanged)
		{
			CSingleLock lock(&m_DataLock);
			lock.Lock();
			m_dwIP = 0;
			m_bSettingsChanged = false; //d'oh! why isn't that reset?!
			lock.Unlock();
			if (ActivePing())
			{
				rc = PassivePing();
				bBlocked = !rc;
			}
		}
		else
			rc = PassivePing();

		CSingleLock lock(&m_StateLock);
		lock.Lock();
		if (bBlocked)
			m_dwState=CONSTATE_BLOCKED;
		else if (rc)
			m_dwState=CONSTATE_ONLINE;
		else
			m_dwState=CONSTATE_OFFLINE;
		if(bInitial)
			rc = m_dwState != CONSTATE_NULL;

		if(!bInitial)
			Signal(2, m_dwState);
		lock.Unlock();
		return rc;
	}

	bool CConChecker::Start()
	{
		if (IsActive())
			return false;

		for (int i = 0; i < cmd_num; ++i)
		{
			if (m_hEvent[i])
				::ResetEvent(m_hEvent[i]);
		}
		m_bRun = false;
		DWORD dwThreadID;
		m_hThread = ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadProc, this, CREATE_SUSPENDED, &dwThreadID);
		if (!m_hThread)
			return false;
		::SetThreadPriority(m_hThread, THREAD_PRIORITY_ABOVE_NORMAL);
		::ResumeThread(m_hThread);
		m_bRun = true;
		return true;
	}

	bool CConChecker::Stop(const bool bWait)
	{
		m_bRun=false;
		if (!IsActive())
			return true;

		// ...and signal kill event...
		::SetEvent(m_hEvent[cmd_exit]);

		if (!bWait)
			return true;

		if (::WaitForSingleObject(m_hKilled, 5000) != WAIT_OBJECT_0)
		{
			DWORD dwExitCode = 0;
			if (!::GetExitCodeThread(m_hThread, &dwExitCode))
				return false;
			if (!::TerminateThread(m_hThread, dwExitCode))
				return false;
		}
		::CloseHandle(m_hThread);
		return true;
	}

	bool CConChecker::IsActive() const
	{
		if (m_hThread==NULL)
			return false;
		DWORD dwExitCode = 0;
		::GetExitCodeThread(m_hThread, &dwExitCode);
		if (dwExitCode==STILL_ACTIVE)
			return true;
		return false;
	}

	void CConChecker::Process(const DWORD& dwTime)
	{
		if (!thePrefs.GetCheckCon()
			|| !IsActive()
			|| !m_bRun)
			return;

		if (!dwTime
			|| !m_dwLastCheck
			|| (dwTime-m_dwLastCheck >= (UINT)CC_INTERVAL))
		{
			::SetEvent(m_hEvent[cmd_step]);
			m_dwLastCheck=dwTime;
		}
	}

	UINT CConChecker::ThreadProc(LPVOID pProcParam)
	{
		TRACE(L"CConChecker (ID: 0x%x) started...", ::GetCurrentThreadId());

		CConChecker* pThis = reinterpret_cast<CConChecker*>(pProcParam);
		pThis->Signal(0, CONSTATE_NULL);

		DWORD dwResult = 0;
		//Loop
		do
		{
			dwResult = ::WaitForMultipleObjects(cmd_num,pThis->m_hEvent,FALSE,500);// - WAIT_OBJECT_0;
			dwResult-=WAIT_OBJECT_0;

			if (dwResult == cmd_step)
				pThis->Check();
		}
		while(dwResult != cmd_exit);

		pThis->Signal(1, CONSTATE_NULL);
		::SetEvent(pThis->m_hKilled);

		TRACE(L"CConChecker (ID: 0x%x) stopped...", ::GetCurrentThreadId());
		return 0;
	}

	//WPARAM 0 = started
	//WPARAM 1 = stopped
	//WRAPAM 2 = status		LPARAM dwState
	void CConChecker::Signal(const WPARAM wParam, const LPARAM lParam)
	{
		if (::IsWindow(theApp.emuledlg->GetSafeHwnd()))
			theApp.emuledlg->PostMessage(UWM_CONCHECKER, wParam, lParam);
	}
}
