// 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-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.

#include "FdFoundation.h"
#include "foundation/PxString.h"
#include "foundation/PxPhysicsVersion.h"
#include "foundation/PxUserAllocated.h"
#include "foundation/PxBroadcast.h"

namespace physx
{
#if PX_VC
#pragma warning(push)
#pragma warning(disable : 4251) // class needs to have dll-interface to be used by clients of class
#endif

class PX_FOUNDATION_API Foundation : public PxFoundation, public PxUserAllocated
{
	PX_NOCOPY(Foundation)

  public:
	// PxFoundation
	virtual	void					release()								PX_OVERRIDE;
	virtual PxErrorCallback&		getErrorCallback()						PX_OVERRIDE	{ return mErrorCallback;			}
	virtual void					setErrorLevel(PxErrorCode::Enum mask)	PX_OVERRIDE	{ mErrorMask = mask;				}
	virtual	PxErrorCode::Enum		getErrorLevel() const					PX_OVERRIDE	{ return mErrorMask;				}
	virtual PxAllocatorCallback&	getAllocatorCallback()					PX_OVERRIDE	{ return mAllocatorCallback;		}
	virtual bool					getReportAllocationNames() const		PX_OVERRIDE	{ return mReportAllocationNames;	}
	virtual void					setReportAllocationNames(bool value)	PX_OVERRIDE	{ mReportAllocationNames = value;	}
	virtual void					registerAllocationListener(physx::PxAllocationListener& listener)	PX_OVERRIDE;
	virtual void					deregisterAllocationListener(physx::PxAllocationListener& listener)	PX_OVERRIDE;
	virtual void					registerErrorCallback(PxErrorCallback& listener)	PX_OVERRIDE;
	virtual void					deregisterErrorCallback(PxErrorCallback& listener)	PX_OVERRIDE;
	virtual bool					error(PxErrorCode::Enum, const char* file, int line, const char* messageFmt, ...)		PX_OVERRIDE;
	virtual bool					error(PxErrorCode::Enum, const char* file, int line, const char* messageFmt, va_list)	PX_OVERRIDE;
	//~PxFoundation

	Foundation(PxErrorCallback& errc, PxAllocatorCallback& alloc);
	~Foundation();

	// init order is tricky here: the mutexes require the allocator, the allocator may require the error stream
	PxAllocatorCallback& mAllocatorCallback;
	PxErrorCallback& mErrorCallback;

	PxBroadcastingAllocator mBroadcastingAllocator;
	PxBroadcastingErrorCallback mBroadcastingError;

	bool mReportAllocationNames;

	PxErrorCode::Enum mErrorMask;
	Mutex mErrorMutex;

	AllocFreeTable mTempAllocFreeTable;
	Mutex mTempAllocMutex;

	Mutex mListenerMutex;

    PxU32 mRefCount;
	static PxU32 mWarnOnceTimestap;
};
#if PX_VC
#pragma warning(pop)
#endif

} // namespace physx

using namespace physx;

static PxProfilerCallback* gProfilerCallback = NULL;
static Foundation* gInstance = NULL;

// PT: not in header so that people don't use it, only for temp allocator, will be removed
AllocFreeTable& getTempAllocFreeTable()
{
	PX_ASSERT(gInstance);
	return gInstance->mTempAllocFreeTable;
}

// PT: not in header so that people don't use it, only for temp allocator, will be removed
Mutex& getTempAllocMutex()
{
	PX_ASSERT(gInstance);
	return gInstance->mTempAllocMutex;
}

Foundation::Foundation(PxErrorCallback& errc, PxAllocatorCallback& alloc) :
	mAllocatorCallback		(alloc),
	mErrorCallback			(errc),
	mBroadcastingAllocator	(alloc, errc),
	mBroadcastingError		(errc),
#if PX_CHECKED
    mReportAllocationNames	(true),
#else
    mReportAllocationNames	(false),
#endif
    mErrorMask				(PxErrorCode::Enum(~0)),
	mErrorMutex				("Foundation::mErrorMutex"),
	mTempAllocMutex			("Foundation::mTempAllocMutex"),
	mRefCount				(0)
{
}

void deallocateTempBufferAllocations(AllocFreeTable& mTempAllocFreeTable);

Foundation::~Foundation()
{
	deallocateTempBufferAllocations(mTempAllocFreeTable);
}

bool Foundation::error(PxErrorCode::Enum c, const char* file, int line, const char* messageFmt, ...)
{
	va_list va;
	va_start(va, messageFmt);
	error(c, file, line, messageFmt, va);
	va_end(va);
	return false;
}

bool Foundation::error(PxErrorCode::Enum e, const char* file, int line, const char* messageFmt, va_list va)
{
	PX_ASSERT(messageFmt);
	if(e & mErrorMask)
	{
		// this function is reentrant but user's error callback may not be, so...
		Mutex::ScopedLock lock(mErrorMutex);

		// using a static fixed size buffer here because:
		// 1. vsnprintf return values differ between platforms
		// 2. va_start is only usable in functions with ellipses
		// 3. ellipses (...) cannot be passed to called function
		// which would be necessary to dynamically grow the buffer here

		static const size_t bufSize = 1024;
		char stringBuffer[bufSize];
		Pxvsnprintf(stringBuffer, bufSize, messageFmt, va);

		mBroadcastingError.reportError(e, stringBuffer, file, line);
	}
	return false;
}

