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

    #include "matrix_mat_abstract.h"
    #include "../stl_checked.h"
    #include <vector>
    #include "matrix_op.h"
    #include "../array2d.h"
    #include "../array.h"
    #include "../image_processing/generic_image.h"

    namespace dlib
    {

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

        template <
            typename EXP
            >
        const matrix_exp<EXP>& mat (
            const matrix_exp<EXP>& m
        )
        {
            return m;
        }

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

        template <typename image_type, typename pixel_type>
        struct op_image_to_mat : does_not_alias
        {
            op_image_to_mat( const image_type& img) : imgview(img){}

            const_image_view<image_type> imgview;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef pixel_type type;
            typedef const pixel_type& const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return imgview[r][c]; }

            long nr () const { return imgview.nr(); }
            long nc () const { return imgview.nc(); }
        };

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

        template <
            typename image_type
            > // The reason we disable this if it is a matrix is because this matrix_op claims
              // to not alias any matrix.  But obviously that would be a problem if we let it
              // take a matrix.
        const typename disable_if<is_matrix<image_type>,matrix_op<op_image_to_mat<image_type, typename image_traits<image_type>::pixel_type> > >::type mat (
            const image_type& img
        )
        {
            typedef op_image_to_mat<image_type, typename image_traits<image_type>::pixel_type> op;
            return matrix_op<op>(op(img));
        }

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

        template <typename image_type>
        struct op_image_view_to_mat : does_not_alias
        {
            op_image_view_to_mat( const image_view<image_type>& img) : imgview(img){}

            typedef typename image_traits<image_type>::pixel_type pixel_type;

            const image_view<image_type>& imgview;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef pixel_type type;
            typedef const pixel_type& const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return imgview[r][c]; }

            long nr () const { return imgview.nr(); }
            long nc () const { return imgview.nc(); }
        };

        template <
            typename image_type
            >
        const matrix_op<op_image_view_to_mat<image_type> > mat (
            const image_view<image_type>& img
        )
        {
            typedef op_image_view_to_mat<image_type> op;
            return matrix_op<op>(op(img));
        }

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

        template <typename image_type>
        struct op_const_image_view_to_mat : does_not_alias
        {
            op_const_image_view_to_mat( const const_image_view<image_type>& img) : imgview(img){}

            typedef typename image_traits<image_type>::pixel_type pixel_type;

            const const_image_view<image_type>& imgview;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef pixel_type type;
            typedef const pixel_type& const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return imgview[r][c]; }

            long nr () const { return imgview.nr(); }
            long nc () const { return imgview.nc(); }
        };

        template <
            typename image_type
            >
        const matrix_op<op_const_image_view_to_mat<image_type> > mat (
            const const_image_view<image_type>& img
        )
        {
            typedef op_const_image_view_to_mat<image_type> op;
            return matrix_op<op>(op(img));
        }

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

        template <typename T>
        struct op_array_to_mat : does_not_alias
        {
            op_array_to_mat( const T& vect_) : vect(vect_){}

            const T& vect;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 1;
            typedef typename T::type type;
            typedef const typename T::type& const_ret_type;
            typedef typename T::mem_manager_type mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long  ) const { return vect[r]; }

            long nr () const { return vect.size(); }
            long nc () const { return 1; }
        };

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

        template <
            typename T,
            typename MM
            >
        const matrix_op<op_array_to_mat<array<T,MM> > > mat (
            const array<T,MM>& m
        )
        {
            typedef op_array_to_mat<array<T,MM> > op;
            return matrix_op<op>(op(m));
        }

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

        namespace impl
        {
            template <typename U>
            struct not_bool { typedef U type; };
            template <>
            struct not_bool<const bool&> { typedef bool type; };
        }

        template <typename T>
        struct op_std_vect_to_mat : does_not_alias
        {
            op_std_vect_to_mat( const T& vect_) : vect(vect_){}

            const T& vect;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 1;
            typedef typename T::value_type type;
            // Since std::vector returns a proxy for bool types we need to make sure we don't
            // return an element by reference if it is a bool type.
            typedef typename impl::not_bool<const typename T::value_type&>::type const_ret_type;

            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long ) const { return vect[r]; }

            long nr () const { return vect.size(); }
            long nc () const { return 1; }
        };

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

        template <
            typename value_type,
            typename alloc
            >
        const matrix_op<op_std_vect_to_mat<std::vector<value_type,alloc> > > mat (
            const std::vector<value_type,alloc>& vector
        )
        {
            typedef op_std_vect_to_mat<std::vector<value_type,alloc> > op;
            return matrix_op<op>(op(vector));
        }

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

        template <
            typename value_type,
            typename alloc
            >
        const matrix_op<op_std_vect_to_mat<std_vector_c<value_type,alloc> > > mat (
            const std_vector_c<value_type,alloc>& vector
        )
        {
            typedef op_std_vect_to_mat<std_vector_c<value_type,alloc> > op;
            return matrix_op<op>(op(vector));
        }

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

        template <typename T>
        struct op_pointer_to_mat;

        template <typename T>
        struct op_pointer_to_col_vect
        {
            op_pointer_to_col_vect(
                const T* ptr_,
                const long size_
            ) : ptr(ptr_), size(size_){}

            const T* ptr;
            const long size;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 1;
            typedef T type;
            typedef const T& const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long ) const { return ptr[r]; }

            long nr () const { return size; }
            long nc () const { return 1; }

            template <typename U> bool aliases               ( const matrix_exp<U>& ) const { return false; }
            template <typename U> bool destructively_aliases ( const matrix_exp<U>& ) const { return false; }

            template <long num_rows, long num_cols, typename mem_manager, typename layout>
            bool aliases (
                const matrix_exp<matrix<T,num_rows,num_cols, mem_manager,layout> >& item
            ) const
            {
                if (item.size() == 0)
                    return false;
                else
                    return (ptr == &item(0,0));
            }

            inline bool aliases (
                const matrix_exp<matrix_op<op_pointer_to_mat<T> > >& item
            ) const;

            bool aliases (
                const matrix_exp<matrix_op<op_pointer_to_col_vect<T> > >& item
            ) const
            {
                return item.ref().op.ptr == ptr;
            }
        };

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

        template <
            typename T
            >
        const matrix_op<op_pointer_to_col_vect<T> > mat (
            const T* ptr,
            long nr
        )
        {
            DLIB_ASSERT(nr >= 0 ,
                        "\tconst matrix_exp mat(ptr, nr)"
                        << "\n\t nr must be >= 0"
                        << "\n\t nr: " << nr
            );
            typedef op_pointer_to_col_vect<T> op;
            return matrix_op<op>(op(ptr, nr));
        }

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

        template <typename T>
        struct op_pointer_to_mat
        {
            op_pointer_to_mat(
                const T* ptr_,
                const long nr_,
                const long nc_
            ) : ptr(ptr_), rows(nr_), cols(nc_), stride(nc_){}

            op_pointer_to_mat(
                const T* ptr_,
                const long nr_,
                const long nc_,
                const long stride_
            ) : ptr(ptr_), rows(nr_), cols(nc_), stride(stride_){}

            const T* ptr;
            const long rows;
            const long cols;
            const long stride;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef T type;
            typedef const T& const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c) const { return ptr[r*stride + c]; }

            long nr () const { return rows; }
            long nc () const { return cols; }

            template <typename U> bool aliases               ( const matrix_exp<U>& ) const { return false; }
            template <typename U> bool destructively_aliases ( const matrix_exp<U>& ) const { return false; }

            template <long num_rows, long num_cols, typename mem_manager, typename layout>
            bool aliases (
                const matrix_exp<matrix<T,num_rows,num_cols, mem_manager,layout> >& item
            ) const
            {
                if (item.size() == 0)
                    return false;
                else
                    return (ptr == &item(0,0));
            }

            bool aliases (
                const matrix_exp<matrix_op<op_pointer_to_mat<T> > >& item
            ) const
            {
                return item.ref().op.ptr == ptr;
            }

            bool aliases (
                const matrix_exp<matrix_op<op_pointer_to_col_vect<T> > >& item
            ) const
            {
                return item.ref().op.ptr == ptr;
            }
        };

        template <typename T>
        bool op_pointer_to_col_vect<T>::
        aliases (
            const matrix_exp<matrix_op<op_pointer_to_mat<T> > >& item
        ) const
        {
            return item.ref().op.ptr == ptr;
        }

        template <typename T, long NR, long NC, typename MM, typename L>
        bool matrix<T,NR,NC,MM,L>::aliases (
            const matrix_exp<matrix_op<op_pointer_to_mat<T> > >& item
        ) const
        {
            if (size() != 0)
                return item.ref().op.ptr == &data(0,0);
            else
                return false;
        }

        template <typename T, long NR, long NC, typename MM, typename L>
        bool matrix<T,NR,NC,MM,L>::aliases (
            const matrix_exp<matrix_op<op_pointer_to_col_vect<T> > >& item
        ) const
        {
            if (size() != 0)
                return item.ref().op.ptr == &data(0,0);
            else
                return false;
        }

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

        template <
            typename T
            >
        const matrix_op<op_pointer_to_mat<T> > mat (
            const T* ptr,
            long nr,
            long nc
        )
        {
            DLIB_ASSERT(nr >= 0 && nc >= 0 ,
                        "\tconst matrix_exp mat(ptr, nr, nc)"
                        << "\n\t nr and nc must be >= 0"
                        << "\n\t nr: " << nr
                        << "\n\t nc: " << nc
            );
            typedef op_pointer_to_mat<T> op;
            return matrix_op<op>(op(ptr,nr,nc));
        }

        template <
            typename T
            >
        const matrix_op<op_pointer_to_mat<T> > mat (
            const T* ptr,
            long nr,
            long nc,
            long stride
        )
        {
            DLIB_ASSERT(nr >= 0 && nc >= 0 && stride > 0 ,
                        "\tconst matrix_exp mat(ptr, nr, nc, stride)"
                        << "\n\t nr and nc must be >= 0 while stride > 0"
                        << "\n\t nr: " << nr
                        << "\n\t nc: " << nc
                        << "\n\t stride: " << stride
            );
            typedef op_pointer_to_mat<T> op;
            return matrix_op<op>(op(ptr,nr,nc,stride));
        }

        template <
            typename T,
            long NR,
            long NC,
            typename MM
            >
        typename enable_if<std::is_trivially_copyable<T>>::type matrix_assign (
            matrix<T,NR,NC,MM,row_major_layout>& dest,
            const matrix_exp<matrix_op<op_pointer_to_mat<T>>>& src
        )
        /*!
            An overload to catch statements of the form:
               some_matrix = mat(ptr,rows,cols, stride)
            and convert them into a memcpy(), which is a faster way to do the copy.
        !*/
        {
            if (dest.size() == 0) return;

            // If the op_pointer_to_mat is referring to a contiguous block of memory then just one memcopy
            // is needed.
            if (src.ref().op.stride == dest.nc())
            {
                std::memcpy(&dest(0, 0), src.ref().op.ptr, dest.nr() * dest.nc() * sizeof(T));
            }
            else
            {
                // Otherwise memcpy() each row separately.
                for (long r=0; r<dest.nr(); r++)
                {
                    std::memcpy(&dest(r, 0), &src(r,0), src.nc() * sizeof(T));
                }
            }
        }

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

    }

    namespace arma
    {
        template <typename T> class Mat;
    }
    namespace dlib
    {
        template <typename T>
        struct op_arma_Mat_to_mat : does_not_alias
        {
            op_arma_Mat_to_mat( const T& array_) : array(array_){}

            const T& array;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef typename T::elem_type type;
            typedef typename T::elem_type const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return array(r,c); }

            long nr () const { return array.n_rows; }
            long nc () const { return array.n_cols; }
        };

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

        template <
            typename T
            >
        const matrix_op<op_arma_Mat_to_mat< ::arma::Mat<T> > > mat (
            const ::arma::Mat<T>& array
        )
        {
            typedef op_arma_Mat_to_mat< ::arma::Mat<T> > op;
            return matrix_op<op>(op(array));
        }
    }

    namespace Eigen
    {
        template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
        class Matrix;
    }

    namespace dlib
    {
        template <typename T, int _Rows, int _Cols>
        struct op_eigen_Matrix_to_mat : does_not_alias
        {
            op_eigen_Matrix_to_mat( const T& array_) : m(array_){}

            const T& m;

            const static long cost = 1;
            const static long NR = (_Rows > 0) ? _Rows : 0;
            const static long NC = (_Cols > 0) ? _Cols : 0;
            typedef typename T::Scalar type;
            typedef typename T::Scalar const_ret_type;
            typedef default_memory_manager mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return m(r,c); }

            long nr () const { return m.rows(); }
            long nc () const { return m.cols(); }
        };

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

        template <
            typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols
            >
        const matrix_op<op_eigen_Matrix_to_mat< ::Eigen::Matrix<_Scalar,_Rows,_Cols,_Options,_MaxRows,_MaxCols>,_Rows,_Cols > > mat (
            const ::Eigen::Matrix<_Scalar,_Rows,_Cols,_Options,_MaxRows,_MaxCols>& m
        )
        {
            typedef op_eigen_Matrix_to_mat< ::Eigen::Matrix<_Scalar,_Rows,_Cols,_Options,_MaxRows,_MaxCols>,_Rows,_Cols > op;
            return matrix_op<op>(op(m));
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //                                  DEPRECATED FUNCTIONS
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

    // vector_to_matrix(), array_to_matrix(), pointer_to_matrix(), and
    // pointer_to_column_vector() have been deprecated in favor of the more uniform mat()
    // function.  But they are here for backwards compatibility.

        template <
            typename vector_type
            >
        const typename disable_if<is_matrix<vector_type>, matrix_op<op_array_to_mat<vector_type> > >::type
        vector_to_matrix (
            const vector_type& vector
        )
        {
            typedef op_array_to_mat<vector_type> op;
            return matrix_op<op>(op(vector));
        }

        template <
            typename vector_type
            >
        const typename enable_if<is_matrix<vector_type>,vector_type>::type& vector_to_matrix (
            const vector_type& vector
        )
        /*!
            This overload catches the case where the argument to this function is
            already a matrix.
        !*/
        {
            return vector;
        }

        template <
            typename value_type,
            typename alloc
            >
        const matrix_op<op_std_vect_to_mat<std::vector<value_type,alloc> > > vector_to_matrix (
            const std::vector<value_type,alloc>& vector
        )
        {
            typedef op_std_vect_to_mat<std::vector<value_type,alloc> > op;
            return matrix_op<op>(op(vector));
        }

        template <
            typename value_type,
            typename alloc
            >
        const matrix_op<op_std_vect_to_mat<std_vector_c<value_type,alloc> > > vector_to_matrix (
            const std_vector_c<value_type,alloc>& vector
        )
        {
            typedef op_std_vect_to_mat<std_vector_c<value_type,alloc> > op;
            return matrix_op<op>(op(vector));
        }

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

        template <
            typename array_type
            >
        const typename enable_if<is_matrix<array_type>,array_type>::type&
        array_to_matrix (
            const array_type& array
        )
        {
            return array;
        }

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

        template <typename T>
        struct op_array2d_to_mat : does_not_alias
        {
            op_array2d_to_mat( const T& array_) : array(array_){}

            const T& array;

            const static long cost = 1;
            const static long NR = 0;
            const static long NC = 0;
            typedef typename T::type type;
            typedef const typename T::type& const_ret_type;
            typedef typename T::mem_manager_type mem_manager_type;
            typedef row_major_layout layout_type;

            const_ret_type apply (long r, long c ) const { return array[r][c]; }

            long nr () const { return array.nr(); }
            long nc () const { return array.nc(); }
        };

        // Note that we have this version of mat() because it's slightly faster executing
        // than the general one that handles any generic image.  This is because it avoids
        // calling image_data() which for array2d involves a single if statement but this
        // version here has no if statement in its construction.
        template < typename T, typename MM >
        const matrix_op<op_array2d_to_mat<array2d<T,MM> > > mat (
            const array2d<T,MM>& array
        )
        {
            typedef op_array2d_to_mat<array2d<T,MM> > op;
            return matrix_op<op>(op(array));
        }

        template <
            typename array_type
            >
        const typename disable_if<is_matrix<array_type>,matrix_op<op_array2d_to_mat<array_type> > >::type
        array_to_matrix (
            const array_type& array
        )
        {
            typedef op_array2d_to_mat<array_type> op;
            return matrix_op<op>(op(array));
        }

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

        template <
            typename T
            >
        const matrix_op<op_pointer_to_mat<T> > pointer_to_matrix (
            const T* ptr,
            long nr,
            long nc
        )
        {
            DLIB_ASSERT(nr > 0 && nc > 0 ,
                        "\tconst matrix_exp pointer_to_matrix(ptr, nr, nc)"
                        << "\n\t nr and nc must be bigger than 0"
                        << "\n\t nr: " << nr
                        << "\n\t nc: " << nc
            );
            typedef op_pointer_to_mat<T> op;
            return matrix_op<op>(op(ptr,nr,nc));
        }

        template <
            typename T
            >
        const matrix_op<op_pointer_to_col_vect<T> > pointer_to_column_vector (
            const T* ptr,
            long nr
        )
        {
            DLIB_ASSERT(nr > 0 ,
                        "\tconst matrix_exp pointer_to_column_vector(ptr, nr)"
                        << "\n\t nr must be bigger than 0"
                        << "\n\t nr: " << nr
            );
            typedef op_pointer_to_col_vect<T> op;
            return matrix_op<op>(op(ptr, nr));
        }

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

        inline matrix<double,1,1> mat (
            double value
        )
        {
            matrix<double,1,1> temp;
            temp(0) = value;
            return temp;
        }

        inline matrix<float,1,1> mat (
            float value
        )
        {
            matrix<float,1,1> temp;
            temp(0) = value;
            return temp;
        }

        inline matrix<long double,1,1> mat (
            long double value
        )
        {
            matrix<long double,1,1> temp;
            temp(0) = value;
            return temp;
        }

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

    }

    #endif // DLIB_MATRIx_MAT_Hh_