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

    #include "../algs.h"
    #include "../is_kind.h"
    #include "matrix_fwd.h"
    #include "matrix_exp_abstract.h"
    #include <iterator>

    namespace dlib
    {

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

        // We want to return the compile time constant if our NR and NC dimensions
        // aren't zero but if they are then we want to call ref_.nx() and return
        // the correct values.
        template < typename exp_type, long NR >
        struct get_nr_helper
        {
            static inline long get(const exp_type&) { return NR; }
        };

        template < typename exp_type >
        struct get_nr_helper<exp_type,0>
        {
            static inline long get(const exp_type& m) { return m.nr(); }
        };

        template < typename exp_type, long NC >
        struct get_nc_helper
        {
            static inline long get(const exp_type&) { return NC; }
        };

        template < typename exp_type >
        struct get_nc_helper<exp_type,0>
        {
            static inline long get(const exp_type& m) { return m.nc(); }
        };

        template <typename EXP>
        struct matrix_traits
        {
            typedef typename EXP::type type;
            typedef typename EXP::const_ret_type const_ret_type;
            typedef typename EXP::mem_manager_type mem_manager_type;
            typedef typename EXP::layout_type layout_type;
            const static long NR = EXP::NR;
            const static long NC = EXP::NC;
            const static long cost = EXP::cost;
        };

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

        template <typename EXP> class matrix_exp;
        template <typename EXP>
        class matrix_exp_iterator
        {
            friend class matrix_exp<EXP>;
            matrix_exp_iterator(const EXP& m_, long r_, long c_)
            {
                r = r_;
                c = c_;
                nc = m_.nc();
                m = &m_;
            }

        public:

            matrix_exp_iterator() : r(0), c(0), nc(0), m(0) {}

            using type = typename matrix_traits<EXP>::type;
            using const_ret_type = typename matrix_traits<EXP>::const_ret_type;
            using iterator_category = std::forward_iterator_tag;
            using value_type = type;
            using difference_type = std::ptrdiff_t;
            using pointer = value_type*;
            using reference = value_type&;

            bool operator == ( const matrix_exp_iterator& itr) const
            { return r == itr.r && c == itr.c; }

            bool operator != ( const matrix_exp_iterator& itr) const
            { return !(*this == itr); }

            matrix_exp_iterator& operator++()
            {
                ++c;
                if (c==nc)
                {
                    c = 0;
                    ++r;
                }
                return *this;
            }

            matrix_exp_iterator operator++(int)
            {
                matrix_exp_iterator temp(*this);
                ++(*this);
                return temp;
            }

            const_ret_type operator* () const { return (*m)(r,c); }

        private:
            long r, c;
            long nc;
            const EXP* m;
        };

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

        template <
            typename EXP
            >
        class matrix_exp
        {
            /*!
                REQUIREMENTS ON EXP
                    EXP should be something convertible to a matrix_exp.  That is,
                    it should inherit from matrix_exp
            !*/

        public:
            typedef typename matrix_traits<EXP>::type type;
            typedef type value_type;
            typedef typename matrix_traits<EXP>::const_ret_type const_ret_type;
            typedef typename matrix_traits<EXP>::mem_manager_type mem_manager_type;
            typedef typename matrix_traits<EXP>::layout_type layout_type;
            const static long NR = matrix_traits<EXP>::NR;
            const static long NC = matrix_traits<EXP>::NC;
            const static long cost = matrix_traits<EXP>::cost;

            typedef matrix<type,NR,NC,mem_manager_type,layout_type> matrix_type;
            typedef EXP exp_type;
            typedef matrix_exp_iterator<EXP> iterator;
            typedef matrix_exp_iterator<EXP> const_iterator;

            inline const_ret_type operator() (
                long r,
                long c
            ) const
            {
                DLIB_ASSERT(r < nr() && c < nc() && r >= 0 && c >= 0,
                    "\tconst type matrix_exp::operator(r,c)"
                    << "\n\tYou must give a valid row and column"
                    << "\n\tr:    " << r
                    << "\n\tc:    " << c
                    << "\n\tnr(): " << nr()
                    << "\n\tnc(): " << nc()
                    << "\n\tthis: " << this
                    );
                return ref()(r,c);
            }

            const_ret_type operator() (
                long i
            ) const
            {
                COMPILE_TIME_ASSERT(NC == 1 || NC == 0 || NR == 1 || NR == 0);
                DLIB_ASSERT(nc() == 1 || nr() == 1,
                    "\tconst type matrix_exp::operator(i)"
                    << "\n\tYou can only use this operator on column or row vectors"
                    << "\n\ti:    " << i
                    << "\n\tnr(): " << nr()
                    << "\n\tnc(): " << nc()
                    << "\n\tthis: " << this
                    );
                DLIB_ASSERT( ((nc() == 1 && i < nr()) || (nr() == 1 && i < nc())) && i >= 0,
                    "\tconst type matrix_exp::operator(i)"
                    << "\n\tYou must give a valid row/column number"
                    << "\n\ti:    " << i
                    << "\n\tnr(): " << nr()
                    << "\n\tnc(): " << nc()
                    << "\n\tthis: " << this
                    );
                if (nc() == 1)
                    return ref()(i,0);
                else
                    return ref()(0,i);
            }

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

            long nr (
            ) const { return get_nr_helper<exp_type,NR>::get(ref()); }

            long nc (
            ) const { return get_nc_helper<exp_type,NC>::get(ref()); }

            template <typename U>
            bool aliases (
                const matrix_exp<U>& item
            ) const { return ref().aliases(item); }

            template <typename U>
            bool destructively_aliases (
                const matrix_exp<U>& item
            ) const { return ref().destructively_aliases(item); }

            inline const exp_type& ref (
            ) const { return *static_cast<const exp_type*>(this); }

            inline operator const type (
            ) const
            {
                COMPILE_TIME_ASSERT(NC == 1 || NC == 0);
                COMPILE_TIME_ASSERT(NR == 1 || NR == 0);
                DLIB_ASSERT(nr() == 1 && nc() == 1,
                    "\tmatrix_exp::operator const type() const"
                    << "\n\tYou can only use this operator on a 1x1 matrix"
                    << "\n\tnr(): " << nr()
                    << "\n\tnc(): " << nc()
                    << "\n\tthis: " << this
                    );

                // Put the expression contained in this matrix_exp into
                // a temporary 1x1 matrix so that the expression will encounter
                // all the overloads of matrix_assign() and have the chance to
                // go through any applicable optimizations.
                matrix<type,1,1,mem_manager_type,layout_type> temp(ref());
                return temp(0);
            }

            const_iterator begin() const { return matrix_exp_iterator<EXP>(ref(),0,0); }
            const_iterator end()   const { return matrix_exp_iterator<EXP>(ref(),nr(),0); }

        protected:
            matrix_exp() {}
            matrix_exp(const matrix_exp& ) {}

        private:

            matrix_exp& operator= (const matrix_exp&);
        };

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

        // something is a matrix if it is convertible to a matrix_exp object
        template <typename T>
        struct is_matrix<T, typename enable_if<is_convertible<T, const matrix_exp<typename T::exp_type>& > >::type >
        { static const bool value = true; };
        /*
            is_matrix<T>::value == 1 if T is a matrix type else 0
        */

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

        template <
            typename EXP
            >
        class matrix_diag_exp : public matrix_exp<EXP>
        {
            /*!
                This is a matrix expression type used to represent diagonal matrices.
                That is, square matrices with all off diagonal elements equal to 0.
            !*/

        protected:
            matrix_diag_exp() {}
            matrix_diag_exp(const matrix_diag_exp& item ):matrix_exp<EXP>(item) {}
        };

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

    }

    #endif // DLIB_MATRIx_EXP_h_