// This code contains NVIDIA Confidential Information and is disclosed to you
// under a form of NVIDIA software license agreement provided separately to you.
//
// Notice
// NVIDIA Corporation and its licensors retain all intellectual property and
// proprietary rights in and to this software and related documentation and
// any modifications thereto. Any use, reproduction, disclosure, or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA Corporation is strictly prohibited.
//
// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
//
// Information and code furnished is believed to be accurate and reliable.
// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
// information or for any infringement of patents or other rights of third parties that may
// result from its use. No license is granted by implication or otherwise under any patent
// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
// This code supersedes and replaces all information previously supplied.
// NVIDIA Corporation products are not authorized for use as critical
// components in life support devices or systems without express written approval of
// NVIDIA Corporation.
//
// Copyright (c) 2008-2020 NVIDIA Corporation. All rights reserved.

// WARNING: before doing any changes to this file
// check comments at the head of BinSerializer.cpp

#include "PlatformOutputStream.h"
#include <stdint.h>

using namespace NvParameterized;

#ifndef WITHOUT_APEX_SERIALIZATION

Reloc::Reloc(RelocType type_, uint32_t ptrPos_, const PlatformOutputStream &parent)
	: type(type_),
	ptrPos(ptrPos_),
	traits(parent.mTraits)
{
	ptrData = reinterpret_cast<PlatformOutputStream *>(traits->alloc(sizeof(PlatformOutputStream)));
	PX_PLACEMENT_NEW(ptrData, PlatformOutputStream)(parent.mTargetParams, parent.mTraits, parent.dict);
}

Reloc::Reloc(const Reloc &cinfo): type(cinfo.type), ptrPos(cinfo.ptrPos), traits(cinfo.traits)
{
	Reloc &info = (Reloc &)cinfo;

	//Take ownership of stream to avoid slow recursive copies (especially when reallocating array elements)
	ptrData = info.ptrData;
	info.ptrData = 0;
}

Reloc::~Reloc()
{
	if( ptrData )
	{
		ptrData->~PlatformOutputStream();
		traits->free(ptrData);
	}
}

PlatformOutputStream::PlatformOutputStream(const PlatformABI &targetParams, Traits *traits, Dictionary &dict_)
	: PlatformStream(targetParams, traits),
	data(traits),
	mRelocs(Traits::Allocator(traits)),
	mStrings(Traits::Allocator(traits)),
	mMerges(Traits::Allocator(traits)),
	dict(dict_),
	mTotalAlign(1)
{}

PlatformOutputStream::PlatformOutputStream(const PlatformOutputStream &s)
	: PlatformStream(s),
	data(s.data),
	mRelocs(Traits::Allocator(s.mTraits)),
	mStrings(Traits::Allocator(s.mTraits)),
	mMerges(Traits::Allocator(s.mTraits)),
	dict(s.dict),
	mTotalAlign(1)
{
	mRelocs.reserve(s.mRelocs.size());
	for(uint32_t i = 0; i < s.mRelocs.size(); ++i)
		mRelocs.pushBack(s.mRelocs[i]);

	mStrings.reserve(s.mStrings.size());
	for(uint32_t i = 0; i < s.mStrings.size(); ++i)
		mStrings.pushBack(s.mStrings[i]);

	mMerges.reserve(s.mMerges.size());
	for(uint32_t i = 0; i < s.mMerges.size(); ++i)
		mMerges.pushBack(s.mMerges[i]);
}

#ifndef NDEBUG
void PlatformOutputStream::dump() const
{
	PlatformStream::dump();

	dumpBytes(data, size());

	fflush(stdout);
	for(uint32_t i = 0; i < mRelocs.size(); ++i)
	{
		printf("Relocation %d at %x:\n", (int)i, mRelocs[i].ptrPos);
		mRelocs[i].ptrData->dump();
	}

	fflush(stdout);
}
#endif

void PlatformOutputStream::storeU32At(uint32_t x, uint32_t i)
{
	if( mCurParams.endian != mTargetParams.endian )
		SwapBytes(reinterpret_cast<char *>(&x), 4U, TYPE_U32);

	*reinterpret_cast<uint32_t *>(&data[i]) = x;
}

uint32_t PlatformOutputStream::storeString(const char *s)
{
	uint32_t off = storeSimple((uint8_t)*s);
	while( *s++ )
		storeSimple((uint8_t)*s);

	return off;
}

