//
// 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-2019 NVIDIA Corporation. All rights reserved.

#include "foundation/PxIO.h"
#include "foundation/PxErrorCallback.h"
#include "SnConvX.h"

#if PX_VC
#pragma warning(disable:4389)	// signed/unsigned mismatch
#endif

using namespace physx;

void Sn::ConvX::setNullPtr(bool flag)
{
	mNullPtr = flag;
}

void Sn::ConvX::setNoOutput(bool flag)
{
	mNoOutput = flag;
}

bool Sn::ConvX::initOutput(PxOutputStream& targetStream)
{
	mOutStream = &targetStream;

	mOutputSize = 0;
	mNullPtr = false;
	mNoOutput = false;

	const MetaData* srcMetaData = getBinaryMetaData(META_DATA_SRC);
	PX_ASSERT(srcMetaData);
	const MetaData* dstMetaData = getBinaryMetaData(META_DATA_DST);
	PX_ASSERT(dstMetaData);

	mSrcPtrSize = srcMetaData->getPtrSize();
	mDstPtrSize = dstMetaData->getPtrSize();

	PX_ASSERT(!srcMetaData->getFlip());
	mMustFlip = dstMetaData->getFlip();
	return true;
}

void Sn::ConvX::closeOutput()
{
	mOutStream = NULL;
}

int Sn::ConvX::getCurrentOutputSize()
{
	return mOutputSize;
}

void Sn::ConvX::output(short value)
{
	if(mNoOutput)
		return;

	if(mMustFlip)
		flip(value);

	PX_ASSERT(mOutStream);
	const size_t size = mOutStream->write(&value, 2);
	PX_ASSERT(size==2);
	mOutputSize += int(size);
}

void Sn::ConvX::output(int value)
{
	if(mNoOutput)
		return;

	if(mMustFlip)
		flip(value);

	PX_ASSERT(mOutStream);
	const size_t size = mOutStream->write(&value, 4);
	PX_ASSERT(size==4);
	mOutputSize += int(size);
}

//ntohll is a macro on apple yosemite
static PxU64 ntohll_internal(const PxU64 value)
{
	union
	{
		PxU64 ull;
		PxU8  c[8];
	} x;

	x.ull = value;

	PxU8 c = 0;
	c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c;
	c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c;
	c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c;
	c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c;

	return x.ull;
}

void Sn::ConvX::output(PxU64 value)
{
	if(mNoOutput)
		return;

	if(mMustFlip)
//		flip(value);
		value = ntohll_internal(value);

	PX_ASSERT(mOutStream);
	const size_t size = mOutStream->write(&value, 8);
	PX_ASSERT(size==8);
	mOutputSize += int(size);
}

void Sn::ConvX::output(const char* buffer, int nbBytes)
{
	if(mNoOutput)
		return;

	if(!nbBytes)
		return;

	PX_ASSERT(mOutStream);
	const PxU32 size = mOutStream->write(buffer, PxU32(nbBytes));
	PX_ASSERT(size== PxU32(nbBytes));
	mOutputSize += int(size);
}

void Sn::ConvX::convert8(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==1*entry.mCount);
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	const PxU32 size = mOutStream->write(src, PxU32(entry.mSize));
	PX_ASSERT(size== PxU32(entry.mSize));
	mOutputSize += int(size);
}

// This is called to convert auto-generated "padding bytes" (or so we think).
// We use a special converter to check the input bytes and issue warnings when it doesn't look like padding
void Sn::ConvX::convertPad8(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	(void)src;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize);
	PX_ASSERT(entry.mSize==1*entry.mCount);
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	// PT: we don't output the source data on purpose, to catch missing meta-data
	// sschirm: changed that to 0xcd, so we can mark the output as "having marked pads" 
	const unsigned char b = 0xcd;
	for(int i=0;i<entry.mSize;i++)
	{
		const size_t size = mOutStream->write(&b, 1);
		(void)size;
	}
	mOutputSize += entry.mSize;
}

