#include "catch.hpp"

// mapnik
#include <mapnik/value.hpp>
#include <mapnik/image_any.hpp>
#include <mapnik/color.hpp>
#include <mapnik/image_filter.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/image_filter_types.hpp>
// stl
#include <sstream>
#include <array>

TEST_CASE("image filter")
{
    SECTION("test bad filter input")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        REQUIRE_THROWS(mapnik::filter::filter_image(im, "foo,asdfasdf()"));
        REQUIRE_THROWS(mapnik::filter::filter_image(im, "colorize-alpha("));
        REQUIRE_THROWS(mapnik::filter::filter_image(im, "color-to-alpha(blue"));
        REQUIRE_THROWS(mapnik::filter::filter_image(im, "color-to-alpha(,blue)"));
        REQUIRE_THROWS(mapnik::filter::filter_image(im, "colorize-alpha()"));

        mapnik::image_rgba8 const& im2 = im;
        REQUIRE_THROWS(mapnik::filter::filter_image(im2, "foo"));

        CHECK(im(0, 0) == 0xffff0000);
        CHECK(im(0, 1) == 0xffff0000);
        CHECK(im(0, 2) == 0xffff0000);
        CHECK(im(1, 0) == 0xffff0000);
        CHECK(im(1, 1) == 0xff0000ff);
        CHECK(im(1, 2) == 0xffff0000);
        CHECK(im(2, 0) == 0xffff0000);
        CHECK(im(2, 1) == 0xffff0000);
        CHECK(im(2, 2) == 0xffff0000);

    } // END SECTION

    SECTION("test blur")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "blur");

        CHECK(im(0, 0) == 0xffc60038);
        CHECK(im(0, 1) == 0xffe2001c);
        CHECK(im(0, 2) == 0xffc60038);
        CHECK(im(1, 0) == 0xffc60038);
        CHECK(im(1, 1) == 0xffe2001c);
        CHECK(im(1, 2) == 0xffc60038);
        CHECK(im(2, 0) == 0xffc60038);
        CHECK(im(2, 1) == 0xffe2001c);
        CHECK(im(2, 2) == 0xffc60038);

    } // END SECTION

    SECTION("test blur constant")
    {
        mapnik::image_rgba8 im_orig(3, 3);
        mapnik::fill(im_orig, mapnik::color("blue"));
        mapnik::set_pixel(im_orig, 1, 1, mapnik::color("red"));

        mapnik::image_rgba8 const& im_new = im_orig;
        mapnik::image_rgba8 im = mapnik::filter::filter_image(im_new, "blur");

        CHECK(im(0, 0) == 0xffc60038);
        CHECK(im(0, 1) == 0xffe2001c);
        CHECK(im(0, 2) == 0xffc60038);
        CHECK(im(1, 0) == 0xffc60038);
        CHECK(im(1, 1) == 0xffe2001c);
        CHECK(im(1, 2) == 0xffc60038);
        CHECK(im(2, 0) == 0xffc60038);
        CHECK(im(2, 1) == 0xffe2001c);
        CHECK(im(2, 2) == 0xffc60038);

    } // END SECTION

    SECTION("test gray")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "gray");

        CHECK(im(0, 0) == 0xff1c1c1c);
        CHECK(im(0, 1) == 0xff1c1c1c);
        CHECK(im(0, 2) == 0xff1c1c1c);
        CHECK(im(1, 0) == 0xff1c1c1c);
        CHECK(im(1, 1) == 0xff4c4c4c);
        CHECK(im(1, 2) == 0xff1c1c1c);
        CHECK(im(2, 0) == 0xff1c1c1c);
        CHECK(im(2, 1) == 0xff1c1c1c);
        CHECK(im(2, 2) == 0xff1c1c1c);

    } // END SECTION

    SECTION("test agg stack blur")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "agg-stack-blur(1,1)");

        CHECK(im(0, 0) == 0xffef000f);
        CHECK(im(0, 1) == 0xffdf001f);
        CHECK(im(0, 2) == 0xffef000f);
        CHECK(im(1, 0) == 0xffdf001f);
        CHECK(im(1, 1) == 0xffbf003f);
        CHECK(im(1, 2) == 0xffdf001f);
        CHECK(im(2, 0) == 0xffef000f);
        CHECK(im(2, 1) == 0xffdf001f);
        CHECK(im(2, 2) == 0xffef000f);

    } // END SECTION

    SECTION("test scale-hsla 1")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "scale-hsla(0.0,0.5,0.0,1.0,0.0,0.5,0.0,0.5)");

        CHECK(im(0, 0) == 0x80004000);
        CHECK(im(0, 1) == 0x80004000);
        CHECK(im(0, 2) == 0x80004000);
        CHECK(im(1, 0) == 0x80004000);
        CHECK(im(1, 1) == 0x80000040);
        CHECK(im(1, 2) == 0x80004000);
        CHECK(im(2, 0) == 0x80004000);
        CHECK(im(2, 1) == 0x80004000);
        CHECK(im(2, 2) == 0x80004000);

    } // END SECTION

    SECTION("test scale-hsla 2")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::set_pixel(im, 0, 0, mapnik::color(255, 0, 0));
        mapnik::set_pixel(im, 0, 1, mapnik::color(147, 112, 219));
        mapnik::set_pixel(im, 0, 2, mapnik::color(128, 128, 128));
        mapnik::set_pixel(im, 1, 0, mapnik::color(72, 209, 204));
        mapnik::set_pixel(im, 1, 1, mapnik::color(218, 112, 214));
        mapnik::set_pixel(im, 1, 2, mapnik::color(30, 144, 255));
        mapnik::set_pixel(im, 2, 0, mapnik::color(238, 130, 238));
        mapnik::set_pixel(im, 2, 1, mapnik::color(154, 205, 50));
        mapnik::set_pixel(im, 2, 2, mapnik::color(160, 82, 45));

        // Should not throw on values out of [0, 1]
        // https://github.com/mapnik/mapnik/issues/3052
        REQUIRE_NOTHROW(mapnik::filter::filter_image(im, "scale-hsla(0.0,1.5,-1.0,1.0,-1.0,2.0,1.0,1.0)"));

        CHECK(im(0, 0) == 0xff0000ff);
        CHECK(im(0, 1) == 0xffefeff4);
        CHECK(im(0, 2) == 0xff818181);
        CHECK(im(1, 0) == 0xffb895a5);
        CHECK(im(1, 1) == 0xffededf3);
        CHECK(im(1, 2) == 0xffd75aff);
        CHECK(im(2, 0) == 0xffffffff);
        CHECK(im(2, 1) == 0xff649b64);
        CHECK(im(2, 2) == 0xff2e343b);

    } // END SECTION

    SECTION("test emboss")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("white"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("orange"));

        mapnik::filter::filter_image(im, "emboss");

        CHECK(im(0, 0) == 0xff004bff);
        CHECK(im(0, 1) == 0xff00a5ff);
        CHECK(im(0, 2) == 0xff004bff);
        CHECK(im(1, 0) == 0xffffffff);
        CHECK(im(1, 1) == 0xff00a5ff);
        CHECK(im(1, 2) == 0xffffffff);
        CHECK(im(2, 0) == 0xffffffff);
        CHECK(im(2, 1) == 0xffffffff);
        CHECK(im(2, 2) == 0xffffffff);

    } // END SECTION

    SECTION("test sharpen")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "sharpen");

        CHECK(im(0, 0) == 0xffff0000);
        CHECK(im(0, 1) == 0xffff0000);
        CHECK(im(0, 2) == 0xffff0000);
        CHECK(im(1, 0) == 0xffff0000);
        CHECK(im(1, 1) == 0xff00ffff);
        CHECK(im(1, 2) == 0xffff0000);
        CHECK(im(2, 0) == 0xffff0000);
        CHECK(im(2, 1) == 0xffff0000);
        CHECK(im(2, 2) == 0xffff0000);

    } // END SECTION

    SECTION("test edge detect")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "edge-detect");

        CHECK(im(0, 0) == 0xff000000);
        CHECK(im(0, 1) == 0xff008080);
        CHECK(im(0, 2) == 0xff000000);
        CHECK(im(1, 0) == 0xff00ffff);
        CHECK(im(1, 1) == 0xffff0000);
        CHECK(im(1, 2) == 0xff00ffff);
        CHECK(im(2, 0) == 0xff000000);
        CHECK(im(2, 1) == 0xff008080);
        CHECK(im(2, 2) == 0xff000000);

    } // END SECTION

    SECTION("test sobel")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "sobel");

        CHECK(im(0, 0) == 0xfffeffff);
        CHECK(im(0, 1) == 0xfffeffff);
        CHECK(im(0, 2) == 0xfffeffff);
        CHECK(im(1, 0) == 0xff000000);
        CHECK(im(1, 1) == 0xff000000);
        CHECK(im(1, 2) == 0xff000000);
        CHECK(im(2, 0) == 0xfffeffff);
        CHECK(im(2, 1) == 0xfffeffff);
        CHECK(im(2, 2) == 0xfffeffff);

    } // END SECTION

    SECTION("test x-gradient")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "x-gradient");

        CHECK(im(0, 0) == 0xff808080);
        CHECK(im(0, 1) == 0xffbf4040);
        CHECK(im(0, 2) == 0xff808080);
        CHECK(im(1, 0) == 0xff808080);
        CHECK(im(1, 1) == 0xff808080);
        CHECK(im(1, 2) == 0xff808080);
        CHECK(im(2, 0) == 0xff808080);
        CHECK(im(2, 1) == 0xff41c0c0);
        CHECK(im(2, 2) == 0xff808080);

    } // END SECTION

    SECTION("test y-gradient")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "y-gradient");

        CHECK(im(0, 0) == 0xff808080);
        CHECK(im(0, 1) == 0xff808080);
        CHECK(im(0, 2) == 0xff808080);
        CHECK(im(1, 0) == 0xffbf4040);
        CHECK(im(1, 1) == 0xff808080);
        CHECK(im(1, 2) == 0xff41c0c0);
        CHECK(im(2, 0) == 0xff808080);
        CHECK(im(2, 1) == 0xff808080);
        CHECK(im(2, 2) == 0xff808080);

    } // END SECTION

    SECTION("test invert")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "invert");

        CHECK(im(0, 0) == 0xff00ffff);
        CHECK(im(0, 1) == 0xff00ffff);
        CHECK(im(0, 2) == 0xff00ffff);
        CHECK(im(1, 0) == 0xff00ffff);
        CHECK(im(1, 1) == 0xff7f7f7f);
        CHECK(im(1, 2) == 0xff00ffff);
        CHECK(im(2, 0) == 0xff00ffff);
        CHECK(im(2, 1) == 0xff00ffff);
        CHECK(im(2, 2) == 0xff00ffff);

    } // END SECTION

    SECTION("test colorize-alpha - one color")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "colorize-alpha(blue)");

        CHECK(im(0, 0) == 0xffff0000);
        CHECK(im(0, 1) == 0xffff0000);
        CHECK(im(0, 2) == 0xffff0000);
        CHECK(im(1, 0) == 0xffff0000);
        CHECK(im(1, 1) == 0xffff0000);
        CHECK(im(1, 2) == 0xffff0000);
        CHECK(im(2, 0) == 0xffff0000);
        CHECK(im(2, 1) == 0xffff0000);
        CHECK(im(2, 2) == 0xffff0000);

    } // END SECTION

    SECTION("test colorize-alpha - two color")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("gray"));

        mapnik::filter::filter_image(im, "colorize-alpha(green,blue)");

        CHECK(im(0, 0) == 0xfffd0000);
        CHECK(im(0, 1) == 0xfffd0000);
        CHECK(im(0, 2) == 0xfffd0000);
        CHECK(im(1, 0) == 0xfffd0000);
        CHECK(im(1, 1) == 0xfffd0000);
        CHECK(im(1, 2) == 0xfffd0000);
        CHECK(im(2, 0) == 0xfffd0000);
        CHECK(im(2, 1) == 0xfffd0000);
        CHECK(im(2, 2) == 0xfffd0000);

    } // END SECTION

    SECTION("test colorize-alpha - one color with transparency")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("#0000ffaa"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("#aaaaaaaa"));

        mapnik::filter::filter_image(im, "colorize-alpha(#0000ff99)");

        CHECK(im(0, 0) == 0x66660000);
        CHECK(im(0, 1) == 0x66660000);
        CHECK(im(0, 2) == 0x66660000);
        CHECK(im(1, 0) == 0x66660000);
        CHECK(im(1, 1) == 0x66660000);
        CHECK(im(1, 2) == 0x66660000);
        CHECK(im(2, 0) == 0x66660000);
        CHECK(im(2, 1) == 0x66660000);
        CHECK(im(2, 2) == 0x66660000);

    } // END SECTION

    SECTION("test colorize-alpha - two color with transparency")
    {
        mapnik::image_rgba8 im(3, 3);
        mapnik::fill(im, mapnik::color("#0000ffaa"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("#aaaaaaaa"));

        mapnik::filter::filter_image(im, "colorize-alpha(#0000ff00,#00ff00ff)");

        CHECK(im(0, 0) == 0x70264a00);
        CHECK(im(0, 1) == 0x70264a00);
        CHECK(im(0, 2) == 0x70264a00);
        CHECK(im(1, 0) == 0x70264a00);
        CHECK(im(1, 1) == 0x70264a00);
        CHECK(im(1, 2) == 0x70264a00);
        CHECK(im(2, 0) == 0x70264a00);
        CHECK(im(2, 1) == 0x70264a00);
        CHECK(im(2, 2) == 0x70264a00);

    } // END SECTION

    SECTION("test parsing image-filters")
    {
        std::string str = ""; // empty string
        std::vector<mapnik::filter::filter_type> filters;
        CHECK(parse_image_filters(str, filters));
        CHECK(filters.size() == 0);

        std::array<std::string, 17> expected = {{"emboss",
                                                 "emboss",
                                                 "blur",
                                                 "gray",
                                                 "edge-detect",
                                                 "sobel",
                                                 "sharpen",
                                                 "x-gradient",
                                                 "y-gradient",
                                                 "invert",
                                                 "color-blind-protanope",
                                                 "color-blind-deuteranope",
                                                 "color-blind-tritanope",
                                                 "agg-stack-blur(1,1)",
                                                 "agg-stack-blur(1,1)",
                                                 "agg-stack-blur(2,2)",
                                                 "agg-stack-blur(2,3)"}};

        str += "emboss emboss() blur,gray ,edge-detect, sobel , , sharpen,,,x-gradient y-gradientinvert";
        str += "color-blind-protanope color-blind-deuteranope color-blind-tritanope "
               "agg-stack-blur,agg-stack-blur(),agg-stack-blur(2),agg-stack-blur(2,3)";
        CHECK(parse_image_filters(str, filters));
        CHECK(filters.size() == expected.size());
        std::size_t count = 0;
        for (auto const& filter : filters)
        {
            std::stringstream ss;
            ss << filter;
            CHECK(expected[count++] == ss.str());
        }
    }

    SECTION("test colorize-alpha - parsing correct input")
    {
        std::string s("colorize-alpha(#0000ff 0%, #00ff00 100%)");
        std::vector<mapnik::filter::filter_type> f;
        REQUIRE(parse_image_filters(s, f));
        mapnik::filter::colorize_alpha const& ca = mapnik::util::get<mapnik::filter::colorize_alpha>(f.front());
        CHECK(ca.size() == 2);

        CHECKED_IF(ca.size() > 0)
        {
            mapnik::filter::color_stop const& s2 = ca[0];
            CHECK(s2.color.alpha() == 0xff);
            CHECK(s2.color.red() == 0x00);
            CHECK(s2.color.green() == 0x00);
            CHECK(s2.color.blue() == 0xff);
            CHECK(s2.offset == 0.0);
        }

        CHECKED_IF(ca.size() > 1)
        {
            mapnik::filter::color_stop const& s2 = ca[1];
            CHECK(s2.color.alpha() == 0xff);
            CHECK(s2.color.red() == 0x00);
            CHECK(s2.color.green() == 0xff);
            CHECK(s2.color.blue() == 0x00);
            CHECK(s2.offset == 1.0);
        }

    } // END SECTION

    SECTION("test colorize-alpha - parsing incorrect input")
    {
        std::string s("colorize-alpha(#0000ff 0%, #00ff00 00 100%)");
        std::vector<mapnik::filter::filter_type> f;
        CHECK(!parse_image_filters(s, f));
        CHECK(f.empty());

    } // END SECTION

    SECTION("test color-blind-protanope")
    {
        mapnik::image_rgba8 im(2, 2);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 0, 1, mapnik::color("green"));
        mapnik::set_pixel(im, 1, 0, mapnik::color("yellow"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "color-blind-protanope");

        CHECK(im(0, 0) == 0xff9a4a00);
        CHECK(im(0, 1) == 0xff006e7c);
        CHECK(im(1, 0) == 0xff00f7ff);
        CHECK(im(1, 1) == 0xff1d7e8e);

    } // END SECTION

    SECTION("test color-blind-deuteranope")
    {
        mapnik::image_rgba8 im(2, 2);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 0, 1, mapnik::color("green"));
        mapnik::set_pixel(im, 1, 0, mapnik::color("yellow"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "color-blind-deuteranope");

        CHECK(im(0, 0) == 0xff824f00);
        CHECK(im(0, 1) == 0xff1c688b);
        CHECK(im(1, 0) == 0xff27e9ff);
        CHECK(im(1, 1) == 0xff0077a0);

    } // END SECTION

    SECTION("test color-blind-tritanope")
    {
        mapnik::image_rgba8 im(2, 2);
        mapnik::fill(im, mapnik::color("blue"));
        mapnik::set_pixel(im, 0, 1, mapnik::color("green"));
        mapnik::set_pixel(im, 1, 0, mapnik::color("yellow"));
        mapnik::set_pixel(im, 1, 1, mapnik::color("red"));

        mapnik::filter::filter_image(im, "color-blind-tritanope");

        CHECK(im(0, 0) == 0xff595500);
        CHECK(im(0, 1) == 0xff80763a);
        CHECK(im(1, 0) == 0xfffeecff);
        CHECK(im(1, 1) == 0xff0017fd);

    } // END SECTION

} // END TEST CASE
