#ifndef FUNCTIONAL_TMP_HPP
#define FUNCTIONAL_TMP_HPP

#include <boost/mpl/if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/less.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/minus.hpp>

using namespace boost;

//  Funkcionális programozás és a template metaprogramozás

//  - Definíció: A functional programming language is a language that emphasises
//    programming with pure functions and immutable data.

//  Ezt már láttuk, hogy teljesül

//  - Dolgok, amiket illik még tudni egy funkcionális nyelvek:
//      - Mintaillesztés: már volt, láttuk, hogy van
//      - Magasabb rendű függvények: olyan függvények, amelynek (legalább) egy
//        paramétere függvény
//      - Curryzés: elmondom...
//      - Lusta kiértékelés: elmondom...

//------------------------------------------------------------------------------

//  Magasabb rendű függvények

//  A jelenlegi megoldásunk alapján nem lehetséges, mivel a template
//  metafüggvényeink jelenleg nem típusok, hanem template osztályok.

//  Függvények bedobozolása egy megoldás, ezeket template metafüggvény
//  osztálynak hívják.

//  Mivel a make_const típus, átadható egy metafüggvénynek argumentumként.
struct make_const {
    template <class T>
    struct apply {
        typedef const T type;
    };
};

make_const::apply<double>::type a = 3.14;

struct apply_twice {
    template <class Fun>
    struct apply {
        template <class A>
        struct apply {
            typedef Fun::apply<Fun::apply<A>>::type type;
        };
    };
};

// Tfh. times is így van megírva
/*
apply_twice::apply<times::apply<2>>::apply<int_<12>>::type::value == 2 * 2 * 12
*/

//------------------------------------------------------------------------------

//  Lusta kiértékelés

//  Egy template metafüggvényt akkor értékelünk ki, ha a type nevű belső típusát
//  megpróbáljuk elérni, hamarabb nem történik meg a példányosítás.
//  => Mi mondjuk meg, ez mikor történjen meg.

//  pl.: faktoriális implementálása

// Mi ezzel a baj?

/*
template <class N>
struct fact :
    mpl::if_<
        typename mpl::less<N, mpl::int_<1>>::type,
        mpl::int_<1>,
        mpl::times<
            typename fact<
                typename mpl::minus<N, mpl::int_<1>>::type
            >::type,
            N
        >
    >
{};
*/

template <class N> struct fact;

template <class N>
struct fact_impl :
    mpl::times<
        typename fact<
            typename mpl::minus<N, mpl::int_<1>>::type
        >::type, N
    >
{};

template <class N>
struct fact :
    mpl::eval_if<
        typename mpl::less<N, mpl::int_<1>>::type,
        mpl::int_<1>,
        fact_impl<N>
    >
{};

//  Ez az implementáció azt a trükköt használja, hogy amikor a rekurzió ágára
//  lépünk, akkor egy nulla paraméteres template metafüggvénnyé értékelődik ki,
//  ami a fact_impl. Ha nem lépünk, akkor pedig elvárja, hogy az int_ önmagává
//  értékelődjön ki.

//------------------------------------------------------------------------------

//  Template metaprogramozási értékek

template <class T>
struct tmp_value {
    typedef T type;
};

template <int N>
struct int_ : tmp_value<int_<N>> {
    static const int value = N;
    typedef int_<N - 1> prev;
    typedef int_<N + 1> next;
};

//  Nem akadnak össze a magasabb rendű függvényekkel

struct inc {
    typedef inc type;

    template <class A>
    struct apply {
        typedef typename A::type::next type;
    };
};

//  Ekvivalens  következővel:
struct inc2 : tmp_value<inc2> {
    template <class A>
    struct apply : A::type::next {};
};

//  Irányelvek a lustaság érdekében:
//      - Minden template metaprogramban használt érték template
//        metaprogramozási érték
//      - Minden metafüggvény egy template metaprogramozási értéket ad vissza

#endif //FUNCTIONAL_TMP_HPP
