// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_EMULATE_CXX11_META_H
#define EIGEN_EMULATE_CXX11_META_H



namespace Eigen {

namespace internal {

/** \internal
  * \file CXX11/util/EmulateCXX11Meta.h
  * This file emulates a subset of the functionality provided by CXXMeta.h for
  * compilers that don't yet support cxx11 such as nvcc.
  */

struct empty_list
{
    static const std::size_t count = 0;
};

template<typename T, typename Tail=empty_list> struct type_list
{
    typedef T HeadType;
    typedef Tail TailType;
    static const T head;
    static const Tail tail;
    static const std::size_t count = 1 + Tail::count;
};

struct null_type { };

template<typename T1 = null_type, typename T2 = null_type, typename T3 = null_type,
         typename T4 = null_type, typename T5 = null_type, typename T6 = null_type,
         typename T7 = null_type, typename T8 = null_type>
struct make_type_list
{
    typedef typename make_type_list<T2, T3, T4, T5, T6, T7, T8>::type tailresult;

    typedef type_list<T1, tailresult> type;
};

template<> struct make_type_list<>
{
    typedef empty_list type;
};


template <std::size_t index, class TList> struct get_type;

template <class Head, class Tail>
struct get_type<0, type_list<Head, Tail> >
{
    typedef Head type;
};

template <std::size_t i, class Head, class Tail>
struct get_type<i, type_list<Head, Tail> >
{
    typedef typename get_type<i-1, Tail>::type type;
};


/* numeric list */
template <typename T, T n>
struct type2val
{
    typedef T type;
    static const T value = n;
};


template<typename T, size_t n, T V> struct gen_numeric_list_repeated;

template<typename T, T V> struct gen_numeric_list_repeated<T, 1, V>
{
    typedef typename make_type_list<type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 2, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 3, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 4, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V>, type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 5, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V>, type2val<T, V>, type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 6, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V>,
            type2val<T, V>, type2val<T, V>, type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 7, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V>,
            type2val<T, V>, type2val<T, V>, type2val<T, V>,
            type2val<T, V> >::type type;
};

template<typename T, T V> struct gen_numeric_list_repeated<T, 8, V>
{
    typedef typename make_type_list<type2val<T, V>, type2val<T, V>, type2val<T, V>,
            type2val<T, V>, type2val<T, V>, type2val<T, V>,
            type2val<T, V>, type2val<T, V> >::type type;
};


template <std::size_t index, class NList> struct get;

template <std::size_t i>
struct get<i, empty_list>
{
    get()
    {
        eigen_assert(false && "index overflow");
    }
    typedef void type;
    static const char value = '\0';
};

template <std::size_t i, class Head>
struct get<i, type_list<Head, empty_list> >
{
    get()
    {
        eigen_assert(false && "index overflow");
    }
    typedef void type;
    static const char value = '\0';
};

template <class Head>
struct get<0, type_list<Head, empty_list> >
{
    typedef typename Head::type type;
    static const type value = Head::value;
};

template <class Head, class Tail>
struct get<0, type_list<Head, Tail> >
{
    typedef typename Head::type type;
    static const type value = Head::value;
};

template <std::size_t i, class Head, class Tail>
struct get<i, type_list<Head, Tail> >
{
    typedef typename Tail::HeadType::type type;
    static const type value = get<i-1, Tail>::value;
};


template <class NList> struct arg_prod
{
    static const typename NList::HeadType::type value = get<0, NList>::value * arg_prod<typename NList::TailType>::value;
};
template <> struct arg_prod<empty_list>
{
    static const int value = 1;
};


template<int n, typename t>
array<t, n>
repeat(t v)
{
    array<t, n> array;
    array.fill(v);
    return array;
}

template<std::size_t I, class Head, class Tail>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename Head::type
array_get(type_list<Head, Tail>&)
{
    return get<I, type_list<Head, Tail> >::value;
}
template<std::size_t I, class Head, class Tail>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename Head::type
array_get(const type_list<Head, Tail>&)
{
    return get<I, type_list<Head, Tail> >::value;
}

template <class NList>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename NList::HeadType::type
array_prod(const NList&)
{
    return arg_prod<NList>::value;
}

template<typename t, std::size_t n>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE t
array_prod(const array<t, n>& a)
{
    t prod = 1;

    for (size_t i = 0; i < n; ++i)
    {
        prod *= a[i];
    }

    return prod;
}
template<typename t>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE t
array_prod(const array<t, 0>& /*a*/)
{
    return 0;
}

template<typename t>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE t
array_prod(const std::vector<t>& a)
{
    eigen_assert(a.size() > 0);
    t prod = 1;

    for (size_t i = 0; i < a.size(); ++i)
    {
        prod *= a[i];
    }

    return prod;
}


template<std::size_t I, class T>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE T&
array_get(std::vector<T>& a)
{
    return a[I];
}
template<std::size_t I, class T>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const T&
array_get(const std::vector<T>& a)
{
    return a[I];
}

struct sum_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a + b;
    }
};
struct product_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a * b;
    }
};

