
namespace cv
{

  using Scalar = double;
  
  struct Mat
  {};
  
  struct Point2i
  {};
  
  inline void swap(Mat& a, Mat& b)
  {}
  
}

// basic_types.hpp

#include <cstdint>

namespace cvip
{

    // Aliases for most used types from std
    //

    using uint_t  = std::uint32_t;
    using upix_t  = std::uint8_t;
    using wpix_t  = std::uint16_t;


    // Aliases for most common types in cv

    using vscalar = double;
    using mscalar = cv::Scalar;

    using matrix  = cv::Mat;

    using point   = cv::Point2i;


    // Algoritms/Scanning execution model
    //

    enum class execution_model : int
    {
        parallel, sequential
    };

}


// i_operator.hpp

#include <memory>
#include <type_traits>

namespace cvip
{

    namespace core
    {

        // Interface for image operator classes
        //

        class i_operator
        {
        protected:

            // operator chain node type
            //
            using opnode_t = std::shared_ptr<i_operator>;

            // apply operator on matrix
            //
            // rhs : A modifiable reference to a smart-reference  image  container.
            //       On input,  it holds a reference  to  the  original (right hand
            //       side) operand.  On output,  it holds  a reference  to an image
            //       with  the  same  depth and size of the input image; this image
            //       will be used as temporary processing  buffer  in the  chain of
            //       operators in an expression. Must be a non empty image.
            //
            // dst : A modifiable reference to a smart-reference  image  container.
            //       On input, assumed to be an empty matrix for the first operator
            //       in the chain of operators in an expression; for remaing calls,
            //       a reference  to the buffer  returned by  the first operator in
            //       rhs. On output, the result of a successful operation.
            //
            virtual void apply(matrix& dst, matrix& src, bool const first) = 0;

            // clone this operator
            //
            virtual opnode_t clone() const = 0;

            friend matrix operator*(i_operator& lhs_op, matrix const& rhs_im);

            friend class operator_expression;

        };


        // Tool for checking i_operator implementation
        //

        template<typename T>
        class is_operator
        {
        public:

            static auto constexpr value = std::is_base_of<i_operator, T>::value;

        };


        // Interface for operator predicates
        //

        class i_operator_predicate
        {
        public:

            //virtual bool supported(matrix const& mat) const noexcept = 0;

            virtual void do_apply(matrix& dst, matrix& src, bool const first) = 0;

            template<typename ...Args>
            void reset(Args&& ...arg);

        };


        // Tool for checking i_operator_predicate implementation
        //

        template<typename P>
        class is_operator_predicate
        {
        private:

            struct has_member { char z[1]; };
            struct no_member  { char z[2]; };

            //template<typename T> static has_member has_supported_member(decltype(&T::supported));
            template<typename T> static has_member has_do_apply_member(decltype(&T::do_apply));

            //template<typename T> static no_member  has_supported_member(...);
            template<typename T> static no_member  has_do_apply_member(...);

            static auto constexpr yes           = sizeof(has_member);
            //static auto constexpr has_supported = sizeof(has_supported_member<P>(nullptr));
            static auto constexpr has_do_apply  = sizeof(has_do_apply_member<P>(nullptr));
            static auto constexpr is_predicate  = std::is_base_of<i_operator_predicate, P>::value;

        public:

            static auto constexpr value = is_predicate
                or /*has_supported == yes and*/ has_do_apply == yes;

        };

    }

}

// operator.hpp


namespace cvip
{

    namespace core
    {

        // Generic abstract base class for image matrix operators, partially
        // implements i_operator providing cloning facility via CRT Pattern.
        //

        template<typename ConcreteOperator>
        class base_operator : public i_operator
        {
        public:

            base_operator() noexcept = default;

            base_operator(base_operator const& src) noexcept = default;

            base_operator(base_operator&& src) noexcept = default;

            virtual ~base_operator() noexcept = default;

            base_operator& operator=(base_operator const& src) noexcept = default;

            base_operator& operator=(base_operator&& src) noexcept = default;


        protected:

            using operator_t = ConcreteOperator;

            virtual opnode_t clone() const override;

        };


        // Predicate based image operator
        //

        template<typename Predicate>
        class basic_operator : public base_operator< basic_operator<Predicate> >
        {
        public:

            template<typename ...Args>
            basic_operator(Args&& ...arg);

