/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2024 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_RENDERER_COMMON_PROCESS_RASTER_SYMBOLIZER_HPP
#define MAPNIK_RENDERER_COMMON_PROCESS_RASTER_SYMBOLIZER_HPP

// mapnik
#include <mapnik/image_util.hpp>
#include <mapnik/warp.hpp>
#include <mapnik/raster.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/raster_colorizer.hpp>
#include <mapnik/proj_transform.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/renderer_common.hpp>

#include <mapnik/warning.hpp>
MAPNIK_DISABLE_WARNING_PUSH
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_rendering_buffer.h"
#include "agg_pixfmt_rgba.h"
#include "agg_pixfmt_gray.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_renderer_scanline.h"
MAPNIK_DISABLE_WARNING_POP

namespace mapnik {

namespace detail {

template<typename F>
struct image_dispatcher
{
    using composite_function = F;
    image_dispatcher(int start_x,
                     int start_y,
                     int width,
                     int height,
                     double scale_x,
                     double scale_y,
                     double offset_x,
                     double offset_y,
                     scaling_method_e method,
                     double filter_factor,
                     double opacity,
                     composite_mode_e comp_op,
                     raster_symbolizer const& sym,
                     feature_impl const& feature,
                     F& composite,
                     std::optional<double> const& nodata,
                     bool need_scaling)
        : start_x_(start_x)
        , start_y_(start_y)
        , width_(width)
        , height_(height)
        , scale_x_(scale_x)
        , scale_y_(scale_y)
        , offset_x_(offset_x)
        , offset_y_(offset_y)
        , method_(method)
        , filter_factor_(filter_factor)
        , opacity_(opacity)
        , comp_op_(comp_op)
        , sym_(sym)
        , feature_(feature)
        , composite_(composite)
        , nodata_(nodata)
        , need_scaling_(need_scaling)
    {}

    void operator()(image_null const&) const {} // no-op
    void operator()(image_rgba8 const& data_in) const
    {
        if (need_scaling_)
        {
            image_rgba8 data_out(width_, height_, true, true);
            scale_image_agg(data_out,
                            data_in,
                            method_,
                            scale_x_,
                            scale_y_,
                            offset_x_,
                            offset_y_,
                            filter_factor_,
                            nodata_);
            composite_(data_out, comp_op_, opacity_, start_x_, start_y_);
        }
        else
        {
            composite_(data_in, comp_op_, opacity_, start_x_, start_y_);
        }
    }

    template<typename T>
    void operator()(T const& data_in) const
    {
        using image_type = T;
        image_rgba8 dst(width_, height_);
        raster_colorizer_ptr colorizer = get<raster_colorizer_ptr>(sym_, keys::colorizer);
        if (need_scaling_)
        {
            image_type data_out(width_, height_);
            scale_image_agg(data_out,
                            data_in,
                            method_,
                            scale_x_,
                            scale_y_,
                            offset_x_,
                            offset_y_,
                            filter_factor_,
                            nodata_);
            if (colorizer)
                colorizer->colorize(dst, data_out, nodata_, feature_);
        }
        else
        {
            if (colorizer)
                colorizer->colorize(dst, data_in, nodata_, feature_);
        }
        premultiply_alpha(dst);
        composite_(dst, comp_op_, opacity_, start_x_, start_y_);
    }

  private:
    int start_x_;
    int start_y_;
    int width_;
    int height_;
    double scale_x_;
    double scale_y_;
    double offset_x_;
    double offset_y_;
    scaling_method_e method_;
    double filter_factor_;
    double opacity_;
    composite_mode_e comp_op_;
    raster_symbolizer const& sym_;
    feature_impl const& feature_;
    composite_function& composite_;
    std::optional<double> const& nodata_;
    bool need_scaling_;
};

template<typename F>
struct image_warp_dispatcher
{
    using composite_function = F;
    image_warp_dispatcher(proj_transform const& prj_trans,
                          int start_x,
                          int start_y,
                          int width,
                          int height,
                          box2d<double> const& target_ext,
                          box2d<double> const& source_ext,
                          double offset_x,
                          double offset_y,
                          unsigned mesh_size,
                          scaling_method_e scaling_method,
                          double filter_factor,
                          double opacity,
                          composite_mode_e comp_op,
                          raster_symbolizer const& sym,
                          feature_impl const& feature,
                          F& composite,
                          std::optional<double> const& nodata)
        : prj_trans_(prj_trans)
        , start_x_(start_x)
        , start_y_(start_y)
        , width_(width)
        , height_(height)
        , target_ext_(target_ext)
        , source_ext_(source_ext)
        , offset_x_(offset_x)
        , offset_y_(offset_y)
        , mesh_size_(mesh_size)
        , scaling_method_(scaling_method)
        , filter_factor_(filter_factor)
        , opacity_(opacity)
        , comp_op_(comp_op)
        , sym_(sym)
        , feature_(feature)
        , composite_(composite)
        , nodata_(nodata)
    {}

    void operator()(image_null const&) const {} // no-op

    void operator()(image_rgba8 const& data_in) const
    {
        image_rgba8 data_out(width_, height_, true, true);
        warp_image(data_out,
                   data_in,
                   prj_trans_,
                   target_ext_,
                   source_ext_,
                   offset_x_,
                   offset_y_,
                   mesh_size_,
                   scaling_method_,
                   filter_factor_,
                   nodata_);
        composite_(data_out, comp_op_, opacity_, start_x_, start_y_);
    }

