// 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.

#include "PxSimpleTypes.h"
#include "NvParameters.h"
#include "nvparameterized/NvParameterizedTraits.h"

#include "NvTraitsInternal.h"

#include "XmlDeserializer.h"

#define XML_WARNING(_format, ...) \
	NV_PARAM_TRAITS_WARNING(mTraits, "XML serializer: " _format, ##__VA_ARGS__)



namespace NvParameterized
{

/*!
Get number of elements in array
*/
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define PX_ARRAY_SIZE(_array) (sizeof(ArraySizeHelper(_array)))

	static uint32_t ReadVersion(const physx::shdfnd::FastXml::AttributePairs& attr)
	{
		const char *versionText = attr.get("version");

		// If there's no version, assume version is 0.0
		if( !versionText )
			return 0;

		//XML stores versions in "x.y"-format
		//FIXME: strtoul is unsafe

		const char *dot = strchr(versionText, '.');
		uint32_t minor = dot ? strtoul(dot + 1, 0, 10) : 0;

		uint32_t major = strtoul(versionText, 0, 10);

		return (major << 16) + minor;
	}

	static PX_INLINE bool isWhiteSpace(char c)
	{
		return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ',';
	}

	static PX_INLINE const char * skipWhiteSpace(const char *scan)
	{
		while ( isWhiteSpace(*scan) && *scan ) scan++;
		return *scan ? scan : 0;
	}

	static PX_FORCE_INLINE const char * skipNonWhiteSpace(const char* scan)
	{
		while ( !isWhiteSpace(*scan) && *scan ) scan++;
		return *scan ? scan : 0;
	}

	bool XmlDeserializer::verifyObject(Interface *obj, const physx::shdfnd::FastXml::AttributePairs& attr)
	{
		if( ReadVersion(attr) != obj->version() )
		{
			XML_WARNING("unknown error");
			DEBUG_ALWAYS_ASSERT();
			mError = Serializer::ERROR_UNKNOWN;
			return false;
		}

		char *checksum = (char *)attr.get("checksum");
		if( checksum && !DoIgnoreChecksum(*obj) )
		{
			uint32_t objBits;
			const uint32_t *objChecksum = obj->checksum(objBits);

			uint32_t bits = 0;

			char *cur = checksum, *next = 0;
			bool sameBits = true;
			for(uint32_t i = 0; ; ++i, cur = next)
			{
				uint32_t val = (uint32_t)strtoul(cur, &next, 0); //FIXME: strtoul is not safe
				if( cur == next )
					break;

				bits += 32;
				if( bits > objBits || val != objChecksum[i] )
				{
					NV_PARAM_TRAITS_WARNING(
						mTraits,
						"Schema checksum is different for object of class %s and version %u, "
							"asset may be corrupted",
						obj->className(),
						(unsigned)obj->version()
						);
					sameBits = false;
					break;
				}
			}

			if( objBits != bits && sameBits )
			{
				NV_PARAM_TRAITS_WARNING(
					mTraits,
					"Schema checksum is different for object of class %s and version %u, "
						"asset may be corrupted",
					obj->className(),
					(unsigned)obj->version()
					);
			}
		} //if( checksum )

		return true;
	}

	bool XmlDeserializer::initAddressString(char *dest, uint32_t len, const char *name)
	{
		char *end = dest + len;

		for (uint32_t i = 0; i < tos().getIndex(); i++)
		{
			FieldInfo &field = tos().getFieldInfo(i);

			const char *n = field.name;
			FieldType type = field.type;

			if( SKIP == type )
				continue;

			while ( n && *n && dest < end )
				*dest++ = *n++;

			if ( ARRAY == type )
			{
				char temp[512];
				physx::shdfnd::snprintf(temp, 512, "[%d]", field.idx);

				if( dest + ::strlen(temp) >= end )
				{
					XML_WARNING("buffer overflow");
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_INTERNAL_BUFFER_OVERFLOW;
					return false;
				}

				const char *scan = temp;
				while ( *scan && dest < end )
				{
					*dest++ = *scan++;
				}
			}
			else
			{
				if( dest + 1 >= end )
				{
					XML_WARNING("buffer overflow");
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_INTERNAL_BUFFER_OVERFLOW;
					return false;
				}

				*dest++ = '.';
			}
		}

		while ( name && *name && dest < end )
		{
			*dest++ = *name++;
		}

		*dest = 0;
		//printf("Fully qualified name: %s\n", scratch );

		return true;
	}

	bool XmlDeserializer::processClose(const char *tag,uint32_t depth,bool &isError)
	{
		isError = true; //By default if we return with false it's due to error

		if( strcmp(tag, "NxParameters") == 0 ||
			strcmp(tag, "NvParameters") == 0)
		{
			mInRootElement = false;

			// in xml there's only 1 root element allowed,
			// so we want to stop after the first NvParameters
			isError = false;
			return false;
		}

		static const char *validTags[] = {
			"struct",
			"value",
			"array"
		};

		for(uint32_t i = 0; i < PX_ARRAY_SIZE(validTags); ++i)
		{
			if( 0 != ::strcmp(validTags[i], tag) )
				continue;

#	ifndef NDEBUG
			uint32_t idx = tos().getIndex();
			DEBUG_ASSERT( idx > 0 );

			static FieldType validTypes[] = {
				STRUCT,
				VALUE,
				ARRAY
			};

			// Make gcc happy
			const FieldType* tmp = &validTypes[0];
			PX_UNUSED(tmp);

			FieldType type = tos().getFieldInfo(idx - 1).type;
			PX_UNUSED(type);
			DEBUG_ASSERT( type == SKIP || type == validTypes[i] );
#	endif

			if( !popField() )
				return false;

			if (depth == 1 && mRootIndex > MAX_ROOT_OBJ)
			{
				DEBUG_ASSERT(i == 1);
				mObjects[0].getObject()->destroy();
			}

			return tos().getIndex() ? depth != 0 : popObj();
		}

		return false;
	}

	bool XmlDeserializer::processElement(
		const char *elementName,
		const char *elementData,
		const physx::shdfnd::FastXml::AttributePairs& attr,
		int32_t /*lineno*/)
	{
		//Force DOCTYPE
		if( !mHasDoctype )
		{
			XML_WARNING("DOCTYPE is missing");
			DEBUG_ALWAYS_ASSERT();
			mError = Serializer::ERROR_MISSING_DOCTYPE;
			return false;
		}

		if( strcmp(elementName, "NxParameters")  == 0 ||
			strcmp(elementName, "NvParameters")  == 0)
		{
			if( mObjIndex )
			{
				XML_WARNING("NvParameters must be root element");
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_MISSING_ROOT_ELEMENT;
				return false;
			}

			if( mInRootElement )
			{
				XML_WARNING("More than one root element encountered");
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_INVALID_NESTING;
				return false;
			}

			uint32_t ver = ReadVersion(attr);
			if( ver != mVer )
			{
				XML_WARNING("unknown version of APX file format: %u", (unsigned)ver);
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_INVALID_FILE_VERSION;
				return false;
			}

			const char* numObjects = attr.get("numObjects");
			if (numObjects != NULL)
			{
				PX_ASSERT(atoi(numObjects) >= 0);
				const uint32_t num = static_cast<uint32_t>(atoi(numObjects));
				if (num > MAX_ROOT_OBJ)
				{
					XML_WARNING("APX file has more than %d root objects, only %d will be read", num, MAX_ROOT_OBJ);
				}
			}


			++mRootTags;
			mInRootElement = true;

			return true;
		}

		if( mRootTags > 0 && !mInRootElement )
		{
			XML_WARNING("element %s not under root element", elementName);
			DEBUG_ALWAYS_ASSERT();
			mError = Serializer::ERROR_MISSING_ROOT_ELEMENT;
			return false;
		}

		if ( strcmp(elementName, "struct") == 0 )
		{
			const char *name = attr.get("name");

			if( !mObjIndex )
			{
				XML_WARNING("struct-element %s not under value-element", name);
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_INVALID_NESTING;
				return false;
			}

			pushField(name, STRUCT);
		}
		else if ( strcmp(elementName, "value") == 0 )
		{
			if( !mObjIndex ) //Root object?
			{
				const char *className = attr.get("className");

				uint32_t version = ReadVersion(attr);

				Interface *obj = mTraits->createNvParameterized(className, version);
				if( !obj )
				{
					XML_WARNING("failed to create object of type %s and version %u", className, (unsigned)version);
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_OBJECT_CREATION_FAILED;
					return false;
				}

				const char *objectName = attr.get("objectName");
				if( objectName )
					obj->setName(objectName);

				if (mRootIndex < MAX_ROOT_OBJ)
				{
					mRootObjs[mRootIndex] = obj;
				}
				mRootIndex++;

				pushObj(obj);
				pushField("", SKIP); //Root <value> should not be used in initAddressString

				return true;
			}

			const char *name = attr.get("name");

			char scratch[2048];
			if( !initAddressString(scratch, sizeof(scratch), name) )
			{
				return false;
			}

			pushField(name, VALUE);

			Interface *obj = tos().getObject();
			if( !obj )
			{
				XML_WARNING("unknown error");
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_UNKNOWN;
				return false;
			}

			Handle handle(*obj, scratch);
			if( !handle.isValid() )
			{
				XML_WARNING("%s: invalid path", scratch);
				DEBUG_ALWAYS_ASSERT();
//				mError = Serializer::ERROR_INVALID_PARAM_HANDLE;
				return true;
			}

			const char *type = attr.get("type");
			const char *expectedType = typeToStr(handle.parameterDefinition()->type());
			if ( type && 0 != physx::shdfnd::stricmp(type, expectedType) )
			{
				XML_WARNING("%s: invalid type %s (expected %s)", scratch, type, expectedType);
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_INVALID_ATTR;
				return false;
			}

			const char *included = attr.get("included");
			if ( included )
			{
				bool isIncludedRef = 0 != atoi(included);
				if( isIncludedRef != handle.parameterDefinition()->isIncludedRef() )
				{
					XML_WARNING("%s: unexpected included-attribute", scratch);
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_INVALID_ATTR;
					return false;
				}

				const char *className = attr.get("className");
				if( !className )
				{
					if( attr.get("classNames") )
					{
						// Ref is NULL

						Interface *oldObj = 0;
						if( NvParameterized::ERROR_NONE != handle.getParamRef(oldObj) )
							return false;
						if( oldObj )
							oldObj->destroy();

						handle.setParamRef(0);

						return true;
					}
					else
					{
						XML_WARNING("%s: missing both className and classNames attribute", scratch);
						DEBUG_ALWAYS_ASSERT();
						mError = Serializer::ERROR_INVALID_ATTR;
						return false;
					}
				}

				uint32_t version = ReadVersion(attr);

				Interface *refObj = 0;
				if( isIncludedRef )
					refObj = mTraits->createNvParameterized(className, version);
				else
				{
					void *buf = mTraits->alloc(sizeof(NvParameters));
					refObj = PX_PLACEMENT_NEW(buf, NvParameters)(mTraits);

					refObj->setClassName(className);
				}

				if( !refObj )
				{
					XML_WARNING("%s: failed to create object of type %s and version %u", scratch, className, (unsigned)version);
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_OBJECT_CREATION_FAILED;
					return false;
				}

				if( refObj && (-1 == handle.parameterDefinition()->refVariantValIndex(refObj->className())) )
				{
					char longName[256];
					handle.getLongName(longName, sizeof(longName));
					NV_PARAM_TRAITS_WARNING(
						mTraits,
						"%s: setting reference of invalid class %s",
						longName,
						refObj->className()
						);
				}

				if( NvParameterized::ERROR_NONE != handle.setParamRef(refObj) )
				{
					XML_WARNING("%s: failed to set reference of type %s", scratch, className);
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_INVALID_REFERENCE;
					return false;
				}

				const char *objectName = attr.get("objectName");
				if( objectName && refObj )
					refObj->setName(objectName);

				if( isIncludedRef )
					pushObj(refObj);
				else if ( elementData && refObj )
					refObj->setName(elementData);
			}
			else
			{
				if ( elementData == 0 )
					elementData = "";

				const char *isNull = attr.get("null");
				if( isNull && 0 != atoi(isNull) )
				{
					//Only strings and enums may be NULL so it's safe to call setParamString

					DataType t = handle.parameterDefinition()->type();
					PX_UNUSED(t);
					DEBUG_ASSERT( TYPE_STRING == t || TYPE_ENUM == t );

					handle.setParamString(0);
				}
				else
				{
					if( NvParameterized::ERROR_NONE != handle.strToValue(elementData, 0) )
					{
						XML_WARNING("%s: failed to convert string to value: %10s", scratch, elementData);
						DEBUG_ALWAYS_ASSERT();
						mError = Serializer::ERROR_STRING2VAL_FAILED;
						return false;
					}
				}
			} //if ( included )
		}
		else if ( strcmp(elementName, "array") == 0 )
		{
			const char *name = attr.get("name");

			if( !mObjIndex )
			{
				XML_WARNING("array-element %s not under value-element", name);
				DEBUG_ALWAYS_ASSERT();
				mError = Serializer::ERROR_INVALID_NESTING;
				return false;
			}

			int32_t arraySize = 0;
			if ( const char *sz = attr.get("size") )
			{
				PX_ASSERT(atoi(sz) >= 0);
				arraySize = (int32_t)atoi(sz);
			}

			if ( arraySize > 0 )
			{
				Interface *obj = tos().getObject();
				if( !obj )
				{
					XML_WARNING("unknown error");
					DEBUG_ALWAYS_ASSERT();
					mError = Serializer::ERROR_UNKNOWN;
					return false;
				}

				char scratch[2048];
				if( !initAddressString(scratch, sizeof(scratch), name) )
				{
					return false;
				}

				Handle handle(*obj, scratch);
				if( !handle.isValid() )
				{

					mError = Serializer::ERROR_INVALID_PARAM_HANDLE;
					XML_WARNING("%s: invalid path", scratch);
					DEBUG_ALWAYS_ASSERT();

					return false;
				}

				if( !handle.parameterDefinition()->arraySizeIsFixed() )
					if( NvParameterized::ERROR_NONE != handle.resizeArray(arraySize) )
					{
						XML_WARNING("%s: failed to resize array", scratch);
						DEBUG_ALWAYS_ASSERT();
						mError = Serializer::ERROR_INVALID_ARRAY;
						return false;
					}
					if ( elementData )
					{
						const char *scan = elementData;

						handle.set(0);
						const Definition *paramDef = handle.parameterDefinition();
						handle.popIndex();


						if ( paramDef->type() == TYPE_STRUCT )
						{
							// read the structElements field
							const char* structElements = attr.get("structElements");

							int32_t* simpleStructRedirect = getSimpleStructRedirect(static_cast<uint32_t>(paramDef->numChildren()));
							uint32_t numRedirects = 0;
							while (structElements && *structElements)
							{
								char fieldName[64];
								char type[16];

								size_t count = 0;
								while(*structElements != 0 && *structElements != ',' && *structElements != '(')
									fieldName[count++] = *structElements++;
								if( count >= sizeof(fieldName) )
								{
									DEBUG_ALWAYS_ASSERT();
									mError = Serializer::ERROR_INTERNAL_BUFFER_OVERFLOW;
									return false;
								}
								fieldName[count] = 0;

								if (*structElements == '(')
								{
									structElements++;
									count = 0;
									while(*structElements != 0 && *structElements != ',' && *structElements != ')')
										type[count++] = *structElements++;
									if( count >= sizeof(type) )
									{
										DEBUG_ALWAYS_ASSERT();
										mError = Serializer::ERROR_INTERNAL_BUFFER_OVERFLOW;
										return false;
									}
									type[count] = 0;

								}
								if (*structElements == ')')
									structElements++;
								if (*structElements == ',')
									structElements++;

								const Definition* childDef = paramDef->child(fieldName, simpleStructRedirect[numRedirects]);
								const char* trueType = childDef ? typeToStr(childDef->type()) : 0;
								if (childDef && ::strcmp(trueType, type) != 0)
								{
									XML_WARNING(
										"%s[].%s: unexpected type: %s (must be %s)",
										scratch, fieldName, type, trueType ? trueType : "");
									DEBUG_ALWAYS_ASSERT();
									mError = Serializer::ERROR_INVALID_DATA_TYPE;
									return false;
								}

								// -2 means to reed the data but not storing it
								// -1 means to not read the data as it was not serialized'
								// i = [0 .. n] means to read the data and store it in child i
								if (childDef == NULL)
								{
									simpleStructRedirect[numRedirects] = -2;

									// Fail fast
									XML_WARNING("%s[]: unexpected structure field: %s", scratch, fieldName);
									DEBUG_ALWAYS_ASSERT();
									mError = Serializer::ERROR_INVALID_PARAM_HANDLE;
									return false;
								}

								numRedirects++;
							}

							const int32_t numChildren = paramDef->numChildren();
							for(int32_t i = 0; i < arraySize; ++i)
							{
								handle.set(i);
								for( int32_t j=0; j<numChildren; j++ )
								{
									if (simpleStructRedirect[j] < 0)
									{
										if (simpleStructRedirect[j] < -1)
										{
											// read the data anyways
											scan = skipWhiteSpace(scan);
											if (scan != NULL)
												scan = skipNonWhiteSpace(scan);
										}
										continue;
									}

									scan = skipWhiteSpace(scan);
									if ( !scan ) break;

									handle.set(simpleStructRedirect[j]);

									if( NvParameterized::ERROR_NONE != handle.strToValue(scan, &scan) )
									{
										XML_WARNING("%s: failed to convert string to value: %10s...", scan);
										DEBUG_ALWAYS_ASSERT();
										mError = Serializer::ERROR_STRING2VAL_FAILED;
										return false;
									}

									handle.popIndex();
									if ( !scan ) break;
								}
								handle.popIndex();
								if ( !scan ) break;
							}
						}
						else
						{
							// LRR: wall clock time is the same for this simple loop as the previous
							//      "unrolled" version
							for (int32_t i = 0; i<arraySize; i++)
							{
								handle.set(i);
								if( NvParameterized::ERROR_NONE != handle.strToValue(scan, &scan) )
								{
									XML_WARNING("%s: failed to convert string to value: %10s", scratch, scan);
									DEBUG_ALWAYS_ASSERT();
									mError = Serializer::ERROR_STRING2VAL_FAILED;
									return false;
								}
								handle.popIndex();
							}
						}
					} //if( elementData )
			} //if ( arraySize > 0 )

			pushField(name, ARRAY);
		}
		else
		{
			XML_WARNING("unknown element %s", elementName);
			DEBUG_ALWAYS_ASSERT();
			mError = Serializer::ERROR_UNKNOWN_XML_TAG;
			return false;
		}

		return true;
	}

	int32_t* XmlDeserializer::getSimpleStructRedirect(uint32_t size)
	{
		if (mSimpleStructRedirectSize < size)
		{
			if (mSimpleStructRedirect != NULL)
				mTraits->free(mSimpleStructRedirect);

			if (size < 16)
				size = 16; // just to not allocate all these small things more than once

			mSimpleStructRedirect = (int32_t*)mTraits->alloc(sizeof(int32_t) * size);
			mSimpleStructRedirectSize = size;
		}

		memset(mSimpleStructRedirect, -1, sizeof(int32_t) * size);
		return mSimpleStructRedirect;
	}

}