struct logical_and_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a && b;
    }
};
struct logical_or_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a || b;
    }
};

struct equal_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a == b;
    }
};
struct not_equal_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a != b;
    }
};
struct lesser_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a < b;
    }
};
struct lesser_equal_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a <= b;
    }
};

struct greater_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a > b;
    }
};
struct greater_equal_op
{
    template<typename A, typename B> static inline bool
    run(A a, B b)
    {
        return a >= b;
    }
};

struct not_op
{
    template<typename A> static inline bool
    run(A a)
    {
        return !a;
    }
};
struct negation_op
{
    template<typename A> static inline bool
    run(A a)
    {
        return -a;
    }
};
struct greater_equal_zero_op
{
    template<typename A> static inline bool
    run(A a)
    {
        return a >= 0;
    }
};


template<typename Reducer, typename Op, typename A, std::size_t N>
struct ArrayApplyAndReduce
{
    static inline bool
    run(const array<A, N>& a)
    {
        EIGEN_STATIC_ASSERT(N >= 2, YOU_MADE_A_PROGRAMMING_MISTAKE);
        bool result = Reducer::run(Op::run(a[0]), Op::run(a[1]));

        for (size_t i = 2; i < N; ++i)
        {
            result = Reducer::run(result, Op::run(a[i]));
        }

        return result;
    }
};

template<typename Reducer, typename Op, typename A>
struct ArrayApplyAndReduce<Reducer, Op, A, 1>
{
    static inline bool
    run(const array<A, 1>& a)
    {
        return Op::run(a[0]);
    }
};

template<typename Reducer, typename Op, typename A, std::size_t N>
inline bool
array_apply_and_reduce(const array<A, N>& a)
{
    return ArrayApplyAndReduce<Reducer, Op, A, N>::run(a);
}

template<typename Reducer, typename Op, typename A, typename B, std::size_t N>
struct ArrayZipAndReduce
{
    static inline bool
    run(const array<A, N>& a, const array<B, N>& b)
    {
        EIGEN_STATIC_ASSERT(N >= 2, YOU_MADE_A_PROGRAMMING_MISTAKE);
        bool result = Reducer::run(Op::run(a[0], b[0]), Op::run(a[1], b[1]));

        for (size_t i = 2; i < N; ++i)
        {
            result = Reducer::run(result, Op::run(a[i], b[i]));
        }

        return result;
    }
};

template<typename Reducer, typename Op, typename A, typename B>
struct ArrayZipAndReduce<Reducer, Op, A, B, 1>
{
    static inline bool
    run(const array<A, 1>& a, const array<B, 1>& b)
    {
        return Op::run(a[0], b[0]);
    }
};

template<typename Reducer, typename Op, typename A, typename B, std::size_t N>
inline bool
array_zip_and_reduce(const array<A, N>& a, const array<B, N>& b)
{
    return ArrayZipAndReduce<Reducer, Op, A, B, N>::run(a, b);
}

}  // end namespace internal

}  // end namespace Eigen



#endif  // EIGEN_EMULATE_CXX11_META_H