uint32_t PlatformOutputStream::storeBytes(const char *s, uint32_t n)
{
	if( !n )
		return size();

	uint32_t off = storeSimple((uint8_t)s[0]);
	for(uint32_t i = 1; i < n; ++i)
		storeSimple((uint8_t)s[i]);

	return off;
}

uint32_t PlatformOutputStream::beginStruct(uint32_t align_, uint32_t pad_)
{
	uint32_t off = size();
	mStack.pushBack(Agregate(Agregate::STRUCT, pad_));
	align(align_); // Align _after_ we push struct to avoid ignored align() when inside array
	return off;
}

uint32_t PlatformOutputStream::beginStruct(uint32_t align_)
{
	return beginStruct(align_, align_);
}

uint32_t PlatformOutputStream::beginStruct(const Definition *pd)
{
	return beginStruct(getTargetAlignment(pd), getTargetPadding(pd));
}

void PlatformOutputStream::closeStruct()
{
	PX_ASSERT(mStack.size() > 0);

	//Tail padding
	align(mStack.back().align);// Align _before_ we pop struct to avoid ignored align() when inside array
	mStack.popBack();
}

uint32_t PlatformOutputStream::beginString()
{
	return beginStruct(physx::PxMax(mTargetParams.aligns.pointer, mTargetParams.aligns.Bool));
}

void PlatformOutputStream::closeString()
{
	closeStruct();
}

uint32_t PlatformOutputStream::beginArray(const Definition *pd)
{
	return beginArray(getTargetAlignment(pd));
}

uint32_t PlatformOutputStream::beginArray(uint32_t align_)
{
	align(align_); // Align _before_ we push array because otherwise align() would be ignored
	uint32_t off = size();
	mStack.pushBack(Agregate(Agregate::ARRAY, align_));
	return off;
}

void PlatformOutputStream::closeArray()
{
	// No tail padding when in array
	mStack.popBack();
}

void PlatformOutputStream::skipBytes(uint32_t nbytes)
{
	data.skipBytes(nbytes);
}

void PlatformOutputStream::align(uint32_t border)
{
	bool isAligned;
	uint32_t newSize = getAlign(size(), border, isAligned);

	if( isAligned )
		mTotalAlign = physx::PxMax(mTotalAlign, border);

	data.skipBytes(newSize - size());
}

void PlatformOutputStream::mergeDict()
{
	for(uint32_t i = 0; i < dict.size(); ++i)
	{
		const char *s = dict.get(i);
		uint32_t off = storeString(s);
		dict.setOffset(s, off);
	}
}

uint32_t PlatformOutputStream::storeNullPtr()
{
	//Do not align on uint32_t or uint64_t boundary (already aligned at pointer boundary)
	align(mTargetParams.aligns.pointer);
	uint32_t off = size();
	if( 4 == mTargetParams.sizes.pointer )
		data.skipBytes(4);
	else
	{
		PX_ASSERT( 8 == mTargetParams.sizes.pointer );
		data.skipBytes(8);
	}
	return off;
}

Reloc &PlatformOutputStream::storePtr(RelocType type, uint32_t align)
{
	uint32_t off = storeNullPtr();
	mRelocs.pushBack(Reloc(type, off, *this));
	mRelocs.back().ptrData->setAlignment(align);
	return mRelocs.back();
}

Reloc &PlatformOutputStream::storePtr(RelocType type, const Definition *pd)
{
	return storePtr(type, getTargetAlignment(pd));
}

void PlatformOutputStream::storeStringPtr(const char *s)
{
	uint32_t off = storeNullPtr();
	if( s )
	{
		mStrings.pushBack(StringReloc(off, s));
		dict.put(s);
	}
}