void Foundation::release()
{
	PX_ASSERT(gInstance);

	if(gInstance->mRefCount == 1)
	{
		PxAllocatorCallback& alloc = gInstance->getAllocatorCallback();
		gInstance->~Foundation();
		alloc.deallocate(gInstance);
        gInstance = NULL;
	}
	else
	{
		gInstance->error(PxErrorCode::eINVALID_OPERATION, PX_FL,
		                 "Foundation destruction failed due to pending module references. Close/release all depending modules first.");
	}
}

PxU32 Foundation::mWarnOnceTimestap = 0;

void Foundation::registerAllocationListener(PxAllocationListener& listener)
{
	Mutex::ScopedLock lock(mListenerMutex);
	mBroadcastingAllocator.registerListener(listener);
}

void Foundation::deregisterAllocationListener(PxAllocationListener& listener)
{
	Mutex::ScopedLock lock(mListenerMutex);
	mBroadcastingAllocator.deregisterListener(listener);
}

void Foundation::registerErrorCallback(PxErrorCallback& callback)
{
	Mutex::ScopedLock lock(mListenerMutex);
	mBroadcastingError.registerListener(callback);
}

void Foundation::deregisterErrorCallback(PxErrorCallback& callback)
{
	Mutex::ScopedLock lock(mListenerMutex);
	mBroadcastingError.deregisterListener(callback);
}

PxFoundation* PxCreateFoundation(PxU32 version, PxAllocatorCallback& allocator, PxErrorCallback& errorCallback)
{
	if(version != PX_PHYSICS_VERSION)
	{
		char buffer[256];
		Pxsnprintf(buffer, 256, "Wrong version: physics version is 0x%08x, tried to create 0x%08x", PX_PHYSICS_VERSION, version);
		errorCallback.reportError(PxErrorCode::eINVALID_PARAMETER, buffer, PX_FL);
		return 0;
	}

	if(!gInstance)
	{
		// if we don't assign this here, the Foundation object can't create member
		// subobjects which require the allocator

		gInstance = reinterpret_cast<Foundation*>(allocator.allocate(sizeof(Foundation), "Foundation", PX_FL));

		if(gInstance)
		{
			PX_PLACEMENT_NEW(gInstance, Foundation)(errorCallback, allocator);

			PX_ASSERT(gInstance->mRefCount == 0);
			gInstance->mRefCount = 1;

			// skip 0 which marks uninitialized timestaps in PX_WARN_ONCE
			gInstance->mWarnOnceTimestap = (gInstance->mWarnOnceTimestap == PX_MAX_U32) ? 1 : gInstance->mWarnOnceTimestap + 1;

			return gInstance;
		}
		else
		{
			errorCallback.reportError(PxErrorCode::eINTERNAL_ERROR, "Memory allocation for foundation object failed.", PX_FL);
		}
	}
	else
	{
		errorCallback.reportError(PxErrorCode::eINVALID_OPERATION, "Foundation object exists already. Only one instance per process can be created.", PX_FL);
	}

	return 0;
}

void PxSetFoundationInstance(PxFoundation& foundation)
{
	gInstance = &static_cast<Foundation&>(foundation);
}

PxAllocatorCallback* PxGetAllocatorCallback()
{
	return &gInstance->getAllocatorCallback();
}

PxAllocatorCallback* PxGetBroadcastAllocator(bool* reportAllocationNames)
{
	PX_ASSERT(gInstance);
	if(reportAllocationNames)
		*reportAllocationNames = gInstance->mReportAllocationNames;

	return &gInstance->mBroadcastingAllocator;
}

PxErrorCallback* PX_CALL_CONV PxGetErrorCallback()
{
	return &gInstance->getErrorCallback();
}

PxErrorCallback* PX_CALL_CONV PxGetBroadcastError()
{
	return &gInstance->mBroadcastingError;
}

PxFoundation& PxGetFoundation()
{
	PX_ASSERT(gInstance);
	return *gInstance;
}

PxFoundation* PxIsFoundationValid()
{
	return gInstance;
}

PxProfilerCallback* PxGetProfilerCallback()
{
	return gProfilerCallback;
}

void PxSetProfilerCallback(PxProfilerCallback* profiler)
{
	gProfilerCallback = profiler;
}

PxU32 PxGetWarnOnceTimeStamp()
{
	PX_ASSERT(gInstance);
	return gInstance->mWarnOnceTimestap;
}

void PxDecFoundationRefCount()
{
	PX_ASSERT(gInstance);

	if(gInstance->mRefCount > 0)
        gInstance->mRefCount--;
	else
		gInstance->error(PxErrorCode::eINVALID_OPERATION, PX_FL, "Foundation: Invalid deregistration detected.");
}

void PxIncFoundationRefCount()
{
	PX_ASSERT(gInstance);

	if(gInstance->mRefCount > 0)
        gInstance->mRefCount++;
	else
		gInstance->error(PxErrorCode::eINVALID_OPERATION, PX_FL, "Foundation: Invalid registration detected.");
}

// Private method to set the global foundation instance to NULL
PX_C_EXPORT PX_FOUNDATION_API void PX_CALL_CONV PxResetFoundationInstance()
{
	gInstance = NULL;
}