            template<typename ...Args>
            basic_operator& operator()(Args&& ...arg);


        protected:

            virtual void apply(matrix& dst, matrix& src, bool const first) override;


        private:

            using predicate_t = Predicate;

            static_assert(is_operator_predicate<predicate_t>::value,
                          "predicate_t does not implement i_operator_predicate");


        private:

            predicate_t m_operation = { };

        };

    }

}

// operator.inl

#include <memory>
#include <utility>

namespace cvip
{

    namespace core
    {

        // base_operator<ConcreteOperator>
        //

        template<typename ConcreteOperator>
        inline typename base_operator<ConcreteOperator>::opnode_t base_operator<ConcreteOperator>::clone() const
        {
            static_assert(is_operator<operator_t>::value, "operator_t is not an i_operator");
            return std::make_shared<operator_t>(*static_cast<operator_t const*>(this));
        }


        // basic_operator<Predicate>
        //

        template<typename Predicate> template<typename ...Args>
        basic_operator<Predicate>::basic_operator(Args&& ...arg) :
            m_operation{ std::forward<Args>(arg)... }
        {
            // NOOP
        }

        template<typename Predicate> template<typename ...Args>
        inline basic_operator<Predicate>& cvip::core::basic_operator<Predicate>::operator()(Args&& ...arg)
        {
            return (m_operation.reset(std::forward<Args>(arg)...), *this);
        }

        template<typename Predicate>
        inline void basic_operator<Predicate>::apply(matrix& dst, matrix& src, bool const first)
        {
            m_operation.do_apply(dst, src, first);
        }


        // image operator operations
        //

        inline matrix operator*(i_operator&& lhs_op, matrix const& rhs_im)
        {
            return lhs_op * rhs_im;
        }

    }

}

// operator.cpp

namespace cvip
{

    namespace core
    {

        matrix operator*(i_operator& lhs_op, matrix const& rhs_im)
        {
            auto src = matrix{ rhs_im };
            auto dst = matrix{ };

            lhs_op.apply(dst, src, true);

            return dst;
        }

    }

}

// expression.hpp

#include <list>
#include <memory>

namespace cvip
{

