
/*
  File: include/basic/win32/threadport.h
  Description: Sonique 2 operating system specific thread functions
  Part of the Mantis project
  (c) 2002 Lycos Inc.

  This file is distributed for the sole purpose of allowing you to write
  software to interface with Sonique 2 and other Mantis programs. It is
  not to be modified or used for any other purpose without written
  authorization.

  If you redistribute this file, it must be in the original package
  complete with all other files provided, including the usage agreement.

  Do not remove this notice.
*/

#ifndef	_WIN32_THREAD_H__
#define _WIN32_THREAD_H__

#ifdef DEBUG_INCLUDE_ORDER
#pragma message("Including " __FILE__ "\n" )
#endif

#if _MSC_VER > 1000
#pragma once
#endif

#include "typesport.h"
#include <process.h>
#include "array.h"

#ifdef _DEBUG
#include "win_ostest.h"
#endif

inline void CRASH() {	int *a = 0; *a = 0; }

#pragma warning( push )
#pragma warning( disable : 4035 )

// only works under NT
__forceinline long GetUniqueThreadValue()//CurrentThreadIDByCheating()
{
	__asm
	{
		mov EAX, FS:[0018h]
		//mov EAX, [EAX+0024h]
	}
}


__forceinline long AtomicAdd( long *var, long val  ) // return the previous value
{
	__asm
	{
		mov eax, val
		mov ecx, var
		lock xadd dword ptr [ecx],eax
	}

	// return value is in EAX
}

__forceinline long AtomicExchange( long *var, long val  ) // return the previous value
{
	__asm
	{
		mov eax, val
		mov ecx, var
		lock xchg dword ptr [ecx],eax
	}

	// return value is in EAX
}


__forceinline long AtomicIncrement( long *var )
{
	//return InterlockedIncrement( var );
//	long lret;
	__asm
	{
		mov ecx, var
		mov eax, 1
		lock xadd dword ptr [ecx],eax
		inc eax
//		mov lret,eax
	}
//	return lret;
}

__forceinline long AtomicDecrement( long *var )
{
	//return InterlockedDecrement( var );
//	long lret;
	__asm
	{
		mov ecx, var
		mov eax, -1
		lock xadd dword ptr [ecx],eax
		dec eax
//		mov lret,eax
	}
//	return lret;
}

#pragma warning( pop )

inline void PauseCurThread( unsigned long nMillSec )
{
	Sleep( nMillSec );
}

typedef uint32 millisecond;
extern int64 TimerFrequency;

inline millisecond GetMillisecondCount( )
{
	__int64 count;
	QueryPerformanceCounter( (LARGE_INTEGER*)&count );
	return (millisecond)(__int64)((count*1000)/TimerFrequency);
}

inline int32 GetNumberOfCPUs()
{
	SYSTEM_INFO SystemInfo;
	GetSystemInfo(&SystemInfo);
	return SystemInfo.dwNumberOfProcessors;
}


class GlobalNamedMutex
{
protected:
	HANDLE	hMutex;
#ifdef _DEBUG
	long				m_LockCount;
    DWORD               m_OwnerThread;
#endif	
public:
	GlobalNamedMutex( const char* Name = (const char*) NULL )
	{
		if( Name != NULL )
			Init( Name );
		hMutex = NULL;

#ifdef _DEBUG
		m_LockCount = 0;
#endif

	}

	~GlobalNamedMutex()
	{
		Init( NULL );
		hMutex = NULL;

		ASSERT(m_LockCount == 0);

	}

	bool Init( const char* Name )
	{
		if( hMutex )
			CloseHandle( hMutex );

		if( Name )
			hMutex = CreateMutex( NULL, false, Name );
		else
			hMutex = NULL;
		
		return NULL != hMutex;
	}

	void Enter()
	{
		ASSERT( hMutex ); // did you Init yet?


		if( !WaitForSingleObject( hMutex, INFINITE ) == WAIT_OBJECT_0 )
		{
			ASSERT(0); /// errror?
		}

#ifdef _DEBUG
		m_LockCount++;
        m_OwnerThread = GetCurrentThreadId();
#endif

	}

	void Leave()
	{
#ifdef _DEBUG
		m_LockCount--;
		int lock = m_LockCount;
		
#endif

		ReleaseMutex( hMutex );

#ifdef _DEBUG		
		ASSERT( lock >= 0 );
#endif
	}

#ifdef _DEBUG
	// only used for debug builds (inside an ASSERT)
	bool IsLocked()
	{
		return m_LockCount > 0 && m_OwnerThread == GetCurrentThreadId();
	}
	void SetDebugName(const char* Name)
	{
		m_DebugName = Name;
	}

	const char* m_DebugName;
#endif

};


