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

#include "matrix_exp.h"


namespace dlib
{

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

    template <typename OP >
    class matrix_op;

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

    template <
        typename OP
        >
    class matrix_op : public matrix_exp<matrix_op<OP> >
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                The matrix_op is simply a tool for reducing the amount of boilerplate
                you need to write when creating matrix expressions.  
        !*/

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

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1>
        matrix_op (T1); 
    public:

        matrix_op (
            const OP& op_
        ) :
            op(op_)
        {}

        const_ret_type operator() (
            long r, 
            long c
        ) const { return op.apply(r,c); }

        const_ret_type operator() ( long i ) const 
        { return matrix_exp<matrix_op>::operator()(i); }

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

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

        long nr (
        ) const { return op.nr(); }

        long nc (
        ) const { return op.nc(); }


        const OP op;
    };

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

    template <typename OP >
    class matrix_diag_op;

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

    template <
        typename OP
        >
    class matrix_diag_op : public matrix_diag_exp<matrix_diag_op<OP> >
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                The matrix_diag_op is simply a tool for reducing the amount of boilerplate
                you need to write when creating matrix expressions.  
        !*/

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

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1>
        matrix_diag_op (T1); 
    public:

        matrix_diag_op (
            const OP& op_
        ) :
            op(op_)
        {}

        const_ret_type operator() (
            long r, 
            long c
        ) const { return op.apply(r,c); }

        const_ret_type operator() ( long i ) const 
        { return matrix_exp<matrix_diag_op>::operator()(i); }

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

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

        long nr (
        ) const { return op.nr(); }

        long nc (
        ) const { return op.nc(); }


        const OP op;
    };

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

    struct does_not_alias 
    {
        /*!
            This is a partial implementation of a matrix operator that never aliases
            another expression.
        !*/

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

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

    template <typename M>
    struct basic_op_m 
    {
        /*!
            This is a partial implementation of a matrix operator that preserves
            the dimensions of its argument and doesn't have destructive aliasing.
        !*/

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1>
        basic_op_m (T1); 
    public:

        basic_op_m(
            const M& m_
        ) : m(m_){}

        const M& m;

        const static long NR = M::NR;
        const static long NC = M::NC;
        typedef typename M::mem_manager_type mem_manager_type;
        typedef typename M::layout_type layout_type;

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

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

    }; 

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

    template <typename M1, typename M2>
    struct basic_op_mm 
    {
        /*!
            This is a partial implementation of a matrix operator that preserves
            the dimensions of its arguments and doesn't have destructive aliasing.
        !*/

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1, typename T2>
        basic_op_mm (T1, T2); 
    public:

        basic_op_mm(
            const M1& m1_,
            const M2& m2_
        ) : m1(m1_), m2(m2_){}

        const M1& m1;
        const M2& m2;

        const static long NR = M1::NR;
        const static long NC = M1::NC;
        typedef typename M1::mem_manager_type mem_manager_type;
        typedef typename M1::layout_type layout_type;

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

        template <typename U> bool aliases               ( const matrix_exp<U>& item) const 
        { return m1.aliases(item) || m2.aliases(item); }
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const 
        { return m1.destructively_aliases(item) || m2.destructively_aliases(item); }

    }; 

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

    template <typename M1, typename M2, typename M3>
    struct basic_op_mmm 
    {
        /*!
            This is a partial implementation of a matrix operator that preserves
            the dimensions of its arguments and doesn't have destructive aliasing.
        !*/

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1, typename T2, typename T3>
        basic_op_mmm (T1, T2, T3); 
    public:

        basic_op_mmm(
            const M1& m1_,
            const M2& m2_,
            const M3& m3_
        ) : m1(m1_), m2(m2_), m3(m3_){}

        const M1& m1;
        const M2& m2;
        const M3& m3;

        const static long NR = M1::NR;
        const static long NC = M1::NC;
        typedef typename M1::mem_manager_type mem_manager_type;
        typedef typename M1::layout_type layout_type;

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

        template <typename U> bool aliases               ( const matrix_exp<U>& item) const 
        { return m1.aliases(item) || m2.aliases(item) || m3.aliases(item); }
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const 
        { return m1.destructively_aliases(item) || m2.destructively_aliases(item) ||
                 m3.destructively_aliases(item);}

    }; 

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

    template <typename M1, typename M2, typename M3, typename M4>
    struct basic_op_mmmm 
    {
        /*!
            This is a partial implementation of a matrix operator that preserves
            the dimensions of its arguments and doesn't have destructive aliasing.
        !*/

    private:
        // This constructor exists simply for the purpose of causing a compile time error if
        // someone tries to create an instance of this object with the wrong kind of object.
        template <typename T1, typename T2, typename T3, typename T4>
        basic_op_mmmm (T1, T2, T3, T4); 
    public:

        basic_op_mmmm(
            const M1& m1_,
            const M2& m2_,
            const M3& m3_,
            const M4& m4_
        ) : m1(m1_), m2(m2_), m3(m3_), m4(m4_){}

        const M1& m1;
        const M2& m2;
        const M3& m3;
        const M4& m4;

        const static long NR = M1::NR;
        const static long NC = M1::NC;
        typedef typename M1::mem_manager_type mem_manager_type;
        typedef typename M1::layout_type layout_type;

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

        template <typename U> bool aliases               ( const matrix_exp<U>& item) const 
        { return m1.aliases(item) || m2.aliases(item) || m3.aliases(item) || m4.aliases(item); }
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const 
        { return m1.destructively_aliases(item) || m2.destructively_aliases(item) ||
                 m3.destructively_aliases(item) || m4.destructively_aliases(item);}

    }; 

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

#define DLIB_DEFINE_OP_M(op_name, function, extra_cost)                                         \
    template <typename M>                                                                       \
    struct op_name                                                                              \
    {                                                                                           \
        op_name(                                                                                \
            const M& m_                                                                         \
        ) : m(m_){}                                                                             \
                                                                                                \
        const M& m;                                                                             \
                                                                                                \
        const static long cost = M::cost+(extra_cost);                                          \
        const static long NR = M::NR;                                                           \
        const static long NC = M::NC;                                                           \
        typedef typename M::type type;                                                          \
        typedef const typename M::type const_ret_type;                                          \
        typedef typename M::mem_manager_type mem_manager_type;                                  \
        typedef typename M::layout_type layout_type;                                            \
                                                                                                \
        const_ret_type apply (long r, long c) const { return function(m(r,c)); }                \
                                                                                                \
        long nr () const { return m.nr(); }                                                     \
        long nc () const { return m.nc(); }                                                     \
                                                                                                \
        template <typename U> bool aliases               ( const matrix_exp<U>& item) const     \
        { return m.aliases(item); }                                                             \
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const     \
        { return m.destructively_aliases(item); }                                               \
                                                                                                \
    }

#define DLIB_DEFINE_FUNCTION_M(op_name, name, function, extra_cost)                             \
    DLIB_DEFINE_OP_M(op_name, function, extra_cost);                                            \
    template < typename M >                                                                     \
    const matrix_op<op_name<M> > name ( const matrix_exp<M>& m)                                 \
    {                                                                                           \
        typedef op_name<M> op;                                                                  \
        return matrix_op<op>(op(m.ref()));                                                      \
    }

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

#define DLIB_DEFINE_OP_MS(op_name, function, extra_cost)                                        \
    template <typename M, typename S>                                                           \
    struct op_name                                                                              \
    {                                                                                           \
        op_name(                                                                                \
            const M& m_,                                                                        \
            const S& s_                                                                         \
        ) : m(m_), s(s_){}                                                                      \
                                                                                                \
        const M& m;                                                                             \
        const S s;                                                                              \
                                                                                                \
        const static long cost = M::cost+(extra_cost);                                          \
        const static long NR = M::NR;                                                           \
        const static long NC = M::NC;                                                           \
        typedef typename M::type type;                                                          \
        typedef const typename M::type const_ret_type;                                          \
        typedef typename M::mem_manager_type mem_manager_type;                                  \
        typedef typename M::layout_type layout_type;                                            \
                                                                                                \
        const_ret_type apply (long r, long c) const { return function(m(r,c), s); }             \
                                                                                                \
        long nr () const { return m.nr(); }                                                     \
        long nc () const { return m.nc(); }                                                     \
                                                                                                \
        template <typename U> bool aliases               ( const matrix_exp<U>& item) const     \
        { return m.aliases(item); }                                                             \
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const     \
        { return m.destructively_aliases(item); }                                               \
                                                                                                \
    }

#define DLIB_DEFINE_FUNCTION_MS(op_name, name, function, extra_cost)                            \
    DLIB_DEFINE_OP_MS(op_name, function, extra_cost);                                           \
    template < typename M, typename S >                                                         \
    const matrix_op<op_name<M, S> > name ( const matrix_exp<M>& m, const S& s)                  \
    {                                                                                           \
        typedef op_name<M, S> op;                                                               \
        return matrix_op<op>(op(m.ref(), s));                                                   \
    }

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

#define DLIB_DEFINE_OP_SM(op_name, function, extra_cost)                                        \
    template <typename S, typename M>                                                           \
    struct op_name                                                                              \
    {                                                                                           \
        op_name(                                                                                \
            const S& s_,                                                                        \
            const M& m_                                                                         \
        ) : m(m_), s(s_){}                                                                      \
                                                                                                \
        const M& m;                                                                             \
        const S s;                                                                              \
                                                                                                \
        const static long cost = M::cost+(extra_cost);                                          \
        const static long NR = M::NR;                                                           \
        const static long NC = M::NC;                                                           \
        typedef typename M::type type;                                                          \
        typedef const typename M::type const_ret_type;                                          \
        typedef typename M::mem_manager_type mem_manager_type;                                  \
        typedef typename M::layout_type layout_type;                                            \
                                                                                                \
        const_ret_type apply (long r, long c) const { return function(s, m(r,c)); }             \
                                                                                                \
        long nr () const { return m.nr(); }                                                     \
        long nc () const { return m.nc(); }                                                     \
                                                                                                \
        template <typename U> bool aliases               ( const matrix_exp<U>& item) const     \
        { return m.aliases(item); }                                                             \
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item) const     \
        { return m.destructively_aliases(item); }                                               \
                                                                                                \
    }

#define DLIB_DEFINE_FUNCTION_SM(op_name, name, function, extra_cost)                            \
    DLIB_DEFINE_OP_SM(op_name, function, extra_cost);                                           \
    template < typename S, typename M >                                                         \
    const matrix_op<op_name<S, M> > name (const S& s, const matrix_exp<M>& m)                   \
    {                                                                                           \
        typedef op_name<S, M> op;                                                               \
        return matrix_op<op>(op(s, m.ref()));                                                   \
    }

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

}

#endif // DLIB_MATRIx_OP_H_