#ifndef FLEMU_ADDER_HPP
#define FLEMU_ADDER_HPP

#include "float32.hpp"

#include <boost/ut.hpp>

#include <random>
#include <tuple>

namespace flemu
{

inline float32 add(const float32& x_, const float32& y_) noexcept
{
    // ------------------------------------------------------------------------
    // always make x <= y
    const auto [x, y] = [&]
    {
        if(x_.exponent() == y_.exponent())
        {
            if(x_.mantissa() < y_.mantissa())
            {
                return std::make_pair(x_, y_);
            }
            else
            {
                return std::make_pair(y_, x_);
            }
        }
        else
        {
            if(x_.exponent() < y_.exponent())
            {
                return std::make_pair(x_, y_);
            }
            else
            {
                return std::make_pair(y_, x_);
            }
        }
    }();

    const std::uint32_t xsgn(x.sign());
    const std::uint32_t xexp(x.exponent());
    const std::uint32_t xman(x.mantissa());

    const std::uint32_t ysgn(y.sign());
    const std::uint32_t yexp(y.exponent());
    const std::uint32_t yman(y.mantissa());

    // ------------------------------------------------------------------------
    // check special values
    const auto xinf  = (xexp == 0b1111'1111) && (xman == 0);
    const auto yinf  = (yexp == 0b1111'1111) && (yman == 0);
    const auto xnan  = (xexp == 0b1111'1111) && (xman != 0);
    const auto ynan  = (yexp == 0b1111'1111) && (yman != 0);
    const auto xzero = (xexp == 0) && (xman == 0);
    const auto yzero = (yexp == 0) && (yman == 0);
    const auto xdenorm = (xexp == 0) && (xman != 0);
    const auto ydenorm = (yexp == 0) && (yman != 0);

    if(xnan || ynan) // z + nan == nan,  nan + z == nan
    {
        return float32(0b0, 0b1111'1111, 0b1);
    }
    else if (xinf || yinf)
    {
        if(xinf && yinf)
        {
            if(xsgn != ysgn) // inf - inf == nan, -inf + inf == nan
            {
                return float32(0b0, 0b1111'1111, 0b1);
            }
            else // inf + inf == inf, -inf + (-inf) = -inf
            {
                return float32(std::uint32_t(xsgn), 0b1111'1111, 0b0);
            }
        }
        else if((xinf && xsgn == 0) || (yinf && ysgn == 0)) // inf + * == inf
        {
            return float32(0b0, 0b1111'1111, 0b0);
        }
        else  // -inf + * == -inf
        {
            return float32(0b1, 0b1111'1111, 0b0);
        }
    }
    else if (xzero || yzero)
    {
        if(xzero && yzero)
        {
            // we here consider only nearest-(even)-rounding.
            // in case of negative-inf-rounding, (+0) + (-0) should be (-0).
            return float32(0);
        }
        else if (xzero)
        {
            return y;
        }
        else // yzero
        {
            return x;
        }
    }

    // ------------------------------------------------------------------------
    // align mantissa (always x.exp <= y.exp)

    const auto [xman_aligned, yman_aligned, xexp_norm, yexp_norm] = [&]
    {
        std::uint32_t xexp_norm = xexp;
        std::uint32_t yexp_norm = yexp;
        std::uint32_t xman_aligned = (1 << 23);
        std::uint32_t yman_aligned = (1 << 23);

        if(xdenorm)
        {
            xman_aligned = 0; // remove implicit 1
            xexp_norm   += 1;
        }
        if(ydenorm)
        {
            yman_aligned = 0;
            yexp_norm   += 1;
        }

        xman_aligned += std::uint32_t(xman);
        yman_aligned += std::uint32_t(yman);
        xman_aligned <<= 3;
        yman_aligned <<= 3;

        if(xexp_norm == yexp_norm)
        {
            return std::make_tuple(xman_aligned, yman_aligned, xexp_norm, yexp_norm);
        }
        // exponent is different
        const std::uint32_t expdiff = std::uint32_t(yexp_norm) - std::uint32_t(xexp_norm);

        //         mantissa      additional bits
        //    .---------------. .---.
        // y:| 1.xxxxxxxxxxxxxx|0|0|0|
        // x:     | 1.xxxxxxxxx|x|x|x|x|x|0|0|0| >> expdiff == e.g. 5
        //                      | | | '-------'
        //                      | | |  sticky region
        //                      | | + sticky bit
        //                      | + round bit
        //                      + guard bit

        if(expdiff >= 27) // 1 + 23 + 3
        {
            xman_aligned = 0;
        }
        else
        {
            std::uint32_t sticky = 0;
            if(expdiff > 3)
            {
                const auto sticky_region = xman_aligned & mask<std::uint32_t>(expdiff - 1, 0);
                sticky = (sticky_region == 0) ? 0 : 1;
            }
            xman_aligned >>= expdiff;
            xman_aligned |= sticky;
        }
        return std::make_tuple(xman_aligned, yman_aligned, xexp_norm, yexp_norm);
    }();

//     std::cerr << "xman_aligned = " << as_bit(xman_aligned) << std::endl;
//     std::cerr << "yman_aligned = " << as_bit(yman_aligned) << std::endl;

    // ------------------------------------------------------------------------
    // add/sub mantissa and round

    //           26 25       22 ...  03 02 01 00
    // y: | 0...| 1| z| z| z| z|... | z| 0| 0| 0|
    // x: | 0...| 0| 0| 0| 1| z|... | z| z| z| z|
    //             '-------------------' |  |  +- sticky
    //                   mantissa        |  +---- round
    //                                   +------- guard

    std::uint32_t zsgn(ysgn);
    std::uint32_t zexp(yexp_norm);
    if(xsgn != ysgn) // subtract. always abs(x) < abs(y), so the sign is y.
    {
        std::uint32_t zman = yman_aligned - xman_aligned;

        if(zman == 0)
        {
            // zero cannot be normalized
            return float32(zsgn, 0u, 0u);
        }

        while(bit_at(zman, 26) == 0)
        {
            zexp  -= 1;
            if(zexp == 0)
            {
                // since it becomes denormalized number, we don't need to
                // normalize it.
                break;
            }
            zman <<= 1;
        }

        if(zexp == 0)
        {
            // if it is 0.111...111, then it will be 1.00 after rounding and
            // will become normalized.
            if((zman & mask<std::uint32_t>(25, 2)) >> 2 == (1<<24)-1)
            {
                return float32(zsgn, std::uint32_t(1), std::uint32_t(0));
            }
        }

        // consider nearest-even rounding only
        if(bit_at(zman, 2) == 1)
        {
            if(bit_at(zman, 1) == 0 && bit_at(zman, 0) == 0) // to even
            {
                if(bit_at(zman, 3) == 0)
                {
                    // already even. do nothing.
                }
                else // its odd.
                {
                    zman += 0b1000;
                }
            }
            else // to nearest (upper)
            {
                zman += 0b1000;
            }
        }

        // check carry-up by rounding (1.11111 -> 10.0000)
        // 10.0000e+2 == 1.0000e+3
        if(bit_at(zman, 27) == 1)
        {
            zexp  += 1;
            zman >>= 1;
        }
        assert(bit_at(zman, 26) == 1 || zexp == 0); // normalized?

        if(zexp == 0b1111'1111)
        {
            // it was not nan, so here it should be inf
            zman = 0;
        }
        return float32(zsgn, zexp, std::uint32_t(bit_proxy(zman, 25, 3)));
    }
    else // add.
    {
        assert(bit_at(xman_aligned, 27) == 0);
        assert(bit_at(yman_aligned, 27) == 0);
        std::uint32_t zman = yman_aligned + xman_aligned;

        // check carry-up by addition
        if(bit_at(zman, 27) == 1)
        {
            // if we shift before checking round, the sticky bit will be lost.

            if(bit_at(zman, 3) == 1) // need to round up.
            {
                if(bit_at(zman, 2) == 0 && bit_at(zman, 1) == 0 && bit_at(zman, 0) == 0) // to even
                {
                    if(bit_at(zman, 4) == 0)
                    {
                        // already even. do nothing.
                    }
                    else
                    {
                        zman += 0b1'0000;
                    }
                }
                else // to nearest (upper)
                {
                    zman += 0b1'0000;
                }
            }
            if(bit_at(zman, 28) == 1)
            {
                zexp  += 2;
                zman >>= 2;
            }
            else if(bit_at(zman, 27) == 1)
            {
                zexp  += 1;
                zman >>= 1;
            }
            else
            {
                assert(false);
            }
        }
        else
        {
            if(bit_at(zman, 2) == 1) // need to round up.
            {
                if(bit_at(zman, 1) == 0 && bit_at(zman, 0) == 0) // to even
                {
                    if(bit_at(zman, 3) == 0)
                    {
                        // already even. do nothing.
                    }
                    else
                    {
                        zman += 0b1000;
                    }
                }
                else // to nearest (upper)
                {
                    zman += 0b1000;
                }
            }
            // check carry-up by rounding.
            if(bit_at(zman, 27) == 1)
            {
                zexp  += 1;
                zman >>= 1;
            }
        }
        assert(bit_at(zman, 26) == 1); // normalized?

        if(zexp == 0b1111'1111)
        {
            // it was not nan, so here it should be inf
            zman = 0;
        }
        return float32(zsgn, zexp, std::uint32_t(bit_proxy(zman, 25, 3)));
    }
}

#ifdef FLEMU_ACTIVATE_UNIT_TESTS
inline boost::ut::suite tests_adder = []
{
    using namespace boost::ut::literals;

    "add(float32, float32)"_test = []
    {
        const auto x1 = to_flemu(1.0f);
        const auto y1 = to_flemu(1.0f);
        const auto z1 = add(x1, y1);

        boost::ut::expect(to_float(z1) == 2.0f);

//         std::cout << to_float(x1) << " + " << to_float(y1) << " = " << to_float(z1) << " != 2.0f"<< std::endl;
//         std::cout << z1.sign() << "|" << z1.exponent() << "|" << z1.mantissa() << std::endl;
//         std::cout << "========================================================================" << std::endl;

        const auto x2 = to_flemu( 1.0f);
        const auto y2 = to_flemu(10.0f);
        const auto z2 = add(x2, y2);

        boost::ut::expect(to_float(z2) == 11.0f);

//         std::cout << to_float(x2) << " + " << to_float(y2) << " = " << to_float(z2) << " != 11.0f"<< std::endl;
//         std::cout << z2.sign() << "|" << z2.exponent() << "|" << z2.mantissa() << std::endl;
//         std::cout << "========================================================================" << std::endl;

        const auto x3 = to_flemu(1.0e-30f);
        const auto y3 = to_flemu(1.0e+30f);
        const auto z3 = add(x3, y3);

        boost::ut::expect(to_float(z3) == 1.0e+30f);

//         std::cout << to_float(x3) << " + " << to_float(y3) << " = " << to_float(z3) << " != 1.0e+30f"<< std::endl;
//         std::cout << z3.sign() << "|" << z3.exponent() << "|" << z3.mantissa() << std::endl;
//         std::cout << "========================================================================" << std::endl;

        std::mt19937 rng(123456789);

        std::uniform_int_distribution<std::uint32_t> sgn(0,   1);
        std::uniform_int_distribution<std::uint32_t> exp(0, 255); // not including denormalized
        std::uniform_int_distribution<std::uint32_t> man(0, 0x007F'FFFF);

        const std::size_t N = 10000;
        for(std::size_t i=0; i<N; ++i)
        {
            const std::uint32_t xi = (sgn(rng) << 31) + (exp(rng) << 23) + man(rng);
            const std::uint32_t yi = (sgn(rng) << 31) + (exp(rng) << 23) + man(rng);
//             const std::uint32_t xi = (0 << 31) + (exp(rng) << 23) + man(rng);
//             const std::uint32_t yi = (0 << 31) + (exp(rng) << 23) + man(rng);
//             const std::uint32_t xi = 0b1101'1100'0000'1001'0010'1110'0100'0000;
//             const std::uint32_t yi = 0b1101'1010'0011'0100'0101'1100'0010'1001;
//             const std::uint32_t xi = 0b1100'1101'1111'1110'1111'1100'1100'1101;
//             const std::uint32_t yi = 0b1100'1011'0010'1101'1011'0101'0000'0100;

            boost::ut::log << "----------------------------------------------";
            boost::ut::log << "     sxxx'xxxx'xmmm'mmmm'mmmm'mmmm'mmmm'mmmm";
            boost::ut::log << "xr =" << as_bit(xi);
            boost::ut::log << "yr =" << as_bit(yi);

            const float xr = bit_cast<float>(xi);
            const float yr = bit_cast<float>(yi);
            const float zr = xr + yr;

            boost::ut::log << "zr =" << as_bit(bit_cast<std::uint32_t>(zr));

            const auto x = to_flemu(xr);
            const auto y = to_flemu(yr);
            const auto z = add(x, y);

            boost::ut::log << "z  =" << as_bit(z.base());

            if(z.is_nan())
            {
                boost::ut::expect(std::isnan(zr))
                    << "z = " << as_bit(z.base()) << " is NaN but "
                    << "zr = " << as_bit(bit_cast<std::uint32_t>(zr)) << " is not nan";
            }
            else
            {
                boost::ut::expect(to_float(z) == zr)
                    << as_bit(z.base()) << " != " << as_bit(bit_cast<std::uint32_t>(zr));
            }
        }
    };
};
#endif

} // flemu
#endif // FLEMU_ADDER_HPP


#include <scitbx/array_family/boost_python/flex_fwd.h>

#include <boost/python/class.hpp>
#include <scitbx/math/quadrature.h>
#include <scitbx/boost_python/iterator_wrappers.h>

namespace scitbx { namespace math {

namespace {

  struct gauss_legendre_engine_wrappers
  {
    typedef scitbx::math::quadrature::gauss_legendre_engine<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("gauss_legendre_engine",no_init)
        .def(init<int const& > ((arg("n_points"))))
        .def("f", &w_t::f)
        .def("refine", &w_t::refine)
        .def("x", &w_t::x)
        .def("w", &w_t::w)
        ;
    }
  };

  struct gauss_hermite_engine_wrappers
  {
    typedef scitbx::math::quadrature::gauss_hermite_engine<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("gauss_hermite_engine",no_init)
        .def(init<int const& > ((arg("n_points"))))
        .def("f", &w_t::f)
        .def("refine", &w_t::refine)
        .def("x", &w_t::x)
        .def("w", &w_t::w)
        .def("w_exp_x_squared", &w_t::w_exp_x_squared)
        ;
    }
  };

  struct seven_twelve_0120_wrappers
  {
    typedef scitbx::math::quadrature::seven_twelve_0120<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("seven_twelve_0120",no_init)
        .def(init<> () )
        .def("coord", &w_t::coord)
        .def("weight", &w_t::weight)
        ;
    }
  };

  struct five_nine_1001_wrappers
  {
    typedef scitbx::math::quadrature::five_nine_1001<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("five_nine_1001",no_init)
        .def(init<> () )
        .def("coord", &w_t::coord)
        .def("weight", &w_t::weight)
        ;
    }
  };

  struct five_nine_1110_wrappers
  {
    typedef scitbx::math::quadrature::five_nine_1110<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("five_nine_1110",no_init)
        .def(init<> () )
        .def("coord", &w_t::coord)
        .def("weight", &w_t::weight)
        ;
    }
  };

  struct nine_twentyone_1012_wrappers
  {
    typedef scitbx::math::quadrature::nine_twentyone_1012<double> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t>("nine_twentyone_1012",no_init)
        .def(init<> () )
        .def("coord", &w_t::coord)
        .def("weight", &w_t::weight)
        ;
    }
  };

} // namespace <anonymous>

namespace boost_python {

  void wrap_quadrature()
  {
    gauss_legendre_engine_wrappers::wrap();
    gauss_hermite_engine_wrappers::wrap();
    seven_twelve_0120_wrappers::wrap();
    five_nine_1001_wrappers::wrap();
    five_nine_1110_wrappers::wrap();
    nine_twentyone_1012_wrappers::wrap();
  }

}}} // namespace scitbx::math::boost_python


/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2015 Artem Pavlenko
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_IMAGE_FILTER_HPP
#define MAPNIK_IMAGE_FILTER_HPP

//mapnik
#include <mapnik/image_filter_types.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/util/hsl.hpp>

#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/gil/gil_all.hpp>
#pragma GCC diagnostic pop

#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_color_rgba.h"
#include "agg_pixfmt_rgba.h"
#include "agg_scanline_u.h"
#include "agg_blur.h"
#include "agg_gradient_lut.h"
#pragma GCC diagnostic pop

// stl
#include <cmath>

// 8-bit YUV
//Y = ( (  66 * R + 129 * G +  25 * B + 128) >> 8) +  16
//U = ( ( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128
//V = ( ( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128

//bits_type x_gradient = (0.125f*c2 + 0.25f*c5 + 0.125f*c8)
//    - (0.125f*c0 + 0.25f*c3 + 0.125f*c6);
//bits_type y_gradient = (0.125f*c0 + 0.25f*c1 + 0.125f*c2)
//    - (0.125f*c6 + 0.25f*c7 + 0.125f*c8);

// c0 c1 c2
// c3 c4 c5
// c6 c7 c8

//sharpen
//  0 -1  0
// -1  5 -1
//  0 -1  0
//bits_type out_value = -c1 - c3 + 5.0*c4 - c5 - c7;

// edge detect
//  0  1  0
//  1 -4  1
//  0  1  0
//bits_type out_value = c1 + c3 - 4.0*c4 + c5 + c7;

//
//if (out_value < 0) out_value = 0;
//if (out_value > 255) out_value = 255;

// emboss
// -2 -1  0
// -1  1  1
//  0  1  2

// bits_type out_value = -2*c0 - c1 - c3 + c4 + c5 + c7 +  2*c8;

// blur
//float out_value = (0.1f*c0 + 0.1f*c1 + 0.1f*c2 +
//                   0.1f*c3 + 0.1f*c4 + 0.1f*c5 +
//                  0.1f*c6 + 0.1f*c7 + 0.1f*c8);

//float out_value  = std::sqrt(std::pow(x_gradient,2) + std::pow(y_gradient,2));
//float theta = std::atan2(x_gradient,y_gradient);
//if (out_value < 0.0) out_value = 0.0;
//if (out_value < 1.0) out_value = 1.0;

//float conv_matrix[]={1/3.0,1/3.0,1/3.0};

//float gaussian_1[]={0.00022923296f,0.0059770769f,0.060597949f,0.24173197f,0.38292751f,
//                    0.24173197f,0.060597949f,0.0059770769f,0.00022923296f};

//float gaussian_2[]={
//    0.00048869418f,0.0024031631f,0.0092463447f,
//   0.027839607f,0.065602221f,0.12099898f,0.17469721f,
//   0.19744757f,
//   0.17469721f,0.12099898f,0.065602221f,0.027839607f,
//   0.0092463447f,0.0024031631f,0.00048869418f
//};

//  kernel_1d_fixed<float,9> kernel(conv,4);

// color_converted_view<rgb8_pixel_t>(src_view);
//using view_t = kth_channel_view_type< 0, const rgba8_view_t>::type;

//view_t red = kth_channel_view<0>(const_view(src_view));

//kernel_1d_fixed<float,3> kernel(sharpen,0);
//convolve_rows_fixed<rgba32f_pixel_t>(src_view,kernel,src_view);
// convolve_cols_fixed<rgba32f_pixel_t>(src_view,kernel,dst_view);

namespace mapnik {  namespace filter { namespace detail {

static const float blur_matrix[] = {0.1111f,0.1111f,0.1111f,0.1111f,0.1111f,0.1111f,0.1111f,0.1111f,0.1111f};
static const float emboss_matrix[] = {-2,-1,0,-1,1,1,0,1,2};
static const float sharpen_matrix[] = {0,-1,0,-1,5,-1,0,-1,0 };
static const float edge_detect_matrix[] = {0,1,0,1,-4,1,0,1,0 };

}

using boost::gil::rgba8_image_t;
using boost::gil::rgba8_view_t;

template <typename Image>
boost::gil::rgba8_view_t rgba8_view(Image & img)
{
    using boost::gil::interleaved_view;
    using boost::gil::rgba8_pixel_t;
    return interleaved_view(img.width(), img.height(),
                            reinterpret_cast<rgba8_pixel_t*>(img.bytes()),
                            img.width() * sizeof(rgba8_pixel_t));
}

template <typename Image>
struct double_buffer
{
    boost::gil::rgba8_image_t   dst_buffer;
    boost::gil::rgba8_view_t    dst_view;
    boost::gil::rgba8_view_t    src_view;

    explicit double_buffer(Image & src)
        : dst_buffer(src.width(), src.height())
        , dst_view(view(dst_buffer))
        , src_view(rgba8_view(src)) {}

    ~double_buffer()
    {
        copy_pixels(dst_view, src_view);
    }
};

template <typename Src, typename Dst, typename Conv>
void process_channel_impl (Src const& src, Dst & dst, Conv const& k)
{
    using boost::gil::bits32f;

    bits32f out_value =
        k[0]*src[0] + k[1]*src[1] + k[2]*src[2] +
        k[3]*src[3] + k[4]*src[4] + k[5]*src[5] +
        k[6]*src[6] + k[7]*src[7] + k[8]*src[8]
        ;
    if (out_value < 0) out_value = 0;
    if (out_value > 255) out_value = 255;
    dst = out_value;
}

template <typename Src, typename Dst, typename Conv>
void process_channel (Src const&, Dst &, Conv const&)
{
}

template <typename Src, typename Dst>
void process_channel (Src const& src, Dst & dst, mapnik::filter::blur)
{
    process_channel_impl(src,dst,mapnik::filter::detail::blur_matrix);
}

template <typename Src, typename Dst>
void process_channel (Src const& src, Dst & dst, mapnik::filter::emboss)
{
    process_channel_impl(src,dst,mapnik::filter::detail::emboss_matrix);
}

template <typename Src, typename Dst>
void process_channel (Src const& src, Dst & dst, mapnik::filter::sharpen)
{
    process_channel_impl(src,dst,mapnik::filter::detail::sharpen_matrix);
}

template <typename Src, typename Dst>
void process_channel (Src const& src, Dst & dst, mapnik::filter::edge_detect)
{
    process_channel_impl(src,dst,mapnik::filter::detail::edge_detect_matrix);
}

template <typename Src, typename Dst>
void process_channel (Src const& src, Dst & dst, mapnik::filter::sobel)
{
    using boost::gil::bits32f;

    bits32f x_gradient = (src[2] + 2*src[5] + src[8])
        - (src[0] + 2*src[3] + src[6]);

    bits32f y_gradient = (src[0] + 2*src[1] + src[2])
        - (src[6] + 2*src[7] + src[8]);

    bits32f  out_value  = std::sqrt(std::pow(x_gradient,2) + std::pow(y_gradient,2));
    //bts32f theta = std::atan2(x_gradient,y_gradient);
    if (out_value < 0) out_value = 0;
    if (out_value > 255) out_value = 255;
    dst = out_value;
}

template <typename Src, typename Dst, typename Filter>
void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& filter)
{
    using boost::gil::bits32f;
    using boost::gil::point2;

    // p0 p1 p2
    // p3 p4 p5
    // p6 p7 p8

    typename Src::xy_locator src_loc = src_view.xy_at(0,0);
    typename Src::xy_locator::cached_location_t loc00 = src_loc.cache_location(-1,-1);
    typename Src::xy_locator::cached_location_t loc10 = src_loc.cache_location( 0,-1);
    typename Src::xy_locator::cached_location_t loc20 = src_loc.cache_location( 1,-1);
    typename Src::xy_locator::cached_location_t loc01 = src_loc.cache_location(-1, 0);
    typename Src::xy_locator::cached_location_t loc11 = src_loc.cache_location( 0, 0);
    typename Src::xy_locator::cached_location_t loc21 = src_loc.cache_location( 1, 0);
    typename Src::xy_locator::cached_location_t loc02 = src_loc.cache_location(-1, 1);
    typename Src::xy_locator::cached_location_t loc12 = src_loc.cache_location( 0, 1);
    typename Src::xy_locator::cached_location_t loc22 = src_loc.cache_location( 1, 1);

    typename Src::x_iterator dst_it = dst_view.row_begin(0);

    // top row
    for (std::ptrdiff_t x = 0 ; x < src_view.width(); ++x)
    {
        (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a
        for (std::ptrdiff_t i = 0; i < 3; ++i)
        {
            bits32f p[9];

            p[4] = src_loc[loc11][i];
            p[7] = src_loc[loc12][i];

            if (x == 0)
            {
                p[3] = p[4];
                p[6] = p[7];
            }
            else
            {
                p[3] = src_loc[loc01][i];
                p[6] = src_loc[loc02][i];
            }

            if ( x == (src_view.width())-1)
            {
                p[5] = p[4];
                p[8] = p[7];
            }
            else
            {
                p[5] = src_loc[loc21][i];
                p[8] = src_loc[loc22][i];
            }

            p[0] = p[6];
            p[1] = p[7];
            p[2] = p[8];

            process_channel(p, (*dst_it)[i], filter);
        }
        ++src_loc.x();
        ++dst_it;
    }
    // carrige-return
    src_loc += point2<std::ptrdiff_t>(-src_view.width(),1);

    // 1... height-1 rows
    for (std::ptrdiff_t y = 1; y < src_view.height()-1; ++y)
    {
        for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
        {
            (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a
            for (std::ptrdiff_t i = 0; i < 3; ++i)
            {
                bits32f p[9];

                p[1] = src_loc[loc10][i];
                p[4] = src_loc[loc11][i];
                p[7] = src_loc[loc12][i];

                if (x == 0)
                {
                    p[0] = p[1];
                    p[3] = p[4];
                    p[6] = p[7];
                }
                else
                {
                    p[0] = src_loc[loc00][i];
                    p[3] = src_loc[loc01][i];
                    p[6] = src_loc[loc02][i];
                }

                if ( x == (src_view.width()) - 1)
                {
                    p[2] = p[1];
                    p[5] = p[4];
                    p[8] = p[7];
                }
                else
                {
                    p[2] = src_loc[loc20][i];
                    p[5] = src_loc[loc21][i];
                    p[8] = src_loc[loc22][i];
                }
                process_channel(p, (*dst_it)[i], filter);
            }
            ++dst_it;
            ++src_loc.x();
        }
        // carrige-return
        src_loc += point2<std::ptrdiff_t>(-src_view.width(),1);
    }

    // bottom row
    //src_loc = src_view.xy_at(0,src_view.height()-1);
    for (std::ptrdiff_t x = 0 ; x < src_view.width(); ++x)
    {
        (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a
        for (std::ptrdiff_t i = 0; i < 3; ++i)
        {
            bits32f p[9];

            p[1] = src_loc[loc10][i];
            p[4] = src_loc[loc11][i];

            if (x == 0)
            {
                p[0] = p[1];
                p[3] = p[4];
            }
            else
            {
                p[0] = src_loc[loc00][i];
                p[3] = src_loc[loc01][i];
            }

            if ( x == (src_view.width())-1)
            {
                p[2] = p[1];
                p[5] = p[4];

            }
            else
            {
                p[2] = src_loc[loc20][i];
                p[5] = src_loc[loc21][i];
            }

            p[6] = p[0];
            p[7] = p[1];
            p[8] = p[2];
            process_channel(p, (*dst_it)[i], filter);
        }
        ++src_loc.x();
        ++dst_it;
    }
}

template <typename Src, typename Filter>
void apply_filter(Src & src, Filter const& filter, double /*scale_factor*/)
{
    demultiply_alpha(src);
    double_buffer<Src> tb(src);
    apply_convolution_3x3(tb.src_view, tb.dst_view, filter);
}

template <typename Src>
void apply_filter(Src & src, agg_stack_blur const& op, double scale_factor)
{
    premultiply_alpha(src);
    agg::rendering_buffer buf(src.bytes(),src.width(),src.height(), src.row_size());
    agg::pixfmt_rgba32_pre pixf(buf);
    agg::stack_blur_rgba32(pixf, op.rx * scale_factor, op.ry * scale_factor);
}

inline double channel_delta(double source, double match)
{
    if (source > match) return (source - match) / (1.0 - match);
    if (source < match) return (match - source) / match;
    return (source - match);
}

inline uint8_t apply_alpha_shift(double source, double match, double alpha)
{
    source = (((source - match) / alpha) + match) * alpha;
    return static_cast<uint8_t>(std::floor((source*255.0)+.5));
}

template <typename Src>
void apply_filter(Src & src, color_to_alpha const& op, double /*scale_factor*/)
{
    using namespace boost::gil;
    bool premultiplied = src.get_premultiplied();
    rgba8_view_t src_view = rgba8_view(src);
    double cr = static_cast<double>(op.color.red())/255.0;
    double cg = static_cast<double>(op.color.green())/255.0;
    double cb = static_cast<double>(op.color.blue())/255.0;
    for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
    {
        rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
        for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
        {
            uint8_t & r = get_color(src_it[x], red_t());
            uint8_t & g = get_color(src_it[x], green_t());
            uint8_t & b = get_color(src_it[x], blue_t());
            uint8_t & a = get_color(src_it[x], alpha_t());
            double sr = static_cast<double>(r)/255.0;
            double sg = static_cast<double>(g)/255.0;
            double sb = static_cast<double>(b)/255.0;
            double sa = static_cast<double>(a)/255.0;
            // demultiply
            if (sa <= 0.0)
            {
                r = g = b = 0;
                continue;
            }
            else if (premultiplied)
            {
                sr /= sa;
                sg /= sa;
                sb /= sa;
            }
            // get that maximum color difference
            double xa = std::max(channel_delta(sr,cr),std::max(channel_delta(sg,cg),channel_delta(sb,cb)));
            if (xa > 0)
            {
                // apply difference to each channel, returning premultiplied
                // TODO - experiment with difference in hsl color space
                r = apply_alpha_shift(sr,cr,xa);
                g = apply_alpha_shift(sg,cg,xa);
                b = apply_alpha_shift(sb,cb,xa);
                // combine new alpha with original
                xa *= sa;
                a = static_cast<uint8_t>(std::floor((xa*255.0)+.5));
                // all color values must be <= alpha
                if (r>a) r=a;
                if (g>a) g=a;
                if (b>a) b=a;
            }
            else
            {
                r = g = b = a = 0;
            }
        }
    }
    // set as premultiplied
    set_premultiplied_alpha(src, true);
}

template <typename Src>
void apply_filter(Src & src, colorize_alpha const& op, double /*scale_factor*/)
{
    using namespace boost::gil;
    std::ptrdiff_t size = op.size();
    if (op.size() == 1)
    {
        // no interpolation if only one stop
        mapnik::filter::color_stop const& stop = op[0];
        mapnik::color const& c = stop.color;
        rgba8_view_t src_view = rgba8_view(src);
        for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
        {
            rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
            for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
            {
                uint8_t & r = get_color(src_it[x], red_t());
                uint8_t & g = get_color(src_it[x], green_t());
                uint8_t & b = get_color(src_it[x], blue_t());
                uint8_t & a = get_color(src_it[x], alpha_t());
                if ( a > 0)
                {
                    a = (c.alpha() * a + 255) >> 8;
                    r = (c.red() * a + 255) >> 8;
                    g = (c.green() * a + 255) >> 8;
                    b = (c.blue() * a + 255) >> 8;
                }
            }
        }
        // set as premultiplied
        set_premultiplied_alpha(src, true);
    }
    else if (size > 1)
    {
        // interpolate multiple stops
        agg::gradient_lut<agg::color_interpolator<agg::rgba8> > grad_lut;
        double step = 1.0/(size-1);
        double offset = 0.0;
        for ( mapnik::filter::color_stop const& stop : op)
        {
            mapnik::color const& c = stop.color;
            double stop_offset = stop.offset;
            if (stop_offset == 0)
            {
                stop_offset = offset;
            }
            grad_lut.add_color(stop_offset, agg::rgba(c.red()/255.0,
                                                      c.green()/255.0,
                                                      c.blue()/255.0,
                                                      c.alpha()/255.0));
            offset += step;
        }
        if (grad_lut.build_lut())
        {
            rgba8_view_t src_view = rgba8_view(src);
            for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
            {
                rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
                for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
                {
                    uint8_t & r = get_color(src_it[x], red_t());
                    uint8_t & g = get_color(src_it[x], green_t());
                    uint8_t & b = get_color(src_it[x], blue_t());
                    uint8_t & a = get_color(src_it[x], alpha_t());
                    if ( a > 0)
                    {
                        agg::rgba8 c = grad_lut[a];
                        a = (c.a * a + 255) >> 8;
                        r = (c.r * a + 255) >> 8;
                        g = (c.g * a + 255) >> 8;
                        b = (c.b * a + 255) >> 8;
        #if 0
                        // rainbow
                        r = 0;
                        g = 0;
                        b = 0;
                        if (a < 64)
                        {
                            g = a * 4;
                            b = 255;
                        }
                        else if (a >= 64 && a < 128)
                        {
                            g = 255;
                            b = 255 - ((a - 64) * 4);
                        }
                        else if (a >= 128 && a < 192)
                        {
                            r = (a - 128) * 4;
                            g = 255;
                        }
                        else // >= 192
                        {
                            r = 255;
                            g = 255 - ((a - 192) * 4);
                        }
                        r = (r * a + 255) >> 8;
                        g = (g * a + 255) >> 8;
                        b = (b * a + 255) >> 8;
        #endif
                    }
                }
            }
        }
        // set as premultiplied
        set_premultiplied_alpha(src, true);
    }
}

template <typename Src>
void apply_filter(Src & src, scale_hsla const& transform, double /*scale_factor*/)
{
    using namespace boost::gil;
    bool tinting = !transform.is_identity();
    bool set_alpha = !transform.is_alpha_identity();
    // todo - filters be able to report if they
    // should be run to avoid overhead of temp buffer
    if (tinting || set_alpha)
    {
        bool premultiplied = src.get_premultiplied();
        rgba8_view_t src_view = rgba8_view(src);
        for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
        {
            rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
            for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
            {
                uint8_t & r = get_color(src_it[x], red_t());
                uint8_t & g = get_color(src_it[x], green_t());
                uint8_t & b = get_color(src_it[x], blue_t());
                uint8_t & a = get_color(src_it[x], alpha_t());
                double r2 = static_cast<double>(r)/255.0;
                double g2 = static_cast<double>(g)/255.0;
                double b2 = static_cast<double>(b)/255.0;
                double a2 = static_cast<double>(a)/255.0;
                // demultiply
                if (a2 <= 0.0)
                {
                    r = g = b = 0;
                    continue;
                }
                else if (premultiplied)
                {
                    r2 /= a2;
                    g2 /= a2;
                    b2 /= a2;
                }

                if (set_alpha)
                {
                    a2 = transform.a0 + (a2 * (transform.a1 - transform.a0));
                    if (a2 <= 0)
                    {
                        r = g = b = a = 0;
                        continue;
                    }
                    else if (a2 > 1)
                    {
                        a2 = 1;
                        a = 255;
                    }
                    else
                    {
                        a = static_cast<uint8_t>(std::floor((a2 * 255.0) +.5));
                    }
                }
                if (tinting)
                {
                    double h;
                    double s;
                    double l;
                    rgb2hsl(r2,g2,b2,h,s,l);
                    double h2 = transform.h0 + (h * (transform.h1 - transform.h0));
                    double s2 = transform.s0 + (s * (transform.s1 - transform.s0));
                    double l2 = transform.l0 + (l * (transform.l1 - transform.l0));
                    if (h2 > 1) { h2 = 1; }
                    else if (h2 < 0) { h2 = 0; }
                    if (s2 > 1) { s2 = 1; }
                    else if (s2 < 0) { s2 = 0; }
                    if (l2 > 1) { l2 = 1; }
                    else if (l2 < 0) { l2 = 0; }
                    hsl2rgb(h2,s2,l2,r2,g2,b2);
                }
                // premultiply
                r2 *= a2;
                g2 *= a2;
                b2 *= a2;
                r = static_cast<uint8_t>(std::floor((r2*255.0)+.5));
                g = static_cast<uint8_t>(std::floor((g2*255.0)+.5));
                b = static_cast<uint8_t>(std::floor((b2*255.0)+.5));
                // all color values must be <= alpha
                if (r>a) r=a;
                if (g>a) g=a;
                if (b>a) b=a;
            }
        }
        // set as premultiplied
        set_premultiplied_alpha(src, true);
    }
}

template <typename Src, typename ColorBlindFilter>
void color_blind_filter(Src & src, ColorBlindFilter const& op)
{
    using namespace boost::gil;
    rgba8_view_t src_view = rgba8_view(src);
    bool premultiplied = src.get_premultiplied();

    for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
    {
        rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
        for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
        {
            // formula taken from boost/gil/color_convert.hpp:rgb_to_luminance
            uint8_t & r = get_color(src_it[x], red_t());
            uint8_t & g = get_color(src_it[x], green_t());
            uint8_t & b = get_color(src_it[x], blue_t());
            uint8_t & a = get_color(src_it[x], alpha_t());
            double dr = static_cast<double>(r)/255.0;
            double dg = static_cast<double>(g)/255.0;
            double db = static_cast<double>(b)/255.0;
            double da = static_cast<double>(a)/255.0;
            // demultiply
            if (da <= 0.0)
            {
                r = g = b = 0;
                continue;
            }
            else if (premultiplied)
            {
                dr /= da;
                dg /= da;
                db /= da;
            }
            // Convert source color into XYZ color space
            double pow_r = std::pow(dr, 2.2);
            double pow_g = std::pow(dg, 2.2);
            double pow_b = std::pow(db, 2.2);
            double X = (0.412424 * pow_r) + (0.357579 * pow_g) + (0.180464 * pow_b);
            double Y = (0.212656 * pow_r) + (0.715158 * pow_g) + (0.0721856 * pow_b);
            double Z = (0.0193324 * pow_r) + (0.119193 * pow_g) + (0.950444 * pow_b);
            // Convert XYZ into xyY Chromacity Coordinates (xy) and Luminance (Y)
            double chroma_x = X / (X + Y + Z);
            double chroma_y = Y / (X + Y + Z);
            // Generate the "Confusion Line" between the source color and the Confusion Point
            double m_div = chroma_x - op.x;
            if (std::abs(m_div) < (std::numeric_limits<double>::epsilon())) continue;
            double m = (chroma_y - op.y) / (chroma_x - op.x); // slope of Confusion Line
            double yint = chroma_y - chroma_x * m; // y-intercept of confusion line (x-intercept = 0.0)
            // How far the xy coords deviate from the simulation
            double m_div2 = m - op.m;
            if (std::abs(m_div2) < (std::numeric_limits<double>::epsilon())) continue;
            double deviate_x = (op.yint - yint) / (m - op.m);
            double deviate_y = (m * deviate_x) + yint;
            if (std::abs(deviate_y) < (std::numeric_limits<double>::epsilon()))
            {
                deviate_y = std::numeric_limits<double>::epsilon() * 2.0;
            }
            // Compute the simulated color's XYZ coords
            X = deviate_x * Y / deviate_y;
            Z = (1.0 - (deviate_x + deviate_y)) * Y / deviate_y;
            // Neutral grey calculated from luminance (in D65)
            double neutral_X = 0.312713 * Y / 0.329016;
            double neutral_Z = 0.358271 * Y / 0.329016;
            // Difference between simulated color and neutral grey
            double diff_X = neutral_X - X;
            double diff_Z = neutral_Z - Z;
            double diff_r = diff_X * 3.24071 + diff_Z * -0.498571; // XYZ->RGB (sRGB:D65)
            double diff_g = diff_X * -0.969258 + diff_Z * 0.0415557;
            double diff_b = diff_X * 0.0556352 + diff_Z * 1.05707;
            if (std::abs(diff_r) < (std::numeric_limits<double>::epsilon()))
            {
                diff_r = std::numeric_limits<double>::epsilon() * 2.0;
            }
            if (std::abs(diff_g) < (std::numeric_limits<double>::epsilon()))
            {
                diff_g = std::numeric_limits<double>::epsilon() * 2.0;
            }
            if (std::abs(diff_b) < (std::numeric_limits<double>::epsilon()))
            {
                diff_b = std::numeric_limits<double>::epsilon() * 2.0;
            }
            // Convert to RGB color space
            dr = X * 3.24071 + Y * -1.53726 + Z * -0.498571; // XYZ->RGB (sRGB:D65)
            dg = X * -0.969258 + Y * 1.87599 + Z * 0.0415557;
            db = X * 0.0556352 + Y * -0.203996 + Z * 1.05707;
            // Compensate simulated color towards a neutral fit in RGB space
            double fit_r = ((dr < 0.0 ? 0.0 : 1.0) - dr) / diff_r;
            double fit_g = ((dg < 0.0 ? 0.0 : 1.0) - dg) / diff_g;
            double fit_b = ((db < 0.0 ? 0.0 : 1.0) - db) / diff_b;
            double adjust = std::max( (fit_r > 1.0 || fit_r < 0.0) ? 0.0 : fit_r,
                                      (fit_g > 1.0 || fit_g < 0.0) ? 0.0 : fit_g
                                    );
            adjust = std::max((fit_b > 1.0 || fit_b < 0.0) ? 0.0 : fit_b, adjust);
            // Shift proportional to the greatest shift
            dr = dr + (adjust * diff_r);
            dg = dg + (adjust * diff_g);
            db = db + (adjust * diff_b);
            // Apply gamma correction
            dr = std::pow(dr, 1.0 / 2.2);
            dg = std::pow(dg, 1.0 / 2.2);
            db = std::pow(db, 1.0 / 2.2);
            // premultiply
            dr *= da;
            dg *= da;
            db *= da;
            // Clamp values
            if(dr < 0.0)  dr = 0.0;
            if(dr > 1.0) dr = 1.0;
            if(dg < 0.0) dg = 0.0;
            if(dg > 1.0) dg = 1.0;
            if(db < 0.0) db = 0.0;
            if(db > 1.0) db = 1.0;
            r = static_cast<uint8_t>(dr * 255.0);
            g = static_cast<uint8_t>(dg * 255.0);
            b = static_cast<uint8_t>(db * 255.0);
        }
    }
    // set as premultiplied
    set_premultiplied_alpha(src, true);
}

template <typename Src>
void apply_filter(Src & src, color_blind_protanope const& op, double /*scale_factor*/)
{
    color_blind_filter(src, op);
}

template <typename Src>
void apply_filter(Src & src, color_blind_deuteranope const& op, double /*scale_factor*/)
{
    color_blind_filter(src, op);
}

template <typename Src>
void apply_filter(Src & src, color_blind_tritanope const& op, double /*scale_factor*/)
{
    color_blind_filter(src, op);
}

template <typename Src>
void apply_filter(Src & src, gray const& /*op*/, double /*scale_factor*/)
{
    premultiply_alpha(src);
    using namespace boost::gil;

    rgba8_view_t src_view = rgba8_view(src);

    for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
    {
        rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
        for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
        {
            // formula taken from boost/gil/color_convert.hpp:rgb_to_luminance
            uint8_t & r = get_color(src_it[x], red_t());
            uint8_t & g = get_color(src_it[x], green_t());
            uint8_t & b = get_color(src_it[x], blue_t());
            uint8_t   v = uint8_t((4915 * r + 9667 * g + 1802 * b + 8192) >> 14);
            r = g = b = v;
        }
    }
}

template <typename Src, typename Dst>
void x_gradient_impl(Src const& src_view, Dst const& dst_view)
{
    for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
    {
        typename Src::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
        typename Dst::x_iterator dst_it = dst_view.row_begin(static_cast<long>(y));

        dst_it[0][0] = 128 + (src_it[0][0] - src_it[1][0]) / 2;
        dst_it[0][1] = 128 + (src_it[0][1] - src_it[1][1]) / 2;
        dst_it[0][2] = 128 + (src_it[0][2] - src_it[1][2]) / 2;

        dst_it[dst_view.width()-1][0] = 128 + (src_it[(src_view.width())-2][0] - src_it[(src_view.width())-1][0]) / 2;
        dst_it[dst_view.width()-1][1] = 128 + (src_it[(src_view.width())-2][1] - src_it[(src_view.width())-1][1]) / 2;
        dst_it[dst_view.width()-1][2] = 128 + (src_it[(src_view.width())-2][2] - src_it[(src_view.width())-1][2]) / 2;

        dst_it[0][3] = dst_it[(src_view.width())-1][3] = 255;

        for (std::ptrdiff_t x = 1; x < src_view.width()-1; ++x)
        {
            dst_it[x][0] = 128 + (src_it[x-1][0] - src_it[x+1][0]) / 2;
            dst_it[x][1] = 128 + (src_it[x-1][1] - src_it[x+1][1]) / 2;
            dst_it[x][2] = 128 + (src_it[x-1][2] - src_it[x+1][2]) / 2;
            dst_it[x][3] = 255;
        }
    }
}

template <typename Src>
void apply_filter(Src & src, x_gradient const& /*op*/, double /*scale_factor*/)
{
    premultiply_alpha(src);
    double_buffer<Src> tb(src);
    x_gradient_impl(tb.src_view, tb.dst_view);
}

template <typename Src>
void apply_filter(Src & src, y_gradient const& /*op*/, double /*scale_factor*/)
{
    premultiply_alpha(src);
    double_buffer<Src> tb(src);
    x_gradient_impl(rotated90ccw_view(tb.src_view),
                    rotated90ccw_view(tb.dst_view));
}

template <typename Src>
void apply_filter(Src & src, invert const& /*op*/, double /*scale_factor*/)
{
    premultiply_alpha(src);
    using namespace boost::gil;

    rgba8_view_t src_view = rgba8_view(src);

    for (std::ptrdiff_t y = 0; y < src_view.height(); ++y)
    {
        rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast<long>(y));
        for (std::ptrdiff_t x = 0; x < src_view.width(); ++x)
        {
            // we only work with premultiplied source,
            // thus all color values must be <= alpha
            uint8_t   a = get_color(src_it[x], alpha_t());
            uint8_t & r = get_color(src_it[x], red_t());
            uint8_t & g = get_color(src_it[x], green_t());
            uint8_t & b = get_color(src_it[x], blue_t());
            r = a - r;
            g = a - g;
            b = a - b;
        }
    }
}

template <typename Src>
struct filter_visitor
{
    filter_visitor(Src & src, double scale_factor=1.0)
    : src_(src),
      scale_factor_(scale_factor) {}

    template <typename T>
    void operator () (T const& filter) const
    {
        apply_filter(src_, filter, scale_factor_);
    }

    Src & src_;
    double scale_factor_;
};

struct filter_radius_visitor
{
    int & radius_;
    filter_radius_visitor(int & radius)
        : radius_(radius) {}
    template <typename T>
    void operator () (T const& /*filter*/)  const {}

    void operator () (agg_stack_blur const& op) const
    {
        if (static_cast<int>(op.rx) > radius_) radius_ = static_cast<int>(op.rx);
        if (static_cast<int>(op.ry) > radius_) radius_ = static_cast<int>(op.ry);
    }
};

template<typename Src>
void filter_image(Src & src, std::string const& filter, double scale_factor=1)
{
    std::vector<filter_type> filter_vector;
    if(!parse_image_filters(filter, filter_vector))
    {
        throw std::runtime_error("Failed to parse filter argument in filter_image: '" + filter + "'");
    }
    filter_visitor<Src> visitor(src, scale_factor);
    for (filter_type const& filter_tag : filter_vector)
    {
        util::apply_visitor(visitor, filter_tag);
    }
}

template<typename Src>
Src filter_image(Src const& src, std::string const& filter, double scale_factor=1)
{
    std::vector<filter_type> filter_vector;
    if(!parse_image_filters(filter, filter_vector))
    {
        throw std::runtime_error("Failed to parse filter argument in filter_image: '" + filter + "'");
    }
    Src new_src(src);
    filter_visitor<Src> visitor(new_src, scale_factor);
    for (filter_type const& filter_tag : filter_vector)
    {
        util::apply_visitor(visitor, filter_tag);
    }
    return new_src;
}

} // End Namespace Filter

} // End Namespace Mapnik

#endif // MAPNIK_IMAGE_FILTER_HPP


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/complex.h>
#include <pybind11/numpy.h>
#include <boost/lexical_cast.hpp>

#include <assert.h>
#include <vector>

#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/Triangulation_vertex_base_with_info_3.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel            Kernel;
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, Kernel> Vb;
typedef CGAL::Triangulation_data_structure_2<Vb>                       Tds;
typedef CGAL::Delaunay_triangulation_2<Kernel, Tds>                    Delaunay;
typedef Kernel::Point_2 Point;

typedef CGAL::Triangulation_vertex_base_with_info_3<unsigned int, Kernel> Vb3;
typedef CGAL::Triangulation_data_structure_3<Vb3>                       Tds3;
typedef CGAL::Delaunay_triangulation_3<Kernel, Tds3>                    Delaunay3;
typedef Kernel::Point_3 Point3;

std::vector<int> c_delaunay2(std::vector<double> &x, std::vector<double> &y)
{
  int num_points = x.size();
  assert(y.size()!=num_points);
  std::vector< std::pair<Point,unsigned> > points;
  // add index information to form face table later
  for(std::size_t i = 0; i < num_points; ++i)
  {
     points.push_back( std::make_pair( Point(x[i],y[i]), i ) );
  }

  Delaunay triangulation;
  triangulation.insert(points.begin(),points.end());

  // save the face table
  int num_faces = triangulation.number_of_faces();
  std::vector<int> faces;
  faces.resize(num_faces*3);

  int i=0;
  for(Delaunay::Finite_faces_iterator fit = triangulation.finite_faces_begin();
    fit != triangulation.finite_faces_end(); ++fit) {

    Delaunay::Face_handle face = fit;
    faces[i*3]=face->vertex(0)->info();
    faces[i*3+1]=face->vertex(1)->info();
    faces[i*3+2]=face->vertex(2)->info();
    i+=1;
  }
  return faces;
}

std::vector<int> c_delaunay3(std::vector<double> &x, std::vector<double> &y, std::vector<double> &z)
{
  int num_points = x.size();
  assert(y.size()!=num_points);
  assert(z.size()!=num_points);
  std::vector< std::pair<Point3,unsigned> > points;
  // add index information to form face table later
  for(std::size_t i = 0; i < num_points; ++i)
  {
     points.push_back( std::make_pair( Point3(x[i],y[i],z[i]), i ) );
  }
  Delaunay3 triangulation;
  triangulation.insert(points.begin(),points.end());
  // save the indices of all cells
  int num_cells = triangulation.number_of_finite_cells();
  std::vector<int> cells;
  cells.resize(num_cells*4);

  int i=0;
  for(Delaunay3::Finite_cells_iterator cit = triangulation.finite_cells_begin();
    cit != triangulation.finite_cells_end(); ++cit) {

    Delaunay3::Cell_handle cell = cit;
    cells[i*4]=cell->vertex(0)->info();
    cells[i*4+1]=cell->vertex(1)->info();
    cells[i*4+2]=cell->vertex(2)->info();
    cells[i*4+3]=cell->vertex(3)->info();
    i+=1;
  }
  return cells;
}

// ----------------
// Python interface
// ----------------
// (from https://github.com/tdegeus/pybind11_examples/blob/master/04_numpy-2D_cpp-vector/example.cpp)

namespace py = pybind11;
py::array delaunay2(py::array_t<double, py::array::c_style | py::array::forcecast> x,
                    py::array_t<double, py::array::c_style | py::array::forcecast> y)
{

  // check input dimensions
  if ( x.ndim() != 1 )
    throw std::runtime_error("Input should be 2 1D NumPy arrays");
  if ( y.ndim() != 1 )
    throw std::runtime_error("Input should be 2 1D NumPy arrays");

  int num_points = x.shape()[0];

  // allocate std::vector (to pass to the C++ function)
  std::vector<double> cppx(num_points);
  std::vector<double> cppy(num_points);

  // copy py::array -> std::vector
  std::memcpy(cppx.data(),x.data(),num_points*sizeof(double));
  std::memcpy(cppy.data(),y.data(),num_points*sizeof(double));
  std::vector<int> faces = c_delaunay2(cppx, cppy);

  ssize_t              soint      = sizeof(int);
  ssize_t              num_faces = faces.size()/3;
  ssize_t              ndim      = 2;
  std::vector<ssize_t> shape     = {num_faces, 3};
  std::vector<ssize_t> strides   = {soint*3, soint};

  // return 2-D NumPy array
  return py::array(py::buffer_info(
    faces.data(),                           /* data as contiguous array  */
    sizeof(int),                          /* size of one scalar        */
    py::format_descriptor<int>::format(), /* data type                 */
    2,                                    /* number of dimensions      */
    shape,                                   /* shape of the matrix       */
    strides                                  /* strides for each axis     */
  ));
}

py::array delaunay3(py::array_t<double, py::array::c_style | py::array::forcecast> x,
                    py::array_t<double, py::array::c_style | py::array::forcecast> y,
                    py::array_t<double, py::array::c_style | py::array::forcecast> z)
{

  // check input dimensions
  if ( x.ndim() != 1 )
    throw std::runtime_error("Input should be three 1D NumPy arrays");
  if ( y.ndim() != 1 )
    throw std::runtime_error("Input should be three 1D NumPy arrays");
  if ( z.ndim() != 1 )
    throw std::runtime_error("Input should be three 1D NumPy arrays");

  int num_points = x.shape()[0];

  // allocate std::vector (to pass to the C++ function)
  std::vector<double> cppx(num_points);
  std::vector<double> cppy(num_points);
  std::vector<double> cppz(num_points);

  // copy py::array -> std::vector
  std::memcpy(cppx.data(),x.data(),num_points*sizeof(double));
  std::memcpy(cppy.data(),y.data(),num_points*sizeof(double));
  std::memcpy(cppz.data(),z.data(),num_points*sizeof(double));
  std::vector<int> cells = c_delaunay3(cppx, cppy, cppz);

  ssize_t              num_cells = cells.size()/4;
  ssize_t              ndim      = 2;
  ssize_t              soint      = sizeof(int);
  std::vector<ssize_t> shape     = {num_cells, 4};
  std::vector<ssize_t> strides   = {soint*4, soint};

  // return 2-D NumPy array
  return py::array(py::buffer_info(
    cells.data(),                           /* data as contiguous array  */
    sizeof(int),                          /* size of one scalar        */
    py::format_descriptor<int>::format(), /* data type                 */
    2,                                    /* number of dimensions      */
    shape,                                   /* shape of the matrix       */
    strides                                  /* strides for each axis     */
  ));
}

PYBIND11_MODULE(simple_cgal, m) {
    m.def("delaunay2", &delaunay2);
    m.def("delaunay3", &delaunay3);
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Framework/TestingFramework.hpp"

#include <array>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <utility>

#include "DataStructures/DataVector.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "Domain/LogicalCoordinates.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoHelpers.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoOscillationIndicator.hpp"
#include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "NumericalAlgorithms/Spectral/Spectral.hpp"
#include "Utilities/ConstantExpressions.hpp"
#include "Utilities/Gsl.hpp"

namespace {

void test_reconstruction_1d_impl(const Spectral::Quadrature quadrature) {
  CAPTURE(quadrature);
  const double neighbor_linear_weight = 0.005;
  const Mesh<1> mesh(5, Spectral::Basis::Legendre, quadrature);
  const auto coords = logical_coordinates(mesh);

  const auto evaluate_polynomial =
      [&coords](const std::array<double, 5>& coeffs) {
        const auto& x = get<0>(coords);
        return DataVector{coeffs[0] + coeffs[1] * x + coeffs[2] * square(x) +
                          coeffs[3] * cube(x) + coeffs[4] * pow<4>(x)};
      };

  DataVector local_data = evaluate_polynomial({{1., 2., 0., 0.5, 0.1}});
  // WENO reconstruction should preserve the mean, so expected = initial
  const double expected_local_mean = mean_value(local_data, mesh);

  const auto shift_data_to_local_mean =
      [&mesh, &expected_local_mean](const DataVector& neighbor_data) {
        return neighbor_data + expected_local_mean -
               mean_value(neighbor_data, mesh);
      };

  std::unordered_map<std::pair<Direction<1>, ElementId<1>>, DataVector,
                     boost::hash<std::pair<Direction<1>, ElementId<1>>>>
      neighbor_data{};
  neighbor_data[std::make_pair(Direction<1>::lower_xi(), ElementId<1>(1))] =
      shift_data_to_local_mean(evaluate_polynomial({{0., 1., 0., 1., 0.}}));
  neighbor_data[std::make_pair(Direction<1>::upper_xi(), ElementId<1>(2))] =
      shift_data_to_local_mean(evaluate_polynomial({{0., 0., 1., 1., 2.}}));

  // Expected result computed in Mathematica by computing oscillation indicator
  // as in oscillation_indicator tests, then WENO weights, then superposition.
  const DataVector expected_reconstructed_data = evaluate_polynomial(
      {{1.0000250662809542, 1.9987344134217362, 3.25819395292328e-7,
        0.5006326303794342, 0.09987412556290375}});

  Limiters::Weno_detail::reconstruct_from_weighted_sum(
      make_not_null(&local_data), neighbor_linear_weight,
      Limiters::Weno_detail::DerivativeWeight::Unity, mesh, neighbor_data);
  CHECK(mean_value(local_data, mesh) == approx(expected_local_mean));
  CHECK_ITERABLE_APPROX(local_data, expected_reconstructed_data);
}

void test_reconstruction_1d() {
  INFO("Testing WENO reconstruction in 1D");
  test_reconstruction_1d_impl(Spectral::Quadrature::GaussLobatto);
  test_reconstruction_1d_impl(Spectral::Quadrature::Gauss);
}

void test_reconstruction_2d_impl(const Spectral::Quadrature quadrature) {
  CAPTURE(quadrature);
  const double neighbor_linear_weight = 0.001;
  const Mesh<2> mesh({{3, 3}}, Spectral::Basis::Legendre, quadrature);
  const auto coords = logical_coordinates(mesh);

  const auto evaluate_polynomial =
      [&coords](const std::array<double, 9>& coeffs) {
        const auto& x = get<0>(coords);
        const auto& y = get<1>(coords);
        return DataVector{
            coeffs[0] + coeffs[1] * x + coeffs[2] * square(x) +
            y * (coeffs[3] + coeffs[4] * x + coeffs[5] * square(x)) +
            square(y) * (coeffs[6] + coeffs[7] * x + coeffs[8] * square(x))};
      };

  DataVector local_data =
      evaluate_polynomial({{2., 1., 0., 1.5, 1., 0., 1., 0., 0.}});
  // WENO reconstruction should preserve the mean, so expected = initial
  const double expected_local_mean = mean_value(local_data, mesh);

  const auto shift_data_to_local_mean =
      [&mesh, &expected_local_mean](const DataVector& neighbor_data) {
        return neighbor_data + expected_local_mean -
               mean_value(neighbor_data, mesh);
      };

  std::unordered_map<std::pair<Direction<2>, ElementId<2>>, DataVector,
                     boost::hash<std::pair<Direction<2>, ElementId<2>>>>
      neighbor_data{};
  neighbor_data[std::make_pair(Direction<2>::lower_xi(), ElementId<2>(1))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{0., 1., 0., 0., 1., 1., 0., 0., 0}}));
  neighbor_data[std::make_pair(Direction<2>::upper_xi(), ElementId<2>(2))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{0., 0., 1., 1., 2., 1., 0., 1., 1.}}));
  neighbor_data[std::make_pair(Direction<2>::lower_eta(), ElementId<2>(3))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{1., 0., 0., 0., 0.5, 0., 0., 0., 0.5}}));
  neighbor_data[std::make_pair(Direction<2>::upper_eta(), ElementId<2>(4))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{1., 0., 0., 0.5, 1., 0., 0., 0., 0.}}));

  // Expected result computed in Mathematica by computing oscillation indicator
  // as in oscillation_indicator tests, then WENO weights, then superposition.
  const DataVector expected_reconstructed_data = evaluate_polynomial(
      {{2.010056442214612, 0.9705606381771584, 0.000026246579961852654,
        1.4682459390241314, 0.9992393252122325, 0.0010535139634810797,
        0.9695333707936392, 0.000026246579961852654, 0.0008131679476911193}});

  Limiters::Weno_detail::reconstruct_from_weighted_sum(
      make_not_null(&local_data), neighbor_linear_weight,
      Limiters::Weno_detail::DerivativeWeight::Unity, mesh, neighbor_data);
  CHECK(mean_value(local_data, mesh) == approx(expected_local_mean));
  CHECK_ITERABLE_APPROX(local_data, expected_reconstructed_data);
}

void test_reconstruction_2d() {
  INFO("Testing WENO reconstruction in 2D");
  test_reconstruction_2d_impl(Spectral::Quadrature::GaussLobatto);
  test_reconstruction_2d_impl(Spectral::Quadrature::Gauss);
}

void test_reconstruction_3d_impl(const Spectral::Quadrature quadrature) {
  CAPTURE(quadrature);
  const double neighbor_linear_weight = 0.001;
  const Mesh<3> mesh({{3, 3, 3}}, Spectral::Basis::Legendre, quadrature);
  const auto coords = logical_coordinates(mesh);

  // 3D case has so many modes... so we simplify by only setting 6 of them, the
  // choice of modes to use here is arbitrary.
  const auto evaluate_polynomial =
      [&coords](const std::array<double, 6>& coeffs) {
        const auto& x = get<0>(coords);
        const auto& y = get<1>(coords);
        const auto& z = get<2>(coords);
        return DataVector{coeffs[0] + coeffs[1] * y + coeffs[2] * x * z +
                          coeffs[3] * x * y * z + coeffs[4] * square(y) * z +
                          coeffs[5] * square(x) * y * square(z)};
      };

  DataVector local_data = evaluate_polynomial({{1., 0.5, 0.5, 0.2, 0.2, 0.1}});
  // WENO reconstruction should preserve the mean, so expected = initial
  const double expected_local_mean = mean_value(local_data, mesh);

  const auto shift_data_to_local_mean =
      [&mesh, &expected_local_mean](const DataVector& neighbor_data) {
        return neighbor_data + expected_local_mean -
               mean_value(neighbor_data, mesh);
      };

  // We skip one neighbor, lower_eta, to simulate an external boundary
  std::unordered_map<std::pair<Direction<3>, ElementId<3>>, DataVector,
                     boost::hash<std::pair<Direction<3>, ElementId<3>>>>
      neighbor_data{};
  neighbor_data[std::make_pair(Direction<3>::lower_xi(), ElementId<3>(1))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{0.3, 0.2, 0.2, 0., 0., 0.1}}));
  neighbor_data[std::make_pair(Direction<3>::upper_xi(), ElementId<3>(2))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{2.5, 1., 0., 0., 1., 1.}}));
  neighbor_data[std::make_pair(Direction<3>::upper_eta(), ElementId<3>(4))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{1., 0.5, 0.5, 0.2, 0.2, 0.2}}));
  neighbor_data[std::make_pair(Direction<3>::lower_zeta(), ElementId<3>(5))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{1., 0.2, 0., 0., 0., 0.}}));
  neighbor_data[std::make_pair(Direction<3>::upper_zeta(), ElementId<3>(6))] =
      shift_data_to_local_mean(
          evaluate_polynomial({{0.1, 0., 0.5, 0.2, 0.2, 0.2}}));

  // Expected result computed in Mathematica by computing oscillation indicator
  // as in oscillation_indicator tests, then WENO weights, then superposition.
  const DataVector expected_reconstructed_data = evaluate_polynomial(
      {{1., 0.32663481881058243, 0.21186828015830592, 0.08447466846582166,
        0.08447504580872492, 0.04260655032204396}});

  Limiters::Weno_detail::reconstruct_from_weighted_sum(
      make_not_null(&local_data), neighbor_linear_weight,
      Limiters::Weno_detail::DerivativeWeight::Unity, mesh, neighbor_data);
  CHECK(mean_value(local_data, mesh) == approx(expected_local_mean));
  CHECK_ITERABLE_APPROX(local_data, expected_reconstructed_data);
}

void test_reconstruction_3d() {
  INFO("Testing WENO reconstruction in 3D");
  test_reconstruction_3d_impl(Spectral::Quadrature::GaussLobatto);
  test_reconstruction_3d_impl(Spectral::Quadrature::Gauss);
}

}  // namespace

SPECTRE_TEST_CASE("Unit.Evolution.DG.Limiters.Weno.Helpers",
                  "[Limiters][Unit]") {
  test_reconstruction_1d();
  test_reconstruction_2d();
  test_reconstruction_3d();
}