// 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_