void Sn::ConvX::convert16(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==int(sizeof(short)*entry.mCount));
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	const short* data = reinterpret_cast<const short*>(src);
	for(int i=0;i<entry.mCount;i++)
	{
		short value = *data++;
		if(mMustFlip)
			flip(value);

		const size_t size = mOutStream->write(&value, sizeof(short));
		PX_ASSERT(size==sizeof(short));
		mOutputSize += int(size);
	}
}

void Sn::ConvX::convert32(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==int(sizeof(int)*entry.mCount));
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	const int* data = reinterpret_cast<const int*>(src);
	for(int i=0;i<entry.mCount;i++)
	{
		int value = *data++;
		if(mMustFlip)
			flip(value);

		const size_t size = mOutStream->write(&value, sizeof(int));
		PX_ASSERT(size==sizeof(int));
		mOutputSize += int(size);
	}
}

void Sn::ConvX::convert64(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==int(sizeof(PxU64)*entry.mCount));
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	const PxU64* data = reinterpret_cast<const PxU64*>(src);
	for(int i=0;i<entry.mCount;i++)
	{
		PxU64 value = *data++;
		if(mMustFlip)
			value = ntohll_internal(value);

		const size_t size = mOutStream->write(&value, sizeof(PxU64));
		PX_ASSERT(size==sizeof(PxU64));
		mOutputSize += int(size);
	}
}

void Sn::ConvX::convertFloat(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==int(sizeof(float)*entry.mCount));
	PX_ASSERT(mOutStream);
	PX_ASSERT(entry.mSize==dstEntry.mSize);

	const float* data = reinterpret_cast<const float*>(src);
	for(int i=0;i<entry.mCount;i++)
	{
		float value = *data++;
		if(mMustFlip)
			flip(value);

		const size_t size = mOutStream->write(&value, sizeof(float));
		PX_ASSERT(size==sizeof(float));
		mOutputSize += int(size);
	}
}