    // Operator expressions
    //
    // The built-int multiplication operator (operator*) is  used  to  indicate
    // operations among image operators and image matrices.
    //
    // Arithmetic operation among operators and matrices is done from right  to
    // left, nevertheless, operation among operators are gathered from  left to
    // right within an operator expression.
    //
    // Applying an operator, P, on a matrix, M1, produces a new matrix, M2:
    //
    //      P * M1 -> M2.
    //
    // Applying an operator, P2, on another operator, P1, produces an  operator
    // expression that gather  both  operators  for  later  application  in the
    // appropriate order:
    //
    //      P2 * P1 -> EX(P1, P2).
    //
    // Applying an operator expression on a matrix, M1, produces a new  matrix,
    // M2, after applying the encapsulated operators in the correct order:
    //
    //      EX(P1, P2) * M1 -> M2.
    //
    // Applying an operator expression on  an  operator,  P3,  produces  a  new
    // operator expression concatenating it with the  existing operators in the
    // proper order:
    //
    //      EX(P1, P2) * P3 -> EX(P3, P1, P2).
    //
    // Applying an operator expression on another operator expression, produces
    // a new operator expression concatenating the existing  operators  in  the
    // proper order:
    //
    //      EX(P1, P2) * EX(P3, P4) -> EX(P3, P4, P1, P2).
    //
    // The commutative property  does  not  hold  for  operators  and  operator
    // expressions:
    //
    //      P2 * P1 -> EX(P1, P2),
    //      P1 * P2 -> EX(P2, P1),
    //
    //      EX(P1, P2) * P3 -> EX(P3, P1, P2),
    //      P3 * EX(P1, P2) -> EX(P1, P2, P3),
    //
    //      EX(P1, P2) * EX(P3, P4) -> EX(P3, P4, P1, P2),
    //      EX(P3, P4) * EX(P1, P2) -> EX(P1, P2, P3, P4).
    //
    // Associative property among operators and operator  expressions  is  just
    // syntactic sugar and  is  implemented  for  semantic  purposes,  e.g. for
    // grouping or emphasise related  operations.  The following operations are
    // the same:
    //
    //      (P1 * P2) * P3 * M  ===  P1 * P2 * P3 * M,
    //
    // however, the next operations are equivalent,  i.e. they produce the same
    // result but the intermediate products are not the same:
    //
    //      (P1 * P2) * P3 * M  ==  P1 * (P2 * P3) * M,
    //
    // but
    //
    //      (P1 * P2) * P3 * M  !==  P1 * (P2 * P3) * M.
    //
    // Listing the intermediate results, we have:
    //
    // Example 1.
    //
    //  1) P3 * P2 * P1 * M1     ... or (P3 * P2) * P1 * M1
    //  2) EX(P2, P3) * P1 * M1
    //  3) EX(P1, P2, P3) * M1   ... operators are applied in the order
    //  4) M2                    ... P1, P2, and P3
    //
    // Example 2.
    //
    //  1) P3 * (P2 * P1) * M1
    //  2) P3 * EX(P1, P2) * M1
    //  3) EX(P1, P2, P3) * M1   ... operators are applied in the order
    //  4) M2                    ... P1, P2, and P3
    //
    // Example 3.
    //
    //  1) P4 * P3 * (P2 * P1) * M1
    //  2) P4 * P3 * EX(P1, P2) * M1
    //  3) EX(P3, P4) * EX(P1, P2) * M1
    //  4) EX(P1, P2, P3, P4) * M1
    //  5) M2
    //
    // Example 4.
    //
    //  1) (P4 * P3) * (P2 * P1) * M1
    //  2) EX(P3, P4) * (P2 * P1) * M1
    //  3) EX(P3, P4) * EX(P1, P2) * M1
    //  4) EX(P1, P2, P3, P4) * M1
    //  5) M2
    //
    // Provided the operators  are  equivalent,  Example 1  and  Example 2, and
    // Example 3 and Example 4, respectively, produce the same result.
    //
    // Applying association to operators or operator expressions and  matrices,
    // force the application  of  the  gathered  operators  to  the  image  and
    // produces a new intermediate image before continuing  with  the  remaning
    // operators:
    //
    // Example 5.
    //
    //  1) P3 * P2 * (P1 * M1)
    //  2) P3 * P2 * M2
    //  3) EX(P2, P3) * M2
    //  4) M3
    //
    // Even though, if the operators  are equivalent,  Example 1 and Example 5,
    // produce the same result.
    //

    namespace core
    {

        //class operator_expression;

        //operator_expression operator*(i_operator const& lhs_op, i_operator const& rhs_op);

        //operator_expression operator*(operator_expression&& lhs_ex, i_operator const& rhs_op);

        //operator_expression operator*(i_operator const& lhs_op, operator_expression&& rhs_ex);

        //operator_expression operator*(operator_expression&& lhs_ex, operator_expression&& rhs_ex);

        //matrix operator*(operator_expression& lhs_ex, matrix const& rhs_im);

        // Operator expression
        //
        // Allows operator semantics for  image matrix  operations.  Operations
        // among image  operators  and image  matrices  are right  associative,
        // while operations among operators are left associative.
        //

        class operator_expression
        {
        public:

            operator_expression() = delete;

            operator_expression(operator_expression const& src) noexcept = default;

            operator_expression(operator_expression&& src) noexcept = default;

            ~operator_expression() noexcept = default;

            operator_expression& operator=(operator_expression const& src) noexcept = default;

            operator_expression& operator=(operator_expression&& src) noexcept = default;


        private:

            operator_expression(i_operator const& lhs_op, i_operator const& rhs_op);


        private:

            matrix apply(matrix const& rhs_im);

            void emplace_back(operator_expression&& lhs_ex);

            void push_back(i_operator const& lhs_op);

            void push_front(i_operator const& rhs_op);


        private:

            friend operator_expression operator*(i_operator const& lhs_op, i_operator const& rhs_op);

            friend operator_expression operator*(operator_expression&& lhs_ex, i_operator const& rhs_op);

            friend operator_expression operator*(i_operator const& lhs_op, operator_expression&& rhs_ex);

            friend operator_expression operator*(operator_expression&& lhs_ex, operator_expression&& rhs_ex);

            friend matrix operator*(operator_expression& lhs_ex, matrix const& rhs_im);


        private:

