//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2021 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.

#ifndef PSFOUNDATION_PSTHREAD_H
#define PSFOUNDATION_PSTHREAD_H

#include "PsUserAllocated.h"

// dsequeira: according to existing comment here (David Black would be my guess)
// "This is useful to reduce bus contention on tight spin locks. And it needs
// to be a macro as the xenon compiler often ignores even __forceinline." What's not
// clear is why a pause function needs inlining...? (TODO: check with XBox team)

// todo: these need to go somewhere else

#if PX_WINDOWS_FAMILY || PX_XBOXONE || PX_XBOX_SERIES_X
#define PxSpinLockPause() __asm pause
#elif PX_LINUX || PX_ANDROID || PX_PS4 || PX_APPLE_FAMILY || PX_SWITCH
#define PxSpinLockPause() asm("nop")
#else
#error "Platform not supported!"
#endif

namespace physx
{
namespace shdfnd
{
struct ThreadPriority // todo: put in some other header file
{
	enum Enum
	{
		/**
	    \brief High priority
	    */
		eHIGH         = 0,

		/**
	    \brief Above Normal priority
	    */
		eABOVE_NORMAL = 1,

		/**
	    \brief Normal/default priority
	    */
		eNORMAL       = 2,

		/**
	    \brief Below Normal priority
	    */
		eBELOW_NORMAL = 3,

		/**
	    \brief Low priority.
	    */
		eLOW          = 4,
		eFORCE_DWORD  = 0xffFFffFF
	};
};

class Runnable
{
  public:
	Runnable()
	{
	}
	virtual ~Runnable()
	{
	}
	virtual void execute(void)
	{
	}
};

class PX_FOUNDATION_API ThreadImpl
{
  public:
	typedef size_t Id; // space for a pointer or an integer
	typedef void* (*ExecuteFn)(void*);

	static uint32_t getDefaultStackSize();
	static Id getId();

	/**
	Construct (but do not start) the thread object. The OS thread object will not be created
	until start() is called. Executes in the context
	of the spawning thread.
	*/

	ThreadImpl();

	/**
	Construct and start the the thread, passing the given arg to the given fn. (pthread style)
	*/

	ThreadImpl(ExecuteFn fn, void* arg, const char* name);

	/**
	Deallocate all resources associated with the thread. Should be called in the
	context of the spawning thread.
	*/

	~ThreadImpl();

	/**
	Create the OS thread and start it running. Called in the context of the spawning thread.
	If an affinity mask has previously been set then it will be applied after the
	thread has been created.
	*/

	void start(uint32_t stackSize, Runnable* r);

	/**
	Violently kill the current thread. Blunt instrument, not recommended since
	it can leave all kinds of things unreleased (stack, memory, mutexes...) Should
	be called in the context of the spawning thread.
	*/

	void kill();

	/**
	Stop the thread. Signals the spawned thread that it should stop, so the
	thread should check regularly
	*/

	void signalQuit();

	/**
	Wait for a thread to stop. Should be called in the context of the spawning
	thread. Returns false if the thread has not been started.
	*/

	bool waitForQuit();

	/**
	check whether the thread is signalled to quit. Called in the context of the
	spawned thread.
	*/

	bool quitIsSignalled();

	/**
	Cleanly shut down this thread. Called in the context of the spawned thread.
	*/
	void quit();

	/**
	Change the affinity mask for this thread. The mask is a platform
	specific value.

	On Windows, Linux, PS4, XboxOne and Switch platforms, each set mask bit represents
	the index of a logical processor that the OS may schedule thread execution on.
	Bits outside the range of valid logical processors may be ignored or cause
	the function to return an error.

	On Apple platforms, this function has no effect.

	If the thread has not yet been started then the mask is stored
	and applied when the thread is started.

	If the thread has already been started then this method	returns the
	previous affinity mask on success, otherwise it returns zero.
	*/
	uint32_t setAffinityMask(uint32_t mask);

	static ThreadPriority::Enum getPriority(Id threadId);

	/** Set thread priority. */
	void setPriority(ThreadPriority::Enum prio);

	/** set the thread's name */
	void setName(const char* name);

	/** Put the current thread to sleep for the given number of milliseconds */
	static void sleep(uint32_t ms);

	/** Yield the current thread's slot on the CPU */
	static void yield();

	/** Return the number of physical cores (does not include hyper-threaded cores), returns 0 on failure */
	static uint32_t getNbPhysicalCores();

