
#include "catch.hpp"

// mapnik
#include <mapnik/image.hpp>
#include <mapnik/image_view.hpp>
#include <mapnik/image_any.hpp>
#include <mapnik/color.hpp>
#include <mapnik/image_util.hpp>

TEST_CASE("image class")
{
    SECTION("test gray16")
    {
        const mapnik::image_gray16 im(4, 4);
        mapnik::image_gray16 im2(im);
        mapnik::image_gray16 im3(5, 5);

        CHECK(im == im);
        CHECK_FALSE(im == im2);
        CHECK_FALSE(im2 == im3);
        CHECK(im < im3);
        CHECK_FALSE(im < im2);

        // Check that width is correct
        CHECK(im.width() == 4);
        CHECK(im2.width() == 4);

        // Check that height is correct
        CHECK(im.height() == 4);
        CHECK(im2.height() == 4);

        CHECK(im(0, 0) == 0);
        CHECK(im2(0, 0) == 0);
        im2(0, 0) = 1;
        CHECK(im2(0, 0) == 1);
        im2.set(514);
        CHECK(im2(0, 0) == 514);
        CHECK(im2(1, 1) == 514);

        // Check that size is correct
        CHECK(im.size() == 32);
        CHECK(im2.size() == 32);

        // Check that row_size is correct
        CHECK(im.row_size() == 8);
        CHECK(im2.row_size() == 8);

        // Check that get_premultiplied is correct
        CHECK_FALSE(im.get_premultiplied());
        CHECK_FALSE(im2.get_premultiplied());

        // Check that set premultiplied works
        im2.set_premultiplied(true);
        CHECK(im2.get_premultiplied());

        // Check that painted is correct
        CHECK_FALSE(im.painted());
        CHECK_FALSE(im2.painted());

        // Check that set premultiplied works
        im2.painted(true);
        CHECK(im2.painted());

        // Check that offset is correct
        CHECK(im.get_offset() == 0.0);
        CHECK(im2.get_offset() == 0.0);

        // Check that set offset works
        im2.set_offset(2.3);
        CHECK(im2.get_offset() == 2.3);

        // Check that scaling is correct
        CHECK(im.get_scaling() == 1.0);
        CHECK(im2.get_scaling() == 1.0);

        // Check that set scaling works
        im2.set_scaling(1.1);
        CHECK(im2.get_scaling() == 1.1);

        // CHECK that image dtype is correct
        CHECK(im.get_dtype() == mapnik::image_dtype_gray16);
        CHECK(im2.get_dtype() == mapnik::image_dtype_gray16);

        using pixel_type = mapnik::image_view_gray16::pixel_type;
        pixel_type expected_val;
        // Check that all data in the view is correct
        // IM
        expected_val = 0;
        pixel_type const* data_im = im.data();
        CHECK(*data_im == expected_val);
        unsigned char const* data_b = im.bytes();
        CHECK(*data_b == 0);
        for (std::size_t y = 0; y < im.height(); ++y)
        {
            std::size_t width = im.width();
            pixel_type const* data_1 = im.get_row(y);
            pixel_type const* data_2 = im.get_row(y, 1);
            for (std::size_t x = 0; x < width; ++x)
            {
                CHECK(*data_1 == expected_val);
                ++data_1;
            }
            for (std::size_t x = 1; x < width; ++x)
            {
                CHECK(*data_2 == expected_val);
                ++data_2;
            }
        }
        // IM2
        expected_val = 514;
        pixel_type* data_im2 = im2.data();
        CHECK(*data_im2 == expected_val);
        unsigned char* data_b2 = im2.bytes();
        CHECK(*data_b2 == 2);
        ++data_b;
        CHECK(*data_b2 == 2);
        for (std::size_t y = 0; y < im2.height(); ++y)
        {
            std::size_t width = im2.width();
            pixel_type const* data_1 = im2.get_row(y);
            pixel_type const* data_2 = im2.get_row(y, 1);
            for (std::size_t x = 0; x < width; ++x)
            {
                CHECK(*data_1 == expected_val);
                ++data_1;
            }
            for (std::size_t x = 1; x < width; ++x)
            {
                CHECK(*data_2 == expected_val);
                ++data_2;
            }
        }

        // Test set row
        std::vector<pixel_type> v1(im2.width(), 30);
        std::vector<pixel_type> v2(im2.width() - 1, 50);
        im2.set_row(0, v1.data(), v1.size());
        im2.set_row(1, 1, v2.size(), v2.data());

        CHECK(im2(0, 0) == 30);
        CHECK(im2(0, 1) == 514);
        CHECK(im2(1, 1) == 50);

    } // END SECTION

    SECTION("image_null")
    {
        mapnik::image_null im_null;
        const mapnik::image_null im_null2(2, 2); // Actually doesn't really set any size
        mapnik::image_null im_null3(im_null2);
        mapnik::image_null im_null4(std::move(im_null3));

        // All nulls are equal
        CHECK(im_null == im_null4);
        CHECK(im_null == im_null2);

        // No null is greater
        CHECK_FALSE(im_null < im_null4);
        CHECK_FALSE(im_null < im_null2);

        // Check defaults
        CHECK(im_null.width() == 0);
        CHECK(im_null.height() == 0);
        CHECK(im_null.size() == 0);
        CHECK(im_null.row_size() == 0);
        // Setting offset does nothing
        im_null.set_offset(10000000.0);
        CHECK(im_null.get_offset() == 0.0);
        // Setting scaling does nothing
        im_null.set_scaling(123123123.0);
        CHECK(im_null.get_scaling() == 1.0);
        CHECK(im_null.get_dtype() == mapnik::image_dtype_null);
        // Setting premultiplied does nothing
        im_null.set_premultiplied(true);
        CHECK_FALSE(im_null.get_premultiplied());
        // Setting painted does nothing
        im_null.painted(true);
        CHECK_FALSE(im_null.painted());

        // Should throw if we try to access or setdata.
        REQUIRE_THROWS(im_null(0, 0));
        REQUIRE_THROWS(im_null2(0, 0));
        REQUIRE_THROWS(im_null(0, 0) = 1);

        unsigned char const* e1 = im_null.bytes();
        unsigned char* e2 = im_null.bytes();
        CHECK(e1 == nullptr);
        CHECK(e2 == nullptr);

    } // END SECTION

    SECTION("image any")
    {
        mapnik::image_null null_im;
        const mapnik::image_any im_any_null(null_im);
        CHECK(im_any_null.get_dtype() == mapnik::image_dtype_null);
        CHECK(im_any_null.bytes() == nullptr);

        mapnik::image_gray16 im(4, 4);
        mapnik::fill(im, 514);
        mapnik::image_any im_any(im);

        CHECK(im_any.get_dtype() == mapnik::image_dtype_gray16);
        unsigned char* foo = im_any.bytes();
        CHECK(*foo == 2);
        ++foo;
        CHECK(*foo == 2);
        CHECK(im_any.width() == 4);
        CHECK(im_any.height() == 4);
        CHECK(im_any.size() == 32);
        CHECK(im_any.row_size() == 8);
        CHECK_FALSE(im_any.get_premultiplied());
        im_any.set_offset(10.0);
        CHECK(im_any.get_offset() == 10.0);
        im_any.set_scaling(2.1);
        CHECK(im_any.get_scaling() == 2.1);
        CHECK_FALSE(im_any.painted());

    } // END SECTION

    SECTION("test image_any initialization")
    {
        {
            mapnik::image_any im(4, 4);
            CHECK(im.get_dtype() == mapnik::image_dtype_rgba8);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_null);
            CHECK(im.get_dtype() == mapnik::image_dtype_null);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray8);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray8);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray8s);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray8s);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray16);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray16);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray16s);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray16s);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray32);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray32);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray32s);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray32s);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray32f);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray32f);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray64);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray64);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray64s);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray64s);
        }
        {
            mapnik::image_any im(4, 4, mapnik::image_dtype_gray64f);
            CHECK(im.get_dtype() == mapnik::image_dtype_gray64f);
        }

    } // END SECTION

    SECTION("Image Buffer")
    {
        mapnik::detail::buffer buf_zero(0);
        CHECK(buf_zero.size() == 0);
        CHECK(!buf_zero);
        mapnik::detail::buffer buf(10);
        CHECK(buf.size() == 10);
        CHECK_FALSE(!buf);
        unsigned char* d = buf.data();
        *d = 9;
        const mapnik::detail::buffer buf2 = buf;
        CHECK(buf2.size() == 10);
        unsigned char const* d2 = buf2.data();
        CHECK(*d2 == 9);

    } // END SECTION

    SECTION("Image copy/move")
    {
        mapnik::detail::buffer buf(16 * 16 * 4); // large enough to hold 16*16 RGBA image
        CHECK(buf.size() == 16 * 16 * 4);
        // fill buffer with 0xff
        std::fill(buf.data(), buf.data() + buf.size(), 0xff);

        // move buffer
        mapnik::detail::buffer buf2(std::move(buf));
        CHECK(buf.size() == 0);
        CHECK(buf.data() == nullptr);

        mapnik::image_rgba8 im(16, 16, buf2.data()); // shallow copy
        std::size_t count = 0;
        for (auto const& pixel : im)
        {
            // expect rgba(255,255,255,1.0)
            CHECK(sizeof(pixel) == sizeof(mapnik::image_rgba8::pixel_type));
            CHECK(pixel == 0xffffffff);
            ++count;
        }
        CHECK(count == im.width() * im.height());
        CHECK(buf2.size() == im.width() * im.height() * sizeof(mapnik::image_rgba8::pixel_type));

        // mutate buffer
        // fill buffer with 0x7f - semi-transparent grey
        std::fill(buf2.data(), buf2.data() + buf2.size(), 0x7f);
        for (auto const& pixel : im)
        {
            // expect rgba(127,127,127,0.5)
            CHECK(pixel == 0x7f7f7f7f);
        }

        // mutate image directly (buf)
        for (auto& pixel : im)
        {
            pixel = mapnik::color(0, 255, 0).rgba(); // green
        }

        // move
        mapnik::image_rgba8 im2(std::move(im));
        CHECK(im.data() == nullptr);
        CHECK(im.bytes() == nullptr);
        CHECK(im.width() == 0);
        CHECK(im.height() == 0);
        for (auto const& pixel : im2)
        {
            // expect `green`
            CHECK(pixel == mapnik::color(0, 255, 0).rgba());
        }

        mapnik::image_rgba8 im3(im2); // shallow copy
        for (auto& pixel : im3)
        {
            // expect `green`
            CHECK(pixel == mapnik::color(0, 255, 0).rgba());
            // mutate
            pixel = mapnik::color(255, 0, 0).rgba(); // red
        }

        for (auto const& pixel : im3)
        {
            // expect `red`
            CHECK(pixel == mapnik::color(255, 0, 0).rgba());
        }
        for (auto const& pixel : im2)
        {
            // expect `red`
            CHECK(pixel == mapnik::color(255, 0, 0).rgba());
        }
    }

    SECTION("image::swap")
    {
        auto blue = mapnik::color(50, 50, 250).rgba();
        auto orange = mapnik::color(250, 150, 0).rgba();

        mapnik::image_rgba8 im;
        mapnik::image_rgba8 im2(16, 16);
        mapnik::image_rgba8 im3(16, 16);

        im2.set(blue);
        im3.set(orange);

        // swap two non-empty images
        CHECK_NOTHROW(im2.swap(im3));
        CHECK(im2(0, 0) == orange);
        CHECK(im3(0, 0) == blue);

        // swap empty <-> non-empty
        CHECK_NOTHROW(im.swap(im3));
        CHECK(im3.data() == nullptr);
        CHECKED_IF(im.data() != nullptr)
        {
            CHECK(im(0, 0) == blue);
        }
    }

} // END TEST CASE
