#include <catch2/catch_test_macros.hpp>

#include <glm/vec3.hpp>
#include <glm/gtc/epsilon.hpp>
#include <glm/ext/scalar_constants.hpp>

#include <fastgltf/parser.hpp>
#include <fastgltf/tools.hpp>
#include "gltf_path.hpp"

template<>
struct fastgltf::ElementTraits<glm::vec3> : fastgltf::ElementTraitsBase<glm::vec3, AccessorType::Vec3, float> {};

static const std::byte* getBufferData(const fastgltf::Buffer& buffer) {
	const std::byte* result = nullptr;

	std::visit(fastgltf::visitor {
		[](auto&) {},
		[&](const fastgltf::sources::Vector& vec) {
			result = reinterpret_cast<const std::byte*>(vec.bytes.data());
		},
		[&](const fastgltf::sources::ByteView& bv) {
			result = bv.bytes.data();
		},
	}, buffer.data);
	
	return result;
}

TEST_CASE("Test data type conversion", "[gltf-tools]") {
	// normalized int-to-float and normalized float-to-int
	for (auto i = std::numeric_limits<std::int8_t>::min(); i < std::numeric_limits<std::int8_t>::max(); ++i) {
		auto converted = fastgltf::internal::convertComponent<float>(i, true);
		REQUIRE(glm::epsilonEqual<float>(converted, fastgltf::max<float>(i / 127.0f, -1), glm::epsilon<float>()));
		REQUIRE(fastgltf::internal::convertComponent<std::int8_t>(converted, true) == std::round(converted * 127.0f));
	}
	for (auto i = std::numeric_limits<std::uint8_t>::min(); i < std::numeric_limits<std::uint8_t>::max(); ++i) {
		auto converted = fastgltf::internal::convertComponent<float>(i, true);
		REQUIRE(glm::epsilonEqual<float>(converted, i / 255.0f, glm::epsilon<float>()));
		REQUIRE(fastgltf::internal::convertComponent<std::uint8_t>(converted, true) == std::round(converted * 255.0f));
	}
	for (auto i = std::numeric_limits<std::int16_t>::min(); i < std::numeric_limits<std::int16_t>::max(); ++i) {
		auto converted = fastgltf::internal::convertComponent<float>(i, true);
		REQUIRE(glm::epsilonEqual<float>(converted, fastgltf::max<float>(i / 32767.0f, -1), glm::epsilon<float>()));
		REQUIRE(fastgltf::internal::convertComponent<std::int16_t>(converted, true) == std::round(converted * 32767.0f));
	}
	for (auto i = std::numeric_limits<std::uint16_t>::min(); i < std::numeric_limits<std::uint16_t>::max(); ++i) {
		auto converted = fastgltf::internal::convertComponent<float>(i, true);
		REQUIRE(glm::epsilonEqual<float>(converted, i / 65535.0f, glm::epsilon<float>()));
		REQUIRE(fastgltf::internal::convertComponent<std::uint16_t>(converted, true) == std::round(converted * 65535.0f));
	}
}

TEST_CASE("Test accessor", "[gltf-tools]") {
    auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
    fastgltf::GltfDataBuffer jsonData;
    REQUIRE(jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf"));

    fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
    auto asset = parser.loadGLTF(&jsonData, lightsLamp, fastgltf::Options::LoadExternalBuffers,
								 fastgltf::Category::Buffers | fastgltf::Category::BufferViews | fastgltf::Category::Accessors);
    REQUIRE(asset.error() == fastgltf::Error::None);

    REQUIRE(asset->accessors.size() == 15);
    auto& accessors = asset->accessors;

    SECTION("getAccessorElement<std::uint16_t>") {
        auto& firstAccessor = accessors[0];
		REQUIRE(firstAccessor.type == fastgltf::AccessorType::Scalar);
		REQUIRE(firstAccessor.componentType == fastgltf::ComponentType::UnsignedShort);

		REQUIRE(firstAccessor.bufferViewIndex.has_value());
		auto& view = asset->bufferViews[*firstAccessor.bufferViewIndex];

		auto* bufferData = getBufferData(asset->buffers[view.bufferIndex]);
		REQUIRE(bufferData != nullptr);

		auto* checkData = reinterpret_cast<const std::uint16_t*>(bufferData + view.byteOffset
				+ firstAccessor.byteOffset);

		REQUIRE(*checkData == fastgltf::getAccessorElement<std::uint16_t>(asset.get(), firstAccessor, 0));
    }

	{
        auto& secondAccessor = accessors[1];
		REQUIRE(secondAccessor.type == fastgltf::AccessorType::Vec3);
		REQUIRE(secondAccessor.componentType == fastgltf::ComponentType::Float);

		REQUIRE(secondAccessor.bufferViewIndex.has_value());
		auto& view = asset->bufferViews[*secondAccessor.bufferViewIndex];

		auto* bufferData = getBufferData(asset->buffers[view.bufferIndex]);
		REQUIRE(bufferData != nullptr);

		auto* checkData = reinterpret_cast<const glm::vec3*>(bufferData + view.byteOffset
				+ secondAccessor.byteOffset);

		SECTION("getAccessorElement<glm::vec3>") {
			REQUIRE(*checkData == fastgltf::getAccessorElement<glm::vec3>(asset.get(), secondAccessor, 0));
		}

		SECTION("iterateAccessor") {
			auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
			std::size_t i = 0;

			fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3) {
				dstCopy[i++] = std::forward<glm::vec3>(v3);
			});

			REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
		}

		SECTION("copyFromAccessor") {
			auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
			fastgltf::copyFromAccessor<glm::vec3>(asset.get(), secondAccessor, dstCopy.get());
			REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
		}

		SECTION("Iterator test") {
			auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
			auto accessor = fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor);
			for (auto it = accessor.begin(); it != accessor.end(); ++it) {
				dstCopy[std::distance(accessor.begin(), it)] = *it;
			}
			REQUIRE(std::memcmp(dstCopy.get(), checkData, secondAccessor.count * sizeof(glm::vec3)) == 0);
		}
	}
}

