// Copyright (C) 2007  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_ASSIGN_IMAGe_
#define DLIB_ASSIGN_IMAGe_

#include "../pixel.h"
#include "assign_image_abstract.h"
#include "../statistics.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void impl_assign_image (
        image_view<dest_image_type>& dest,
        const src_image_type& src
    )
    {
        dest.set_size(src.nr(),src.nc());
        for (long r = 0; r < src.nr(); ++r)
        {
            for (long c = 0; c < src.nc(); ++c)
            {
                assign_pixel(dest[r][c], src(r,c));
            }
        }
    }

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void impl_assign_image (
        dest_image_type& dest_,
        const src_image_type& src
    )
    {
        image_view<dest_image_type> dest(dest_);
        impl_assign_image(dest, src);
    }

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void assign_image (
        dest_image_type& dest,
        const src_image_type& src
    )
    {
        // check for the case where dest is the same object as src
        if (is_same_object(dest,src))
            return;

        impl_assign_image(dest, mat(src));
    }

// ----------------------------------------------------------------------------------------

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void impl_assign_image_scaled (
        image_view<dest_image_type>& dest,
        const src_image_type& src,
        const double thresh 
    )
    {
        DLIB_ASSERT( thresh > 0,
            "\tvoid assign_image_scaled()"
            << "\n\t You have given an threshold value"
            << "\n\t thresh: " << thresh 
            );


        typedef typename image_traits<dest_image_type>::pixel_type dest_pixel;

        // If the destination has a dynamic range big enough to contain the source image data then just do a 
        // regular assign_image()
        if (pixel_traits<dest_pixel>::max() >= pixel_traits<typename src_image_type::type>::max() &&
            pixel_traits<dest_pixel>::min() <= pixel_traits<typename src_image_type::type>::min() )
        {
            impl_assign_image(dest, src);
            return;
        }

        dest.set_size(src.nr(),src.nc());

        if (src.size() == 0)
            return;

        if (src.size() == 1)
        {
            impl_assign_image(dest, src);
            return;
        }

        // gather image statistics 
        running_stats<double> rs;
        for (long r = 0; r < src.nr(); ++r)
        {
            for (long c = 0; c < src.nc(); ++c)
            {
                rs.add(get_pixel_intensity(src(r,c)));
            }
        }
        typedef typename pixel_traits<typename src_image_type::type>::basic_pixel_type spix_type;

        if (std::numeric_limits<spix_type>::is_integer)
        {
            // If the destination has a dynamic range big enough to contain the source image data then just do a 
            // regular assign_image()
            if (pixel_traits<dest_pixel>::max() >= rs.max() &&
                pixel_traits<dest_pixel>::min() <= rs.min() )
            {
                impl_assign_image(dest, src);
                return;
            }
        }

        // Figure out the range of pixel values based on image statistics.  There might be some huge
        // outliers so don't just pick the min and max values.
        const double upper = std::min(rs.mean() + thresh*rs.stddev(), rs.max());
        const double lower = std::max(rs.mean() - thresh*rs.stddev(), rs.min());


        const double dest_min = pixel_traits<dest_pixel>::min();
        const double dest_max = pixel_traits<dest_pixel>::max();

        const double scale = (upper!=lower)? ((dest_max - dest_min) / (upper - lower)) : 0;

        for (long r = 0; r < src.nr(); ++r)
        {
            for (long c = 0; c < src.nc(); ++c)
            {
                const double val = get_pixel_intensity(src(r,c)) - lower;

                assign_pixel(dest[r][c], scale*val + dest_min);
            }
        }
    }

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void impl_assign_image_scaled (
        dest_image_type& dest_,
        const src_image_type& src,
        const double thresh 
    )
    {
        image_view<dest_image_type> dest(dest_);
        impl_assign_image_scaled(dest, src, thresh);
    }

    template <
        typename dest_image_type,
        typename src_image_type
        >
    void assign_image_scaled (
        dest_image_type& dest,
        const src_image_type& src,
        const double thresh = 4
    )
    {
        // check for the case where dest is the same object as src
        if (is_same_object(dest,src))
            return;

        impl_assign_image_scaled(dest, mat(src),thresh);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename dest_image_type,
        typename src_pixel_type
        >
    void assign_all_pixels (
        image_view<dest_image_type>& dest_img,
        const src_pixel_type& src_pixel
    )
    {
        for (long r = 0; r < dest_img.nr(); ++r)
        {
            for (long c = 0; c < dest_img.nc(); ++c)
            {
                assign_pixel(dest_img[r][c], src_pixel);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename dest_image_type,
        typename src_pixel_type
        >
    void assign_all_pixels (
        dest_image_type& dest_img_,
        const src_pixel_type& src_pixel
    )
    {
        image_view<dest_image_type> dest_img(dest_img_);
        assign_all_pixels(dest_img, src_pixel);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type
        >
    void assign_border_pixels (
        image_view<image_type>& img,
        long x_border_size,
        long y_border_size,
        const typename image_traits<image_type>::pixel_type& p
    )
    {
        DLIB_ASSERT( x_border_size >= 0 && y_border_size >= 0,
            "\tvoid assign_border_pixels(img, p, border_size)"
            << "\n\tYou have given an invalid border_size"
            << "\n\tx_border_size: " << x_border_size
            << "\n\ty_border_size: " << y_border_size
            );

        y_border_size = std::min(y_border_size, img.nr()/2+1);
        x_border_size = std::min(x_border_size, img.nc()/2+1);

        // assign the top border
        for (long r = 0; r < y_border_size; ++r)
        {
            for (long c = 0; c < img.nc(); ++c)
            {
                img[r][c] = p;
            }
        }

        // assign the bottom border
        for (long r = img.nr()-y_border_size; r < img.nr(); ++r)
        {
            for (long c = 0; c < img.nc(); ++c)
            {
                img[r][c] = p;
            }
        }

        // now assign the two sides
        for (long r = y_border_size; r < img.nr()-y_border_size; ++r)
        {
            // left border
            for (long c = 0; c < x_border_size; ++c)
                img[r][c] = p;

            // right border
            for (long c = img.nc()-x_border_size; c < img.nc(); ++c)
                img[r][c] = p;
        }
    }

    template <
        typename image_type
        >
    void assign_border_pixels (
        image_type& img_,
        long x_border_size,
        long y_border_size,
        const typename image_traits<image_type>::pixel_type& p
    )
    {
        image_view<image_type> img(img_);
        assign_border_pixels(img, x_border_size, y_border_size, p);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type
        >
    void zero_border_pixels (
        image_type& img,
        long x_border_size,
        long y_border_size
    )
    {
        DLIB_ASSERT( x_border_size >= 0 && y_border_size >= 0,
            "\tvoid zero_border_pixels(img, p, border_size)"
            << "\n\tYou have given an invalid border_size"
            << "\n\tx_border_size: " << x_border_size
            << "\n\ty_border_size: " << y_border_size
            );

        typename image_traits<image_type>::pixel_type zero_pixel;
        assign_pixel_intensity(zero_pixel, 0);
        assign_border_pixels(img, x_border_size, y_border_size, zero_pixel);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type
        >
    void zero_border_pixels (
        image_view<image_type>& img,
        long x_border_size,
        long y_border_size
    )
    {
        DLIB_ASSERT( x_border_size >= 0 && y_border_size >= 0,
            "\tvoid zero_border_pixels(img, p, border_size)"
            << "\n\tYou have given an invalid border_size"
            << "\n\tx_border_size: " << x_border_size
            << "\n\ty_border_size: " << y_border_size
            );

        typename image_traits<image_type>::pixel_type zero_pixel;
        assign_pixel_intensity(zero_pixel, 0);
        assign_border_pixels(img, x_border_size, y_border_size, zero_pixel);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type
        >
    void zero_border_pixels (
        image_view<image_type>& img,
        rectangle inside
    )
    {
        inside = inside.intersect(get_rect(img));
        if (inside.is_empty())
        {
            assign_all_pixels(img, 0);
            return;
        }

        for (long r = 0; r < inside.top(); ++r)
        {
            for (long c = 0; c < img.nc(); ++c)
                assign_pixel(img[r][c], 0);
        }
        for (long r = inside.top(); r <= inside.bottom(); ++r)
        {
            for (long c = 0; c < inside.left(); ++c)
                assign_pixel(img[r][c], 0);
            for (long c = inside.right()+1; c < img.nc(); ++c)
                assign_pixel(img[r][c], 0);
        }
        for (long r = inside.bottom()+1; r < img.nr(); ++r)
        {
            for (long c = 0; c < img.nc(); ++c)
                assign_pixel(img[r][c], 0);
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type
        >
    void zero_border_pixels (
        image_type& img_,
        const rectangle& inside
    )
    {
        image_view<image_type> img(img_);
        zero_border_pixels(img, inside);
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_ASSIGN_IMAGe_