/************************************************************************
 *
 * Copyright (C) 2009-2026 IRCAD France
 * Copyright (C) 2012-2020 IHU Strasbourg
 *
 * 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/memory/stream/in/raw.hpp>
#include <core/profiling.hpp>
#include <core/tools/system.hpp>

#include <data/image.hpp>
#include <data/iterator.hpp>
#include <data/reconstruction.hpp>

#include <utest_data/generator/image.hpp>

#include <doctest/doctest.h>

#include <algorithm>
#include <exception>
#include <iostream>
#include <map>
#include <ostream>
#include <vector>

TEST_SUITE("sight::data::image")
{
//------------------------------------------------------------------------------

    TEST_CASE("getter_setter")
    {
        const sight::data::image::spacing_t spacing         = {2.5, 2.6};
        const sight::data::image::origin_t origin           = {2.7, 2.8};
        const sight::data::image::orientation_t orientation = {0.36, 0.48, -0.8, -0.8, 0.6, 0.0, 0.48, 0.64, 0.6};
        const double windowcenter                           = 10.10;
        const double windowwidth                            = 11.34;

        // process
        sight::data::image::sptr img1 = std::make_shared<sight::data::image>();

        img1->set_spacing(spacing);
        img1->set_origin(origin);
        img1->set_orientation(orientation);
        img1->set_window_center({windowcenter});
        img1->set_window_width({windowwidth});

        // check
        CHECK(img1->spacing() == spacing);
        CHECK(img1->origin() == origin);
        CHECK(img1->orientation() == orientation);
        CHECK_EQ(windowcenter, img1->window_center().front());
        CHECK_EQ(windowwidth, img1->window_width().front());
    }

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

    TEST_CASE("allocation")
    {
        const sight::core::type type              = sight::core::type::INT16;
        const sight::data::image::size_t img_size = {14, 15, 26};
        const std::size_t nb_elts                 = 14LL * 15 * 26;
        const std::size_t size                    = nb_elts * type.size();

        // process
        sight::data::image::sptr img1 = std::make_shared<sight::data::image>();

        CHECK_EQ(static_cast<std::size_t>(0), img1->num_elements());

        img1->resize(img_size, type, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(size, img1->size_in_bytes());
        CHECK_EQ(nb_elts, img1->num_elements());
        CHECK(img1->type() == type);

        sight::data::image::sptr img3 = std::make_shared<sight::data::image>();
        img3->resize(img_size, type, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(size, img3->size_in_bytes());
        CHECK(img1->type() == type);
        CHECK_EQ(sight::data::image::pixel_format_t::gray_scale, img3->pixel_format());
    }

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

    TEST_CASE("reallocation")
    {
        const sight::core::type typ_e1              = sight::core::type::INT16;
        const sight::core::type typ_e2              = sight::core::type::INT64;
        const sight::core::type typ_e3              = sight::core::type::UINT8;
        const sight::data::image::size_t img_siz_e1 = {10, 10, 10};
        const sight::data::image::size_t img_siz_e2 = {20, 20, 20};
        const sight::data::image::size_t img_siz_e3 = {5, 5, 5};
        const sight::data::image::size_t img_siz_e5 = {0, 0, 0};
        const sight::data::image::size_t img_siz_e6 = {42, 20};
        const std::size_t siz_e1                    = 10LL * 10 * 10 * typ_e1.size();
        const std::size_t siz_e2                    = 20LL * 20 * 20 * typ_e2.size();
        const std::size_t siz_e3                    = 5LL * 5 * 5 * typ_e3.size();
        const std::size_t siz_e4                    = 5LL * 5 * 5 * 4 * typ_e3.size();
        const std::size_t siz_e5                    = 0;
        const std::size_t siz_e6                    = 42LL * 20;
        const std::size_t siz_e7                    = 42LL * 20 * 3;

        // process
        sight::data::image::sptr img1 = std::make_shared<sight::data::image>();

        const std::size_t resized1 = img1->resize(img_siz_e1, typ_e1, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(resized1, img1->size_in_bytes());
        CHECK_EQ(siz_e1, img1->size_in_bytes());

        const std::size_t resized2 = img1->resize(img_siz_e2, typ_e2, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(resized2, img1->size_in_bytes());
        CHECK_EQ(siz_e2, img1->size_in_bytes());

        const std::size_t resized3 = img1->resize(img_siz_e3, typ_e3, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(resized3, img1->size_in_bytes());
        CHECK_EQ(siz_e3, img1->size_in_bytes());

        const std::size_t resized4 = img1->resize(img_siz_e3, typ_e3, sight::data::image::pixel_format_t::rgba);
        CHECK_EQ(resized4, img1->size_in_bytes());
        CHECK_EQ(siz_e4, img1->size_in_bytes());

        const std::size_t resized5 = img1->resize(img_siz_e5, typ_e3, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(resized5, img1->size_in_bytes());
        CHECK_EQ(siz_e5, img1->size_in_bytes());

        const std::size_t resized6 = img1->resize(img_siz_e6, typ_e3, sight::data::image::pixel_format_t::gray_scale);
        CHECK_EQ(resized6, img1->size_in_bytes());
        CHECK_EQ(siz_e6, img1->size_in_bytes());

        const std::size_t resized7 = img1->resize(img_siz_e6, typ_e3, sight::data::image::pixel_format_t::rgb);
        CHECK_EQ(resized7, img1->size_in_bytes());
        CHECK_EQ(siz_e7, img1->size_in_bytes());
    }

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

    TEST_CASE("set_get_pixel")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::INT16;
        const sight::data::image::size_t size = {10, 20, 30};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::gray_scale);

        CHECK_EQ(size[0] * size[1] * size[2] * 2, allocated_size);

        const auto lock     = img->dump_lock();
        auto iter           = img->begin<std::int16_t>();
        const auto iter_end = img->end<std::int16_t>();

        // test 1 : get pixel value
        std::int16_t count = 0;
        for( ; iter != iter_end ; ++iter)
        {
            *iter = count++;
        }

        sight::data::image::csptr const_img = img;
        for(std::size_t x = 0 ; x < size[0] ; ++x)
        {
            for(std::size_t y = 0 ; y < size[1] ; ++y)
            {
                for(std::size_t z = 0 ; z < size[2] ; ++z)
                {
                    const sight::data::image::index_t index = x + y * size[0] + z * size[0] * size[1];
                    const auto val                          = static_cast<std::int16_t>(index);
                    CHECK_EQ(val, img->at<std::int16_t>(x, y, z));
                    CHECK_EQ(val, const_img->at<std::int16_t>(x, y, z));
                    CHECK_EQ(val, img->at<std::int16_t>(index));
                    CHECK_EQ(val, const_img->at<std::int16_t>(index));
                    CHECK_EQ(val, *reinterpret_cast<std::int16_t*>(img->get_pixel(index)));
                    CHECK_EQ(val, *reinterpret_cast<const std::int16_t*>(const_img->get_pixel(index)));

                    std::stringstream ss;
                    ss << val;
                    CHECK_EQ(ss.str(), img->get_pixel_as_string(x, y, z));
                }
            }
        }

        // test 2 : set pixel value
        for(std::size_t x = 0 ; x < size[0] ; ++x)
        {
            for(std::size_t y = 0 ; y < size[1] ; ++y)
            {
                for(std::size_t z = 0 ; z < size[2] ; ++z)
                {
                    const auto index = x + y * size[0] + z * size[0] * size[1];

                    if(index % 2 == 0)
                    {
                        const auto val = static_cast<std::int16_t>(index * 2);
                        img->at<std::int16_t>(index) = val;
                    }
                    else
                    {
                        auto val = static_cast<std::int16_t>(index * 2);
                        img->set_pixel(index, reinterpret_cast<sight::data::image::buffer_t*>(&val));
                    }
                }
            }
        }

        count = 0;
        auto iter2       = img->begin<std::int16_t>();
        auto const_iter2 = const_img->begin<std::int16_t>();
        for( ; iter2 != iter_end ; ++iter2, ++const_iter2)
        {
            CHECK_EQ(static_cast<std::int16_t>(count * 2), *iter2);
            CHECK_EQ(static_cast<std::int16_t>(count++ * 2), *const_iter2);
        }

        sight::data::image::csptr img2 = sight::data::image::copy(img);

        const auto lock2 = img2->dump_lock();
        {
            auto iter_img2           = img2->begin<std::int16_t>();
            auto iter_img1           = img->begin<std::int16_t>();
            const auto iter_img2_end = img2->end<std::int16_t>();

            for( ; iter_img2 != iter_img2_end ; ++iter_img2, ++iter_img1)
            {
                CHECK_EQ(*iter_img1, *iter_img2);
            }
        }
        std::fill(img->begin(), img->end(), std::int8_t(0));

        for(const auto& element : *img)
        {
            CHECK_EQ(static_cast<char>(0), element);
        }

        auto iter3 = img->begin<std::int16_t>();
        for( ; iter3 != iter_end ; ++iter3)
        {
            CHECK_EQ(static_cast<std::int16_t>(0), *iter3);
        }

        std::copy(img2->begin(), img2->end(), img->begin());

        {
            auto iter_img1           = img->begin<std::int16_t>();
            auto iter_img2           = img2->begin<std::int16_t>();
            const auto iter_img2_end = img2->end<std::int16_t>();

            std::size_t i = 0;
            for( ; iter_img2 != iter_img2_end ; ++iter_img2, ++iter_img1)
            {
                ++i;
                CHECK_MESSAGE(*iter_img2 == *iter_img1, "iteration: ", std::to_string(i));
            }
        }
    }

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

    TEST_CASE("set_get_pixel_rgba")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::UINT8;
        const sight::data::image::size_t size = {6, 5, 3};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::rgba);

        CHECK_EQ(size[0] * size[1] * size[2] * 4, allocated_size);

        const auto lock     = img->dump_lock();
        auto iter           = img->begin<std::uint8_t>();
        const auto iter_end = img->end<std::uint8_t>();

        // test 1 : get pixel value
        std::uint8_t count = 0;
        for( ; iter != iter_end ; ++iter)
        {
            *iter = count++;
        }

        sight::data::image::csptr const_img = img;
        for(std::size_t x = 0 ; x < size[0] ; ++x)
        {
            for(std::size_t y = 0 ; y < size[1] ; ++y)
            {
                for(std::size_t z = 0 ; z < size[2] ; ++z)
                {
                    for(std::size_t c = 0 ; c < img->num_components() ; ++c)
                    {
                        const sight::data::image::index_t index = c + 4 * (x + y * size[0] + z * size[0] * size[1]);
                        const auto val                          = static_cast<std::uint8_t>(index);
                        CHECK_EQ(val, img->at<std::uint8_t>(x, y, z, c));
                        CHECK_EQ(val, const_img->at<std::uint8_t>(x, y, z, c));
                    }

                    const sight::data::image::index_t index = x + y * size[0] + z * size[0] * size[1];
                    const std::array val                    = {static_cast<std::uint8_t>(4 * index),
                                                               static_cast<std::uint8_t>(4 * index + 1),
                                                               static_cast<std::uint8_t>(4 * index + 2),
                                                               static_cast<std::uint8_t>(4 * index + 3)
                    };
                    CHECK_EQ(val[0], img->at<std::uint8_t>(index));
                    CHECK_EQ(val[0], img->at<sight::data::iterator::rgba>(index).r);
                    CHECK_EQ(val[1], img->at<sight::data::iterator::rgba>(index).g);
                    CHECK_EQ(val[2], img->at<sight::data::iterator::rgba>(index).b);
                    CHECK_EQ(val[3], img->at<sight::data::iterator::rgba>(index).a);
                    CHECK_EQ(val[0], const_img->at<std::uint8_t>(index));
                    CHECK_EQ(val[0], const_img->at<sight::data::iterator::rgba>(index).r);
                    CHECK_EQ(val[1], const_img->at<sight::data::iterator::rgba>(index).g);
                    CHECK_EQ(val[2], const_img->at<sight::data::iterator::rgba>(index).b);
                    CHECK_EQ(val[3], const_img->at<sight::data::iterator::rgba>(index).a);
                    CHECK_EQ(val[0], *reinterpret_cast<std::uint8_t*>(img->get_pixel(index)));
                    CHECK_EQ(val[0], *reinterpret_cast<const std::uint8_t*>(const_img->get_pixel(index)));
                }
            }
        }

        // test 2 : set pixel value
        for(std::size_t z = 0 ; z < size[2] ; ++z)
        {
            for(std::size_t y = 0 ; y < size[1] ; ++y)
            {
                for(std::size_t x = 0 ; x < size[0] ; ++x)
                {
                    const auto index = x + y * size[0] + z * size[0] * size[1];

                    if(index % 2 == 0)
                    {
                        for(std::size_t c = 0 ; c < img->num_components() ; ++c)
                        {
                            const auto val = static_cast<std::uint8_t>((index * 4 + c) * 2);
                            img->at<std::uint8_t>(x, y, z, c) = val;
                        }
                    }
                    else
                    {
                        std::array val = {static_cast<std::uint8_t>(index * 4 * 2),
                                          static_cast<std::uint8_t>((index * 4 + 1) * 2),
                                          static_cast<std::uint8_t>((index * 4 + 2) * 2),
                                          static_cast<std::uint8_t>((index * 4 + 3) * 2)
                        };
                        img->set_pixel(
                            index,
                            reinterpret_cast<sight::data::image::buffer_t*>(&val)
                        );
                    }
                }
            }
        }

        count = 0;
        auto iter2       = img->begin<std::uint8_t>();
        auto const_iter2 = const_img->begin<std::uint8_t>();
        for( ; iter2 != iter_end ; ++iter2, ++const_iter2)
        {
            CHECK_EQ(static_cast<std::uint8_t>(count * 2), *iter2);
            CHECK_EQ(static_cast<std::uint8_t>(count++ * 2), *const_iter2);
        }

        sight::data::image::csptr img2 = sight::data::image::copy(img);

        const auto lock2 = img2->dump_lock();
        {
            auto iter_img2           = img2->begin<sight::data::iterator::rgba>();
            auto iter_img1           = img->begin<sight::data::iterator::rgba>();
            const auto iter_img2_end = img2->end<sight::data::iterator::rgba>();

            for( ; iter_img2 != iter_img2_end ; ++iter_img2, ++iter_img1)
            {
                CHECK_EQ(iter_img1->r, iter_img2->r);
                CHECK_EQ(iter_img1->g, iter_img2->g);
                CHECK_EQ(iter_img1->b, iter_img2->b);
                CHECK_EQ(iter_img1->a, iter_img2->a);
            }
        }
        std::fill(img->begin(), img->end(), std::int8_t(0));

        for(const auto& element : *img)
        {
            CHECK_EQ(static_cast<char>(0), element);
        }

        auto iter3 = img->begin<std::uint8_t>();
        for( ; iter3 != iter_end ; ++iter3)
        {
            CHECK_EQ(static_cast<std::uint8_t>(0), *iter3);
        }

        std::copy(img2->begin(), img2->end(), img->begin());

        {
            auto iter_img1           = img->begin<std::uint8_t>();
            auto iter_img2           = img2->begin<std::uint8_t>();
            const auto iter_img2_end = img2->end<std::uint8_t>();

            std::size_t i = 0;
            for( ; iter_img2 != iter_img2_end ; ++iter_img2, ++iter_img1)
            {
                ++i;
                CHECK_MESSAGE(*iter_img2 == *iter_img1, "iteration: ", std::to_string(i));
            }
        }
    }

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

    TEST_CASE("iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::INT16;
        const sight::data::image::size_t size = {10, 20, 15};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::gray_scale);

        CHECK_EQ(size[0] * size[1] * size[2] * 2, allocated_size);
        CHECK_EQ(size[0] * size[1] * size[2], img->num_elements());

        {
            CHECK_THROWS_AS(img->buffer(), sight::core::exception);
            CHECK_THROWS_AS(img->begin(), sight::core::exception);
        }

        const auto lock = img->dump_lock();
        {
            CHECK_NOTHROW(img->buffer());
            CHECK_NOTHROW(img->begin());

            // check default raw int8 iterator
            auto itr     = img->begin();
            auto itr_end = img->end();

            CHECK_EQ(static_cast<std::ptrdiff_t>(allocated_size), itr_end - itr);
        }

        {
            // check raw int16 iterator
            const auto itr     = img->begin<std::int16_t>();
            const auto itr_end = img->end<std::int16_t>();

            CHECK_EQ(static_cast<std::ptrdiff_t>(img->num_elements()), itr_end - itr);
        }

        const auto allocated_size2 = img->resize(size, type, sight::data::image::pixel_format_t::rgb);

        CHECK_EQ(size[0] * size[1] * size[2] * 3 * 2, allocated_size2);
        CHECK_EQ(size[0] * size[1] * size[2] * 3, img->num_elements());

        {
            // check raw int16 iterator
            const auto itr     = img->begin<std::int16_t>();
            const auto itr_end = img->end<std::int16_t>();

            CHECK_EQ(static_cast<std::ptrdiff_t>(img->num_elements()), itr_end - itr);
        }

        {
            // check RGB int8 iterator
            const auto itr     = img->begin<sight::data::iterator::rgb>();
            const auto itr_end = img->end<sight::data::iterator::rgb>();

            CHECK_EQ(static_cast<std::ptrdiff_t>(img->num_elements() / 3 * 2), itr_end - itr);
        }

        {
            // check Raw int64 iterator
            const auto itr     = img->begin<std::int64_t>();
            const auto itr_end = img->end<std::int64_t>();

            CHECK_EQ(static_cast<std::ptrdiff_t>(img->size_in_bytes() / 8), itr_end - itr);
        }
    }

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

    TEST_CASE("rgb_iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::UINT8;
        const sight::data::image::size_t size = {10, 20, 15};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::rgb);

        CHECK_EQ(size[0] * size[1] * size[2] * 3, allocated_size);

        CHECK_EQ(size[0] * size[1] * size[2] * 3, img->num_elements());

        const auto lock = img->dump_lock();

        const sight::data::iterator::rgb value = {.r = 18, .g = 12, .b = 68};
        std::fill(img->begin<sight::data::iterator::rgb>(), img->end<sight::data::iterator::rgb>(), value);

        std::uint8_t count = 0;

        auto iter_rgb           = img->begin<sight::data::iterator::rgb>();
        const auto iter_end_rgb = img->end<sight::data::iterator::rgb>();

        for( ; iter_rgb != iter_end_rgb ; ++iter_rgb)
        {
            CHECK_MESSAGE(value.r == iter_rgb->r, "buff[", std::to_string(count), "].r");
            CHECK_MESSAGE(value.g == iter_rgb->g, "buff[", std::to_string(count), "].g");
            CHECK_MESSAGE(value.b == iter_rgb->b, "buff[", std::to_string(count), "].b");
            ++count;
        }

        count = 0;
        auto iter_rgb2 = img->begin<sight::data::iterator::rgb>();
        for( ; iter_rgb2 != iter_end_rgb ; ++iter_rgb2)
        {
            iter_rgb2->r = count++;
            iter_rgb2->g = count++;
            iter_rgb2->b = count++;
        }

        count = 0;
        for(auto& rgb3 : img->range<sight::data::iterator::rgb>())
        {
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count) == rgb3.r,
                "buff[",
                std::to_string(count),
                "].r"
            );
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count + 1) == rgb3.g,
                "buff[",
                std::to_string(count),
                "].g"
            );
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count + 2) == rgb3.b,
                "buff[",
                std::to_string(count),
                "].b"
            );
            ++count;
        }

        auto begin_iter_rgb   = img->begin<sight::data::iterator::rgb>();
        auto current_iter_rgb = img->begin<sight::data::iterator::rgb>();

        CHECK_EQ(static_cast<uint8_t>(0), current_iter_rgb->r);
        CHECK_EQ(static_cast<uint8_t>(1), current_iter_rgb->g);
        CHECK_EQ(static_cast<uint8_t>(2), current_iter_rgb->b);

        CHECK_EQ(current_iter_rgb->r, begin_iter_rgb[0].r);
        CHECK_EQ(current_iter_rgb->g, begin_iter_rgb[0].g);
        CHECK_EQ(current_iter_rgb->b, begin_iter_rgb[0].b);

        ++current_iter_rgb;
        CHECK_EQ(static_cast<uint8_t>(3), current_iter_rgb->r);
        CHECK_EQ(static_cast<uint8_t>(4), current_iter_rgb->g);
        CHECK_EQ(static_cast<uint8_t>(5), current_iter_rgb->b);

        CHECK_EQ(current_iter_rgb->r, begin_iter_rgb[1].r);
        CHECK_EQ(current_iter_rgb->g, begin_iter_rgb[1].g);
        CHECK_EQ(current_iter_rgb->b, begin_iter_rgb[1].b);

        ++current_iter_rgb;
        CHECK_EQ(static_cast<uint8_t>(6), current_iter_rgb->r);
        CHECK_EQ(static_cast<uint8_t>(7), current_iter_rgb->g);
        CHECK_EQ(static_cast<uint8_t>(8), current_iter_rgb->b);

        CHECK_EQ(current_iter_rgb->r, begin_iter_rgb[2].r);
        CHECK_EQ(current_iter_rgb->g, begin_iter_rgb[2].g);
        CHECK_EQ(current_iter_rgb->b, begin_iter_rgb[2].b);
    }

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

    TEST_CASE("bgr_iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::UINT8;
        const sight::data::image::size_t size = {10, 20};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::bgr);

        CHECK_EQ(size[0] * size[1] * 3, allocated_size);

        CHECK_EQ(size[0] * size[1] * 3, img->num_elements());

        const auto lock = img->dump_lock();

        auto iter           = img->begin<std::uint8_t>();
        const auto iter_end = img->end<std::uint8_t>();

        std::uint8_t count = 0;
        for( ; iter != iter_end ; ++iter)
        {
            *iter = count++;
        }

        count = 0;

        auto iter2           = img->begin<sight::data::iterator::bgr>();
        const auto iter_end2 = img->end<sight::data::iterator::bgr>();
        for( ; iter2 != iter_end2 ; ++iter2)
        {
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count) == iter2->b,
                "buff[",
                std::to_string(count),
                "].b"
            );
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count + 1) == iter2->g,
                "buff[",
                std::to_string(count),
                "].g"
            );
            CHECK_MESSAGE(
                static_cast<std::uint8_t>(3 * count + 2) == iter2->r,
                "buff[",
                std::to_string(count),
                "].r"
            );
            ++count;
        }
    }

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

    TEST_CASE("bgra_iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::UINT8;
        const sight::data::image::size_t size = {10, 20};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::bgra);

        CHECK_EQ(size[0] * size[1] * 4, allocated_size);

        CHECK_EQ(size[0] * size[1] * 4, img->num_elements());

        const auto lock = img->dump_lock();

        auto iter           = img->begin<std::uint8_t>();
        const auto iter_end = img->end<std::uint8_t>();

        std::uint8_t count = 0;
        for( ; iter != iter_end ; ++iter)
        {
            *iter = count++;
        }

        count = 0;

        auto iter2           = img->begin<sight::data::iterator::bgra>();
        const auto iter_end2 = img->end<sight::data::iterator::bgra>();
        for( ; iter2 != iter_end2 ; ++iter2)
        {
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].b",
                static_cast<std::uint8_t>(4 * count) == iter2->b
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].g",
                static_cast<std::uint8_t>(4 * count + 1) == iter2->g
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].r",
                static_cast<std::uint8_t>(4 * count + 2) == iter2->r
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].a",
                static_cast<std::uint8_t>(4 * count + 3) == iter2->a
            );
            ++count;
        }
    }

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

    TEST_CASE("rgba_iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type          = sight::core::type::UINT16;
        const sight::data::image::size_t size = {10, 20, 15};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::rgba);
        CHECK_EQ(size[0] * size[1] * size[2] * 4 * 2, allocated_size);
        CHECK_EQ(size[0] * size[1] * size[2] * 4, img->num_elements());

        const auto lock = img->dump_lock();

        auto iter           = img->begin<sight::data::iterator::rgba16>();
        const auto iter_end = img->end<sight::data::iterator::rgba16>();

        std::uint16_t count = 0;
        std::for_each(
            iter,
            iter_end,
            [&count](auto& _x)
        {
            _x.r = count++;
            _x.g = count++;
            _x.b = count++;
            _x.a = count++;
        });

        auto iter2           = img->begin<std::uint16_t>();
        const auto iter_end2 = img->end<std::uint16_t>();
        for(count = 0 ; iter2 != iter_end2 ; ++iter2)
        {
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].v",
                count == *iter2
            );
            ++count;
        }

        auto iter3           = img->begin<sight::data::iterator::rgba16>();
        const auto iter3_end = img->end<sight::data::iterator::rgba16>();

        for(count = 0 ; iter3 != iter3_end ; ++iter3)
        {
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].r",
                static_cast<std::uint16_t>(4 * count),
                (*iter3).r
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].g",
                static_cast<std::uint16_t>(4 * count + 1),
                (*iter3).g
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].b",
                static_cast<std::uint16_t>(4 * count + 2),
                (*iter3).b
            );
            CHECK_MESSAGE(
                "buff[",
                std::to_string(count),
                "].a",
                static_cast<std::uint16_t>(4 * count + 3),
                (*iter3).a
            );
            ++count;
        }
    }

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

    TEST_CASE("benchmark_iterator")
    {
        sight::data::image::sptr img = std::make_shared<sight::data::image>();

        const sight::core::type type              = sight::core::type::UINT16;
        constexpr sight::data::image::size_t size = {200, 100, 100};

        const auto allocated_size = img->resize(size, type, sight::data::image::pixel_format_t::rgba);
        {
            const auto img_dump_lock = img->dump_lock();
            sight::utest_data::generator::image::randomize_image(img);
        }

        CHECK_EQ(size[0] * size[1] * size[2] * 4 * type.size(), allocated_size);
        CHECK_EQ(size[0] * size[1] * size[2] * 4, img->num_elements());
        CHECK_EQ(size[0] * size[1] * size[2] * 4 * 2, img->size_in_bytes());

        sight::data::image::sptr img2 = std::make_shared<sight::data::image>();
        img2->copy_information(img);
        img2->resize(img2->size(), img2->type(), img2->pixel_format());

        CHECK_EQ(size[0] * size[1] * size[2] * 4, img2->num_elements());
        CHECK_EQ(size[0] * size[1] * size[2] * 4 * 2, img2->size_in_bytes());

        const auto img_dump_lock  = img->dump_lock();
        const auto img_dump_lock2 = img2->dump_lock();
        auto begin1               = img->begin<sight::data::iterator::rgba16>();
        auto end1                 = img->end<sight::data::iterator::rgba16>();
        auto begin2               = img2->begin<sight::data::iterator::rgba16>();
        auto end2                 = img2->end<sight::data::iterator::rgba16>();

        constexpr auto new_size = size[0] * size[1] * size[2];
        std::vector<sight::data::iterator::rgba16> std_vector_image;
        std::vector<sight::data::iterator::rgba16> std_vector_image2;

        std_vector_image.resize(new_size);
        std_vector_image2.resize(new_size);
        {
            std::copy(
                img->begin<sight::data::iterator::rgba16>(),
                img->end<sight::data::iterator::rgba16>(),
                std_vector_image.begin()
            );
        }

        CHECK_EQ(new_size, std_vector_image.size());
        CHECK_EQ(new_size, std_vector_image2.size());

        // Here the std::vector will always be faster than our iterator because the STL internally optimizes the loop
        // iteration with a memmove instead. We can't achieve this on our own, we don't have access to std::__niter
        {
            FW_PROFILE("std::copy - ImageIterator");
            std::copy(begin1, end1, begin2);
        }
        {
            FW_PROFILE("std::copy - std::vector");
            std::ranges::copy(std_vector_image, std_vector_image2.begin());
        }

        // Here is the real test, we expect to have comparable timings with the STL when have a real loop on both sides
        auto fn = [](auto& _x){_x.r = _x.r + 2; _x.g = _x.g + 8; _x.a = _x.a + 2; _x.b = _x.b + 7;};
        {
            FW_PROFILE("std::for_each - ImageIterator");
            std::for_each(begin2, end2, fn);
        }
        {
            FW_PROFILE("std::for_each - std::vector");
            std::ranges::for_each(std_vector_image, fn);
        }

        // Here is the real test, we expect to have comparable timings with the STL when have a real loop on both sides

        auto begin3 = img2->begin<sight::data::iterator::rgba16>();
        auto end3   = img2->end<sight::data::iterator::rgba16>();

        auto fn2 = [](auto& _pix){_pix.r = _pix.r + 2; _pix.g = _pix.g + 8; _pix.a = _pix.a + 2; _pix.b = _pix.b + 7;};
        {
            FW_PROFILE("std::for_each - image_iterator");
            std::for_each(begin3, end3, fn2);
        }

        // {
        //     FW_PROFILE("std::fill - image_iterator");
        //     sight::data::RGBA16 zero = {0, 0, 0, 0};
        //     std::fill(begin3, end3, zero);
        // }

        auto begin5 = img->cbegin<sight::data::iterator::rgba16>();
        auto end5   = img->cend<sight::data::iterator::rgba16>();

        {
            FW_PROFILE("std::copy - image_iterator");
            std::copy(begin5, end5, begin3);
        }
    }

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

    TEST_CASE("image_deep_copy")
    {
        {
            const sight::data::image::sptr img = std::make_shared<sight::data::image>();
            const sight::data::image::size_t size {32, 32, 32};
            const sight::data::image::origin_t origin {0.2, 123.4, 999.666};
            const sight::data::image::orientation_t orientation {0.36, 0.48, -0.8, -0.8, 0.6, 0.0, 0.48, 0.64, 0.6};
            const sight::data::image::spacing_t spacing {0.6, 0.6, 1.8};
            const auto type   = sight::core::type::UINT8;
            const auto format = sight::data::image::pixel_format_t::rgb;

            sight::utest_data::generator::image::generate_image(
                img,
                size,
                spacing,
                origin,
                orientation,
                type,
                format,
                0
            );

            const sight::data::image::sptr img_copy = std::make_shared<sight::data::image>();

            // Lock the imgCopy buffer to make sure the underlying array isn't deleted.
            // Attempting to delete a locked array raises an assert in
            // `sight::core::memory::buffer_manager::unregisterBufferImpl()`.
            const auto img_copy_lock = img_copy->dump_lock();

            // Test a bit more image equality operator, which ensure the copy test is really working
            sight::utest_data::generator::image::generate_image(
                img_copy,
                size,
                spacing,
                origin,
                orientation,
                type,
                format,
                1
            );

            CHECK(*img != *img_copy);

            img_copy->deep_copy(img);

            CHECK(*img == *img_copy);
        }

        {
            const sight::data::image::sptr img = std::make_shared<sight::data::image>();
            const sight::data::image::size_t size {156, 126, 0};
            const sight::data::image::origin_t origin {1., 1., 0.};
            const sight::data::image::orientation_t orientation {0.36, 0.48, -0.8, -0.8, 0.6, 0.0, 0.48, 0.64, 0.6};
            const sight::data::image::spacing_t spacing {10., 10., 0.};
            const auto type   = sight::core::type::FLOAT;
            const auto format = sight::data::image::pixel_format_t::gray_scale;

            sight::utest_data::generator::image::generate_image(
                img,
                size,
                spacing,
                origin,
                orientation,
                type,
                format,
                0
            );

            const sight::data::image::sptr img_copy = std::make_shared<sight::data::image>();

            CHECK(*img != *img_copy);

            const auto img_copy_lock = img_copy->dump_lock();

            // Test a bit more image equality operator, which ensure the copy test is really working
            sight::utest_data::generator::image::generate_image(
                img_copy,
                size,
                spacing,
                origin,
                orientation,
                type,
                format,
                1
            );

            CHECK(*img != *img_copy);

            img_copy->deep_copy(img);

            CHECK(*img == *img_copy);
        }
    }

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

    TEST_CASE("empty_iterator")
    {
        sight::data::image::sptr image = std::make_shared<sight::data::image>();
        sight::utest_data::generator::image::generate_random_image(image, sight::core::type::INT16);

        const auto dump_lock = image->dump_lock();

        auto iter      = image->begin<std::int16_t>();
        const auto end = image->end<std::int16_t>();

        sight::data::image::iterator<std::int16_t> max_iter;

        std::int16_t max_value = *iter;

        for( ; iter != end ; ++iter)
        {
            if(*iter > max_value)
            {
                max_iter  = iter;
                max_value = *iter;
            }
        }

        CHECK_EQ(*max_iter, max_value);
    }

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

    TEST_CASE("equality")
    {
        static constexpr std::array s_WHITE_PIXEL {std::uint8_t(0), std::uint8_t(0), std::uint8_t(0)};
        static constexpr std::array s_BLACK_PIXEL {std::uint8_t(255), std::uint8_t(255), std::uint8_t(255)};
        auto image1 = std::make_shared<sight::data::image>();
        auto image2 = std::make_shared<sight::data::image>();

        CHECK(*image1 == *image2);
        CHECK(!(*image1 != *image2));

        // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    #define TEST(op) \
            image1->op; \
            CHECK_MESSAGE( \
                *image1 != *image2, \
                "Images should be different when applying " #op " to the first one" \
            ); \
            CHECK_MESSAGE( \
                !(*image1 == *image2), \
                "Images should be different when applying " #op " to the first one" \
            ); \
            image2->op; \
            CHECK_MESSAGE( \
                *image1 == *image2, \
                "Images should be equal when applying " #op " to both" \
            ); \
            CHECK_MESSAGE( \
                !(*image1 != *image2), \
                "Images should be equal when applying " #op " to both" \
            );

        TEST(set_spacing({1, 2, 3}));
        TEST(set_origin({4, 5, 6}));
        TEST(set_orientation({0.36, 0.48, -0.8, -0.8, 0.6, 0.0, 0.48, 0.64, 0.6}));
        TEST(set_window_center({7, 8, 9}));
        TEST(set_window_width({10, 11, 12}));
        image1->resize({1, 0, 0}, sight::core::type::UINT8, sight::data::image::pixel_format_t::rgb);
        CHECK(*image1 != *image2);
        CHECK(!(*image1 == *image2));
        image2->resize({1, 0, 0}, sight::core::type::UINT8, sight::data::image::pixel_format_t::rgb);
        auto lock1 = image1->dump_lock();
        auto lock2 = image2->dump_lock();
        image1->set_pixel(0, s_WHITE_PIXEL.data());
        image2->set_pixel(0, s_WHITE_PIXEL.data());
        TEST(resize({1, 1, 0}, sight::core::type::UINT8, sight::data::image::pixel_format_t::rgb));
        TEST(resize({1, 1, 1}, sight::core::type::UINT8, sight::data::image::pixel_format_t::rgb));
        TEST(resize({0, 0, 0}, sight::core::type::INT8, sight::data::image::pixel_format_t::rgb));
        TEST(resize({0, 0, 0}, sight::core::type::UINT8, sight::data::image::pixel_format_t::bgr));
        TEST(set_pixel(0, s_BLACK_PIXEL.data()));

    #undef TEST
    }
} // TEST_SUITE("sight::data::image")
