// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
// All rights reserved.
// Code licensed under the BSD License.
// http://www.anki3d.org/LICENSE

#include <anki/gr/utils/Functions.h>

namespace anki
{

template<typename T>
static void writeShaderBlockMemorySanityChecks(const ShaderVariableBlockInfo& varBlkInfo,
	const void* elements,
	U32 elementsCount,
	void* buffBegin,
	const void* buffEnd)
{
	// Check args
	ANKI_ASSERT(elements != nullptr);
	ANKI_ASSERT(elementsCount > 0);
	ANKI_ASSERT(buffBegin != nullptr);
	ANKI_ASSERT(buffEnd != nullptr);
	ANKI_ASSERT(buffBegin < buffEnd);

	// Check varBlkInfo
	ANKI_ASSERT(varBlkInfo.m_offset != -1);
	ANKI_ASSERT(varBlkInfo.m_arraySize > 0);

	if(varBlkInfo.m_arraySize > 1)
	{
		ANKI_ASSERT(varBlkInfo.m_arrayStride > 0);
	}

	// Check array size
	ANKI_ASSERT(static_cast<I16>(elementsCount) <= varBlkInfo.m_arraySize);
}

template<typename T>
static void writeShaderBlockMemorySimple(const ShaderVariableBlockInfo& varBlkInfo,
	const void* elements,
	U32 elementsCount,
	void* buffBegin,
	const void* buffEnd)
{
	writeShaderBlockMemorySanityChecks<T>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);

	U8* buff = static_cast<U8*>(buffBegin) + varBlkInfo.m_offset;
	for(U i = 0; i < elementsCount; i++)
	{
		ANKI_ASSERT(buff + sizeof(T) <= static_cast<const U8*>(buffEnd));

		T* out = reinterpret_cast<T*>(buff);
		const T* in = reinterpret_cast<const T*>(elements) + i;
		*out = *in;

		buff += varBlkInfo.m_arrayStride;
	}
}

template<typename T, typename Vec>
static void writeShaderBlockMemoryMatrix(const ShaderVariableBlockInfo& varBlkInfo,
	const void* elements,
	U32 elementsCount,
	void* buffBegin,
	const void* buffEnd)
{
	writeShaderBlockMemorySanityChecks<T>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
	ANKI_ASSERT(varBlkInfo.m_matrixStride > 0);
	ANKI_ASSERT(varBlkInfo.m_matrixStride >= static_cast<I16>(sizeof(Vec)));

	U8* buff = static_cast<U8*>(buffBegin) + varBlkInfo.m_offset;
	for(U i = 0; i < elementsCount; i++)
	{
		U8* subbuff = buff;
		const T& matrix = static_cast<const T*>(elements)[i];
		for(U j = 0; j < sizeof(T) / sizeof(Vec); j++)
		{
			ANKI_ASSERT((subbuff + sizeof(Vec)) <= static_cast<const U8*>(buffEnd));

			Vec* out = reinterpret_cast<Vec*>(subbuff);
			*out = matrix.getRow(j);
			subbuff += varBlkInfo.m_matrixStride;
		}
		buff += varBlkInfo.m_arrayStride;
	}
}

void writeShaderBlockMemory(ShaderVariableDataType type,
	const ShaderVariableBlockInfo& varBlkInfo,
	const void* elements,
	U32 elementsCount,
	void* buffBegin,
	const void* buffEnd)
{
	switch(type)
	{
	case ShaderVariableDataType::INT:
		writeShaderBlockMemorySimple<I32>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::UINT:
		writeShaderBlockMemorySimple<U32>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::FLOAT:
		writeShaderBlockMemorySimple<F32>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::IVEC2:
		writeShaderBlockMemorySimple<IVec2>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::UVEC2:
		writeShaderBlockMemorySimple<UVec2>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::VEC2:
		writeShaderBlockMemorySimple<Vec2>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::IVEC3:
		writeShaderBlockMemorySimple<IVec3>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::UVEC3:
		writeShaderBlockMemorySimple<UVec3>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::VEC3:
		writeShaderBlockMemorySimple<Vec3>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::IVEC4:
		writeShaderBlockMemorySimple<IVec4>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::UVEC4:
		writeShaderBlockMemorySimple<UVec4>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::VEC4:
		writeShaderBlockMemorySimple<Vec4>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::MAT3:
		writeShaderBlockMemoryMatrix<Mat3, Vec3>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	case ShaderVariableDataType::MAT4:
		writeShaderBlockMemoryMatrix<Mat4, Vec4>(varBlkInfo, elements, elementsCount, buffBegin, buffEnd);
		break;
	default:
		ANKI_ASSERT(0);
	}
}

const CString shaderVariableDataTypeToString(ShaderVariableDataType t)
{
#define ANKI_SVDT_MACRO(svdt, akType) \
	case ShaderVariableDataType::svdt: \
		return ANKI_STRINGIZE(akType);

#define ANKI_SVDT_MACRO_2(svdt, akType) ANKI_SVDT_MACRO(svdt, akType)

	switch(t)
	{
	case ShaderVariableDataType::NONE:
		return "NONE";
#include <anki/gr/ShaderVariableDataTypeDefs.h>
	default:
		ANKI_ASSERT(0);
	}

#undef ANKI_SVDT_MACRO
#undef ANKI_SVDT_MACRO_2

	ANKI_ASSERT(0);
	return "";
}

} // end namespace anki