            using opnode_t  = i_operator::opnode_t;
            using opchain_t = std::list<opnode_t>;
            using exdata_t  = std::shared_ptr<opchain_t>;


        private:

            exdata_t construct_data(i_operator const& lhs_op, i_operator const& rhs_op);


        private:

            exdata_t m_data = { };

        };

    }

}

// expression.inl

#include <utility>

namespace cvip
{

    namespace core
    {

        // operator_expression
        //

        inline void operator_expression::emplace_back(operator_expression&& lhs_ex)
        {
            m_data->splice(m_data->end(), std::move(*lhs_ex.m_data));
        }

        inline void operator_expression::push_back(i_operator const& lhs_op)
        {
            m_data->emplace_back(lhs_op.clone());
        }

        inline void operator_expression::push_front(i_operator const& rhs_op)
        {
            m_data->emplace_front(rhs_op.clone());
        }


        // operator_expression operator* (op * op)
        //

        inline operator_expression operator*(i_operator const& lhs_op, i_operator const& rhs_op)
        {
            return { lhs_op, rhs_op };
        }


        // operator_expression operator* (ex * op, op * ex)
        //

        inline operator_expression operator*(operator_expression&& lhs_ex, i_operator const& rhs_op)
        {
            return (lhs_ex.push_front(rhs_op), std::move(lhs_ex));
        }

        inline operator_expression operator*(i_operator const& lhs_op, operator_expression&& rhs_ex)
        {
            return (rhs_ex.push_back(lhs_op), std::move(rhs_ex));
        }

        inline operator_expression operator*(operator_expression const& lhs_ex, i_operator const& rhs_op)
        {
            return operator_expression{ lhs_ex } * rhs_op;
        }

        inline operator_expression operator*(i_operator const& lhs_op, operator_expression const& rhs_ex)
        {
            return lhs_op * operator_expression{ rhs_ex };
        }


        // operator_expression operator* (ex * ex)
        //

        inline operator_expression operator*(operator_expression&& lhs_ex, operator_expression&& rhs_ex)
        {
            return (rhs_ex.emplace_back(std::move(lhs_ex)), std::move(rhs_ex));
        }

        inline operator_expression operator*(operator_expression&& lhs_ex, operator_expression const& rhs_ex)
        {
            return std::move(lhs_ex) * operator_expression{ rhs_ex };
        }

        inline operator_expression operator*(operator_expression const& lhs_ex, operator_expression&& rhs_ex)
        {
            return operator_expression{ lhs_ex } * std::move(rhs_ex);
        }

        inline operator_expression operator*(operator_expression const& lhs_ex, operator_expression const& rhs_ex)
        {
            return operator_expression{ lhs_ex } * operator_expression{ rhs_ex };
        }

        inline matrix operator*(operator_expression& lhs_ex, matrix const& rhs_im)
        {
            return lhs_ex.apply(rhs_im);
        }

        inline matrix operator*(operator_expression&& lhs_ex, matrix const& rhs_im)
        {
            return lhs_ex * rhs_im;
        }

    }

}

// expression.cpp

#include <initializer_list>
#include <cassert>

namespace cvip
{

    namespace core
    {

        operator_expression::operator_expression(i_operator const& lhs_op, i_operator const& rhs_op) :
            m_data{ construct_data(lhs_op, rhs_op) }
        {
            assert(m_data.use_count() == 1);
        }

        matrix operator_expression::apply(matrix const& rhs_im)
        {
            auto src = matrix{ rhs_im };
            auto dst = matrix{ };

            auto first = true;

            for (auto& op : *m_data)
            {
                op->apply(dst, src, first);

                cv::swap(dst, src);

                first = false;
            }

            // REMARK: The result is in src due to the swap
            //         at the end of each iteration!

            return src;
        }

        inline operator_expression::exdata_t operator_expression::construct_data(i_operator const& lhs_op,
                                                                                 i_operator const& rhs_op)
        {
            return std::make_shared<opchain_t>(opchain_t{ rhs_op.clone(), lhs_op.clone() });
        }

    }

}

// test entry-point

#include <iostream>

using namespace std;

int main()
{
    std::cout << "Hello world!";

    return 0;
}

// struct i_op


// using opnode_t  = i_operator::opnode_t;
// using opchain_t = std::list<opnode_t>;
// using exdata_t  = std::shared_ptr<opchain_t>;
// 