typedef enum ThreadObjectPriority{
	priority_idle		= THREAD_PRIORITY_IDLE,
	priority_lowest		= THREAD_PRIORITY_LOWEST,
	priority_low		= THREAD_PRIORITY_BELOW_NORMAL,
	priority_normal		= THREAD_PRIORITY_NORMAL,
	priority_high		= THREAD_PRIORITY_ABOVE_NORMAL,
	priority_highest 	= THREAD_PRIORITY_HIGHEST,
	priority_realtime	= THREAD_PRIORITY_TIME_CRITICAL,

    //specialized priority classes
	priority_graphic    = priority_normal
} ThreadObjectPriority;

#define THREADENTRY __stdcall

class ThreadObject
{

public:
	HANDLE					tH;
	DWORD					tID;
	BOOL					bClose;
public:
	ThreadObject()
	{
		tH		= NULL;
		tID		= 0;
		bClose  = false;
	};
	~ThreadObject()
	{
		if( bClose )
			CloseHandle( tH );
	};

	bool operator==( ThreadObject& other )
	{
		return tID == other.tID;
	};
	bool operator!=( ThreadObject& other )
	{
		return tID != other.tID;
	};

	uint32 GetThreadID()
	{
		return (uint32) tID;
	}
	static uint32 GetCurrentThreadID()
	{
		return ::GetCurrentThreadId();//GetCurrentThreadIDByCheating();
	}
	bool AttachCurrent()
	{
		bClose  = false;
		HANDLE hProcess = GetCurrentProcess();

		DuplicateHandle( 
				hProcess,
				GetCurrentThread(),
				hProcess,
				&tH,
				THREAD_ALL_ACCESS,
				FALSE,
				DUPLICATE_SAME_ACCESS );
		
		tID = ::GetCurrentThreadId();//ByCheating();

		return true;
	};
	
	bool Start( int32 (__stdcall *function)(void*), ThreadObjectPriority priority, void* data, char* /*threadname = 0*/ )
	{
		// threadname is ignored
		
//		tH = CreateThread(NULL, 0, (unsigned long(__stdcall *)(void*))function, data, 0, &tID);
		tH = (HANDLE) _beginthreadex( NULL, 0, (unsigned int(__stdcall *)(void*))function, data, 0, (unsigned int*)&tID );
		
		if( (int)tH == -1 )
		{
			return false;
		}
		else
		{
			bClose = true;
			SetPriority( priority );
			return true;	// success
		}
	};
	
	bool SetPriority( ThreadObjectPriority priority )
	{
		return SetThreadPriority( tH, (int32) priority ) != 0;
	};
	static bool SetCurrentThreadPriority( ThreadObjectPriority priority )
	{
		return SetThreadPriority( GetCurrentThread(), (int32) priority ) != 0;
	};
	bool Suspend()
	{
		return SuspendThread( tH ) != 0xFFFFFFFF;
	};
	bool Resume()
	{	
		return ResumeThread( tH ) != 0xFFFFFFFF;
	};
	bool Terminate()
	{
		return TerminateThread( tH, 0xFFFFFFFF ) != 0;
	};
	bool WaitForDeath( int32* exitValue = (int32*)NULL )
	{
		if( !IsCurrentThread() )
		{
			bool Result;
			Result = (WaitForSingleObject( tH, INFINITE ) == WAIT_OBJECT_0);

			if( Result && exitValue )
				GetExitCodeThread( tH, (LPDWORD)exitValue );

			return Result;
		} else {
			ASSERT(!"huh? waiting for YOURSELF to die?");
			return false;
		}
	};
	/*
	bool RequestThreadTermination()	// useage requires that thread being asked to quit handles the request
	{
		while( !PostThreadMessage( tID, WM_QUIT,0,0) )
			Sleep(10);
		return true;
	};
	*/
	bool IsCurrentThread()
	{
		return tID == (uint32)GetCurrentThreadId();//ByCheating();
	}
};

inline ThreadObject  GetCurrentThreadObject()
{
	ThreadObject to;
	VERIFY(to.AttachCurrent());

	return to;
}


typedef enum {
	signal_set = 0,
	signal_timeout = -1,
	signal_error = -2
} SignalObjectCode; 

#define INFINITE 0xFFFFFFFF


class SignalObject
{
protected:
	HANDLE					hEvent;
public:

	SignalObject()
	{
		hEvent = CreateEvent( NULL, 0, 0, NULL);
	};

	~SignalObject()
	{
		if( hEvent )
		{
			CloseHandle( hEvent );
			hEvent = NULL;
		}
	};

