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

    #include "active_learning_abstract.h"

    #include "svm_c_linear_dcd_trainer.h"
    #include <vector>

    namespace dlib
    {

        enum active_learning_mode
        {
            max_min_margin,
            ratio_margin
        };

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

        template <
            typename kernel_type,
            typename in_sample_vector_type,
            typename in_scalar_vector_type,
            typename in_sample_vector_type2
            >
        std::vector<unsigned long> impl_rank_unlabeled_training_samples (
            const svm_c_linear_dcd_trainer<kernel_type>& trainer,
            const in_sample_vector_type& samples,
            const in_scalar_vector_type& labels,
            const in_sample_vector_type2& unlabeled_samples,
            const active_learning_mode mode
        )
        {
            DLIB_ASSERT(is_vector(unlabeled_samples) &&
                         (samples.size() == 0 || is_learning_problem(samples, labels)) ,
                    "\t std::vector<unsigned long> rank_unlabeled_training_samples()"
                    << "\n\t Invalid inputs were given to this function"
                    << "\n\t is_vector(unlabeled_samples):         " << is_vector(unlabeled_samples)
                    << "\n\t is_learning_problem(samples, labels): " << is_learning_problem(samples, labels)
                    << "\n\t samples.size(): " << samples.size()
                    << "\n\t labels.size():  " << labels.size()
                    );

            // If there aren't any training samples then all unlabeled_samples are equally good.
            // So just report an arbitrary ordering.
            if (samples.size() == 0 || unlabeled_samples.size() == 0)
            {
                std::vector<unsigned long> ret(unlabeled_samples.size());
                for (unsigned long i = 0; i < ret.size(); ++i)
                    ret[i] = i;

                return ret;
            }

            // We are going to score each unlabeled sample and put the score and index into
            // results.  Then at the end of this function we just sort it and return the indices.
            std::vector<std::pair<double, unsigned long> > results;
            results.resize(unlabeled_samples.size());

            // make sure we use this trainer's ability to warm start itself since that will make
            // this whole function run a lot faster.  But first, we need to find out what the state
            // we will be warm starting from is.
            typedef typename svm_c_linear_dcd_trainer<kernel_type>::optimizer_state optimizer_state;
            optimizer_state state;
            trainer.train(samples, labels, state); // call train() just to get state

            decision_function<kernel_type> df;

            std::vector<typename kernel_type::sample_type> temp_samples;
            std::vector<typename kernel_type::scalar_type> temp_labels;
            temp_samples.reserve(samples.size()+1);
            temp_labels.reserve(labels.size()+1);
            temp_samples.assign(samples.begin(), samples.end());
            temp_labels.assign(labels.begin(), labels.end());
            temp_samples.resize(temp_samples.size()+1);
            temp_labels.resize(temp_labels.size()+1);

            for (long i = 0; i < unlabeled_samples.size(); ++i)
            {
                temp_samples.back() = unlabeled_samples(i);
                // figure out the margin for each possible labeling of this sample.

                optimizer_state temp(state);
                temp_labels.back() = +1;
                df = trainer.train(temp_samples, temp_labels, temp);
                const double margin_p = temp_labels.back()*df(temp_samples.back());

                temp = state;
                temp_labels.back() = -1;
                df = trainer.train(temp_samples, temp_labels, temp);
                const double margin_n = temp_labels.back()*df(temp_samples.back());

                if (mode == max_min_margin)
                {
                    // The score for this sample is its min possible margin over possible labels.
                    // Therefore, this score measures how much flexibility we have to label this
                    // sample however we want.  The intuition being that the most useful points to
                    // label are the ones that are still free to obtain either label.
                    results[i] = std::make_pair(std::min(margin_p, margin_n), i);
                }
                else
                {
                    // In this case, the score for the sample is a ratio that tells how close the
                    // two margin values are to each other.  The closer they are the better.  So in
                    // this case we are saying we are looking for samples that have the same
                    // preference for either class label.
                    if (std::abs(margin_p) >= std::abs(margin_n))
                    {
                        if (margin_p != 0)
                            results[i] = std::make_pair(margin_n/margin_p, i);
                        else // if both are == 0 then say 0/0 == 1
                            results[i] = std::make_pair(1, i);
                    }
                    else
                    {
                        results[i] = std::make_pair(margin_p/margin_n, i);
                    }
                }
            }

            // sort the results so the highest scoring samples come first.
            std::sort(results.rbegin(), results.rend());

            // transfer results into a vector with just sample indices so we can return it.
            std::vector<unsigned long> ret(results.size());
            for (unsigned long i = 0; i < ret.size(); ++i)
                ret[i] = results[i].second;
            return ret;
        }

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

        template <
            typename kernel_type,
            typename in_sample_vector_type,
            typename in_scalar_vector_type,
            typename in_sample_vector_type2
            >
        std::vector<unsigned long> rank_unlabeled_training_samples (
            const svm_c_linear_dcd_trainer<kernel_type>& trainer,
            const in_sample_vector_type& samples,
            const in_scalar_vector_type& labels,
            const in_sample_vector_type2& unlabeled_samples,
            const active_learning_mode mode = max_min_margin
        )
        {
            return impl_rank_unlabeled_training_samples(trainer,
                                                        mat(samples),
                                                        mat(labels),
                                                        mat(unlabeled_samples),
                                                        mode);
        }

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

    }

    #endif // DLIB_ACTIVE_LEARnING_Hh_