// Copyright (C) 2010  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#undef DLIB_RANDOM_SUBSeT_SELECTOR_ABSTRACT_H_
#ifdef DLIB_RANDOM_SUBSeT_SELECTOR_ABSTRACT_H_

#include <vector>
#include "../rand/rand_kernel_abstract.h"
#include "../algs.h"
#include "../string.h"

namespace dlib
{
    template <
        typename T,
        typename Rand_type = dlib::rand
        >
    class random_subset_selector
    {
        /*!
            REQUIREMENTS ON T
                T must be a copyable type

            REQUIREMENTS ON Rand_type
                must be an implementation of dlib/rand/rand_kernel_abstract.h

            INITIAL VALUE
                - size() == 0
                - max_size() == 0
                - next_add_accepts() == false

            WHAT THIS OBJECT REPRESENTS
                This object is a tool to help you select a random subset of a large body of data.  
                In particular, it is useful when the body of data is too large to fit into memory.

                So for example, suppose you have 1000000 data samples and you want to select a
                random subset of size 1000.   Then you could do that as follows:
                    
                    random_subset_selector<sample_type> rand_subset;
                    rand_subset.set_max_size(1000)
                    for (int i = 0; i < 1000000; ++i)
                        rand_subset.add( get_next_data_sample());

              
                At the end of the for loop you will have your random subset of 1000 samples.  And by
                random I mean that each of the 1000000 data samples has an equal chance of ending
                up in the rand_subset object.


                Note that the above example calls get_next_data_sample() for each data sample.  This 
                may be inefficient since most of the data samples are just ignored.  An alternative 
                method that doesn't require you to load each sample can also be used.  Consider the 
                following:

                    random_subset_selector<sample_type> rand_subset;
                    rand_subset.set_max_size(1000)
                    for (int i = 0; i < 1000000; ++i)
                        if (rand_subset.next_add_accepts())
                            rand_subset.add(get_data_sample(i));
                        else
                            rand_subset.add() 

                In the above example we only actually fetch the data sample into memory if we
                know that the rand_subset would include it into the random subset.  Otherwise,
                we can just call the empty add().
                

                Finally, note that the random_subset_selector uses a deterministic pseudo-random
                number generator under the hood.  Moreover, the default constructor always seeds
                the random number generator in the same way.  So unless you call set_seed() 
                each instance of the random_subset_selector will function identically.
        !*/
    public:
        typedef T type;
        typedef T value_type;
        typedef default_memory_manager mem_manager_type;
        typedef Rand_type rand_type;

        typedef typename std::vector<T>::iterator iterator;
        typedef typename std::vector<T>::const_iterator const_iterator;

        random_subset_selector (
        );
        /*!
            ensures
                - this object is properly initialized
        !*/

        void set_seed(
            const std::string& value
        );
        /*!
            ensures
                - sets the seed of the random number generator that is embedded in
                  this object to the given value.
        !*/

        void make_empty (
        );
        /*!
            ensures
                - #size() == 0
        !*/

        unsigned long size (
        ) const;
        /*!
            ensures
                - returns the number of items of type T currently contained in this object
        !*/

        void set_max_size (
            unsigned long new_max_size
        );
        /*!
            ensures
                - #max_size() == new_max_size
                - #size() == 0
        !*/

        unsigned long max_size (
        ) const;
        /*!
            ensures
                - returns the maximum allowable size for this object
        !*/

        T& operator[] (
            unsigned long idx
        );
        /*!
            requires
                - idx < size()
            ensures
                - returns a non-const reference to the idx'th element of this object
        !*/

        const T& operator[] (
            unsigned long idx
        ) const;
        /*!
            requires
                - idx < size()
            ensures
                - returns a const reference to the idx'th element of this object
        !*/

        bool next_add_accepts (
        ) const;
        /*!
            ensures
                - if (the next call to add(item) will result in item being included
                  into *this) then
                    - returns true
                    - Note that the next item will always be accepted if size() < max_size().
                - else
                    - returns false
                    - Note that the next item will never be accepted if max_size() == 0.
        !*/

        void add (
            const T& new_item
        );
        /*!
            ensures
                - if (next_add_accepts()) then
                    - places new_item into *this object at a random location
                    - if (size() < max_size()) then
                        - #size() == size() + 1
                - #next_add_accepts() == The updated information about the acceptance
                  of the next call to add()
        !*/