    template<typename T>
    void operator()(T const& data_in) const
    {
        using image_type = T;
        image_type data_out(width_, height_);
        if (nodata_)
            data_out.set(*nodata_);
        warp_image(data_out,
                   data_in,
                   prj_trans_,
                   target_ext_,
                   source_ext_,
                   offset_x_,
                   offset_y_,
                   mesh_size_,
                   scaling_method_,
                   filter_factor_,
                   nodata_);
        image_rgba8 dst(width_, height_);
        raster_colorizer_ptr colorizer = get<raster_colorizer_ptr>(sym_, keys::colorizer);
        if (colorizer)
            colorizer->colorize(dst, data_out, nodata_, feature_);
        premultiply_alpha(dst);
        composite_(dst, comp_op_, opacity_, start_x_, start_y_);
    }

  private:
    proj_transform const& prj_trans_;
    int start_x_;
    int start_y_;
    int width_;
    int height_;
    box2d<double> const& target_ext_;
    box2d<double> const& source_ext_;
    double offset_x_;
    double offset_y_;
    unsigned mesh_size_;
    scaling_method_e scaling_method_;
    double filter_factor_;
    double opacity_;
    composite_mode_e comp_op_;
    raster_symbolizer const& sym_;
    feature_impl const& feature_;
    composite_function& composite_;
    std::optional<double> const& nodata_;
};

} // namespace detail

template<typename F>
void render_raster_symbolizer(raster_symbolizer const& sym,
                              mapnik::feature_impl& feature,
                              proj_transform const& prj_trans,
                              renderer_common& common,
                              F composite)
{
    raster_ptr const& source = feature.get_raster();
    if (source)
    {
        box2d<double> target_ext = box2d<double>(source->ext_);
        box2d<double> target_query_ext = box2d<double>(source->query_ext_);
        if (!prj_trans.equal())
        {
            prj_trans.backward(target_ext, PROJ_ENVELOPE_POINTS);
            prj_trans.backward(target_query_ext, PROJ_ENVELOPE_POINTS);
        }
        box2d<double> ext = common.t_.forward(target_ext);
        box2d<double> query_ext = common.t_.forward(target_query_ext);
        int start_x = static_cast<int>(std::floor(query_ext.minx() + .5));
        int start_y = static_cast<int>(std::floor(query_ext.miny() + .5));
        int end_x = static_cast<int>(std::floor(query_ext.maxx() + .5));
        int end_y = static_cast<int>(std::floor(query_ext.maxy() + .5));
        int raster_width = end_x - start_x;
        int raster_height = end_y - start_y;
        if (raster_width > 0 && raster_height > 0)
        {
            scaling_method_e scaling_method =
              get<scaling_method_e>(sym, keys::scaling, feature, common.vars_, SCALING_NEAR);
            composite_mode_e comp_op = get<composite_mode_e>(sym, keys::comp_op, feature, common.vars_, src_over);
            double opacity = get<double>(sym, keys::opacity, feature, common.vars_, 1.0);
            // only premultiply rgba8 images
            if (source->data_.is<image_rgba8>())
            {
                auto is_premultiplied = get_optional<bool>(sym, keys::premultiplied, feature, common.vars_);
                if (is_premultiplied && *is_premultiplied)
                {
                    mapnik::set_premultiplied_alpha(source->data_, true);
                }
                mapnik::premultiply_alpha(source->data_);
            }

            if (!prj_trans.equal())
            {
                // This path does not currently work and is still being figured out.
                double offset_x = query_ext.minx() - start_x;
                double offset_y = query_ext.miny() - start_y;
                unsigned mesh_size =
                  static_cast<unsigned>(get<value_integer>(sym, keys::mesh_size, feature, common.vars_, 16));
                detail::image_warp_dispatcher<F> dispatcher(prj_trans,
                                                            start_x,
                                                            start_y,
                                                            raster_width,
                                                            raster_height,
                                                            target_query_ext,
                                                            source->ext_,
                                                            offset_x,
                                                            offset_y,
                                                            mesh_size,
                                                            scaling_method,
                                                            source->get_filter_factor(),
                                                            opacity,
                                                            comp_op,
                                                            sym,
                                                            feature,
                                                            composite,
                                                            source->nodata());
                util::apply_visitor(dispatcher, source->data_);
            }
            else
            {
                double offset_x = query_ext.minx() - ext.minx();
                double offset_y = query_ext.miny() - ext.miny();
                double image_ratio_x = ext.width() / source->data_.width();
                double image_ratio_y = ext.height() / source->data_.height();
                double eps = 1e-5;
                bool scale = (std::fabs(image_ratio_x - 1.0) > eps) || (std::fabs(image_ratio_y - 1.0) > eps) ||
                             (std::abs(start_x) > eps) || (std::abs(start_y) > eps);
                detail::image_dispatcher<F> dispatcher(start_x,
                                                       start_y,
                                                       raster_width,
                                                       raster_height,
                                                       image_ratio_x,
                                                       image_ratio_y,
                                                       offset_x,
                                                       offset_y,
                                                       scaling_method,
                                                       source->get_filter_factor(),
                                                       opacity,
                                                       comp_op,
                                                       sym,
                                                       feature,
                                                       composite,
                                                       source->nodata(),
                                                       scale);
                util::apply_visitor(dispatcher, source->data_);
            }
        }
    }
}

} // namespace mapnik

#endif // MAPNIK_RENDERER_COMMON_PROCESS_RASTER_SYMBOLIZER_HPP