uint32_t PlatformOutputStream::storeSimpleStructArray(Handle &handle)
{
	int32_t n;
	handle.getArraySize(n);

	const NvParameterized::Definition *pdStruct = handle.parameterDefinition()->child(0);
	int32_t nfields = pdStruct->numChildren();

	uint32_t align_ = getTargetAlignment(pdStruct),
		size_ = getTargetSize(pdStruct),
		pad_ = getTargetPadding(pdStruct);

	align(align_);
	uint32_t off = size();

	data.reserve(size() + n * physx::PxMax(align_, size_));

	char *p = data;
	p += data.size();

	for(int32_t i = 0; i < n; ++i)
	{
		beginStruct(align_, pad_);
		handle.set(i);

		for(int32_t j = 0; j < nfields; ++j)
		{
			handle.set(j);

			const Definition *pdField = pdStruct->child(j);

			if( pdField->alignment() )
				align( pdField->alignment() );

			if( pdField->hint("DONOTSERIALIZE") )
			{
				//Simply skip bytes
				align(getTargetAlignment(pdField));
				skipBytes(getTargetSize(pdField));
			}
			else
			{
				//No need to align structs because of tail padding
				switch( pdField->type() )
				{
#				define NV_PARAMETERIZED_TYPES_NO_LEGACY_TYPES
#				define NV_PARAMETERIZED_TYPES_ONLY_SIMPLE_TYPES
#				define NV_PARAMETERIZED_TYPES_NO_STRING_TYPES
#				define NV_PARAMETERIZED_TYPE(type_name, enum_name, c_type) \
					case TYPE_##enum_name: \
					{ \
						c_type val; \
						handle.getParam##type_name(val); \
						storeSimple<c_type>(val); \
						break; \
					}
#					include "nvparameterized/NvParameterized_types.h"

				case TYPE_MAT34:
				{
					float val[12];
					handle.getParamMat34Legacy(val);
					storeSimple(val, 12);
					break;
				}

				NV_PARAMETRIZED_NO_MATH_DATATYPE_LABELS 
				default:
					DEBUG_ASSERT( 0 && "Unexpected type" );
					return UINT32_MAX;
				}
			}

			handle.popIndex();
		} //j

		handle.popIndex();
		closeStruct();
	} //i

	return off;
}

uint32_t PlatformOutputStream::storeObjHeader(const NvParameterized::Interface &obj, bool isIncluded)
{
	uint32_t align_ = NvMax3(mTargetParams.aligns.Bool, mTargetParams.aligns.i32, mTargetParams.aligns.pointer);

	uint32_t off = beginStruct(align_);

	uint32_t hdrOff = data.size();
	PX_ASSERT( hdrOff % sizeof(uint32_t) == 0 );

	storeSimple(uint32_t(0)); //Data offset

	//className
	storeStringPtr(obj.className());

	//name
	storeStringPtr(obj.name());

	//isIncluded
	storeSimple(isIncluded);

	//version
	storeSimple<uint32_t>(obj.version());

	//checksum size
	uint32_t bits = (uint32_t)-1;
	const uint32_t *checksum = obj.checksum(bits);
	PX_ASSERT( bits % 32 == 0 ); //32 bits in uint32_t
	uint32_t i32s = bits / 32;
	storeSimple(i32s);

	//checksum pointer
	Reloc &reloc = storePtr(RELOC_ABS_RAW, mTargetParams.aligns.i32);
	for(uint32_t i = 0; i < i32s; ++i)
		reloc.ptrData->storeSimple(checksum[i]);

	closeStruct();

	//We force alignment to calculate dataOffset
	//(when object is inserted no additional padding will be inserted)

	const Definition *pd = obj.rootParameterDefinition();
	uint32_t customAlign = pd ? getTargetAlignment(pd) : 1;
	align(physx::PxMax(16U, customAlign)); //16 for safety

	storeU32At(data.size() - hdrOff, hdrOff); //Now we know object data offset

	return off;
}

uint32_t PlatformOutputStream::beginObject(const NvParameterized::Interface &obj, bool /*isRoot*/, const Definition *pd)
{
	//NvParameterized objects is derived from NvParameters so we need to store its fields as well.

	//WARN: this implementation _heavily_ depends on implementation of NvParameters

	//Alignment of NvParameters
	uint32_t parentAlign = physx::PxMax(mTargetParams.aligns.pointer, mTargetParams.aligns.Bool),
		childAlign = pd ? getTargetAlignment(pd) : 1,
		totalAlign = physx::PxMax(parentAlign, childAlign);

	uint32_t off = beginStruct(totalAlign);

	//NvParameters fields
	for(uint32_t i = 0; i < 6; storeNullPtr(), ++i); //vtable and other fields
	storeStringPtr(obj.name()); //mName
	storeStringPtr(obj.className()); //mClassName
	storeSimple(true); //mDoDeallocateSelf (all objects are responsible for memory deallocation)
	storeSimple(false); //mDoDeallocateName
	storeSimple(false); //mDoDeallocateClassName

	//Some general theory of alignment handling
	//Imagine that we have class:
	//class A: A1, A2, ... An
	//{
	//	T1 f1;
	//	T2 f2;
	//};
	//Then all Ai/fi are aligned on natural boundary. Whether or not padding bytes for Ai are inserted
	//and whether or not Ai+1 or fi may reuse those bytes depends on compiler;
	//we store this info in PlatformABI's doReusePadding flag.
	//doReusePadding == false means that padding bytes are always there.
	//doReusePadding == true means that padding bytes are _not_ inserted for non-POD Ai but are inserted for POD Ai.
	//(I have yet to see compiler that does not insert padding bytes for POD base class!).

	//Compilers may handle derived classes in two different ways.
	//Say we have
	//	class B {int x; char y; };
	//	class A: public B {char z; };
	//Then in pure C code this may look like either as
	//	struct A { struct { int x; char y; } b; char z; };
	//or as
	//	struct A { int x; char y; char z; };
	//(the latter is usual if B is not POD).
	//Take care of that here (NvParameters is not POD!).
	if( !mTargetParams.doReuseParentPadding )
		align(parentAlign); //Insert tail padding for NvParameters

	//ParametersStruct is aligned on natural boundary
	align(childAlign);

	return off;
}

