/*
	Copyright 2004-2005 Electronic Arts Inc.
*/


#include "GameTalkLog.hpp"
#include "GameTalkThread.hpp"

#include "psapi.h"  // for enumerating processes
#include <windows.h>  // for enumerating processes
#include <winnt.h>  // for registry key methods
#include <direct.h>  // for creating directories
#include "shlobj.h"  // for SHGetFolderPath

namespace EA
{	
namespace GameTalk
{


GameTalkThread::GameTalkThread(const char *nm, int socketId, ReceiveCallback callback)
{
	Thread::setName(nm);

	// Create an network event
	mhEvent	= WSA_INVALID_EVENT;
	mhEvent	= WSACreateEvent();
	mSocketId = socketId;
	mCallback = callback;
	WSAEventSelect(mSocketId, mhEvent, FD_READ);
}

void GameTalkThread::run()
{
	m_threadState = THREAD_RUNNING;
	while(m_threadState == THREAD_RUNNING)
	{
		try 
		{
			// Set the time out in milliseconds
			DWORD timeOutDelay = 10;

			// We need to timeout here as it's possible to have a hang ...
			// The worst case is a lost packet
			int nReturnCode	= WSAWaitForMultipleEvents(1, &mhEvent, FALSE, timeOutDelay, FALSE);

			WSANETWORKEVENTS wsaEvent;
			WSAEnumNetworkEvents(mSocketId, mhEvent, &wsaEvent);
			if ((wsaEvent.lNetworkEvents & FD_READ) && (wsaEvent.iErrorCode[FD_READ_BIT] == 0)) 
			{
				// get the data from the buffer				
				int msgSize = mCallback();
			}

			// Reset the signaled event
			WSAResetEvent(mhEvent);
		}
		catch (ThreadException ex) 
		{
			*Log::GetInstance() << ex.getMessage().c_str() << endl;
			m_threadState = THREAD_ENDING;
		}
	}

	*Log::GetInstance() << "The GameTalk listen thread has ended." << endl;
	m_threadState=THREAD_STOPPED;
}

void GameTalkThread::destroy()
{
	if (m_threadState == THREAD_RUNNING)
	{
		m_threadState = THREAD_ENDING;
	}
	while (m_threadState != THREAD_STOPPED)
	{
		Sleep(1);
	}
	WSACloseEvent(mhEvent);
}

const int Thread::P_ABOVE_NORMAL	= THREAD_PRIORITY_ABOVE_NORMAL;
const int Thread::P_BELOW_NORMAL	= THREAD_PRIORITY_BELOW_NORMAL;
const int Thread::P_HIGHEST			= THREAD_PRIORITY_HIGHEST;
const int Thread::P_IDLE			= THREAD_PRIORITY_IDLE;
const int Thread::P_LOWEST			= THREAD_PRIORITY_LOWEST;
const int Thread::P_NORMAL			= THREAD_PRIORITY_NORMAL;
const int Thread::P_CRITICAL		= THREAD_PRIORITY_TIME_CRITICAL;


Thread::Thread() 
{
	m_hThread = NULL;
	m_strName = "null";
	m_threadState=THREAD_STOPPED;
}

Thread::Thread(const char *nm) 
{
	m_hThread = NULL;
	m_strName = nm;
	m_threadState=THREAD_STOPPED;
}

Thread::~Thread() 
{
	if (m_hThread != NULL) 
	{
		stop();
	}
}

void Thread::setName(const char *nm) 
{	
	m_strName = nm;
}

string Thread::getName() const 
{	
	return m_strName;
}

void Thread::run() 
{
	m_threadState=THREAD_RUNNING;
	// Base run
	m_threadState=THREAD_STOPPED;
}

void Thread::sleep(long ms) 
{
	Sleep(ms);
}

// global function called by the thread object.
// this in turn calls the overridden run()
extern "C" 
{
	unsigned int _gt_thread_proc(void *param);
}

void Thread::start() 
{
	DWORD tid = 0;	
	m_hThread = (unsigned long*)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_gt_thread_proc, (Thread*)this, 0, &tid);

