/************************************************************************
 *
 * Copyright (C) 2024-2026 IRCAD France
 *
 * This file is part of Sight.
 *
 * Sight is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sight is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
 *
 ***********************************************************************/

#include <core/tools/uuid.hpp>

#include <data/fiducials_series.hpp>

#include <doctest/doctest.h>

#include <gdcmDataSet.h>
#include <gdcmExplicitDataElement.h>
#include <gdcmTagKeywords.h>
#include <gdcmTagToVR.h>
#include <gdcmUIDs.h>

TEST_SUITE("sight::data::fiducials_series")
{
    namespace
    {

//------------------------------------------------------------------------------

    const std::array TEST_VECTOR {
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 0,
            .m_fiducial_index       = 0,
            .m_shape_index          = 0,
            .m_group_name           = "group_0",
            .m_visible              = true,
            .m_size                 = 16.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::cube,
            .m_color                = std::array {0.21F, 0.22F, 0.23F, 0.24F},
            .m_shape                = sight::data::fiducials_series::shape::point,
            .m_contour_data         = std::vector {32.0, 33.0, 34.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 1,
            .m_fiducial_index       = 0,
            .m_shape_index          = 0,
            .m_group_name           = "group_1",
            .m_visible              = false,
            .m_size                 = 32.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::sphere,
            .m_color                = std::array {0.13F, 0.14F, 0.15F, 0.16F},
            .m_shape                = sight::data::fiducials_series::shape::point,
            .m_contour_data         = std::vector {23.0, 24.0, 25.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 1,
            .m_fiducial_index       = 1,
            .m_shape_index          = 1,
            .m_group_name           = "group_1",
            .m_visible              = false,
            .m_size                 = 32.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::sphere,
            .m_color                = std::array {0.13F, 0.14F, 0.15F, 0.16F},
            .m_shape                = sight::data::fiducials_series::shape::point,
            .m_contour_data         = std::vector {26.0, 27.0, 28.0, 29.0, 30.0, 31.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 2,
            .m_fiducial_index       = 0,
            .m_shape_index          = 0,
            .m_group_name           = "group_2",
            .m_visible              = true,
            .m_size                 = 16.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::cube,
            .m_color                = std::array {0.1F, 0.2F, 0.3F, 0.4F},
            .m_shape                = sight::data::fiducials_series::shape::point,
            .m_contour_data         = std::vector {5.0, 6.0, 7.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 2,
            .m_fiducial_index       = 1,
            .m_shape_index          = 0,
            .m_group_name           = "group_2",
            .m_visible              = true,
            .m_size                 = 16.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::cube,
            .m_color                = std::array {0.1F, 0.2F, 0.3F, 0.4F},
            .m_shape                = sight::data::fiducials_series::shape::plane,
            .m_contour_data         = std::vector {8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 2,
            .m_fiducial_index       = 2,
            .m_shape_index          = 0,
            .m_group_name           = "group_2",
            .m_visible              = true,
            .m_size                 = 16.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::cube,
            .m_color                = std::array {0.1F, 0.2F, 0.3F, 0.4F},
            .m_shape                = sight::data::fiducials_series::shape::shape,
            .m_contour_data         = std::vector {17.0, 18.0, 19.0, 20.0, 21.0, 22.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
        sight::data::fiducials_series::query_result {
            .m_fiducial_set_index   = 2,
            .m_fiducial_index       = 3,
            .m_shape_index          = 1,
            .m_group_name           = "group_2",
            .m_visible              = true,
            .m_size                 = 16.F,
            .m_private_shape        = sight::data::fiducials_series::private_shape::cube,
            .m_color                = std::array {0.1F, 0.2F, 0.3F, 0.4F},
            .m_shape                = sight::data::fiducials_series::shape::point,
            .m_contour_data         = std::vector {8.0, 9.0, 10.0},
            .m_fiducial_description = sight::core::tools::uuid::generate(),
            .m_fiducial_identifier  = sight::core::tools::uuid::generate(),
            .m_fiducial_uid         = sight::core::tools::uuid::generate()
        },
    };

//------------------------------------------------------------------------------

    void check_fiducial_with_gdcm(
        const gdcm::DataSet& _fiducial_set_dataset,
        const gdcm::DataSet& _fiducial_dataset,
        const std::optional<std::string>& _group_name,
        const std::optional<sight::vec4f_t>& _color,
        const std::optional<float>& _size,
        const std::optional<sight::data::fiducials_series::private_shape>& _private_shape,
        const std::optional<bool>& _visible,
        const std::optional<std::vector<double> >& _contour_data,
        const std::optional<std::string>& _fiducial_description,
        const std::optional<std::string>& _fiducial_identifier,
        const std::optional<std::string>& _fiducial_uid
)
    {
        // Check attributes values in the fiducial set dataset
        static constexpr std::uint16_t s_PRIVATE_GROUP {0x0099};
        static constexpr std::uint16_t s_PRIVATE_DATA_ELEMENT {0x9910};

        static constexpr std::uint16_t s_GROUP_NAME_ELEMENT {0};
        static constexpr std::uint16_t s_COLOR_ELEMENT {1};
        static constexpr std::uint16_t s_SIZE_ELEMENT {2};
        static constexpr std::uint16_t s_PRIVATE_SHAPE_ELEMENT {3};
        static constexpr std::uint16_t s_VISIBLE_ELEMENT {4};

        // Group name
        if(_group_name)
        {
            const auto* group_name_value = _fiducial_set_dataset.GetDataElement(
                gdcm::Tag(s_PRIVATE_GROUP, s_PRIVATE_DATA_ELEMENT + s_GROUP_NAME_ELEMENT)
            ).GetByteValue();

            const auto& actual_group_name = gdcm::String<>(
                group_name_value->GetPointer(),
                group_name_value->GetLength()
            ).Trim();

            CHECK_EQ(*_group_name, actual_group_name);
        }

        // Color
        if(_color)
        {
            const auto* color_value = _fiducial_set_dataset.GetDataElement(
                gdcm::Tag(s_PRIVATE_GROUP, s_PRIVATE_DATA_ELEMENT + s_COLOR_ELEMENT)
            ).GetByteValue();

            const auto& actual_color = gdcm::String<>(
                color_value->GetPointer(),
                color_value->GetLength()
            ).Trim();

            std::stringstream expected_stream;
            expected_stream << (*_color)[0] << "," << (*_color)[1] << "," << (*_color)[2] << "," << (*_color)[3];

            CHECK_EQ(expected_stream.str(), actual_color);
        }

        // Size
        if(_size)
        {
            const auto* size_value = _fiducial_set_dataset.GetDataElement(
                gdcm::Tag(s_PRIVATE_GROUP, s_PRIVATE_DATA_ELEMENT + s_SIZE_ELEMENT)
            ).GetByteValue();

            const auto& actual_size = gdcm::String<>(
                size_value->GetPointer(),
                size_value->GetLength()
            ).Trim();

            CHECK_EQ(*_size, std::stof(actual_size));
        }

        // Private shape
        if(_private_shape)
        {
            const auto* private_shape_value = _fiducial_set_dataset.GetDataElement(
                gdcm::Tag(s_PRIVATE_GROUP, s_PRIVATE_DATA_ELEMENT + s_PRIVATE_SHAPE_ELEMENT)
            ).GetByteValue();

            const auto& actual_private_shape = gdcm::String<>(
                private_shape_value->GetPointer(),
                private_shape_value->GetLength()
            ).Trim();

            CHECK_EQ(
                std::string(*_private_shape == sight::data::fiducials_series::private_shape::cube ? "CUBE" : "SPHERE"),
                actual_private_shape
            );
        }

        // Visible
        if(_visible)
        {
            const auto* visible_value = _fiducial_set_dataset.GetDataElement(
                gdcm::Tag(s_PRIVATE_GROUP, s_PRIVATE_DATA_ELEMENT + s_VISIBLE_ELEMENT)
            ).GetByteValue();

            const auto& actual_visible = gdcm::String<>(
                visible_value->GetPointer(),
                visible_value->GetLength()
            ).Trim();

            CHECK_EQ(std::string(*_visible ? "true" : "false"), actual_visible);
        }

        // Check attributes values in the fiducial dataset
        // contour_data
        if(_contour_data)
        {
            gdcm::Keywords::ContourData contour_data_attribute;
            contour_data_attribute.SetFromDataSet(_fiducial_dataset);

            const std::vector<double> actual_contour_data(
                contour_data_attribute.GetValues(),
                contour_data_attribute.GetValues() + contour_data_attribute.GetNumberOfValues()
            );

            std::stringstream expected_stream;
            expected_stream << (*_contour_data)[0] << "," << (*_contour_data)[1] << "," << (*_contour_data)[2];

            std::stringstream actual_stream;
            actual_stream << actual_contour_data[0] << "," << actual_contour_data[1] << "," << actual_contour_data[2];

            CHECK_EQ(expected_stream.str(), actual_stream.str());
        }

        // fiducial_description
        if(_fiducial_description)
        {
            gdcm::Keywords::FiducialDescription fiducial_description_attribute;
            fiducial_description_attribute.SetFromDataSet(_fiducial_dataset);
            const auto actual_fiducial_description = fiducial_description_attribute.GetValue().Trim();
            CHECK_EQ(*_fiducial_description, actual_fiducial_description);
        }

        // fiducial_identifier
        if(_fiducial_identifier)
        {
            gdcm::Keywords::FiducialIdentifier fiducial_identifier_attribute;
            fiducial_identifier_attribute.SetFromDataSet(_fiducial_dataset);
            const auto actual_fiducial_identifier = fiducial_identifier_attribute.GetValue().Trim();
            CHECK_EQ(*_fiducial_identifier, actual_fiducial_identifier);
        }

        // fiducial_uid
        if(_fiducial_uid)
        {
            gdcm::Keywords::FiducialUID fiducial_uid_attribute;
            fiducial_uid_attribute.SetFromDataSet(_fiducial_dataset);
            const auto actual_fiducial_uid = fiducial_uid_attribute.GetValue().Trim();
            CHECK_EQ(*_fiducial_uid, actual_fiducial_uid);
        }
    }

//------------------------------------------------------------------------------

    void compare_queries(
        const sight::data::fiducials_series::query_result& _expected,
        const sight::data::fiducials_series::query_result& _actual
)
    {
        CHECK_EQ(_expected.m_fiducial_set_index, _actual.m_fiducial_set_index);
        CHECK_EQ(_expected.m_fiducial_index, _actual.m_fiducial_index);
        CHECK_EQ(_expected.m_shape_index, _actual.m_shape_index);
        CHECK_EQ(*_expected.m_group_name, *_actual.m_group_name);
        CHECK_EQ(*_expected.m_visible, *_actual.m_visible);
        CHECK_EQ(*_expected.m_size, *_actual.m_size);
        CHECK_EQ(*_expected.m_private_shape, *_actual.m_private_shape);
        CHECK(sight::core::is_equal(*_expected.m_color, *_actual.m_color));
        CHECK_EQ(*_expected.m_shape, *_actual.m_shape);
        CHECK(sight::core::is_equal(*_expected.m_contour_data, *_actual.m_contour_data));
        CHECK_EQ(*_expected.m_fiducial_description, *_actual.m_fiducial_description);
        CHECK_EQ(*_expected.m_fiducial_identifier, *_actual.m_fiducial_identifier);
        CHECK_EQ(*_expected.m_fiducial_uid, *_actual.m_fiducial_uid);
    }

//------------------------------------------------------------------------------

    sight::data::fiducials_series::sptr init_fiducials_series()
    {
        auto fs = std::make_shared<sight::data::fiducials_series>();
        fs->set_content_label("TestLabel");
        fs->set_content_label("1");
        fs->set_content_description("FS");
        fs->set_content_creator_name("John Doe");

        // Adds a dummy fiducial set
        sight::data::fiducials_series::fiducial_set fiducial_set;

        sight::data::fiducials_series::referenced_image referenced_image;
        referenced_image.referenced_sop_class_uid    = "1";
        referenced_image.referenced_sop_instance_uid = "2";
        referenced_image.referenced_frame_number     = {3};
        referenced_image.referenced_segment_number   = {4};
        fiducial_set.referenced_image_sequence       = {referenced_image};

        fiducial_set.frame_of_reference_uid = "5";

        sight::data::fiducials_series::fiducial fiducial;
        fiducial.shape_type           = sight::data::fiducials_series::shape::point;
        fiducial.fiducial_description = "6";
        fiducial.fiducial_identifier  = "7";

        sight::data::fiducials_series::graphic_coordinates_data graphic_coordinates_data;
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_class_uid    = "8";
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_instance_uid = "9";
        graphic_coordinates_data.referenced_image_sequence.referenced_frame_number     = {10};
        graphic_coordinates_data.referenced_image_sequence.referenced_segment_number   = {11};
        graphic_coordinates_data.graphic_data                                          = {{.x = 12, .y = 13}};
        fiducial.graphic_coordinates_data_sequence                                     = {graphic_coordinates_data};

        fiducial.fiducial_uid = "14";
        fiducial.contour_data = {{.x = 15, .y = 16, .z = 17}};
        fiducial_set.fiducial_sequence.push_back(fiducial);

        fiducial_set.group_name = "18";
        fiducial_set.color      = {{19, 20, 21, 22}};
        fiducial_set.size       = 23.F;
        fiducial_set.shape      = sight::data::fiducials_series::private_shape::cube;
        fiducial_set.visibility = true;

        // Test setFiducialSets method
        fs->set_fiducial_sets({fiducial_set});

        return fs;
    }

    } // namespace

//------------------------------------------------------------------------------

    TEST_CASE("shallow_copy")
    {
        auto fs1 = init_fiducials_series();
        auto fs2 = std::make_shared<sight::data::fiducials_series>();

        CHECK(*fs1 != *fs2);
        fs2->shallow_copy(fs1);
        CHECK(*fs1 == *fs2);
    }

//------------------------------------------------------------------------------

    TEST_CASE("deep_copy")
    {
        auto fs1 = init_fiducials_series();
        auto fs2 = std::make_shared<sight::data::fiducials_series>();

        CHECK(*fs1 != *fs2);
        fs2->deep_copy(fs1);
        CHECK(*fs1 == *fs2);
    }

//------------------------------------------------------------------------------

    TEST_CASE("simple_setter_getter")
    {
        // This method checks that plain method getters/setters work correctly.

        using namespace std::literals::string_literals;
        auto fiducialsSeries = std::make_shared<sight::data::fiducials_series>();

        // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    #define COMPARE_GETSETTERS(attribute, before, after, ...) \
            CHECK_EQ(before, fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \
            fiducialsSeries->set_ ## attribute(__VA_ARGS__ __VA_OPT__( , ) after); \
            CHECK_EQ(after, fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \

        // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    #define COMPARE_GETSETTERS_DEMI_OPTIONAL(attribute, before, after, ...) \
            CHECK(before == fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \
            fiducialsSeries->set_ ## attribute(__VA_ARGS__ __VA_OPT__( , ) * after); \
            CHECK(after == fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \

        // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    #define COMPARE_GETSETTERS_ARRAYS(attribute, after, ...) \
            CHECK(fiducialsSeries->get_ ## attribute(__VA_ARGS__).empty()); \
            fiducialsSeries->set_ ## attribute(__VA_ARGS__ __VA_OPT__( , ) after); \
            CHECK(after == fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \

        // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    #define COMPARE_GETSETTERS_OPTIONAL(attribute, before, after, ...) \
            CHECK(before == fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \
            fiducialsSeries->set_ ## attribute(__VA_ARGS__ __VA_OPT__( , ) after); \
            CHECK(after == fiducialsSeries->get_ ## attribute(__VA_ARGS__)); \

        COMPARE_GETSETTERS(content_date, ""s, "0"s);
        COMPARE_GETSETTERS(content_label, ""s, "1"s);
        COMPARE_GETSETTERS(content_description, ""s, "2"s);
        COMPARE_GETSETTERS(content_creator_name, ""s, "3"s);

        CHECK(fiducialsSeries->get_fiducial_sets().empty());
        CHECK(!fiducialsSeries->get_referenced_images(0));

        COMPARE_GETSETTERS_DEMI_OPTIONAL(
            referenced_sop_class_uid,
            std::optional<std::string> {},
            std::optional {"4"s},
            0,
            0
        );
        COMPARE_GETSETTERS_DEMI_OPTIONAL(
            referenced_sop_instance_uid,
            std::optional<std::string> {},
            std::optional {"5"s},
            0,
            0
        );
        COMPARE_GETSETTERS_ARRAYS(referenced_frame_number, std::vector<std::int32_t> {6}, 0, 0);
        COMPARE_GETSETTERS_ARRAYS(referenced_segment_number, std::vector<std::uint16_t> {7}, 0, 0);

        COMPARE_GETSETTERS_OPTIONAL(frame_of_reference_uid, std::optional<std::string> {}, std::optional {"8"s}, 0);

        CHECK(fiducialsSeries->get_fiducials(0).empty());

        COMPARE_GETSETTERS(
            shape_type,
            sight::data::fiducials_series::shape::invalid,
            sight::data::fiducials_series::shape::point,
            0,
            0
        );
        COMPARE_GETSETTERS(fiducial_description, ""s, "9"s, 0, 0);
        COMPARE_GETSETTERS(fiducial_identifier, ""s, "10"s, 0, 0);

        CHECK(!fiducialsSeries->get_graphic_coordinates_data_sequence(0, 0).has_value());

        COMPARE_GETSETTERS(referenced_sop_class_uid, ""s, "11"s, 0, 0, 0);
        COMPARE_GETSETTERS(referenced_sop_instance_uid, ""s, "12"s, 0, 0, 0);
        COMPARE_GETSETTERS_ARRAYS(referenced_frame_number, std::vector<std::int32_t> {13}, 0, 0, 0);
        COMPARE_GETSETTERS_ARRAYS(referenced_segment_number, std::vector<std::uint16_t> {14}, 0, 0, 0);

        COMPARE_GETSETTERS_ARRAYS(
            graphic_data,
            (std::vector<sight::data::fiducials_series::point2> {{15, 16}}),
            0,
            0,
            0
        );

        COMPARE_GETSETTERS_OPTIONAL(fiducial_uid, std::optional<std::string> {}, std::optional {"17"s}, 0, 0);
        COMPARE_GETSETTERS_ARRAYS(
            contour_data,
            (std::vector<sight::data::fiducials_series::point3> {{{18, 19, 20}}}),
            0,
            0
        );

        COMPARE_GETSETTERS_DEMI_OPTIONAL(group_name, std::optional<std::string> {}, std::optional {"21"s}, 0);
        COMPARE_GETSETTERS_DEMI_OPTIONAL(
            color,
            (std::optional<sight::vec4f_t> {}),
            (std::optional {std::array {22.F, 23.F, 24.F, 25.F}}),
            0
        );
        COMPARE_GETSETTERS_DEMI_OPTIONAL(size, std::optional<float> {}, std::optional {26.F}, 0);
        COMPARE_GETSETTERS_DEMI_OPTIONAL(
            shape,
            std::optional<sight::data::fiducials_series::private_shape> {},
            std::optional {sight::data::fiducials_series::private_shape::sphere},
            0
        );
        COMPARE_GETSETTERS_DEMI_OPTIONAL(visibility, std::optional<bool> {}, std::optional {true}, 0);
    }

//------------------------------------------------------------------------------

    static void compare_referenced_images(
        sight::data::fiducials_series::referenced_image _expected,
        sight::data::fiducials_series::referenced_image _actual
)
    {
        CHECK_EQ(_expected.referenced_sop_class_uid, _actual.referenced_sop_class_uid);
        CHECK_EQ(_expected.referenced_sop_instance_uid, _actual.referenced_sop_instance_uid);
        CHECK(_expected.referenced_frame_number == _actual.referenced_frame_number);
        CHECK(_expected.referenced_segment_number == _actual.referenced_segment_number);
    }

//------------------------------------------------------------------------------

    static void compare_graphic_coordinates_data(
        sight::data::fiducials_series::graphic_coordinates_data _expected,
        sight::data::fiducials_series::graphic_coordinates_data _actual
)
    {
        compare_referenced_images(_expected.referenced_image_sequence, _actual.referenced_image_sequence);
        CHECK(_expected.graphic_data == _actual.graphic_data);
    }

//------------------------------------------------------------------------------

    static void compare_fiducials(
        sight::data::fiducials_series::fiducial _expected,
        sight::data::fiducials_series::fiducial _actual
)
    {
        CHECK_EQ(_expected.shape_type, _actual.shape_type);
        CHECK_EQ(_expected.fiducial_description, _actual.fiducial_description);
        CHECK_EQ(_expected.fiducial_identifier, _actual.fiducial_identifier);

        CHECK_EQ(
            _expected.graphic_coordinates_data_sequence.has_value(),
            _actual.graphic_coordinates_data_sequence.has_value()
        );
        if(_expected.graphic_coordinates_data_sequence.has_value())
        {
            CHECK_EQ(
                _expected.graphic_coordinates_data_sequence->size(),
                _actual.graphic_coordinates_data_sequence->size()
            );
            for(std::size_t i = 0 ; i < _expected.graphic_coordinates_data_sequence->size() ; i++)
            {
                compare_graphic_coordinates_data(
                    _expected.graphic_coordinates_data_sequence->at(i),
                    _actual.graphic_coordinates_data_sequence->at(i)
                );
            }
        }

        CHECK(_expected.fiducial_uid == _actual.fiducial_uid);
        CHECK(_expected.contour_data == _actual.contour_data);
    }

//------------------------------------------------------------------------------

    static void compare_fiducial_sets(
        sight::data::fiducials_series::fiducial_set _expected,
        sight::data::fiducials_series::fiducial_set _actual
)
    {
        CHECK_EQ(
            _expected.referenced_image_sequence.has_value(),
            _actual.referenced_image_sequence.has_value()
        );
        if(_expected.referenced_image_sequence.has_value())
        {
            CHECK_EQ(_expected.referenced_image_sequence->size(), _actual.referenced_image_sequence->size());
            for(std::size_t i = 0 ; i < _expected.referenced_image_sequence->size() ; i++)
            {
                compare_referenced_images(
                    _expected.referenced_image_sequence->at(i),
                    _actual.referenced_image_sequence->at(i)
                );
            }
        }

        CHECK(_expected.frame_of_reference_uid == _actual.frame_of_reference_uid);

        CHECK_EQ(_expected.fiducial_sequence.size(), _actual.fiducial_sequence.size());
        for(std::size_t i = 0 ; i < _expected.fiducial_sequence.size() ; i++)
        {
            compare_fiducials(_expected.fiducial_sequence[i], _actual.fiducial_sequence[i]);
        }

        CHECK(_expected.group_name == _actual.group_name);
        CHECK(_expected.color == _actual.color);
        CHECK(_expected.size == _actual.size);
        CHECK(_expected.shape == _actual.shape);
    }

//------------------------------------------------------------------------------

    TEST_CASE("fiducial_set_setter_getter")
    {
        // This method checks that adding fiducial sets works correctly.

        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();
        CHECK(fiducials_series->get_fiducial_sets().empty());

        sight::data::fiducials_series::fiducial_set fiducial_set;

        sight::data::fiducials_series::referenced_image referenced_image;
        referenced_image.referenced_sop_class_uid    = "1";
        referenced_image.referenced_sop_instance_uid = "2";
        referenced_image.referenced_frame_number     = {3};
        referenced_image.referenced_segment_number   = {4};
        fiducial_set.referenced_image_sequence       = {referenced_image};

        fiducial_set.frame_of_reference_uid = "5";

        sight::data::fiducials_series::fiducial fiducial;
        fiducial.shape_type           = sight::data::fiducials_series::shape::point;
        fiducial.fiducial_description = "6";
        fiducial.fiducial_identifier  = "7";

        sight::data::fiducials_series::graphic_coordinates_data graphic_coordinates_data;
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_class_uid    = "8";
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_instance_uid = "9";
        graphic_coordinates_data.referenced_image_sequence.referenced_frame_number     = {10};
        graphic_coordinates_data.referenced_image_sequence.referenced_segment_number   = {11};
        graphic_coordinates_data.graphic_data                                          = {{.x = 12, .y = 13}};
        fiducial.graphic_coordinates_data_sequence                                     = {graphic_coordinates_data};

        fiducial.fiducial_uid = "14";
        fiducial.contour_data = {{.x = 15, .y = 16, .z = 17}};
        fiducial_set.fiducial_sequence.push_back(fiducial);

        fiducial_set.group_name = "18";
        fiducial_set.color      = {{19, 20, 21, 22}};
        fiducial_set.size       = 23.F;
        fiducial_set.shape      = sight::data::fiducials_series::private_shape::cube;
        fiducial_set.visibility = true;

        // Test setFiducialSets method
        fiducials_series->set_fiducial_sets({fiducial_set});
        std::vector<sight::data::fiducials_series::fiducial_set> fiducial_sets = fiducials_series->get_fiducial_sets();
        CHECK_EQ(std::size_t(1), fiducial_sets.size());
        compare_fiducial_sets(fiducial_set, fiducial_sets[0]);

        // Test setFiducialSet method
        fiducials_series->set_fiducial_set(1, fiducial_set);
        fiducial_sets = fiducials_series->get_fiducial_sets();
        CHECK_EQ(std::size_t(2), fiducial_sets.size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_fiducial_sets(fiducial_set, fiducial_sets[i]);
        }

        // Test appendFiducialSet
        fiducials_series->append_fiducial_set(fiducial_set);
        fiducial_sets = fiducials_series->get_fiducial_sets();
        CHECK_EQ(std::size_t(3), fiducial_sets.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducial_sets(fiducial_set, fiducial_sets[i]);
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("referenced_image_setter_getter")
    {
        // This method checks that adding referenced images works correctly.

        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();
        CHECK(!fiducials_series->get_referenced_images(0));

        sight::data::fiducials_series::referenced_image referenced_image;
        referenced_image.referenced_sop_class_uid    = "1";
        referenced_image.referenced_sop_instance_uid = "2";
        referenced_image.referenced_frame_number     = {3};
        referenced_image.referenced_segment_number   = {4};

        // Test setReferencedImages method
        fiducials_series->set_referenced_images(0, {{referenced_image}});
        std::optional<std::vector<sight::data::fiducials_series::referenced_image> > referenced_images =
            fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(1), referenced_images->size());
        compare_referenced_images(referenced_image, referenced_images->at(0));

        // Test setReferencedImage method
        fiducials_series->set_referenced_image(0, 1, referenced_image);
        referenced_images = fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(2), referenced_images->size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        // Test appendReferencedImage method
        fiducials_series->append_referenced_image(0, referenced_image);
        referenced_images = fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(3), referenced_images->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        // Test setReferencedImages method in a second fiducial set
        fiducials_series->set_referenced_images(1, {{referenced_image}});
        referenced_images = fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(3), referenced_images->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        referenced_images = fiducials_series->get_referenced_images(1);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(1), referenced_images->size());
        compare_referenced_images(referenced_image, referenced_images->at(0));

        // Test setReferencedImage method in a second fiducial set
        fiducials_series->set_referenced_image(1, 1, referenced_image);
        referenced_images = fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(3), referenced_images->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        referenced_images = fiducials_series->get_referenced_images(1);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(2), referenced_images->size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        // Test appendReferencedImage method in a second fiducial set
        fiducials_series->append_referenced_image(1, referenced_image);
        referenced_images = fiducials_series->get_referenced_images(0);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(3), referenced_images->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }

        referenced_images = fiducials_series->get_referenced_images(1);
        CHECK(referenced_images);
        CHECK_EQ(std::size_t(3), referenced_images->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_referenced_images(referenced_image, referenced_images->at(i));
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("fiducial_setter_getter")
    {
        // This method checks that adding fiducials works correctly.

        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();
        CHECK(fiducials_series->get_fiducials(0).empty());

        sight::data::fiducials_series::fiducial fiducial;
        fiducial.shape_type           = sight::data::fiducials_series::shape::point;
        fiducial.fiducial_description = "1";
        fiducial.fiducial_identifier  = "2";

        sight::data::fiducials_series::graphic_coordinates_data graphic_coordinates_data;
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_class_uid    = "3";
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_instance_uid = "4";
        graphic_coordinates_data.referenced_image_sequence.referenced_frame_number     = {5};
        graphic_coordinates_data.referenced_image_sequence.referenced_segment_number   = {6};
        graphic_coordinates_data.graphic_data                                          = {{.x = 7, .y = 8}};
        fiducial.graphic_coordinates_data_sequence                                     = {graphic_coordinates_data};

        fiducial.fiducial_uid = "9";
        fiducial.contour_data = {{.x = 10, .y = 11, .z = 12}};

        // Test setFiducials method
        fiducials_series->set_fiducials(0, {fiducial});
        std::vector<sight::data::fiducials_series::fiducial> fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(1), fiducials.size());
        compare_fiducials(fiducial, fiducials[0]);

        // Test setFiducial method
        fiducials_series->set_fiducial(0, 1, fiducial);
        fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(2), fiducials.size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        // Test appendFiducial method
        fiducials_series->append_fiducial(0, fiducial);
        fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(3), fiducials.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        // Test setFiducials method in a second fiducial set
        fiducials_series->set_fiducials(1, {fiducial});
        fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(3), fiducials.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        fiducials = fiducials_series->get_fiducials(1);
        CHECK_EQ(std::size_t(1), fiducials.size());
        compare_fiducials(fiducial, fiducials[0]);

        // Test setFiducial method in a second fiducial set
        fiducials_series->set_fiducial(1, 1, fiducial);
        fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(3), fiducials.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        fiducials = fiducials_series->get_fiducials(1);
        CHECK_EQ(std::size_t(2), fiducials.size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        // Test appendFiducial method in a second fiducial set
        fiducials_series->append_fiducial(1, fiducial);
        fiducials = fiducials_series->get_fiducials(0);
        CHECK_EQ(std::size_t(3), fiducials.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }

        fiducials = fiducials_series->get_fiducials(1);
        CHECK_EQ(std::size_t(3), fiducials.size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_fiducials(fiducial, fiducials[i]);
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("graphic_coordinates_data_setter_getter")
    {
        // This method checks that adding graphic coordinates data works correctly.

        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();
        CHECK(!fiducials_series->get_graphic_coordinates_data_sequence(0, 0));

        sight::data::fiducials_series::graphic_coordinates_data graphic_coordinates_data;
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_class_uid    = "1";
        graphic_coordinates_data.referenced_image_sequence.referenced_sop_instance_uid = "2";
        graphic_coordinates_data.referenced_image_sequence.referenced_frame_number     = {3};
        graphic_coordinates_data.referenced_image_sequence.referenced_segment_number   = {4};
        graphic_coordinates_data.graphic_data                                          = {{.x = 5, .y = 6}};

        // Test setGraphicCoordinatesDataSequence method
        fiducials_series->set_graphic_coordinates_data_sequence(0, 0, {{graphic_coordinates_data}});
        auto graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(1), graphic_coordinates_data_sequence->size());
        compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(0));

        // Test setGraphicCoordinatesData method
        fiducials_series->set_graphic_coordinates_data(0, 0, 1, graphic_coordinates_data);
        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(2), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        // Test appendGraphicCoordinatesData method
        fiducials_series->append_graphic_coordinates_data(0, 0, graphic_coordinates_data);
        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(3), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        // Test setGraphicCoordinatesDataSequence method in a second fiducial
        fiducials_series->set_graphic_coordinates_data_sequence(0, 1, {{graphic_coordinates_data}});
        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(3), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 1);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(1), graphic_coordinates_data_sequence->size());
        compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(0));

        // Test setGraphicCoordinatesData method in a second fiducial
        fiducials_series->set_graphic_coordinates_data(0, 1, 1, graphic_coordinates_data);
        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(3), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 1);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(2), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 2 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        // Test appendGraphicCoordinatesData method in a second fiducial
        fiducials_series->append_graphic_coordinates_data(0, 1, graphic_coordinates_data);
        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 0);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(3), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }

        graphic_coordinates_data_sequence = fiducials_series->get_graphic_coordinates_data_sequence(0, 1);
        CHECK(graphic_coordinates_data_sequence);
        CHECK_EQ(std::size_t(3), graphic_coordinates_data_sequence->size());
        for(std::size_t i = 0 ; i < 3 ; i++)
        {
            compare_graphic_coordinates_data(graphic_coordinates_data, graphic_coordinates_data_sequence->at(i));
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("static")
    {
        CHECK_EQ(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::point
            ),
            std::string("POINT")
        );
        CHECK_EQ(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::line
            ),
            std::string("LINE")
        );
        CHECK_EQ(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::surface
            ),
            std::string("SURFACE")
        );
        CHECK_EQ(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::l_shape
            ),
            std::string("L_SHAPE")
        );
        CHECK_EQ(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::t_shape
            ),
            std::string("T_SHAPE")
        );
        CHECK(
            sight::data::fiducials_series::shape_to_string(
                sight::data::fiducials_series::shape::invalid
            ).empty()
        );
    }

//------------------------------------------------------------------------------

    TEST_CASE("bounding_box")
    {
        sight::data::fiducials_series::fiducial fiducial;

        {
            auto [min, max] = fiducial.contour_data_bounding_box();
            CHECK_EQ(min.x, doctest::Approx(std::numeric_limits<double>::max()).epsilon(1e-3));
            CHECK_EQ(min.y, doctest::Approx(std::numeric_limits<double>::max()).epsilon(1e-3));
            CHECK_EQ(min.z, doctest::Approx(std::numeric_limits<double>::max()).epsilon(1e-3));

            CHECK_EQ(max.x, doctest::Approx(std::numeric_limits<double>::lowest()).epsilon(1e-3));
            CHECK_EQ(max.y, doctest::Approx(std::numeric_limits<double>::lowest()).epsilon(1e-3));
            CHECK_EQ(max.z, doctest::Approx(std::numeric_limits<double>::lowest()).epsilon(1e-3));
        }

        {
            auto bb = fiducial.graphic_coordinates_data_bounding_box(0);
            CHECK(!bb.has_value());
        }

        // Copy the current contour in the current fiducial data
        fiducial.contour_data.emplace_back(0., 0., 0.);
        fiducial.contour_data.emplace_back(16., 16., 16.);
        {
            auto [min, max] = fiducial.contour_data_bounding_box();

            CHECK_EQ(min.x, doctest::Approx(0.).epsilon(1.e-3));
            CHECK_EQ(min.y, doctest::Approx(0.).epsilon(1.e-3));
            CHECK_EQ(min.z, doctest::Approx(0.).epsilon(1.e-3));

            CHECK_EQ(max.x, doctest::Approx(16.).epsilon(1.e-3));
            CHECK_EQ(max.y, doctest::Approx(16.).epsilon(1.e-3));
            CHECK_EQ(max.z, doctest::Approx(16.).epsilon(1.e-3));
        }

        fiducial.graphic_coordinates_data_sequence = std::vector<sight::data::fiducials_series::graphic_coordinates_data>();
        // We only need 1 element as the contour only spans the current frame
        fiducial.graphic_coordinates_data_sequence->emplace_back();

        auto& gcds0 = fiducial.graphic_coordinates_data_sequence.value().at(0);

        gcds0.graphic_data.emplace_back(0., 0.);
        gcds0.graphic_data.emplace_back(8., 8.);

        {
            auto bb = fiducial.graphic_coordinates_data_bounding_box(0);
            CHECK(bb.has_value());

            CHECK_EQ(std::get<0>(*bb).x, doctest::Approx(0.).epsilon(1.e-3));
            CHECK_EQ(std::get<0>(*bb).y, doctest::Approx(0.).epsilon(1.e-3));

            CHECK_EQ(std::get<1>(*bb).x, doctest::Approx(8.).epsilon(1.e-3));
            CHECK_EQ(std::get<1>(*bb).y, doctest::Approx(8.).epsilon(1.e-3));
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("add_fiducial")
    {
        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

        const std::string group_name {sight::core::tools::uuid::generate()};

        const sight::vec4f_t color {0.1F, 0.2F, 0.3F, 0.4F};
        const float size {32.F};
        const sight::data::fiducials_series::private_shape private_shape {sight::data::fiducials_series::private_shape::
                                                                          cube
        };
        const bool visible {false};

        {
            const std::vector<double> contour_data {1.F, 2.F, 3.F};
            const std::string fiducial_description {sight::core::id::join(group_name, 0)};
            const std::string fiducial_identifier {sight::core::id::join(group_name, 0)};
            const std::string fiducial_uid {sight::core::tools::uuid::generate()};

            const auto& fiducial_add_predicate =
                [&](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    // Fiducial set part
                    _result.m_group_name    = group_name;
                    _result.m_color         = color;
                    _result.m_size          = size;
                    _result.m_private_shape = private_shape;
                    _result.m_visible       = visible;

                    // Fiducial part
                    _result.m_contour_data         = contour_data;
                    _result.m_fiducial_description = fiducial_description;
                    _result.m_fiducial_identifier  = fiducial_identifier;
                    _result.m_fiducial_uid         = fiducial_uid;

                    return true;
                };

            fiducials_series->add_fiducial(
                fiducial_add_predicate,
                sight::data::fiducials_series::shape::point,
                group_name
            );

            // Check the result using GDCM
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_set_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(1));
            const auto& fiducial_set_dataset = fiducial_set_sequence->GetItem(1).GetNestedDataSet();

            // Get fiducial dataset
            const auto& fiducial_sequence_tag = gdcm::Keywords::FiducialSequence::GetTag();
            CHECK(fiducial_set_dataset.FindDataElement(fiducial_sequence_tag));
            const auto& fiducial_sequence = fiducial_set_dataset.GetDataElement(fiducial_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(1));
            const auto& fiducial_dataset = fiducial_sequence->GetItem(1).GetNestedDataSet();

            check_fiducial_with_gdcm(
                fiducial_set_dataset,
                fiducial_dataset,
                group_name,
                color,
                size,
                private_shape,
                visible,
                contour_data,
                fiducial_description,
                fiducial_identifier,
                fiducial_uid
            );
        }

        // Add a plane fiducial in the same group
        {
            const std::vector<double> contour_data {4.F, 5.F, 6.F, 7.F, 8.F, 9.F, 10.F, 11.F, 12.F};
            const std::string fiducial_description {sight::core::id::join(group_name, 1)};
            const std::string fiducial_identifier {sight::core::id::join(group_name, 1)};
            const std::string fiducial_uid {sight::core::tools::uuid::generate()};

            const auto& fiducial_add_predicate =
                [&](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    // Fiducial set part
                    _result.m_group_name    = group_name;
                    _result.m_color         = color;
                    _result.m_size          = size;
                    _result.m_private_shape = private_shape;
                    _result.m_visible       = visible;

                    // Fiducial part
                    _result.m_shape                = sight::data::fiducials_series::shape::plane;
                    _result.m_contour_data         = contour_data;
                    _result.m_fiducial_description = fiducial_description;
                    _result.m_fiducial_identifier  = fiducial_identifier;
                    _result.m_fiducial_uid         = fiducial_uid;

                    return true;
                };

            fiducials_series->add_fiducial(
                fiducial_add_predicate,
                sight::data::fiducials_series::shape::plane,
                group_name
            );

            // Check the result using GDCM
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_set_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(1));
            const auto& fiducial_set_dataset = fiducial_set_sequence->GetItem(1).GetNestedDataSet();

            // Get fiducial dataset
            const auto& fiducial_sequence_tag = gdcm::Keywords::FiducialSequence::GetTag();
            CHECK(fiducial_set_dataset.FindDataElement(fiducial_sequence_tag));
            const auto& fiducial_sequence = fiducial_set_dataset.GetDataElement(fiducial_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(2));
            const auto& fiducial_dataset = fiducial_sequence->GetItem(2).GetNestedDataSet();

            check_fiducial_with_gdcm(
                fiducial_set_dataset,
                fiducial_dataset,
                group_name,
                color,
                size,
                private_shape,
                visible,
                contour_data,
                fiducial_description,
                fiducial_identifier,
                fiducial_uid
            );
        }

        // Add a point fiducial in a second group
        {
            const std::string group_name_2 {sight::core::tools::uuid::generate()};

            const sight::vec4f_t color_2 {0.9F, 0.10F, 0.12F, 0.13F};
            const float size_2 {16.F};
            const sight::data::fiducials_series::private_shape private_shape_2 {sight::data::fiducials_series::
                                                                                private_shape::sphere
            };
            const bool visible_2 {true};

            const std::vector<double> contour_data {7.F, 8.F, 9.F};
            const std::string fiducial_description {sight::core::id::join(group_name_2, 0)};
            const std::string fiducial_identifier {sight::core::id::join(group_name_2, 0)};
            const std::string fiducial_uid {sight::core::tools::uuid::generate()};

            const auto& fiducial_add_predicate =
                [&](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    // Fiducial set part
                    _result.m_group_name    = group_name_2;
                    _result.m_color         = color_2;
                    _result.m_size          = size_2;
                    _result.m_private_shape = private_shape_2;
                    _result.m_visible       = visible_2;

                    // Fiducial part
                    _result.m_contour_data         = contour_data;
                    _result.m_fiducial_description = fiducial_description;
                    _result.m_fiducial_identifier  = fiducial_identifier;
                    _result.m_fiducial_uid         = fiducial_uid;

                    return true;
                };

            fiducials_series->add_fiducial(
                fiducial_add_predicate,
                sight::data::fiducials_series::shape::point,
                group_name_2
            );

            // Check the result using GDCM
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_set_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(2));
            const auto& fiducial_set_dataset = fiducial_set_sequence->GetItem(2).GetNestedDataSet();

            // Get fiducial dataset
            const auto& fiducial_sequence_tag = gdcm::Keywords::FiducialSequence::GetTag();
            CHECK(fiducial_set_dataset.FindDataElement(fiducial_sequence_tag));
            const auto& fiducial_sequence = fiducial_set_dataset.GetDataElement(fiducial_sequence_tag).GetValueAsSQ();
            CHECK_EQ(fiducial_sequence->GetNumberOfItems(), gdcm::SequenceOfItems::SizeType(1));
            const auto& fiducial_dataset = fiducial_sequence->GetItem(1).GetNestedDataSet();

            check_fiducial_with_gdcm(
                fiducial_set_dataset,
                fiducial_dataset,
                group_name_2,
                color_2,
                size_2,
                private_shape_2,
                visible_2,
                contour_data,
                fiducial_description,
                fiducial_identifier,
                fiducial_uid
            );
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("query_fiducials")
    {
        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

        // Fill the created fiducials series with some fiducials
        for(const auto& query_result : TEST_VECTOR)
        {
            fiducials_series->add_fiducial(
                [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
            {
                _result = query_result;
                return true;
            },
                *query_result.m_shape,
                *query_result.m_group_name
            );
        }

        // Basic checks using GDCM (this is somewhat redundant with add_fiducial_test)
        {
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();

            /// 3 "groups" should have been created
            CHECK_EQ(
                fiducial_set_sequence->GetNumberOfItems(),
                gdcm::SequenceOfItems::SizeType(3)
            );

            for(std::size_t fiducial_set_index = 1 ; fiducial_set_index <= 3 ; ++fiducial_set_index)
            {
                const auto& fiducial_set_dataset =
                    fiducial_set_sequence->GetItem(fiducial_set_index).GetNestedDataSet();
                const auto& fiducial_sequence_tag = gdcm::Keywords::FiducialSequence::GetTag();
                CHECK(fiducial_set_dataset.FindDataElement(fiducial_sequence_tag));
                const auto& fiducial_sequence =
                    fiducial_set_dataset.GetDataElement(fiducial_sequence_tag).GetValueAsSQ();

                switch(fiducial_set_index)
                {
                    case 1:
                        CHECK_EQ(
                            fiducial_sequence->GetNumberOfItems(),
                            gdcm::SequenceOfItems::SizeType(1)
                        );
                        break;

                    case 2:
                        CHECK_EQ(
                            fiducial_sequence->GetNumberOfItems(),
                            gdcm::SequenceOfItems::SizeType(2)
                        );
                        break;

                    case 3:
                        CHECK_EQ(
                            fiducial_sequence->GetNumberOfItems(),
                            gdcm::SequenceOfItems::SizeType(4)
                        );
                        break;

                    default:
                        break;
                }
            }
        }

        // Query all fiducials, without any filtering
        {
            const auto& query_results = fiducials_series->query_fiducials();
            CHECK_EQ(TEST_VECTOR.size(), query_results.size());

            for(std::size_t index = 0 ; index < TEST_VECTOR.size() ; ++index)
            {
                compare_queries(TEST_VECTOR[index], query_results[index]);
            }
        }

        // Query all fiducials from group 1
        {
            const auto& query_results = fiducials_series->query_fiducials(std::nullopt, std::nullopt, "group_1");
            CHECK_EQ(query_results.size(), std::size_t(2));

            compare_queries(TEST_VECTOR[1], query_results[0]);
            compare_queries(TEST_VECTOR[2], query_results[1]);
        }

        // Query all point fiducials from group 2
        {
            const auto& query_results = fiducials_series->query_fiducials(
                std::nullopt,
                sight::data::fiducials_series::shape::point,
                "group_2"
            );

            CHECK_EQ(query_results.size(), std::size_t(2));

            compare_queries(TEST_VECTOR[3], query_results[0]);
            compare_queries(TEST_VECTOR[6], query_results[1]);
        }

        // Query all fiducials with predicate
        {
            const auto& query_results = fiducials_series->query_fiducials(
                [](const sight::data::fiducials_series::query_result& _result) -> bool
            {
                return _result.m_visible.value_or(true);
            });

            CHECK_EQ(query_results.size(), std::size_t(5));

            compare_queries(TEST_VECTOR[0], query_results[0]);
            compare_queries(TEST_VECTOR[3], query_results[1]);
            compare_queries(TEST_VECTOR[4], query_results[2]);
            compare_queries(TEST_VECTOR[5], query_results[3]);
            compare_queries(TEST_VECTOR[6], query_results[4]);
        }

        // Query on fiducial
        {
            const auto& query_results = fiducials_series->query_fiducials(
                std::nullopt,
                sight::data::fiducials_series::shape::point,
                "group_2",
                1
            );

            CHECK_EQ(std::size_t(1), query_results.size());
            compare_queries(TEST_VECTOR[6], query_results[0]);
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("remove_fiducials")
    {
        // Test removing all fiducials
        {
            auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

            // Fill the created fiducials series with some fiducials
            for(const auto& query_result : TEST_VECTOR)
            {
                fiducials_series->add_fiducial(
                    [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    _result = query_result;
                    return true;
                },
                    *query_result.m_shape,
                    *query_result.m_group_name
                );
            }

            // Remove all fiducials
            const auto& removed_results = fiducials_series->remove_fiducials();
            CHECK_EQ(TEST_VECTOR.size(), removed_results.first.size());

            for(std::size_t index = 0 ; index < TEST_VECTOR.size() ; ++index)
            {
                compare_queries(TEST_VECTOR[index], removed_results.first[index]);
            }

            CHECK(
                sight::core::is_equal(
                    std::set<std::string> {"group_0", "group_1", "group_2"},
                    removed_results.second
                )
            );

            // Check the result using GDCM
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();
            CHECK_EQ(gdcm::SequenceOfItems::SizeType(0), fiducial_set_sequence->GetNumberOfItems());
        }

        // Test removing all fiducials from group 1
        {
            auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

            // Fill the created fiducials series with some fiducials
            for(const auto& query_result : TEST_VECTOR)
            {
                fiducials_series->add_fiducial(
                    [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    _result = query_result;
                    return true;
                },
                    *query_result.m_shape,
                    *query_result.m_group_name
                );
            }

            // Remove all group_1 fiducials and group_1 fiducial set
            const auto& removed_results = fiducials_series->remove_fiducials(std::nullopt, std::nullopt, "group_1");
            CHECK_EQ(std::size_t(2), removed_results.first.size());

            compare_queries(TEST_VECTOR[1], removed_results.first[0]);
            compare_queries(TEST_VECTOR[2], removed_results.first[1]);

            CHECK(sight::core::is_equal(std::set<std::string> {"group_1"}, removed_results.second));

            // Check the result using GDCM
            const auto& fiducial_set_sequence_tag = gdcm::Keywords::FiducialSetSequence::GetTag();
            const auto& fiducials_dataset         = fiducials_series->get_data_set();

            // Get fiducial set dataset
            CHECK(fiducials_dataset.FindDataElement(fiducial_set_sequence_tag));
            const auto& fiducial_set_sequence =
                fiducials_dataset.GetDataElement(fiducial_set_sequence_tag).GetValueAsSQ();
            CHECK_EQ(gdcm::SequenceOfItems::SizeType(2), fiducial_set_sequence->GetNumberOfItems());

            // Query again and check the remaining fiducials
            std::array adjusted_test_vector {
                TEST_VECTOR[0], TEST_VECTOR[3], TEST_VECTOR[4], TEST_VECTOR[5], TEST_VECTOR[6]
            };

            // Since the previous fiducial set at index 1 has been removed, the fiducial set at index 2 is now at index
            // 1
            adjusted_test_vector[1].m_fiducial_set_index = 1;
            adjusted_test_vector[2].m_fiducial_set_index = 1;
            adjusted_test_vector[3].m_fiducial_set_index = 1;
            adjusted_test_vector[4].m_fiducial_set_index = 1;

            const auto& query_results = fiducials_series->query_fiducials();
            CHECK_EQ(adjusted_test_vector.size(), query_results.size());

            for(std::size_t index = 0 ; index < query_results.size() ; ++index)
            {
                compare_queries(adjusted_test_vector[index], query_results[index]);
            }
        }

        // Test removing all point fiducials (only group 2 should be kept)
        {
            auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

            // Fill the created fiducials series with some fiducials
            for(const auto& query_result : TEST_VECTOR)
            {
                fiducials_series->add_fiducial(
                    [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    _result = query_result;
                    return true;
                },
                    *query_result.m_shape,
                    *query_result.m_group_name
                );
            }

            // Remove all point fiducials
            const auto& removed_results = fiducials_series->remove_fiducials(
                std::nullopt,
                sight::data::fiducials_series::shape::point
            );

            CHECK_EQ(std::size_t(5), removed_results.first.size());

            std::array adjusted_test_vector {
                TEST_VECTOR[0], TEST_VECTOR[1], TEST_VECTOR[2], TEST_VECTOR[3], TEST_VECTOR[6]
            };

            for(std::size_t index = 0 ; index < removed_results.first.size() ; ++index)
            {
                compare_queries(adjusted_test_vector[index], removed_results.first[index]);
            }

            CHECK(sight::core::is_equal(std::set<std::string> {"group_0", "group_1"}, removed_results.second));

            // Query again and check the remaining fiducials
            std::array remaining_test_vector {
                TEST_VECTOR[4], TEST_VECTOR[5]
            };

            // Since the previous fiducial has been removed, set the fiducial set at index 0
            remaining_test_vector[0].m_fiducial_set_index = 0;
            remaining_test_vector[0].m_fiducial_index     = 0;
            remaining_test_vector[1].m_fiducial_set_index = 0;
            remaining_test_vector[1].m_fiducial_index     = 1;

            const auto& query_results = fiducials_series->query_fiducials();
            CHECK_EQ(remaining_test_vector.size(), query_results.size());

            for(std::size_t index = 0 ; index < query_results.size() ; ++index)
            {
                compare_queries(remaining_test_vector[index], query_results[index]);
            }
        }

        // Test executing the predicate on all point fiducials of group 2
        {
            auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

            // Fill the created fiducials series with some fiducials
            for(const auto& query_result : TEST_VECTOR)
            {
                fiducials_series->add_fiducial(
                    [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
                {
                    _result = query_result;
                    return true;
                },
                    *query_result.m_shape,
                    *query_result.m_group_name
                );
            }

            // Remove the point fiducial from group 2 with the given fiducial_uid
            const auto& removed_results = fiducials_series->remove_fiducials(
                [](const sight::data::fiducials_series::query_result& _result) -> bool
            {
                return _result.m_fiducial_uid == TEST_VECTOR[6].m_fiducial_uid;
            },
                sight::data::fiducials_series::shape::point,
                "group_2"
            );

            CHECK_EQ(std::size_t(1), removed_results.first.size());

            compare_queries(TEST_VECTOR[6], removed_results.first[0]);

            CHECK(sight::core::is_equal(std::set<std::string> {}, removed_results.second));

            // Query again and check the remaining fiducials
            const auto& query_results = fiducials_series->query_fiducials();
            CHECK_EQ(TEST_VECTOR.size() - 1, query_results.size());

            for(std::size_t index = 0 ; index < query_results.size() ; ++index)
            {
                compare_queries(TEST_VECTOR[index], query_results[index]);
            }
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("modify_fiducials")
    {
        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

        // Fill the created fiducials series with some fiducials
        for(const auto& query_result : TEST_VECTOR)
        {
            fiducials_series->add_fiducial(
                [&query_result](sight::data::fiducials_series::query_result& _result) -> bool
            {
                _result = query_result;
                return true;
            },
                *query_result.m_shape,
                *query_result.m_group_name
            );
        }

        // Modify the contour data of the point fiducial from group 2 at index 1
        fiducials_series->modify_fiducials(
            [](sight::data::fiducials_series::query_result& _result) -> bool
        {
            _result.m_contour_data = {6.F, 6.F, 6.F};
            return true;
        },
            sight::data::fiducials_series::shape::point,
            "group_2",
            1
        );

        // Check the fiducials
        {
            const auto& query_results = fiducials_series->query_fiducials();
            CHECK_EQ(TEST_VECTOR.size(), query_results.size());

            auto adjusted_test_vector = TEST_VECTOR;
            adjusted_test_vector[6].m_contour_data = {6.F, 6.F, 6.F};

            for(std::size_t index = 0 ; index < query_results.size() ; ++index)
            {
                compare_queries(adjusted_test_vector[index], query_results[index]);
            }
        }
    }

//------------------------------------------------------------------------------

    TEST_CASE("point")
    {
        auto fiducials_series = std::make_shared<sight::data::fiducials_series>();

        const std::string group_name {sight::core::tools::uuid::generate()};

        // Should fail, because the group does not exists
        CHECK(!fiducials_series->add_point(group_name, {0., 0., 0.}));

        // Should now succeed
        fiducials_series->add_group(group_name, {1.0, 1.0, 1.0, 1.0}, 1.F);
        CHECK(fiducials_series->add_point(group_name, {0., 0., 0.}));
        CHECK(fiducials_series->add_point(group_name, {1., 1., 1.}));

        // Count the number of points
        std::vector<sight::data::fiducials_series::query_result> query_results;
        query_results = fiducials_series->query_fiducials(
            std::nullopt,
            sight::data::fiducials_series::shape::point
        );
        CHECK_EQ(static_cast<std::size_t>(2), query_results.size());

        // Remove a point and count again
        fiducials_series->remove_point(group_name, 1);
        query_results = fiducials_series->query_fiducials(
            std::nullopt,
            sight::data::fiducials_series::shape::point
        );
        CHECK_EQ(static_cast<std::size_t>(1), query_results.size());
    }
} // TEST_SUITE("sight::data::fiducials_series")