uint32_t PlatformOutputStream::merge(const PlatformOutputStream &mergee)
{
	//All structs should be closed
	PX_ASSERT( !mergee.mStack.size() );

	align(mergee.alignment());

	uint32_t base = data.size();

	data.appendBytes(mergee.data, mergee.data.size());

	//Update relocations

	mRelocs.reserve(mRelocs.size() + mergee.mRelocs.size());
	for(uint32_t i = 0; i < mergee.mRelocs.size(); ++i)
	{
		mRelocs.pushBack(mergee.mRelocs[i]);
		mRelocs.back().ptrPos += base;
	}

	mStrings.reserve(mStrings.size() + mergee.mStrings.size());
	for(uint32_t i = 0; i < mergee.mStrings.size(); ++i)
	{
		mStrings.pushBack(mergee.mStrings[i]);
		mStrings.back().ptrPos += base;
	}

	mMerges.reserve(mMerges.size() + mergee.mMerges.size());
	for(uint32_t i = 0; i < mergee.mMerges.size(); ++i)
	{
		mMerges.pushBack(mergee.mMerges[i]);

		mMerges.back().ptrPos += base;
		if( !mergee.mMerges[i].isExtern )
			mMerges.back().targetPos += base;
	}

	return base;
}

void PlatformOutputStream::flatten()
{
	//It's very important that data for child objects is stored
	//after current object to allow safe initialization

	//Generic pointers
	for(uint32_t i = 0; i < mRelocs.size(); ++i)
	{
		Reloc &reloc = mRelocs[i];

		//Recursively add data which is pointed-to

		align( reloc.ptrData->alignment() );

		MergedReloc m = { reloc.ptrPos, data.size(), reloc.type, false };
		mMerges.pushBack(m);

		merge(*reloc.ptrData); //Internal pointers are recursively added here
	}

	//String pointers
	for(uint32_t i = 0; i < mStrings.size(); ++i)
	{
		//String pointers are external and absolute
		MergedReloc m = { mStrings[i].ptrPos, dict.getOffset(mStrings[i].s), RELOC_ABS_RAW, true };
		mMerges.pushBack(m);
	}

	mRelocs.clear();
	mStrings.clear();
}

uint32_t PlatformOutputStream::writeRelocs()
{
	uint32_t ptrOff = storeSimple<uint32_t>(mMerges.size()); //Offset of relocation table

	data.reserve(mMerges.size() * 2 * physx::PxMax(4U, mTargetParams.aligns.i32));

	for(uint32_t i = 0; i < mMerges.size(); ++i)
	{
		char *ptr = &data[mMerges[i].ptrPos];
		if( 4 == mTargetParams.sizes.pointer )
		{
			uint32_t *ptrAsInt = reinterpret_cast<uint32_t *>(ptr);
			*ptrAsInt = mMerges[i].targetPos;
			if( mTargetParams.endian != mCurParams.endian )
				SwapBytes(ptr, 4U, TYPE_U32);
		}
		else
		{
			PX_ASSERT(8 == mTargetParams.sizes.pointer);

			uint64_t *ptrAsInt = reinterpret_cast<uint64_t *>(ptr);
			*ptrAsInt = mMerges[i].targetPos;
			if( mTargetParams.endian != mCurParams.endian )
				SwapBytes(ptr, 8U, TYPE_U64);
		}

		//BinaryReloc struct
		beginStruct(mTargetParams.aligns.i32);
		storeSimple<uint32_t>(static_cast<uint32_t>(mMerges[i].type));
		storeSimple<uint32_t>(mMerges[i].ptrPos);
		closeStruct();
	}

	mMerges.clear();

	return ptrOff;
}

#endif
