//
// Copyright 2020 Debabrata Mandal <mandaldebabrata123@gmail.com>
//
// Use, modification and distribution are subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP
#define BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP

#include <boost/gil/histogram.hpp>
#include <boost/gil/image.hpp>

#include <cmath>
#include <map>
#include <vector>

namespace boost { namespace gil {

/////////////////////////////////////////
/// Histogram Equalization(HE)
/////////////////////////////////////////
/// \defgroup HE HE
/// \brief Contains implementation and description of the algorithm used to compute
///        global histogram equalization of input images.
///
///        Algorithm :-
///        1. If histogram A is to be equalized compute the cumulative histogram of A.
///        2. Let CFD(A) refer to the cumulative histogram of A
///        3. For a uniform histogram A', CDF(A') = A'
///        4. We need to transform A to A' such that
///        5. CDF(A') = CDF(A) => A' = CDF(A)
///        6. Hence the pixel transform , px => histogram_of_ith_channel[px].
///

/// \fn histogram_equalization
/// \ingroup HE
/// \tparam SrcKeyType Key Type of input histogram
/// @param src_hist INPUT Input source histogram
/// \brief Overload for histogram equalization algorithm, takes in a single source histogram
///        and returns the color map used for histogram equalization.
///
template <typename SrcKeyType>
auto histogram_equalization(histogram<SrcKeyType> const& src_hist)
    -> std::map<SrcKeyType, SrcKeyType>
{
    histogram<SrcKeyType> dst_hist;
    return histogram_equalization(src_hist, dst_hist);
}

/// \overload histogram_equalization
/// \ingroup HE
/// \tparam SrcKeyType Key Type of input histogram
/// \tparam DstKeyType Key Type of output histogram
/// @param src_hist INPUT source histogram
/// @param dst_hist OUTPUT Output histogram
/// \brief Overload for histogram equalization algorithm, takes in both source histogram &
///        destination histogram and returns the color map used for histogram equalization
///        as well as transforming the destination histogram.
///
template <typename SrcKeyType, typename DstKeyType>
auto histogram_equalization(histogram<SrcKeyType> const& src_hist, histogram<DstKeyType>& dst_hist)
    -> std::map<SrcKeyType, DstKeyType>
{
    static_assert(
        std::is_integral<SrcKeyType>::value &&
        std::is_integral<DstKeyType>::value,
        "Source and destination histogram types are not appropriate");

    using value_t = typename histogram<SrcKeyType>::value_type;
    dst_hist.clear();
    double sum          = src_hist.sum();
    SrcKeyType min_key  = (std::numeric_limits<DstKeyType>::min)();
    SrcKeyType max_key  = (std::numeric_limits<DstKeyType>::max)();
    auto cumltv_srchist = cumulative_histogram(src_hist);
    std::map<SrcKeyType, DstKeyType> color_map;
    std::for_each(cumltv_srchist.begin(), cumltv_srchist.end(), [&](value_t const& v) {
        DstKeyType trnsfrmd_key =
            static_cast<DstKeyType>((v.second * (max_key - min_key)) / sum + min_key);
        color_map[std::get<0>(v.first)] = trnsfrmd_key;
    });
    std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
        dst_hist[color_map[std::get<0>(v.first)]] += v.second;
    });
    return color_map;
}

/// \overload histogram_equalization
/// \ingroup HE
/// @param src_view  INPUT source image view
/// @param dst_view  OUTPUT Output image view
/// @param bin_width INPUT Histogram bin width
/// @param mask      INPUT Specify is mask is to be used
/// @param src_mask  INPUT Mask vector over input image
/// \brief Overload for histogram equalization algorithm, takes in both source & destination
///        image views and histogram equalizes the input image.
///
template <typename SrcView, typename DstView>
void histogram_equalization(
    SrcView const& src_view,
    DstView const& dst_view,
    std::size_t bin_width = 1,
    bool mask = false,
    std::vector<std::vector<bool>> src_mask = {})
{
    gil_function_requires<ImageViewConcept<SrcView>>();
    gil_function_requires<MutableImageViewConcept<DstView>>();

    static_assert(
        color_spaces_are_compatible<
            typename color_space_type<SrcView>::type,
            typename color_space_type<DstView>::type>::value,
        "Source and destination views must have same color space");

    // Defining channel type
    using source_channel_t = typename channel_type<SrcView>::type;
    using dst_channel_t    = typename channel_type<DstView>::type;
    using coord_t          = typename SrcView::x_coord_t;

    std::size_t const channels = num_channels<SrcView>::value;
    coord_t const width        = src_view.width();
    coord_t const height       = src_view.height();
    std::size_t pixel_max      = (std::numeric_limits<dst_channel_t>::max)();
    std::size_t pixel_min      = (std::numeric_limits<dst_channel_t>::min)();

    for (std::size_t i = 0; i < channels; i++)
    {
        histogram<source_channel_t> h;
        fill_histogram(nth_channel_view(src_view, i), h, bin_width, false, false, mask, src_mask);
        h.normalize();
        auto h2 = cumulative_histogram(h);
        for (std::ptrdiff_t src_y = 0; src_y < height; ++src_y)
        {
            auto src_it = nth_channel_view(src_view, i).row_begin(src_y);
            auto dst_it = nth_channel_view(dst_view, i).row_begin(src_y);
            for (std::ptrdiff_t src_x = 0; src_x < width; ++src_x)
            {
                if (mask && !src_mask[src_y][src_x])
                    dst_it[src_x][0] = channel_convert<dst_channel_t>(src_it[src_x][0]);
                else
                    dst_it[src_x][0] = static_cast<dst_channel_t>(
                        h2[src_it[src_x][0]] * (pixel_max - pixel_min) + pixel_min);
            }
        }
    }
}

}}  //namespace boost::gil

#endif