	/**
	Size of this class.
	*/
	static uint32_t getSize();
};

/**
Thread abstraction API
*/
template <typename Alloc = ReflectionAllocator<ThreadImpl> >
class ThreadT : protected Alloc, public UserAllocated, public Runnable
{
  public:
	typedef ThreadImpl::Id Id; // space for a pointer or an integer

	/**
	Construct (but do not start) the thread object. Executes in the context
	of the spawning thread
	*/
	ThreadT(const Alloc& alloc = Alloc()) : Alloc(alloc)
	{
		mImpl = reinterpret_cast<ThreadImpl*>(Alloc::allocate(ThreadImpl::getSize(), __FILE__, __LINE__));
		PX_PLACEMENT_NEW(mImpl, ThreadImpl)();
	}

	/**
	Construct and start the the thread, passing the given arg to the given fn. (pthread style)
	*/
	ThreadT(ThreadImpl::ExecuteFn fn, void* arg, const char* name, const Alloc& alloc = Alloc()) : Alloc(alloc)
	{
		mImpl = reinterpret_cast<ThreadImpl*>(Alloc::allocate(ThreadImpl::getSize(), __FILE__, __LINE__));
		PX_PLACEMENT_NEW(mImpl, ThreadImpl)(fn, arg, name);
	}

	/**
	Deallocate all resources associated with the thread. Should be called in the
	context of the spawning thread.
	*/
	virtual ~ThreadT()
	{
		mImpl->~ThreadImpl();
		Alloc::deallocate(mImpl);
	}

	/**
	start the thread running. Called in the context of the spawning thread.
	*/

	void start(uint32_t stackSize = ThreadImpl::getDefaultStackSize())
	{
		mImpl->start(stackSize, this);
	}

	/**
	Violently kill the current thread. Blunt instrument, not recommended since
	it can leave all kinds of things unreleased (stack, memory, mutexes...) Should
	be called in the context of the spawning thread.
	*/

	void kill()
	{
		mImpl->kill();
	}

	/**
	The virtual execute() method is the user defined function that will
	run in the new thread. Called in the context of the spawned thread.
	*/

	virtual void execute(void)
	{
	}

	/**
	stop the thread. Signals the spawned thread that it should stop, so the
	thread should check regularly
	*/

	void signalQuit()
	{
		mImpl->signalQuit();
	}

	/**
	Wait for a thread to stop. Should be called in the context of the spawning
	thread. Returns false if the thread has not been started.
	*/

	bool waitForQuit()
	{
		return mImpl->waitForQuit();
	}

	/**
	check whether the thread is signalled to quit. Called in the context of the
	spawned thread.
	*/

	bool quitIsSignalled()
	{
		return mImpl->quitIsSignalled();
	}

	/**
	Cleanly shut down this thread. Called in the context of the spawned thread.
	*/
	void quit()
	{
		mImpl->quit();
	}

	uint32_t setAffinityMask(uint32_t mask)
	{
		return mImpl->setAffinityMask(mask);
	}

	static ThreadPriority::Enum getPriority(ThreadImpl::Id threadId)
	{
		return ThreadImpl::getPriority(threadId);
	}

	/** Set thread priority. */
	void setPriority(ThreadPriority::Enum prio)
	{
		mImpl->setPriority(prio);
	}

	/** set the thread's name */
	void setName(const char* name)
	{
		mImpl->setName(name);
	}

	/** Put the current thread to sleep for the given number of milliseconds */
	static void sleep(uint32_t ms)
	{
		ThreadImpl::sleep(ms);
	}

	/** Yield the current thread's slot on the CPU */
	static void yield()
	{
		ThreadImpl::yield();
	}

	static uint32_t getDefaultStackSize()
	{
		return ThreadImpl::getDefaultStackSize();
	}

	static ThreadImpl::Id getId()
	{
		return ThreadImpl::getId();
	}

	static uint32_t getNbPhysicalCores()
	{
		return ThreadImpl::getNbPhysicalCores();
	}

  private:
	class ThreadImpl* mImpl;
};

typedef ThreadT<> Thread;

PX_FOUNDATION_API uint32_t TlsAlloc();
PX_FOUNDATION_API void TlsFree(uint32_t index);
PX_FOUNDATION_API void* TlsGet(uint32_t index);
PX_FOUNDATION_API size_t TlsGetValue(uint32_t index);
PX_FOUNDATION_API uint32_t TlsSet(uint32_t index, void* value);
PX_FOUNDATION_API uint32_t TlsSetValue(uint32_t index, size_t value);

} // namespace shdfnd
} // namespace physx

#endif // #ifndef PSFOUNDATION_PSTHREAD_H