        void add (
        );
        /*!
            requires
                - next_add_accepts() == false
            ensures
                - This function does nothing but update the value of #next_add_accepts()
        !*/

        iterator begin(
        );
        /*!
            ensures
                - if (size() > 0) then
                    - returns an iterator referring to the first element in 
                      this container.
                - else
                    - returns end()
        !*/
        
        const_iterator begin(
        ) const;
        /*!
            ensures
                - if (size() > 0) then
                    - returns a const_iterator referring to the first element in 
                      this container.
                - else
                    - returns end()
        !*/

        iterator end(
        ); 
        /*!
            ensures
                - returns an iterator that represents one past the end of
                  this container
        !*/

        const_iterator end(
        ) const;
        /*!
            ensures
                - returns an iterator that represents one past the end of
                  this container
        !*/

        const std::vector<T>& to_std_vector(
        ) const;
        /*!
            ensures
                - returns a const reference to the underlying std::vector<T> that contains
                  all elements in this object.  That is, this function returns a vector, V,
                  which has the following properties:  
                    - V.size()  == this->size()
                    - V.begin() == this->begin()
                    - V.end()   == this->end()
        !*/

        void swap (
            random_subset_selector& item
        );
        /*!
            ensures
                - swaps *this and item
        !*/

    };

    template <
        typename T,
        typename rand_type 
        >
    void swap (
        random_subset_selector<T,rand_type>& a,
        random_subset_selector<T,rand_type>& b
    ) { a.swap(b); }
    /*!
        provides global swap support
    !*/

    template <
        typename T,
        typename rand_type 
        >
    void serialize (
        const random_subset_selector<T,rand_type>& item,
        std::ostream& out 
    );   
    /*!
        provides serialization support 
    !*/

    template <
        typename T,
        typename rand_type 
        >
    void deserialize (
        random_subset_selector<T,rand_type>& item,
        std::istream& in
    );   
    /*!
        provides deserialization support 
    !*/

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

    template <
        typename T,
        typename alloc
        >
    random_subset_selector<T> randomly_subsample (
        const std::vector<T,alloc>& samples,
        unsigned long num
    );
    /*!
        ensures
            - returns a random subset R such that:
                - R contains a random subset of the given samples
                - R.size() == min(num, samples.size())
                - R.max_size() == num
            - The random number generator used by this function will always be
              initialized in the same way.  I.e. this function will always pick
              the same random subsample if called multiple times.
    !*/

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

    template <
        typename T,
        typename alloc,
        typename U
        >
    random_subset_selector<T> randomly_subsample (
        const std::vector<T,alloc>& samples,
        unsigned long num,
        const U& random_seed
    );
    /*!
        requires
            - random_seed must be convertible to a string by dlib::cast_to_string()
        ensures
            - returns a random subset R such that:
                - R contains a random subset of the given samples
                - R.size() == min(num, samples.size())
                - R.max_size() == num
            - The given random_seed will be used to initialize the random number
              generator used by this function.
    !*/

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

    template <
        typename T
        >
    random_subset_selector<T> randomly_subsample (
        const random_subset_selector<T>& samples,
        unsigned long num
    );
    /*!
        ensures
            - returns a random subset R such that:
                - R contains a random subset of the given samples
                - R.size() == min(num, samples.size())
                - R.max_size() == num
            - The random number generator used by this function will always be
              initialized in the same way.  I.e. this function will always pick
              the same random subsample if called multiple times.
    !*/

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

    template <
        typename T,
        typename U
        >
    random_subset_selector<T> randomly_subsample (
        const random_subset_selector<T>& samples,
        unsigned long num,
        const U& random_seed
    );
    /*!
        requires
            - random_seed must be convertible to a string by dlib::cast_to_string()
        ensures
            - returns a random subset R such that:
                - R contains a random subset of the given samples
                - R.size() == min(num, samples.size())
                - R.max_size() == num
            - The given random_seed will be used to initialize the random number
              generator used by this function.
    !*/

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

    template <
        typename T
        >
    const matrix_exp mat (
        const random_subset_selector<T>& m 
    );
    /*!
        ensures
            - returns a matrix R such that:
                - is_col_vector(R) == true 
                - R.size() == m.size()
                - for all valid r:
                  R(r) == m[r]
    !*/

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

}

#endif // DLIB_RANDOM_SUBSeT_SELECTOR_ABSTRACT_H_