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

    #include "array_kernel_abstract.h"
    #include "../interfaces/enumerable.h"
    #include "../algs.h"
    #include "../serialize.h"
    #include "../sort.h"
    #include "../is_kind.h"

    namespace dlib
    {

        template <
            typename T,
            typename mem_manager = default_memory_manager
            >
        class array : public enumerable<T>
        {

            /*!
                INITIAL VALUE
                    - array_size == 0
                    - max_array_size == 0
                    - array_elements == 0
                    - pos == 0
                    - last_pos == 0
                    - _at_start == true

                CONVENTION
                    - array_size == size()
                    - max_array_size == max_size()
                    - if (max_array_size > 0)
                        - array_elements == pointer to max_array_size elements of type T
                    - else
                        - array_elements == 0

                    - if (array_size > 0)
                        - last_pos == array_elements + array_size - 1
                    - else
                        - last_pos == 0

                    - at_start() == _at_start
                    - current_element_valid() == pos != 0
                    - if (current_element_valid()) then
                        - *pos == element()
            !*/

        public:

            // These typedefs are here for backwards compatibility with old versions of dlib.
            typedef array kernel_1a;
            typedef array kernel_1a_c;
            typedef array kernel_2a;
            typedef array kernel_2a_c;
            typedef array sort_1a;
            typedef array sort_1a_c;
            typedef array sort_1b;
            typedef array sort_1b_c;
            typedef array sort_2a;
            typedef array sort_2a_c;
            typedef array sort_2b;
            typedef array sort_2b_c;
            typedef array expand_1a;
            typedef array expand_1a_c;
            typedef array expand_1b;
            typedef array expand_1b_c;
            typedef array expand_1c;
            typedef array expand_1c_c;
            typedef array expand_1d;
            typedef array expand_1d_c;

            typedef T type;
            typedef T value_type;
            typedef mem_manager mem_manager_type;

            array (
            ) :
                array_size(0),
                max_array_size(0),
                array_elements(0),
                pos(0),
                last_pos(0),
                _at_start(true)
            {}

            array(const array&) = delete;
            array& operator=(array&) = delete;

            array(
                array&& item
            ) : array()
            {
                swap(item);
            }

            array& operator=(
                array&& item
            )
            {
                swap(item);
                return *this;
            }

            explicit array (
                size_t new_size
            ) :
                array_size(0),
                max_array_size(0),
                array_elements(0),
                pos(0),
                last_pos(0),
                _at_start(true)
            {
                resize(new_size);
            }

            ~array (
            );

            void clear (
            );

            inline const T& operator[] (
                size_t pos
            ) const;

            inline T& operator[] (
                size_t pos
            );

            void set_size (
                size_t size
            );

            inline size_t max_size(
            ) const;

            void set_max_size(
                size_t max
            );

            void swap (
                array& item
            );

            // functions from the enumerable interface
            inline size_t size (
            ) const;

            inline bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

            inline const T& element (
            ) const;

            inline T& element (
            );

            bool move_next (
            ) const;

            void sort (
            );

            void resize (
                size_t new_size
            );

            const T& back (
            ) const;

            T& back (
            );

            void pop_back (
            );

            void pop_back (
                T& item
            );

            void push_back (
                T& item
            );

            void push_back (
                T&& item
            );

            typedef T* iterator;
            typedef const T* const_iterator;
            iterator                begin()                         { return array_elements; }
            const_iterator          begin() const                   { return array_elements; }
            iterator                end()                           { return array_elements+array_size; }
            const_iterator          end() const                     { return array_elements+array_size; }

        private:

            typename mem_manager::template rebind<T>::other pool;

            // data members
            size_t array_size;
            size_t max_array_size;
            T* array_elements;

            mutable T* pos;
            T* last_pos;
            mutable bool _at_start;

        };

        template <
            typename T,
            typename mem_manager
            >
        inline void swap (
            array<T,mem_manager>& a,
            array<T,mem_manager>& b
        ) { a.swap(b); }

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

        template <
            typename T,
            typename mem_manager
            >
        void serialize (
            const array<T,mem_manager>& item,
            std::ostream& out
        )
        {
            try
            {
                serialize(item.max_size(),out);
                serialize(item.size(),out);

                for (size_t i = 0; i < item.size(); ++i)
                    serialize(item[i],out);
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while serializing object of type array");
            }
        }

        template <
            typename T,
            typename mem_manager
            >
        void deserialize (
            array<T,mem_manager>& item,
            std::istream& in
        )
        {
            try
            {
                size_t max_size, size;
                deserialize(max_size,in);
                deserialize(size,in);
                item.set_max_size(max_size);
                item.set_size(size);
                for (size_t i = 0; i < size; ++i)
                    deserialize(item[i],in);
            }
            catch (serialization_error& e)
            {
                item.clear();
                throw serialization_error(e.info + "\n   while deserializing object of type array");
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename T,
            typename mem_manager
            >
        array<T,mem_manager>::
        ~array (
        )
        {
            if (array_elements)
            {
                pool.deallocate_array(array_elements);
            }
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        clear (
        )
        {
            reset();
            last_pos = 0;
            array_size = 0;
            if (array_elements)
            {
                pool.deallocate_array(array_elements);
            }
            array_elements = 0;
            max_array_size = 0;

        }

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

        template <
            typename T,
            typename mem_manager
            >
        const T& array<T,mem_manager>::
        operator[] (
            size_t pos
        ) const
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( pos < this->size() ,
                "\tconst T& array::operator[]"
                << "\n\tpos must < size()"
                << "\n\tpos: " << pos
                << "\n\tsize(): " << this->size()
                << "\n\tthis: " << this
                );

            return array_elements[pos];
        }

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

        template <
            typename T,
            typename mem_manager
            >
        T& array<T,mem_manager>::
        operator[] (
            size_t pos
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( pos < this->size() ,
                "\tT& array::operator[]"
                << "\n\tpos must be < size()"
                << "\n\tpos: " << pos
                << "\n\tsize(): " << this->size()
                << "\n\tthis: " << this
                );

            return array_elements[pos];
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        set_size (
            size_t size
        )
        {
            // make sure requires clause is not broken
            DLIB_CASSERT(( size <= this->max_size() ),
                "\tvoid array::set_size"
                << "\n\tsize must be <= max_size()"
                << "\n\tsize: " << size
                << "\n\tmax size: " << this->max_size()
                << "\n\tthis: " << this
                );

            reset();
            array_size = size;
            if (size > 0)
                last_pos = array_elements + size - 1;
            else
                last_pos = 0;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        size_t array<T,mem_manager>::
        size (
        ) const
        {
            return array_size;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        set_max_size(
            size_t max
        )
        {
            reset();
            array_size = 0;
            last_pos = 0;
            if (max != 0)
            {
                // if new max size is different
                if (max != max_array_size)
                {
                    if (array_elements)
                    {
                        pool.deallocate_array(array_elements);
                    }
                    // try to get more memroy
                    try { array_elements = pool.allocate_array(max); }
                    catch (...) { array_elements = 0;  max_array_size = 0; throw; }
                    max_array_size = max;
                }

            }
            // if the array is being made to be zero
            else
            {
                if (array_elements)
                    pool.deallocate_array(array_elements);
                max_array_size = 0;
                array_elements = 0;
            }
        }

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

        template <
            typename T,
            typename mem_manager
            >
        size_t array<T,mem_manager>::
        max_size (
        ) const
        {
            return max_array_size;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        swap (
            array<T,mem_manager>& item
        )
        {
            auto             array_size_temp        = item.array_size;
            auto             max_array_size_temp    = item.max_array_size;
            T*               array_elements_temp    = item.array_elements;

            item.array_size         = array_size;
            item.max_array_size     = max_array_size;
            item.array_elements     = array_elements;

            array_size        = array_size_temp;
            max_array_size    = max_array_size_temp;
            array_elements    = array_elements_temp;

            exchange(_at_start,item._at_start);
            exchange(pos,item.pos);
            exchange(last_pos,item.last_pos);
            pool.swap(item.pool);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //           enumerable function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename T,
            typename mem_manager
            >
        bool array<T,mem_manager>::
        at_start (
        ) const
        {
            return _at_start;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        reset (
        ) const
        {
            _at_start = true;
            pos = 0;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        bool array<T,mem_manager>::
        current_element_valid (
        ) const
        {
            return pos != 0;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        const T& array<T,mem_manager>::
        element (
        ) const
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(this->current_element_valid(),
                "\tconst T& array::element()"
                << "\n\tThe current element must be valid if you are to access it."
                << "\n\tthis: " << this
                );

            return *pos;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        T& array<T,mem_manager>::
        element (
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(this->current_element_valid(),
                "\tT& array::element()"
                << "\n\tThe current element must be valid if you are to access it."
                << "\n\tthis: " << this
                );

            return *pos;
        }

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

        template <
            typename T,
            typename mem_manager
            >
        bool array<T,mem_manager>::
        move_next (
        ) const
        {
            if (!_at_start)
            {
                if (pos < last_pos)
                {
                    ++pos;
                    return true;
                }
                else
                {
                    pos = 0;
                    return false;
                }
            }
            else
            {
                _at_start = false;
                if (array_size > 0)
                {
                    pos = array_elements;
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //                              Yet more functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        sort (
        )
        {
            if (this->size() > 1)
            {
                // call the quick sort function for arrays that is in algs.h
                dlib::qsort_array(*this,0,this->size()-1);
            }
            this->reset();
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        resize (
            size_t new_size
        )
        {
            if (this->max_size() < new_size)
            {
                array temp;
                temp.set_max_size(new_size);
                temp.set_size(new_size);
                for (size_t i = 0; i < this->size(); ++i)
                {
                    exchange((*this)[i],temp[i]);
                }
                temp.swap(*this);
            }
            else
            {
                this->set_size(new_size);
            }
        }

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

        template <
            typename T,
            typename mem_manager
            >
        T& array<T,mem_manager>::
        back (
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( this->size() > 0 ,
                          "\tT& array::back()"
                          << "\n\tsize() must be bigger than 0"
                          << "\n\tsize(): " << this->size()
                          << "\n\tthis:   " << this
            );

            return (*this)[this->size()-1];
        }

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

        template <
            typename T,
            typename mem_manager
            >
        const T& array<T,mem_manager>::
        back (
        ) const
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( this->size() > 0 ,
                          "\tconst T& array::back()"
                          << "\n\tsize() must be bigger than 0"
                          << "\n\tsize(): " << this->size()
                          << "\n\tthis:   " << this
            );

            return (*this)[this->size()-1];
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        pop_back (
            T& item
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( this->size() > 0 ,
                          "\tvoid array::pop_back()"
                          << "\n\tsize() must be bigger than 0"
                          << "\n\tsize(): " << this->size()
                          << "\n\tthis:   " << this
            );

            exchange(item,(*this)[this->size()-1]);
            this->set_size(this->size()-1);
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        pop_back (
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT( this->size() > 0 ,
                          "\tvoid array::pop_back()"
                          << "\n\tsize() must be bigger than 0"
                          << "\n\tsize(): " << this->size()
                          << "\n\tthis:   " << this
            );

            this->set_size(this->size()-1);
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        push_back (
            T& item
        )
        {
            if (this->max_size() == this->size())
            {
                // double the size of the array
                array temp;
                temp.set_max_size(this->size()*2 + 1);
                temp.set_size(this->size()+1);
                for (size_t i = 0; i < this->size(); ++i)
                {
                    exchange((*this)[i],temp[i]);
                }
                exchange(item,temp[temp.size()-1]);
                temp.swap(*this);
            }
            else
            {
                this->set_size(this->size()+1);
                exchange(item,(*this)[this->size()-1]);
            }
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array<T,mem_manager>::
        push_back (
            T&& item
        ) { push_back(item); }

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

        template <typename T, typename MM>
        struct is_array <array<T,MM> >
        {
            const static bool value = true;
        };

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

    }

    #endif // DLIB_ARRAY_KERNEl_2_