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

    #include "remove_unobtainable_rectangles_abstract.h"
    #include "scan_image_pyramid.h"
    #include "scan_image_boxes.h"
    #include "scan_image_custom.h"
    #include "scan_fhog_pyramid.h"
    #include "../svm/structural_object_detection_trainer.h"
    #include "../geometry.h"

    namespace dlib
    {

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

        namespace impl
        {
            inline bool matches_rect (
                const std::vector<rectangle>& rects,
                const rectangle& rect,
                const double eps
            )
            {
                for (unsigned long i = 0; i < rects.size(); ++i)
                {
                    const double score = (rect.intersect(rects[i])).area()/(double)(rect+rects[i]).area();
                    if (score > eps)
                        return true;
                }

                return false;
            }

            inline rectangle get_best_matching_rect (
                const std::vector<rectangle>& rects,
                const rectangle& rect
            )
            {
                double best_score = -1;
                rectangle best_rect;
                for (unsigned long i = 0; i < rects.size(); ++i)
                {
                    const double score = (rect.intersect(rects[i])).area()/(double)(rect+rects[i]).area();
                    if (score > best_score)
                    {
                        best_score = score;
                        best_rect = rects[i];
                    }
                }
                return best_rect;
            }

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

            template <
                typename image_array_type,
                typename image_scanner_type
                >
            std::vector<std::vector<rectangle> > pyramid_remove_unobtainable_rectangles (
                const structural_object_detection_trainer<image_scanner_type>& trainer,
                const image_array_type& images,
                std::vector<std::vector<rectangle> >& object_locations
            )
            {
                using namespace dlib::impl;
                // make sure requires clause is not broken
                DLIB_ASSERT(images.size() == object_locations.size(),
                    "\t std::vector<std::vector<rectangle>> remove_unobtainable_rectangles()"
                    << "\n\t Invalid inputs were given to this function."
                );

                std::vector<std::vector<rectangle> > rejects(images.size());

                // If the trainer is setup to automatically fit the overlap tester to the data then
                // we should use the loosest possible overlap tester here.  Otherwise we should use
                // the tester the trainer will use.
                test_box_overlap boxes_overlap(0.9999999,1);
                if (!trainer.auto_set_overlap_tester())
                    boxes_overlap = trainer.get_overlap_tester();

                for (unsigned long k = 0; k < images.size(); ++k)
                {
                    std::vector<rectangle> objs = object_locations[k];

                    // First remove things that don't have any matches with the candidate object
                    // locations.
                    std::vector<rectangle> good_rects;
                    for (unsigned long j = 0; j < objs.size(); ++j)
                    {
                        const rectangle rect = trainer.get_scanner().get_best_matching_rect(objs[j]);
                        const double score = (objs[j].intersect(rect)).area()/(double)(objs[j] + rect).area();
                        if (score > trainer.get_match_eps())
                            good_rects.push_back(objs[j]);
                        else
                            rejects[k].push_back(objs[j]);
                    }
                    object_locations[k] = good_rects;

                    // Remap these rectangles to the ones that can come out of the scanner.  That
                    // way when we compare them to each other in the following loop we will know if
                    // any distinct truth rectangles get mapped to overlapping boxes.
                    objs.resize(good_rects.size());
                    for (unsigned long i = 0; i < good_rects.size(); ++i)
                        objs[i] = trainer.get_scanner().get_best_matching_rect(good_rects[i]);

                    good_rects.clear();
                    // now check for truth rects that are too close together.
                    for (unsigned long i = 0; i < objs.size(); ++i)
                    {
                        // check if objs[i] hits another box
                        bool hit_box = false;
                        for (unsigned long j = i+1; j < objs.size(); ++j)
                        {
                            if (boxes_overlap(objs[i], objs[j]))
                            {
                                hit_box = true;
                                break;
                            }
                        }
                        if (hit_box)
                            rejects[k].push_back(object_locations[k][i]);
                        else
                            good_rects.push_back(object_locations[k][i]);
                    }
                    object_locations[k] = good_rects;
                }

                return rejects;
            }

        }

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

        template <
            typename image_array_type,
            typename Pyramid_type,
            typename Feature_extractor_type
            >
        std::vector<std::vector<rectangle> > remove_unobtainable_rectangles (
            const structural_object_detection_trainer<scan_image_pyramid<Pyramid_type, Feature_extractor_type> >& trainer,
            const image_array_type& images,
            std::vector<std::vector<rectangle> >& object_locations
        )
        {
            return impl::pyramid_remove_unobtainable_rectangles(trainer, images, object_locations);
        }

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

        template <
            typename image_array_type,
            typename Pyramid_type
            >
        std::vector<std::vector<rectangle> > remove_unobtainable_rectangles (
            const structural_object_detection_trainer<scan_fhog_pyramid<Pyramid_type> >& trainer,
            const image_array_type& images,
            std::vector<std::vector<rectangle> >& object_locations
        )
        {
            return impl::pyramid_remove_unobtainable_rectangles(trainer, images, object_locations);
        }

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

        namespace impl
        {
            template <
                typename image_array_type,
                typename scanner_type,
                typename get_boxes_functor
                >
            std::vector<std::vector<rectangle> > remove_unobtainable_rectangles (
                get_boxes_functor& bg,
                const structural_object_detection_trainer<scanner_type>& trainer,
                const image_array_type& images,
                std::vector<std::vector<rectangle> >& object_locations
            )
            {
                using namespace dlib::impl;
                // make sure requires clause is not broken
                DLIB_ASSERT(images.size() == object_locations.size(),
                    "\t std::vector<std::vector<rectangle>> remove_unobtainable_rectangles()"
                    << "\n\t Invalid inputs were given to this function."
                );

                std::vector<rectangle> rects;

                std::vector<std::vector<rectangle> > rejects(images.size());

                // If the trainer is setup to automatically fit the overlap tester to the data then
                // we should use the loosest possible overlap tester here.  Otherwise we should use
                // the tester the trainer will use.
                test_box_overlap boxes_overlap(0.9999999,1);
                if (!trainer.auto_set_overlap_tester())
                    boxes_overlap = trainer.get_overlap_tester();

                for (unsigned long k = 0; k < images.size(); ++k)
                {
                    std::vector<rectangle> objs = object_locations[k];
                    // Don't even bother computing the candidate rectangles if there aren't any
                    // object locations for this image since there isn't anything to do anyway.
                    if (objs.size() == 0)
                        continue;

                    bg(images[k], rects);

                    // First remove things that don't have any matches with the candidate object
                    // locations.
                    std::vector<rectangle> good_rects;
                    for (unsigned long j = 0; j < objs.size(); ++j)
                    {
                        if (matches_rect(rects, objs[j], trainer.get_match_eps()))
                            good_rects.push_back(objs[j]);
                        else
                            rejects[k].push_back(objs[j]);
                    }
                    object_locations[k] = good_rects;

                    // Remap these rectangles to the ones that can come out of the scanner.  That
                    // way when we compare them to each other in the following loop we will know if
                    // any distinct truth rectangles get mapped to overlapping boxes.
                    objs.resize(good_rects.size());
                    for (unsigned long i = 0; i < good_rects.size(); ++i)
                        objs[i] = get_best_matching_rect(rects, good_rects[i]);

                    good_rects.clear();
                    // now check for truth rects that are too close together.
                    for (unsigned long i = 0; i < objs.size(); ++i)
                    {
                        // check if objs[i] hits another box
                        bool hit_box = false;
                        for (unsigned long j = i+1; j < objs.size(); ++j)
                        {
                            if (boxes_overlap(objs[i], objs[j]))
                            {
                                hit_box = true;
                                break;
                            }
                        }
                        if (hit_box)
                            rejects[k].push_back(object_locations[k][i]);
                        else
                            good_rects.push_back(object_locations[k][i]);
                    }
                    object_locations[k] = good_rects;
                }

                return rejects;
            }

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

            template <typename T>
            struct load_to_functor
            {
                load_to_functor(T& obj_) : obj(obj_) {}
                T& obj;

                template <typename U, typename V>
                void operator()(const U& u, V& v)
                {
                    obj.load(u,v);
                }
            };
        }

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

        template <
            typename image_array_type,
            typename feature_extractor,
            typename box_generator
            >
        std::vector<std::vector<rectangle> > remove_unobtainable_rectangles (
            const structural_object_detection_trainer<scan_image_boxes<feature_extractor, box_generator> >& trainer,
            const image_array_type& images,
            std::vector<std::vector<rectangle> >& object_locations
        )
        {
            box_generator bg = trainer.get_scanner().get_box_generator();
            return impl::remove_unobtainable_rectangles(bg, trainer, images, object_locations);
        }

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

        template <
            typename image_array_type,
            typename feature_extractor
            >
        std::vector<std::vector<rectangle> > remove_unobtainable_rectangles (
            const structural_object_detection_trainer<scan_image_custom<feature_extractor> >& trainer,
            const image_array_type& images,
            std::vector<std::vector<rectangle> >& object_locations
        )
        {
            feature_extractor fe;
            fe.copy_configuration(trainer.get_scanner().get_feature_extractor());
            impl::load_to_functor<feature_extractor> bg(fe);
            return impl::remove_unobtainable_rectangles(bg, trainer, images, object_locations);
        }

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

    }

    #endif // DLIB_REMOVE_UnOBTAINABLE_RECTANGLES_H__