void Sn::ConvX::convertPtr(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(entry.mSize==mSrcPtrSize*entry.mCount);
	PX_ASSERT(mOutStream);

	char buffer[16];
	for(int i=0;i<entry.mCount;i++)
	{
		PxU64 testValue=0;
		// Src pointer can be 4 or 8 bytes so we can't use "void*" here
		if(mSrcPtrSize==4)
		{
			PX_ASSERT(sizeof(PxU32)==4);
			const PxU32* data = reinterpret_cast<const PxU32*>(src);
			PxU32 value = *data++;
			src = reinterpret_cast<const char*>(data);

			if(mPointerActiveRemap)
			{
				PxU32 ref;
				if(mPointerActiveRemap->getObjectRef(value, ref))
				{
					value = ref;
				}
				else if(value)
				{
					// all pointers not in the pointer remap table get set as 0x12345678, this also applies to PhysX name properties (mName)
					value=0x12345678;
				}
			}
			else
			{
				//we should only get here during convertReferenceTables to build up the pointer map
				PxU32 ref;
				if (mPointerRemap.getObjectRef(value, ref))
				{
					value = ref;
				}
				else if(value)
				{
					const PxU32 remappedRef = 0x80000000 | (mPointerRemapCounter++ +1);
					mPointerRemap.setObjectRef(value, remappedRef);
					value = remappedRef;
				}
			}

			if(mMustFlip)
				flip(value);

			if(mNullPtr)
				value = 0;

			*reinterpret_cast<PxU32*>(buffer) = value;
		}
		else
		{
			PX_ASSERT(mSrcPtrSize==8);
			PX_ASSERT(sizeof(PxU64)==8);
			const PxU64* data = reinterpret_cast<const PxU64*>(src);
			PxU64 value = *data++;
			src = reinterpret_cast<const char*>(data);

			if(mPointerActiveRemap)
			{
				PxU32 ref;
				if(mPointerActiveRemap->getObjectRef(value, ref))
				{
					value = ref;
				}
				else if(value)
				{
					// all pointers not in the pointer remap table get set as 0x12345678, this also applies to PhysX name properties (mName)
					value=0x12345678;
				}
			}
			else
			{
				//we should only get here during convertReferenceTables to build up the pointer map
				PxU32 ref;
				if (mPointerRemap.getObjectRef(value, ref))
				{
					value = ref;
				}
				else if(value)
				{
					const PxU32 remappedRef = 0x80000000 | (mPointerRemapCounter++ +1);
					mPointerRemap.setObjectRef(value, remappedRef);
					value = remappedRef;
				}
			}

			if(mNullPtr)
				value = 0;

			testValue = value;

			*reinterpret_cast<PxU64*>(buffer) = value;
		}

		if(mSrcPtrSize==mDstPtrSize)
		{
			const size_t size = mOutStream->write(buffer, PxU32(mSrcPtrSize));
			PX_ASSERT(size==PxU32(mSrcPtrSize));
			mOutputSize += int(size);
		}
		else
		{
			if(mDstPtrSize>mSrcPtrSize)
			{
				// 32bit to 64bit
				PX_ASSERT(mDstPtrSize==8);
				PX_ASSERT(mSrcPtrSize==4);

				// We need to output the lower 32bits first for PC. Might be different on a 64bit console....

				// Output src ptr for the lower 32bits
				const size_t size = mOutStream->write(buffer, PxU32(mSrcPtrSize));
				PX_ASSERT(size==PxU32(mSrcPtrSize));
				mOutputSize += int(size);

				// Output zeros for the higher 32bits
				const int zero = 0;
				const size_t size0 = mOutStream->write(&zero, 4);
				PX_ASSERT(size0==4);
				mOutputSize += int(size0);
			}
			else
			{
				// 64bit to 32bit
				PX_ASSERT(mSrcPtrSize==8);
				PX_ASSERT(mDstPtrSize==4);

				// Not sure how we can safely convert 64bit ptrs to 32bit... just drop the high 32 bits?!?

				PxU32 ptr32 = *reinterpret_cast<PxU32*>(buffer);
				(void)ptr32;
				PxU32 ptr32b = PxU32(testValue);
				(void)ptr32b;

				if(mMustFlip)
					flip(ptr32b);

				// Output src ptr for the lower 32bits
				const size_t size = mOutStream->write(&ptr32b, 4);
				PX_ASSERT(size==4);
				mOutputSize += int(size);
			}
		}
	}
}

void Sn::ConvX::convertHandle16(const char* src, const PxMetaDataEntry& entry, const PxMetaDataEntry& dstEntry)
{
	(void)dstEntry;
	if(mNoOutput)
		return;

	PX_ASSERT(strcmp(entry.mType, "PxU16") == 0);
	PX_ASSERT(entry.mSize==dstEntry.mSize);
	PX_ASSERT(mOutStream);

	const PxU16* handles = reinterpret_cast<const PxU16*>(src);

	for(int i=0;i<entry.mCount;i++)
	{
		PxU16 value = handles[i];

		if (mHandle16ActiveRemap)
		{
			PxU16 ref;
			bool isMapped = mHandle16ActiveRemap->getObjectRef(value, ref);
			PX_UNUSED(isMapped);
			PX_ASSERT(isMapped);
			value = ref;
		}
		else
		{
			//we should only get here during convertReferenceTables to build up the pointer map
			PxU16 ref;
			if (mHandle16Remap.getObjectRef(value, ref))
			{
				value = ref;
			}
			else
			{
				const PxU16 remappedRef = mHandle16RemapCounter++;
				mHandle16Remap.setObjectRef(value, remappedRef);
				value = remappedRef;
			}
		}
		
		if(mMustFlip)
			flip(value);

		const size_t size = mOutStream->write(&value, sizeof(PxU16));
		PX_UNUSED(size);
		PX_ASSERT(size==sizeof(PxU16));
		mOutputSize += sizeof(PxU16);
	}
}

