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