#ifndef __expressions_h__
#define __expressions_h__

#include "ranges/dynamic_range.h"
#include "multi_array_base.h"
#include "multi_array_operation.h"
#include "xfor/xfor.h"

#define Y() ,

namespace MultiArrayTools
{

    //class Expressions1;

    template <class EC>
    using DDMA = MultiArrayBase<double,DynamicRange<EC>>;

    template <class EC>
    using DDMMA = MutableMultiArrayBase<double,DynamicRange<EC>>;

    template <class EC, class MA>
    using oo = decltype(std::declval<MA>()(std::declval<std::shared_ptr<DynamicIndex<EC>>>()));

    template <template <class> class OpF, class... oos>
    using OO = Operation<double,OpF<double>,oos...>;

    template <class EC, template <class> class OpF, class... MAs>
    using OX = Operation<double,OpF<double>,oo<EC,MAs>...>;
    /*
    template <class EC>
    class ECInterface
    {
    public:

        EC& THIS() { return static_cast<EC&>(*this); }
        const EC& THIS() const { return static_cast<EC const&>(*this); }
        
        template <class Expr>
        auto ifor(size_t step, Expr ex) const
            -> decltype(THIS().ifor(step, ex))
        {
            return THIS().ifor(step, ex);
        }

        template <class Expr>
        auto iforh(size_t step, Expr ex) const
            -> decltype(THIS().iforh(step, ex))
        {
            return THIS().iforh(step, ex);
        }
    };
    */
    template <class EC, class Index>
    EC makeec(const std::shared_ptr<Index>& i);

#define V_IFOR_X(Expr) \
    virtual ExpressionHolder<Expr> iforx(size_t step, Expr ex) const = 0; \
    virtual ExpressionHolder<Expr> iforhx(size_t step, Expr ex) const = 0

    template <class Index>
    class E1;

    class Expressions1 //: public ECInterface<Expressions1<X>>
    {
    public:
        typedef Expressions1 EX;
        
        template <class Index>
        static auto make(const std::shared_ptr<Index>& i)
            -> decltype(makeec<E1<Index>>(i))
        {
            return makeec<E1<Index>>(i);
        }

    private:
        V_IFOR_X(OX<EX Y() plus Y() DDMA<EX> Y() DDMA<EX>>);
        V_IFOR_X(OX<EX Y() minus Y() DDMA<EX> Y() DDMA<EX>>);
        V_IFOR_X(OX<EX Y() multiplies Y() DDMA<EX> Y() DDMA<EX>>);
        V_IFOR_X(OX<EX Y() divides Y() DDMA<EX> Y() DDMA<EX>>);

    public:
        template <class Expr>
        auto ifor(size_t step, Expr ex) const
            -> decltype(iforx(step, ex))
        {
            return iforx(step, ex);
        }

        template <class Expr>
        auto iforh(size_t step, Expr ex) const
            -> decltype(iforhx(step, ex))
        {
            return iforhx(step, ex);
        }

    };

    
#define O_IFOR_X(Expr,Ind) \
    virtual ExpressionHolder<Expr> iforx(size_t step, Expr ex) const override final \
    { return ExpressionHolder<Expr>(Ind->ifor(step, ex)); } \
    virtual ExpressionHolder<Expr> iforhx(size_t step, Expr ex) const override final \
    { return ExpressionHolder<Expr>(Ind->iforh(step, ex)); }
    
    template <class Index>
    class E1 : public Expressions1
    {
    private:

        typedef Expressions1 EX;
        
        E1() = default;
        
        std::shared_ptr<Index> mI;

        O_IFOR_X(OX<EX Y() plus Y() DDMA<EX> Y() DDMA<EX>>, mI);
        O_IFOR_X(OX<EX Y() minus Y() DDMA<EX> Y() DDMA<EX>>, mI);
        O_IFOR_X(OX<EX Y() multiplies Y() DDMA<EX> Y() DDMA<EX>>, mI);
        O_IFOR_X(OX<EX Y() divides Y() DDMA<EX> Y() DDMA<EX>>, mI);

    public:
        E1(const E1& in) = default;
        E1(E1&& in) = default;
        E1& operator=(const E1& in) = default;
        E1& operator=(E1&& in) = default;

        E1(const std::shared_ptr<Index>& i) : mI(i) {} 
    };

    template <class EC, class Index>
    EC makeec(const std::shared_ptr<Index>& i)
    {
        return EC(i);
    }


} // namespace MultiArrayTools

#endif