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

    #include "create_random_projection_hash_abstract.h"
    #include "projection_hash.h"
    #include "../matrix.h"
    #include "../rand.h"
    #include "../statistics.h"
    #include "../svm.h"
    #include <vector>

    namespace dlib
    {

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

        template <
            typename vector_type
            >
        projection_hash create_random_projection_hash (
            const vector_type& v,
            const int bits,
            dlib::rand& rnd
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(0 < bits && bits <= 32 &&
                        v.size() > 1,
                "\t projection_hash create_random_projection_hash()"
                << "\n\t Invalid arguments were given to this function."
                << "\n\t bits: " << bits
                << "\n\t v.size(): " << v.size()
                );

    #ifdef ENABLE_ASSERTS
            for (unsigned long i = 0; i < v.size(); ++i)
            {
                DLIB_ASSERT(v[0].size() == v[i].size() && v[i].size() > 0 && is_col_vector(v[i]),
                        "\t projection_hash create_random_projection_hash()"
                       << "\n\t Invalid arguments were given to this function."
                       << "\n\t m(0).size(): " << v[0].size()
                       << "\n\t m("<<i<<").size(): " << v[i].size()
                       << "\n\t is_col_vector(v["<<i<<"]): " << is_col_vector(v[i])
                    );
            }
    #endif

            running_covariance<matrix<double> > rc;
            for (unsigned long i = 0; i < v.size(); ++i)
                rc.add(matrix_cast<double>(v[i]));

            // compute a whitening matrix
            matrix<double> whiten = trans(chol(pinv(rc.covariance())));

            // hashes
            std::vector<unsigned long> h(v.size(),0);

            std::vector<double> vals(v.size(),0);

            // number of hits for each hash value
            std::vector<unsigned long> counts;

            std::vector<double> temp;

            // build a random projection matrix
            matrix<double> proj(bits, v[0].size());
            for (long r = 0; r < proj.nr(); ++r)
                for (long c = 0; c < proj.nc(); ++c)
                    proj(r,c) = rnd.get_random_gaussian();

            // merge whitening matrix with projection matrix
            proj = proj*whiten;

            matrix<double,0,1> offset(bits);

            // figure out what the offset values should be
            for (int itr = 0; itr < offset.size(); ++itr)
            {
                counts.assign(static_cast<unsigned long>(std::pow(2.0,bits)), 0);
                // count the popularity of each hash value
                for (unsigned long i = 0; i < h.size(); ++i)
                {
                    h[i] <<= 1;
                    counts[h[i]] += 1;
                }

                const unsigned long max_h = index_of_max(mat(counts));

                temp.clear();
                for (unsigned long i = 0; i < v.size(); ++i)
                {
                    vals[i] = dot(rowm(proj,itr), matrix_cast<double>(v[i]));
                    if (h[i] == max_h)
                        temp.push_back(vals[i]);
                }

                // split down the middle
                std::sort(temp.begin(), temp.end());
                const double split = temp[temp.size()/2];
                offset(itr) = -split;

                for (unsigned long i = 0; i < vals.size(); ++i)
                {
                    if (vals[i] - split > 0)
                        h[i] |= 1;
                }
            }

            return projection_hash(proj, offset);
        }

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

        template <
            typename vector_type
            >
        projection_hash create_random_projection_hash (
            const vector_type& v,
            const int bits
        )
        {
            dlib::rand rnd;
            return create_random_projection_hash(v,bits,rnd);
        }

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

        template <
            typename vector_type
            >
        projection_hash create_max_margin_projection_hash (
            const vector_type& v,
            const int bits,
            const double C,
            dlib::rand& rnd
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(0 < bits && bits <= 32 &&
                        v.size() > 1,
                "\t projection_hash create_max_margin_projection_hash()"
                << "\n\t Invalid arguments were given to this function."
                << "\n\t bits: " << bits
                << "\n\t v.size(): " << v.size()
                );

    #ifdef ENABLE_ASSERTS
            for (unsigned long i = 0; i < v.size(); ++i)
            {
                DLIB_ASSERT(v[0].size() == v[i].size() && v[i].size() > 0 && is_col_vector(v[i]),
                        "\t projection_hash create_max_margin_projection_hash()"
                       << "\n\t Invalid arguments were given to this function."
                       << "\n\t m(0).size(): " << v[0].size()
                       << "\n\t m("<<i<<").size(): " << v[i].size()
                       << "\n\t is_col_vector(v["<<i<<"]): " << is_col_vector(v[i])
                    );
            }
    #endif

            running_covariance<matrix<double> > rc;
            for (unsigned long i = 0; i < v.size(); ++i)
                rc.add(matrix_cast<double>(v[i]));

            // compute a whitening matrix
            matrix<double> whiten = trans(chol(pinv(rc.covariance())));
            const matrix<double,0,1> meanval = whiten*rc.mean();

            typedef matrix<double,0,1> sample_type;
            random_subset_selector<sample_type> training_samples;
            random_subset_selector<double> training_labels;
            // We set this up to use enough samples to cover the vector space used by elements
            // of v.
            training_samples.set_max_size(v[0].size()*10);
            training_labels.set_max_size(v[0].size()*10);

            matrix<double> proj(bits, v[0].size());
            matrix<double,0,1> offset(bits);

            // learn the random planes and put them into proj and offset.
            for (int itr = 0; itr < offset.size(); ++itr)
            {
                training_samples.make_empty();
                training_labels.make_empty();
                // pick random training data and give each sample a random label.
                for (unsigned long i = 0; i < v.size(); ++i)
                {
                    training_samples.add(whiten*v[i]-meanval);
                    if (rnd.get_random_double() > 0.5)
                        training_labels.add(+1);
                    else
                        training_labels.add(-1);
                }

                svm_c_linear_dcd_trainer<linear_kernel<sample_type> > trainer;
                trainer.set_c(C);
                decision_function<linear_kernel<sample_type> > df = trainer.train(training_samples, training_labels);
                offset(itr) = -df.b;
                set_rowm(proj,itr) = trans(df.basis_vectors(0));
            }

            return projection_hash(proj*whiten, offset-proj*meanval);
        }

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

        template <
            typename vector_type
            >
        projection_hash create_max_margin_projection_hash (
            const vector_type& v,
            const int bits,
            const double C = 10
        )
        {
            dlib::rand rnd;
            return create_max_margin_projection_hash(v,bits,C,rnd);
        }

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

    }

    #endif // DLIB_CREATE_RANDOM_PROJECTION_HAsH_H__