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

    #include "hashes_abstract.h"
    #include "../hash.h"
    #include "../matrix.h"

    namespace dlib
    {

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

        class hash_similar_angles_64
        {
        public:
            hash_similar_angles_64 (
            ) : seed(0) {}

            hash_similar_angles_64 (
                const uint64 seed_
            ) : seed(seed_) {}

            uint64 get_seed (
            ) const { return seed; }

            typedef uint64 result_type;

            template <
                typename sparse_vector_type
                >
            typename disable_if<is_matrix<sparse_vector_type>,uint64>::type operator() (
                const sparse_vector_type& v
            ) const
            {
                typedef typename sparse_vector_type::value_type::second_type scalar_type;

                uint64 temp = 0;
                for (int i = 0; i < 64; ++i)
                {
                    // compute the dot product between v and a Gaussian random vector.
                    scalar_type val = 0;
                    for (typename sparse_vector_type::const_iterator j = v.begin(); j != v.end(); ++j)
                        val += j->second*gaussian_random_hash(j->first, i, seed);

                    if (val > 0)
                        temp |= 1;
                    temp <<= 1;
                }
                return temp;
            }

            template <typename EXP>
            uint64 operator() (
                const matrix_exp<EXP>& v
            ) const
            {
                typedef typename EXP::type T;
                uint64 temp = 0;
                for (unsigned long i = 0; i < 64; ++i)
                {
                    if (dot(matrix_cast<T>(gaussian_randm(v.size(),1,i+seed*64)), v) > 0)
                        temp |= 1;
                    temp <<= 1;
                }
                return temp;
            }

            unsigned int distance (
                const result_type& a,
                const result_type& b
            ) const
            {
                return hamming_distance(a,b);
            }

        private:
            const uint64 seed;
        };

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

        class hash_similar_angles_128
        {
        public:
            hash_similar_angles_128 (
            ) : seed(0),hasher1(0), hasher2(1) {}

            hash_similar_angles_128 (
                const uint64 seed_
            ) : seed(seed_),hasher1(2*seed),hasher2(2*seed+1) {}

            uint64 get_seed (
            ) const { return seed; }

            typedef std::pair<uint64,uint64> result_type;

            template <
                typename vector_type
                >
            result_type operator() (
                const vector_type& v
            ) const
            {
                return std::make_pair(hasher1(v), hasher2(v));
            }

            unsigned int distance (
                const result_type& a,
                const result_type& b
            ) const
            {
                return hamming_distance(a.first,b.first) +
                       hamming_distance(a.second,b.second);
            }

        private:
            const uint64 seed;
            hash_similar_angles_64 hasher1;
            hash_similar_angles_64 hasher2;

        };

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

        class hash_similar_angles_256
        {
        public:
            hash_similar_angles_256 (
            ) : seed(0), hasher1(0), hasher2(1) {}

            hash_similar_angles_256 (
                const uint64 seed_
            ) : seed(seed_),hasher1(2*seed),hasher2(2*seed+1) {}

            uint64 get_seed (
            ) const { return seed; }

            typedef std::pair<uint64,uint64> hash128_type;
            typedef std::pair<hash128_type,hash128_type> result_type;

            template <
                typename vector_type
                >
            result_type operator() (
                const vector_type& v
            ) const
            {
                return std::make_pair(hasher1(v), hasher2(v));
            }

            unsigned int distance (
                const result_type& a,
                const result_type& b
            ) const
            {
                return hasher1.distance(a.first,b.first) +
                       hasher1.distance(a.second,b.second);
            }

        private:
            const uint64 seed;
            hash_similar_angles_128 hasher1;
            hash_similar_angles_128 hasher2;

        };

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

        class hash_similar_angles_512
        {
        public:
            hash_similar_angles_512 (
            ) : seed(0), hasher1(0), hasher2(1) {}

            hash_similar_angles_512 (
                const uint64 seed_
            ) : seed(seed_),hasher1(2*seed),hasher2(2*seed+1) {}

            uint64 get_seed (
            ) const { return seed; }

            typedef hash_similar_angles_256::result_type hash256_type;
            typedef std::pair<hash256_type,hash256_type> result_type;

            template <
                typename vector_type
                >
            result_type operator() (
                const vector_type& v
            ) const
            {
                return std::make_pair(hasher1(v), hasher2(v));
            }

            unsigned int distance (
                const result_type& a,
                const result_type& b
            ) const
            {
                return hasher1.distance(a.first,b.first) +
                       hasher1.distance(a.second,b.second);
            }

        private:
            const uint64 seed;
            hash_similar_angles_256 hasher1;
            hash_similar_angles_256 hasher2;
        };

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

    }

    #endif // DLIB_LSH_HAShES_Hh_