	SignalObjectCode WaitForSignal( unsigned long nTimeout = INFINITE )
	{
		int ret = WaitForSingleObject( hEvent, nTimeout );

		if( ret == WAIT_OBJECT_0 )
			return signal_set;
		else if( ret == WAIT_TIMEOUT )
			return signal_timeout;
		else
		{
#ifdef _DEBUG
			if (ret == WAIT_FAILED)
			{
				DWORD dwErr = GetLastError();
				LPVOID lpMsgBuf;
				FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,	0, NULL );
				TRACE2("WaitForSingleObject failed - reason = %d - %s\n", dwErr, lpMsgBuf);
				LocalFree( lpMsgBuf );
			}
#endif
			return signal_error;
		}
	};
	
	static SignalObjectCode WaitForMultipleSignals( const Array<SignalObject*>& Signals, unsigned long nTimeout = INFINITE )
	{
		HANDLE* Handles;
		uint32 i;
		Handles = new HANDLE[Signals.GetCount()];
		if( !Handles )
			return signal_error;
		for( i = 0; i < Signals.GetCount(); i++ )
			Handles[i] = Signals[i]->hEvent;
		int ret = WaitForMultipleObjects(i, Handles, false, nTimeout);
		delete [] Handles;

		if( ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + 256 )
			return (SignalObjectCode)(signal_set + (ret - WAIT_OBJECT_0));
		else if( ret == WAIT_TIMEOUT )
			return signal_timeout;
		else
		{
#ifdef _DEBUG
			if (ret == WAIT_FAILED)
			{
				DWORD dwErr = GetLastError();
				LPVOID lpMsgBuf;
				FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,	0, NULL );
				TRACE2("WaitForSingleObject failed - reason = %d - %s\n", dwErr, lpMsgBuf);
				LocalFree( lpMsgBuf );
			}
#endif
			return signal_error;
		}
	}

	void SetSignal()
	{
		SetEvent( hEvent );
	}


};

/*
class CriticalSectionObject
{
protected:
	long LockCount;
	long OwnerThread;
	long InternalLockCount;
	long Spin;
	SignalObject Signal;
public:
	CriticalSectionObject()
	{
		LockCount = -1;
		OwnerThread = 0;
		InternalLockCount = 0;
		Spin = GetNumberOfCPUs() > 1 ? 512 : 0;
	}
	~CriticalSectionObject()
	{
		ASSERT(LockCount == -1);
	}

	void Enter()
	{
		long ThreadID = GetCurrentThreadId();//ByCheating();
		long OurSpin = Spin;

		while(true)
		{
			if( AtomicIncrement(&LockCount) == 0 )
			{
				OwnerThread = ThreadID;
				break;
			}
			if( OwnerThread == ThreadID )
				break;

			AtomicDecrement(&LockCount);
			if( OurSpin == 0 )
				Signal.WaitForSignal(1);
			else
				OurSpin--;
		}
		InternalLockCount++;
	}

	void Leave()
	{
		bool bSet = false;
		if( --InternalLockCount == 0 )
		{
			OwnerThread = 0;
			bSet = true;
		}
		AtomicDecrement(&LockCount);
		if( bSet )
			Signal.SetSignal();
	}

#ifdef _DEBUG
	bool IsLocked()
	{
		return InternalLockCount > 0;
	}
#endif
};
*/


// this is a pointer to a CRITICAL_SECTION so that the CSO can be moved in memory and not cause problems under Win9X systems
class CriticalSectionObject  
{
	CRITICAL_SECTION *m_pcs;

public:

#ifdef _DEBUG
	long				m_LockCount;
	long				m_EntryCount;
#endif	
	
	CriticalSectionObject()
	{
		m_pcs = new CRITICAL_SECTION;

		InitializeCriticalSection( m_pcs );
#ifdef _DEBUG
		m_LockCount  = 0;
		m_EntryCount = 0;
#endif
	}
	~CriticalSectionObject()
	{
#ifdef _DEBUG
		ASSERT(m_LockCount == 0);

		if( IsUnicodeFileSystem() ) // NT stuff for now
		{
			if( ((RTL_CRITICAL_SECTION*)m_pcs)->DebugInfo->ContentionCount > 0 || m_EntryCount > 1000 )
			{
				TRACE3("CriticalSectionObject %p contention:%d entrys:%d\n", this, ((RTL_CRITICAL_SECTION*)m_pcs)->DebugInfo->ContentionCount, m_EntryCount );
//				ASSERT(0); //? test.. comment this out if it happens?
			}
		}
#endif
		DeleteCriticalSection( m_pcs );


		ASSERT( m_pcs ); // destroying twice?
		delete m_pcs;
		m_pcs = NULL;
		
	}

	void Enter()
	{
		EnterCriticalSection( m_pcs );
#ifdef _DEBUG
		m_LockCount++;
		m_EntryCount++;
#endif
	}

	void Leave()
	{
#ifdef _DEBUG
		m_LockCount--;
		int lock = m_LockCount;
#endif
		LeaveCriticalSection( m_pcs );
#ifdef _DEBUG		
		ASSERT( lock >= 0 );
#endif
	}

#ifdef _DEBUG
	// only used for debug builds (inside an ASSERT)
	bool IsLocked()
	{
		return m_LockCount > 0;
	}

#endif

};



#ifdef DEBUG_INCLUDE_ORDER
#pragma message("Done including " __FILE__ "\n" )
#endif

#endif 