TEST_CASE("Test sparse accessor", "[gltf-tools]") {
    auto simpleSparseAccessor = sampleModels / "2.0" / "SimpleSparseAccessor" / "glTF";
    auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
    REQUIRE(jsonData->loadFromFile(simpleSparseAccessor / "SimpleSparseAccessor.gltf"));

    fastgltf::Parser parser;
    auto asset = parser.loadGLTF(jsonData.get(), simpleSparseAccessor, fastgltf::Options::LoadExternalBuffers,
								 fastgltf::Category::Buffers | fastgltf::Category::BufferViews | fastgltf::Category::Accessors);
    REQUIRE(asset.error() == fastgltf::Error::None);

    REQUIRE(asset->accessors.size() == 2);
    REQUIRE(!asset->accessors[0].sparse.has_value());
    REQUIRE(asset->accessors[1].sparse.has_value());
    auto& sparse = asset->accessors[1].sparse.value();
    REQUIRE(sparse.count == 3);
    REQUIRE(sparse.indicesBufferView == 2);
    REQUIRE(sparse.indicesByteOffset == 0);
    REQUIRE(sparse.valuesBufferView == 3);
    REQUIRE(sparse.valuesByteOffset == 0);
    REQUIRE(sparse.indexComponentType == fastgltf::ComponentType::UnsignedShort);

	auto& secondAccessor = asset->accessors[1];
	auto& viewIndices = asset->bufferViews[secondAccessor.sparse->indicesBufferView];
	auto& viewValues = asset->bufferViews[secondAccessor.sparse->valuesBufferView];

	auto& viewData = asset->bufferViews[*secondAccessor.bufferViewIndex];
	auto* bufferData = getBufferData(asset->buffers[viewData.bufferIndex]) + viewData.byteOffset
			+ secondAccessor.byteOffset;
	auto dataStride = viewData.byteStride ? *viewData.byteStride
			: fastgltf::getElementByteSize(secondAccessor.type, secondAccessor.componentType);

	auto* dataIndices = reinterpret_cast<const std::uint16_t*>(getBufferData(asset->buffers[viewIndices.bufferIndex])
			+ viewIndices.byteOffset + secondAccessor.sparse->indicesByteOffset);
	auto* dataValues = reinterpret_cast<const glm::vec3*>(getBufferData(asset->buffers[viewValues.bufferIndex])
			+ viewValues.byteOffset + secondAccessor.sparse->valuesByteOffset);

	auto checkValues = std::make_unique<glm::vec3[]>(secondAccessor.count);

	for (std::size_t i = 0, sparseIndex = 0; i < secondAccessor.count; ++i) {
		if (sparseIndex < secondAccessor.sparse->count && dataIndices[sparseIndex] == i) {
			checkValues[i] = dataValues[sparseIndex];
			++sparseIndex;
		} else {
			checkValues[i] = *reinterpret_cast<const glm::vec3*>(bufferData + dataStride * i);
		}
	}

	SECTION("getAccessorElement") {
		for (std::size_t i = 0; i < secondAccessor.count; ++i) {
			REQUIRE(checkValues[i] == fastgltf::getAccessorElement<glm::vec3>(asset.get(), secondAccessor, i));
		}
	}

	SECTION("iterateAccessor") {
		auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
		std::size_t i = 0;

		fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3) {
			dstCopy[i++] = std::forward<glm::vec3>(v3);
		});

		REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
	}

	SECTION("iterateAccessor with idx") {
		auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);

		fastgltf::iterateAccessorWithIndex<glm::vec3>(asset.get(), secondAccessor, [&](auto&& v3, std::size_t i) {
			dstCopy[i] = std::forward<glm::vec3>(v3);
		});

		REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
	}

	SECTION("copyFromAccessor") {
		auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
		fastgltf::copyFromAccessor<glm::vec3>(asset.get(), secondAccessor, dstCopy.get());
		REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
	}

	SECTION("Iterator test") {
		auto dstCopy = std::make_unique<glm::vec3[]>(secondAccessor.count);
		auto accessor = fastgltf::iterateAccessor<glm::vec3>(asset.get(), secondAccessor);
		for (auto it = accessor.begin(); it != accessor.end(); ++it) {
			dstCopy[std::distance(accessor.begin(), it)] = *it;
		}
		REQUIRE(std::memcmp(dstCopy.get(), checkValues.get(), secondAccessor.count * sizeof(glm::vec3)) == 0);
	}
}
