#include <catch2/catch_test_macros.hpp>

#include <fastgltf/types.hpp>
#include <fastgltf/parser.hpp>

#include "gltf_path.hpp"

TEST_CASE("Test basic URIs", "[uri-tests]") {
    const fastgltf::URI uri1(std::string_view(""));
    REQUIRE(uri1.scheme().empty());
    REQUIRE(uri1.path().empty());

    std::string_view path = "path/somewhere.xyz";
    SECTION("Basic local path") {
        const fastgltf::URI uri2(path);
        REQUIRE(uri2.scheme().empty());
        REQUIRE(uri2.path() == path);
        REQUIRE(uri2.isLocalPath());
        REQUIRE(uri2.fspath() == path);
    }

    std::string_view abspath = "/path/somewhere.xyz";
    SECTION("File scheme path") {
        const std::string_view filePath = "file:/path/somewhere.xyz";
        const fastgltf::URI uri3(filePath);
        REQUIRE(uri3.scheme() == "file");
        REQUIRE(uri3.isLocalPath());
        REQUIRE(uri3.path() == abspath);
    }

    SECTION("File scheme localhost path") {
        const std::string_view localhostPath = "file://localhost/path/somewhere.xyz";
        const fastgltf::URI uri4(localhostPath);
        REQUIRE(uri4.scheme() == "file");
        REQUIRE(uri4.path() == abspath);
        REQUIRE(!uri4.isLocalPath());
    }
}

TEST_CASE("Test generic URIs", "[uri-tests]") {
    // These are a bunch of example URIs from https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Example_URIs
    const fastgltf::URI uri(std::string_view("https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top"));
    REQUIRE(uri.scheme() == "https");
    REQUIRE(uri.userinfo() == "john.doe");
    REQUIRE(uri.host() == "www.example.com");
    REQUIRE(uri.port() == "123");
    REQUIRE(uri.path() == "/forum/questions/");
    REQUIRE(uri.query() == "tag=networking&order=newest");
    REQUIRE(uri.fragment() == "top");

    const fastgltf::URI uri1(std::string_view("ldap://[2001:db8::7]/c=GB?objectClass?one"));
    REQUIRE(uri1.scheme() == "ldap");
    REQUIRE(uri1.host() == "2001:db8::7");
    REQUIRE(uri1.path() == "/c=GB");
    REQUIRE(uri1.query() == "objectClass?one");

    const fastgltf::URI uri2(std::string_view("mailto:John.Doe@example.com"));
    REQUIRE(uri2.scheme() == "mailto");
    REQUIRE(uri2.path() == "John.Doe@example.com");

    const fastgltf::URI uri3(std::string_view("telnet://192.0.2.16:80/"));
    REQUIRE(uri3.scheme() == "telnet");
    REQUIRE(uri3.host() == "192.0.2.16");
    REQUIRE(uri3.port() == "80");
    REQUIRE(uri3.path() == "/");
}

TEST_CASE("Test percent decoding", "[uri-tests]") {
    std::string test = "%22 %25";
    fastgltf::URI::decodePercents(test);
    REQUIRE(test == "\" %");
}

TEST_CASE("Test data URI parsing", "[uri-tests]") {
    // This example base64 data is from an example on https://en.wikipedia.org/wiki/Data_URI_scheme.
    const std::string_view data = "data:image/png;base64,iVBORw0KGgoAAA"
                            "ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4"
                            "//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU"
                            "5ErkJggg==";
    const fastgltf::URI uri(data);
    REQUIRE(uri.scheme() == "data");
    REQUIRE(uri.path() == data.substr(5));
}

TEST_CASE("Validate URI copying/moving", "[uri-tests]") {
    const std::string_view data = "test.bin";
    SECTION("Copy semantics") {
        fastgltf::URI uri(data);
        REQUIRE(uri.path() == data);
        fastgltf::URI uri2(uri);
        REQUIRE(uri2.string().data() != uri.string().data());
        REQUIRE(uri2.path() == data);
    }

    SECTION("Move semantics") {
        fastgltf::URI uri;
        {
            fastgltf::URI uri2(data);
            uri = std::move(uri2);
            REQUIRE(uri2.string().empty());
        }
        // Test that the values were copied over and that the string views are still valid.
        REQUIRE(uri.string() == data);
        REQUIRE(uri.path() == uri.string());
    }
}

TEST_CASE("Validate escaped/percent-encoded URI", "[uri-tests]") {
	const std::string_view gltfString = R"({"images": [{"uri": "grande_sph\u00E8re.png"}]})";
	fastgltf::GltfDataBuffer dataBuffer;
	dataBuffer.copyBytes((uint8_t*) gltfString.data(), gltfString.size());

	fastgltf::Parser parser;
	auto asset = parser.loadGLTF(&dataBuffer, "", fastgltf::Options::DontRequireValidAssetMember);
	REQUIRE(asset.error() == fastgltf::Error::None);

	auto escaped = std::get<fastgltf::sources::URI>(asset->images.front().data);

	// This only tests wether the default ctor of fastgltf::URI can handle percent-encoding correctly.
	const fastgltf::URI original(std::string_view("grande_sphère.png"));
	const fastgltf::URI encoded(std::string_view("grande_sph%C3%A8re.png"));
	REQUIRE(original.string() == escaped.uri.string());
	REQUIRE(original.string() == encoded.string());
}

TEST_CASE("Test percent-encoded URIs in glTF", "[uri-tests]") {
	auto boxWithSpaces = sampleModels / "2.0" / "Box With Spaces" / "glTF";
	fastgltf::GltfDataBuffer jsonData;
	REQUIRE(jsonData.loadFromFile(boxWithSpaces / "Box With Spaces.gltf"));

	fastgltf::Parser parser;
	auto asset = parser.loadGLTF(&jsonData, boxWithSpaces);
	REQUIRE(asset.error() == fastgltf::Error::None);
	REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);

	REQUIRE(asset->images.size() == 3);

	auto* image0 = std::get_if<fastgltf::sources::URI>(&asset->images[0].data);
	REQUIRE(image0 != nullptr);
	REQUIRE(image0->uri.path() == "Normal Map.png");

	auto* image1 = std::get_if<fastgltf::sources::URI>(&asset->images[1].data);
	REQUIRE(image1 != nullptr);
	REQUIRE(image1->uri.path() == "glTF Logo With Spaces.png");

	auto* image2 = std::get_if<fastgltf::sources::URI>(&asset->images[2].data);
	REQUIRE(image2 != nullptr);
	REQUIRE(image2->uri.path() == "Roughness Metallic.png");

	auto* buffer0 = std::get_if<fastgltf::sources::URI>(&asset->buffers[0].data);
	REQUIRE(buffer0 != nullptr);
	REQUIRE(buffer0->uri.path() == "Box With Spaces.bin");
}