	if (m_hThread == NULL) 
	{
		throw ThreadException("Failed to create thread");
	}
	else 
	{
		setPriority(Thread::P_NORMAL);
	}
}

void Thread::stop() 
{
	if (m_hThread == NULL) 
		return;	

	m_threadState=THREAD_ENDING;
	//@dan remember - I woun't wait indefinitely here any more
	// WaitForSingleObject(m_hThread,INFINITE);
	WaitForSingleObject(m_hThread, 10);
	CloseHandle(m_hThread);
	m_hThread = NULL;
}

void Thread::setPriority(int tp) 
{
	if (m_hThread == NULL) 
	{
		throw ThreadException("Thread object is null");
	}
	else 
	{
		if (SetThreadPriority(m_hThread,tp) == 0) 
		{
			throw ThreadException("Failed to set priority");
		}
	}
}

void Thread::suspend() 
{
	if (m_hThread == NULL) 
	{
		throw ThreadException("Thread object is null");
	}
	else 
	{
		if (SuspendThread(m_hThread) < 0) 
		{
			throw ThreadException("Failed to suspend thread");
		}
		m_threadState=THREAD_PAUSED;
	}
}

void Thread::resume() 
{
	if (m_hThread == NULL) 
	{
		throw ThreadException("Thread object is null");
	}
	else 
	{
		if (ResumeThread(m_hThread) < 0) 
		{
			throw ThreadException("Failed to resume thread");
		}
		m_threadState=THREAD_RUNNING;
	}
}

bool Thread::wait(const char *m, long ms) 
{
	HANDLE h = OpenMutex(MUTEX_ALL_ACCESS,FALSE,m);
	if (h == NULL) 
	{
		throw ThreadException("Mutex not found");
	}

#if defined(DEBUG)
	ms=INFINITE;  // So breakpoints don't cause the thread to die prematurely
#endif
	DWORD d = WaitForSingleObject(h,ms);
	switch(d) 
	{
	case WAIT_ABANDONED:
		throw ThreadException("Mutex not signaled");
		break;
	case WAIT_OBJECT_0:
		return true;
	case WAIT_TIMEOUT:
		throw ThreadException("Wait timed out");
		break;
	}
	return false;
}

void Thread::release(const char *m) 
{
	HANDLE h = OpenMutex(MUTEX_ALL_ACCESS,FALSE,m);
	if (h == NULL) 
	{
		throw ThreadException("Invalid mutex handle");
	}
	if (ReleaseMutex(h) == 0) 
	{
		throw ThreadException("Failed to release mutex");
	}
}

//
// Mutex
Mutex::Mutex() 
{
	m_hMutex	= NULL;
	m_strName	= "";
}

Mutex::Mutex(const char *nm) 
{	
	m_strName	= nm;	
	m_hMutex	= (unsigned long *)CreateMutex(NULL, FALSE, nm);
	if (m_hMutex == NULL) 
	{
		throw ThreadException("Failed to create mutex");
	}
}

void Mutex::create(const char *nm) 
{
	if (m_hMutex != NULL) 
	{
		CloseHandle(m_hMutex);
		m_hMutex = NULL;
	}
	m_strName	= nm;
	m_hMutex	= (unsigned long *)CreateMutex(NULL,FALSE,nm);
	if (m_hMutex == NULL) 
	{
		throw ThreadException("Failed to create mutex");
	}
}

unsigned long *Mutex::getMutexHandle() 
{
	return m_hMutex;
}

string Mutex::getName() 
{
	return m_strName;
}

void Mutex::release() 
{
	if (m_hMutex != NULL) 
	{
		CloseHandle(m_hMutex);
		m_hMutex = NULL;
	}
}

Mutex::~Mutex() 
{
	if (m_hMutex != NULL) 
	{
		CloseHandle(m_hMutex);
		m_hMutex = NULL;
	}
}

//
// ThreadException
ThreadException::ThreadException(const char *m) 
{
	msg = m;
}

string ThreadException::getMessage() const 
{
	return msg;
}

//
// global thread callback
unsigned int _gt_thread_proc(void *param) 
{
	Thread *tp = (Thread*)param;

	tp->run();

	return 0;
}

} // namespace GameTalk
} // namespace EA