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

    #include "array2d_kernel_abstract.h"
    #include "../algs.h"
    #include "../interfaces/enumerable.h"
    #include "../serialize.h"
    #include "../geometry/rectangle.h"

    namespace dlib
    {
        template <
            typename T,
            typename mem_manager = default_memory_manager
            >
        class array2d : public enumerable<T>
        {

            /*!
                INITIAL VALUE
                    - nc_ == 0
                    - nr_ == 0
                    - data == 0
                    - at_start_ == true
                    - cur == 0
                    - last == 0

                CONVENTION
                    - nc_ == nc()
                    - nr_ == nc()
                    - if (data != 0) then
                        - last == a pointer to the last element in the data array
                        - data == pointer to an array of nc_*nr_ T objects
                    - else
                        - nc_ == 0
                        - nr_ == 0
                        - data == 0
                        - last == 0

                    - nr_ * nc_ == size()
                    - if (cur == 0) then
                        - current_element_valid() == false
                    - else
                        - current_element_valid() == true
                        - *cur == element()

                    - at_start_ == at_start()
            !*/

            class row_helper;
        public:

            // These typedefs are here for backwards compatibility with older versions of dlib.
            typedef array2d kernel_1a;
            typedef array2d kernel_1a_c;

            typedef T type;
            typedef mem_manager mem_manager_type;
            typedef T*          iterator;
            typedef const T*    const_iterator;

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

            class row
            {
                /*!
                    CONVENTION
                        - nc_ == nc()
                        - for all x < nc_:
                            - (*this)[x] == data[x]
                !*/

                friend class array2d<T,mem_manager>;
                friend class row_helper;

            public:
                long nc (
                ) const { return nc_; }

                const T& operator[] (
                    long column
                ) const
                {
                    // make sure requires clause is not broken
                    DLIB_ASSERT(column < nc() && column >= 0,
                        "\tconst T& array2d::operator[](long column) const"
                        << "\n\tThe column index given must be less than the number of columns."
                        << "\n\tthis:    " << this
                        << "\n\tcolumn:  " << column
                        << "\n\tnc(): " << nc()
                    );

                    return data[column];
                }

                T& operator[] (
                    long column
                )
                {
                    // make sure requires clause is not broken
                    DLIB_ASSERT(column < nc() && column >= 0,
                        "\tT& array2d::operator[](long column)"
                        << "\n\tThe column index given must be less than the number of columns."
                        << "\n\tthis:    " << this
                        << "\n\tcolumn:  " << column
                        << "\n\tnc(): " << nc()
                    );

                    return data[column];
                }

            private:

                row(T* data_, long cols) : data(data_), nc_(cols) {}
                row(row&& r) = default;
                row& operator=(row&& r) = default;

                T* data = nullptr;
                long nc_ = 0;

                // restricted functions
                row(const row&) = delete;
                row& operator=(const row&) = delete;
            };

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

            array2d (
            ) :
                data(0),
                nc_(0),
                nr_(0),
                cur(0),
                last(0),
                at_start_(true)
            {
            }

            array2d(
                long rows,
                long cols
            ) :
                data(0),
                nc_(0),
                nr_(0),
                cur(0),
                last(0),
                at_start_(true)
            {
                // make sure requires clause is not broken
                DLIB_ASSERT((cols >= 0 && rows >= 0),
                            "\t array2d::array2d(long rows, long cols)"
                            << "\n\t The array2d can't have negative rows or columns."
                            << "\n\t this: " << this
                            << "\n\t cols: " << cols
                            << "\n\t rows: " << rows
                );

                set_size(rows,cols);
            }

            array2d(const array2d&) = delete;        // copy constructor
            array2d& operator=(const array2d&) = delete;    // assignment operator

    #ifdef DLIB_HAS_RVALUE_REFERENCES
            array2d(array2d&& item) : array2d()
            {
                swap(item);
            }

            array2d& operator= (
                array2d&& rhs
            )
            {
                swap(rhs);
                return *this;
            }
    #endif

            virtual ~array2d (
            ) { clear(); }

            long nc (
            ) const { return nc_; }

            long nr (
            ) const { return nr_; }

            row operator[] (
                long row_
            )
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(row_ < nr() && row_ >= 0,
                    "\trow array2d::operator[](long row_)"
                    << "\n\tThe row index given must be less than the number of rows."
                    << "\n\tthis:     " << this
                    << "\n\trow_:      " << row_
                    << "\n\tnr(): " << nr()
                    );

                return row(data+row_*nc_, nc_);
            }

            const row operator[] (
                long row_
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(row_ < nr() && row_ >= 0,
                    "\tconst row array2d::operator[](long row_) const"
                    << "\n\tThe row index given must be less than the number of rows."
                    << "\n\tthis:     " << this
                    << "\n\trow_:      " << row_
                    << "\n\tnr(): " << nr()
                );

                return row(data+row_*nc_, nc_);
            }

            void swap (
                array2d& item
            )
            {
                exchange(data,item.data);
                exchange(nr_,item.nr_);
                exchange(nc_,item.nc_);
                exchange(at_start_,item.at_start_);
                exchange(cur,item.cur);
                exchange(last,item.last);
                pool.swap(item.pool);
            }

            void clear (
            )
            {
                if (data != 0)
                {
                    pool.deallocate_array(data);
                    nc_ = 0;
                    nr_ = 0;
                    data = 0;
                    at_start_ = true;
                    cur = 0;
                    last = 0;
                }
            }

            void set_size (
                long rows,
                long cols
            );

            bool at_start (
            ) const { return at_start_; }

            void reset (
            ) const { at_start_ = true; cur = 0; }

            bool current_element_valid (
            ) const { return (cur != 0); }

            const T& element (
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(current_element_valid() == true,
                    "\tconst T& array2d::element()()"
                    << "\n\tYou can only call element() when you are at a valid one."
                    << "\n\tthis:    " << this
                );

                return *cur;
            }

            T& element (
            )
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(current_element_valid() == true,
                             "\tT& array2d::element()()"
                             << "\n\tYou can only call element() when you are at a valid one."
                             << "\n\tthis:    " << this
                );

                return *cur;
            }

            bool move_next (
            ) const
            {
                if (cur != 0)
                {
                    if (cur != last)
                    {
                        ++cur;
                        return true;
                    }
                    cur = 0;
                    return false;
                }
                else if (at_start_)
                {
                    cur = data;
                    at_start_ = false;
                    return (data != 0);
                }
                else
                {
                    return false;
                }
            }

            size_t size (
            ) const { return static_cast<size_t>(nc_) * static_cast<size_t>(nr_); }

            long width_step (
            ) const
            {
                return nc_*sizeof(T);
            }

            iterator begin()
            {
                return data;
            }

            iterator end()
            {
                return data+size();
            }

            const_iterator begin()  const
            {
                return data;
            }

            const_iterator end() const
            {
                return data+size();
            }

        private:

            T* data;
            long nc_;
            long nr_;

            typename mem_manager::template rebind<T>::other pool;
            mutable T* cur;
            T* last;
            mutable bool at_start_;

        };

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

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

        template <
            typename T,
            typename mem_manager
            >
        void serialize (
            const array2d<T,mem_manager>& item,
            std::ostream& out
        )
        {
            try
            {
                // The reason the serialization is a little funny is because we are trying to
                // maintain backwards compatibility with an older serialization format used by
                // dlib while also encoding things in a way that lets the array2d and matrix
                // objects have compatible serialization formats.
                serialize(-item.nr(),out);
                serialize(-item.nc(),out);

                item.reset();
                while (item.move_next())
                    serialize(item.element(),out);
                item.reset();
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while serializing object of type array2d");
            }
        }

        template <
            typename T,
            typename mem_manager
            >
        void deserialize (
            array2d<T,mem_manager>& item,
            std::istream& in
        )
        {
            try
            {
                long nr, nc;
                deserialize(nr,in);
                deserialize(nc,in);

                // this is the newer serialization format
                if (nr < 0 || nc < 0)
                {
                    nr *= -1;
                    nc *= -1;
                }
                else
                {
                    std::swap(nr,nc);
                }

                item.set_size(nr,nc);

                while (item.move_next())
                    deserialize(item.element(),in);
                item.reset();
            }
            catch (serialization_error& e)
            {
                item.clear();
                throw serialization_error(e.info + "\n   while deserializing object of type array2d");
            }
        }

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

        template <
            typename T,
            typename mem_manager
            >
        void array2d<T,mem_manager>::
        set_size (
            long rows,
            long cols
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT((cols >= 0 && rows >= 0) ,
                   "\tvoid array2d::set_size(long rows, long cols)"
                   << "\n\tThe array2d can't have negative rows or columns."
                   << "\n\tthis: " << this
                   << "\n\tcols: " << cols
                   << "\n\trows: " << rows
            );

            // set the enumerator back at the start
            at_start_ = true;
            cur = 0;

            // don't do anything if we are already the right size.
            if (nc_ == cols && nr_ == rows)
            {
                return;
            }

            nc_ = cols;
            nr_ = rows;

            // free any existing memory
            if (data != 0)
            {
                pool.deallocate_array(data);
                data = 0;
            }

            // now setup this object to have the new size
            try
            {
                if (nr_ > 0)
                {
                    data = pool.allocate_array(nr_*nc_);
                    last = data + nr_*nc_ - 1;
                }
            }
            catch (...)
            {
                if (data)
                    pool.deallocate_array(data);

                data = 0;
                nc_ = 0;
                nr_ = 0;
                last = 0;
                throw;
            }
        }

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

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

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

    }

    #endif // DLIB_ARRAY2D_KERNEl_1_