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

    #include "scan_image_pyramid_tools_abstract.h"
    #include "../statistics.h"
    #include <list>
    #include "../geometry.h"
    #include <iostream>

    namespace dlib
    {

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

        namespace impl
        {
            inline bool compare_first (
                const std::pair<unsigned long,rectangle>& a,
                const std::pair<unsigned long,rectangle>& b
            )
            {
                return a.first < b.first;
            }
        }

        template <typename image_scanner_type>
        std::vector<rectangle> determine_object_boxes (
            const image_scanner_type& scanner,
            const std::vector<rectangle>& rects,
            double min_match_score
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(0 < min_match_score && min_match_score <= 1,
                "\t std::vector<rectangle> determine_object_boxes()"
                << "\n\t Invalid inputs were given to this function. "
                << "\n\t min_match_score: " << min_match_score
                );

            typename image_scanner_type::pyramid_type pyr;

            typedef std::list<std::pair<unsigned long, rectangle> > list_type;

            unsigned long max_area = 0;

            // Copy rects into sorted_rects and sort them in order of increasing area.  But
            // only include the rectangles that aren't already obtainable by the scanner.
            list_type sorted_rects;
            for (unsigned long i = 0; i < rects.size(); ++i)
            {
                if (scanner.get_num_detection_templates() > 0)
                {
                    rectangle temp = scanner.get_best_matching_rect(rects[i]);
                    const double match_score = (rects[i].intersect(temp).area())/(double)(rects[i] + temp).area();
                    // skip this rectangle if it's already matched well enough.
                    if (match_score > min_match_score)
                        continue;
                }
                max_area = std::max(rects[i].area(), max_area);
                sorted_rects.push_back(std::make_pair(rects[i].area(), rects[i]));
            }
            sorted_rects.sort(dlib::impl::compare_first);

            // Make sure this area value is comfortably larger than all the
            // rectangles' areas.
            max_area = 3*max_area + 100;

            std::vector<rectangle> object_boxes;

            while (sorted_rects.size() != 0)
            {
                rectangle cur = sorted_rects.front().second;
                sorted_rects.pop_front();
                object_boxes.push_back(centered_rect(point(0,0), cur.width(), cur.height()));

                // Scale cur up the image pyramid and remove any rectangles which match.
                // But also stop when cur gets large enough to not match anything.
                for (unsigned long itr = 0;
                     itr < scanner.get_max_pyramid_levels() && cur.area() < max_area;
                     ++itr)
                {
                    list_type::iterator i = sorted_rects.begin();
                    while (i != sorted_rects.end())
                    {
                        const rectangle temp = move_rect(i->second, cur.tl_corner());
                        const double match_score = (cur.intersect(temp).area())/(double)(cur + temp).area();
                        if (match_score > min_match_score)
                        {
                            i = sorted_rects.erase(i);
                        }
                        else
                        {
                            ++i;
                        }
                    }

                    cur = pyr.rect_up(cur);
                }

            }

            return object_boxes;
        }

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

        template <typename image_scanner_type>
        std::vector<rectangle> determine_object_boxes (
            const image_scanner_type& scanner,
            const std::vector<std::vector<rectangle> >& rects,
            double min_match_score
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(0 < min_match_score && min_match_score <= 1,
                "\t std::vector<rectangle> determine_object_boxes()"
                << "\n\t Invalid inputs were given to this function. "
                << "\n\t min_match_score: " << min_match_score
                );

            std::vector<rectangle> temp;
            for (unsigned long i = 0; i < rects.size(); ++i)
            {
                for (unsigned long j = 0; j < rects[i].size(); ++j)
                {
                    temp.push_back(rects[i][j]);
                }
            }

            return determine_object_boxes(scanner, temp, min_match_score);
        }

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

        template <typename image_scanner_type>
        void setup_grid_detection_templates (
            image_scanner_type& scanner,
            const std::vector<std::vector<rectangle> >& rects,
            unsigned int cells_x,
            unsigned int cells_y,
            double min_match_score = 0.75
        )
        {
            const std::vector<rectangle>& object_boxes = determine_object_boxes(scanner, rects, min_match_score);
            for (unsigned long i = 0; i < object_boxes.size(); ++i)
            {
                scanner.add_detection_template(object_boxes[i], create_grid_detection_template(object_boxes[i], cells_x, cells_y));
            }
        }

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

        template <typename image_scanner_type>
        void setup_grid_detection_templates_verbose (
            image_scanner_type& scanner,
            const std::vector<std::vector<rectangle> >& rects,
            unsigned int cells_x,
            unsigned int cells_y,
            double min_match_score = 0.75
        )
        {
            const std::vector<rectangle>& object_boxes = determine_object_boxes(scanner, rects, min_match_score);
            std::cout << "number of detection templates: "<< object_boxes.size() << std::endl;
            for (unsigned long i = 0; i < object_boxes.size(); ++i)
            {
                std::cout << "  object box " << i << ":  width: " << object_boxes[i].width()
                          << "  height: "<< object_boxes[i].height() << std::endl;
                scanner.add_detection_template(object_boxes[i], create_grid_detection_template(object_boxes[i], cells_x, cells_y));
            }
        }

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

    }

    #endif // DLIB_SCAN_IMaGE_PYRAMID_TOOLS_Hh_