/*!
 * \file TmetaTypelist.cpp
 *
 * Copyright (C) 2018 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <utl/meta/meta.h>
#include <gtest/gtest.h>
#include <type_traits>

namespace TmetaTypelist {
   using namespace utl;
   using namespace meta;

   /*
    * Types to behave like Fixtures
    */
   // Test type_of fixture
   template<class T> struct Identity {
      using type = T;
   };
   template<class T1, class T2> struct MfunBin {
      using type = int;
   };
   template<int a, int b> struct MfunBin_i {
      using type = int;
   };
   template<class T1> struct MfunUn1 {
      using type = int;
   };
   template<class T1> struct MfunUn2 {
      using type = int;
   };

   template <typename T> struct Pred_isInt {
      using type = std::is_integral<T>;
   };

   template <typename T> struct Pred_isVoid {
      using type = std::is_void<T>;
   };


   /*
    * Test high order metaFun tools
    */
   TEST(TmetaTypelist, Invoke) {
      using W = wrap <MfunBin>;
      using Wi = wrap_i<int, MfunBin_i>;
      using W1 = wrap<MfunUn1>;
      using W2 = wrap<MfunUn2>;
      using Q = quote<MfunBin>;
      using Qi = quote_i<int, MfunBin_i>;
      using Q1 = quote<MfunUn1>;
      using Q2 = quote<MfunUn2>;

      // identity
      EXPECT_EQ (true, (std::is_same<int, eval<identity<int>>>()));
      EXPECT_EQ (true, (std::is_same<int, identity_t<int>>()));
      EXPECT_EQ (true, (std::is_same<void*, identity_t<void*>>()));

      // invoke, check that invoke un-wraps and un-quotes staff
      EXPECT_EQ (true,  (std::is_same< invoke<wrap<MfunBin>, int, char>, MfunBin<int, char> >()));
      EXPECT_EQ (true,  (std::is_same< invoke<quote<MfunBin>, int, char>, MfunBin<int, char> >()));
      EXPECT_EQ (true,  (std::is_same< invoke<wrap_i<int, MfunBin_i>, int_<7>, int_<42>>, MfunBin_i<7, 42> >()));
      EXPECT_EQ (true,  (std::is_same< invoke<quote_i<int, MfunBin_i>, int_<7>, int_<42>>, MfunBin_i<7, 42> >()));

      // Wrap
      EXPECT_EQ (true,  (std::is_same< wrap<MfunBin>::template apply<int, char>, MfunBin<int, char> >()));
      EXPECT_EQ (false, (std::is_same< wrap<MfunBin>::template apply<int, char>, MfunBin<int, int>  >()));
      EXPECT_EQ (true,  (std::is_same< wrap_i<int, MfunBin_i>::template apply<int_<7>, int_<42>>, MfunBin_i<7, 42> >()));
      EXPECT_EQ (false, (std::is_same< wrap_i<int, MfunBin_i>::template apply<int_<7>, int_<42>>, MfunBin_i<42, 7> >()));

      // applicable trait
      EXPECT_EQ (true,  (is_applicable_t<MfunBin, int, long>()));
      EXPECT_EQ (false, (is_applicable_t<MfunBin, int>()));

      EXPECT_EQ (true,  (is_applicable_qt<Q, int, long>()));
      EXPECT_EQ (false, (is_applicable_qt<Q, int>()));
      EXPECT_EQ (true,  (is_applicable_qt<W, int, long>()));
      EXPECT_EQ (false, (is_applicable_qt<W, int>()));

      EXPECT_EQ (true,  (is_applicable_it<int, MfunBin_i, 7, 42>()));
      EXPECT_EQ (false, (is_applicable_it<int, MfunBin_i, 42>()));

      // defer
      EXPECT_EQ (true, (std::is_same<defer<MfunBin, int, void>::type, MfunBin<int, void>>()));
      EXPECT_EQ (true, (std::is_same<defer<MfunBin, void>::type, nil_>()));
      EXPECT_EQ (true, (std::is_same<defer_i<int, MfunBin_i, 7, 42>::type, MfunBin_i<7, 42>>()));
      EXPECT_EQ (true, (std::is_same<defer_i<int, MfunBin_i, 7>::type, nil_>()));

      // quote
      EXPECT_EQ (true, (std::is_same< quote<MfunBin>::template apply<int, void*>, MfunBin<int, void*> >()));
      EXPECT_EQ (false,(std::is_same< quote<MfunBin>::template apply<int, void*>, MfunBin<int, int>   >()));
      EXPECT_EQ (true, (std::is_same< quote_i<int, MfunBin_i>::template apply< int_<7>, int_<42>>, MfunBin_i<7, 42> >()));
      EXPECT_EQ (false,(std::is_same< quote_i<int, MfunBin_i>::template apply< int_<7>, int_<42>>, MfunBin_i<42, 7> >()));

      // compose
      EXPECT_EQ (true, (std::is_same<invoke<compose_f<MfunUn1>, int>, MfunUn1<int>>()));
      EXPECT_EQ (true, (std::is_same<invoke<compose_f<MfunUn1, MfunUn2>, int>, MfunUn1<MfunUn2<int>>>()));

      EXPECT_EQ (true, (std::is_same<invoke<compose<W1>, int>, MfunUn1<int>>()));
      EXPECT_EQ (true, (std::is_same<invoke<compose<W1, W2>, int>, MfunUn1<MfunUn2<int>>>()));
      EXPECT_EQ (true, (std::is_same<
         invoke<compose<W1, W2, Wi>, int_<7>, int_<42>>, MfunUn1<MfunUn2<MfunBin_i<7, 42>>>
      >()));

      EXPECT_EQ (true, (std::is_same<invoke<compose<Q1>, int>, MfunUn1<int>>()));
      EXPECT_EQ (true, (std::is_same<invoke<compose<Q1, Q2>, int>, MfunUn1<MfunUn2<int>>>()));
      EXPECT_EQ (true, (std::is_same<
         invoke<compose<Q1, Q2, Qi>, int_<7>, int_<42>>, MfunUn1<MfunUn2<MfunBin_i<7, 42>>>
      >()));

      // bind
      EXPECT_EQ (true, (std::is_same<invoke<bind_front<Q, int>, long>, MfunBin<int, long>>()));
      EXPECT_EQ (true, (std::is_same<invoke<bind_back<Q, int>, long>, MfunBin<long, int>>()));

      // Check the case of ill formed parameter composition. Quote must save us
      EXPECT_EQ (true, (std::is_same< nil_, invoke<Q, int> >()));
      EXPECT_EQ (true, (std::is_same< nil_, invoke<Qi, int_<42>> >()));
      EXPECT_EQ (true, (std::is_same< MfunUn1<MfunUn2<nil_>>, invoke<compose<Q1, Q2, Qi>, int_<42>> >()));
   }

   /*
    * Test typelist
    */
   TEST(TmetaTypelist, Basics) {
      using l1 = typelist<int, int, int>;
      using l2 = typelist<int, void*, int, void*>;
      using l3 = typelist<>;

      EXPECT_EQ (true, (std::is_same<l1, typelist<int>::times<3>>()));
      EXPECT_EQ (true, (std::is_same<l2, typelist<int, void*>::times<2>>()));
      EXPECT_EQ (true, (std::is_same<l3, typelist<>::times<3>>()));
      EXPECT_EQ (true, (std::is_same<l3, typelist<int>::times<0>>()));
      EXPECT_EQ (true, (std::is_same<l3, typelist<int, void>::times<0>>()));
      EXPECT_EQ (true, (std::is_same<typelist<short, double>, pair<short, double>>()));

      EXPECT_EQ (true, (std::is_same<l1, repeat <int_<3>, int>>()));
      EXPECT_EQ (true, (std::is_same<l2, repeat_c <2, int, void*>>()));


      EXPECT_EQ (3U,   size<l1>());
      EXPECT_EQ (0U,   size<l3>());
      EXPECT_EQ (true, empty<l3>());

      // pass typelist to an invocable
      EXPECT_EQ (true, (std::is_same< apply_t<quote<MfunUn1>, typelist<int>>, MfunUn1<int> >()));
      EXPECT_EQ (true, (std::is_same< apply_t<quote<MfunBin>, typelist<int, long>>, MfunBin<int, long> >()));
   }

   TEST(TmetaTypelist, ElementAccess) {
      using l = typelist<char*, void, void*, long, double, short>;

      EXPECT_EQ (true, (std::is_same<char*, at_c<l, 0>>()));
      EXPECT_EQ (true, (std::is_same<void*, at_c<l, 2>>()));
      EXPECT_EQ (true, (std::is_same<short, at_c<l, 5>>()));
      EXPECT_EQ (true, (std::is_same<nil_,  at_c<l, 6>>()));

      EXPECT_EQ (true, (std::is_same<void, at<l, int_<1>>>()));
      EXPECT_EQ (true, (std::is_same<long, at<l, int_<3>>>()));

      EXPECT_EQ (true, (std::is_same<char*, front<l>>()));
      EXPECT_EQ (true, (std::is_same<short, back<l>>()));
   }

   TEST(TmetaTypelist, Concat) {
      using l1 = typelist<int, long, void>;
      using l2 = typelist<void*, int*>;
      using l3 = typelist<double, long double, short>;
      using l4 = typelist<>;
      using conc = typelist<int, long, void, void*, int*, double, long double, short>;

      EXPECT_EQ(true, (std::is_same<l4, cat<l4, l4>>()));
      EXPECT_EQ(true, (std::is_same<conc, cat<l1, l2, l3, l4>>()));
      EXPECT_EQ(true, (std::is_same<conc, cat<l1, l4, l2, l3>>()));
      EXPECT_EQ(true, (std::is_same<conc, cat<l4, l1, l2, l3>>()));
   }

   template<class T1, class T2> struct F {}; // binary invocable
   TEST(TmetaTypelist, Fold) {
      struct X1 {};
      struct X2 {};
      struct X3 {};
      struct X4 {};
      using Q = quote<F>;

      EXPECT_EQ(true, (std::is_same<fold<typelist<>, void, Q>, void>()));
      EXPECT_EQ(true, (std::is_same<fold<typelist<X1>, void, Q>, F<void, X1>>()));
      EXPECT_EQ(true, (std::is_same<fold<typelist<X1, X2>, void, Q>, F<F<void, X1>, X2>>()));
      EXPECT_EQ(true, (std::is_same<fold<typelist<X1, X2, X3>, void, Q>, F<F<F<void, X1>, X2>, X3>>()));
      EXPECT_EQ(true, (std::is_same<fold<typelist<X1, X2, X3, X4>, void, Q>, F<F<F<F<void, X1>, X2>, X3>, X4>>()));

      EXPECT_EQ(true, (std::is_same<rev_fold<typelist<>, void, Q>, void>()));
      EXPECT_EQ(true, (std::is_same<rev_fold<typelist<X1>, void, Q>, F<X1, void>>()));
      EXPECT_EQ(true, (std::is_same<rev_fold<typelist<X1, X2>, void, Q>, F<X1, F<X2, void>>>()));
      EXPECT_EQ(true, (std::is_same<rev_fold<typelist<X1, X2, X3>, void, Q>, F<X1, F<X2, F<X3, void>>>>()));
      EXPECT_EQ(true, (std::is_same<rev_fold<typelist<X1, X2, X3, X4>, void, Q>, F<X1, F<X2, F<X3, F<X4, void>>>>>()));
   }

   TEST(TmetaTypelist, PushPopReverse) {
      using list   = typelist            <int, long, void>;
      using l_char = typelist            <int, long, void, char>;
      using l_cc   = typelist            <int, long, void, char, char>;
      using char_l = typelist      <char, int, long, void>;
      using cc_l   = typelist<char, char, int, long, void>;
      using rev    = typelist<void, long, int>;

      EXPECT_EQ (true, (std::is_same< char_l, push_front<list, char> >()));
      EXPECT_EQ (true, (std::is_same< cc_l,   push_front<list, char, char> >()));
      EXPECT_EQ (true, (std::is_same< list,   pop_front <char_l> >()));
      EXPECT_EQ (true, (std::is_same< l_char, push_back <list, char> >()));
      EXPECT_EQ (true, (std::is_same< l_cc,   push_back <list, char, char> >()));
      EXPECT_EQ (true, (std::is_same< list,   pop_back  <l_char> >()));

      EXPECT_EQ (true, (std::is_same< rev, reverse <list> >()));
   }

   TEST(TmetaTypelist, Transform) {
      using QBin = quote<MfunBin>;  // both metafuctions return int
      using QUn  = quote<MfunUn1>;

      using l1 = typelist<char, int, float>;
      using l2 = typelist<void, void, void>;
      using r  = typelist<int, int, int>;
      using r_ulazy = typelist <MfunUn1<char>, MfunUn1<int>, MfunUn1<float>>;
      using r_blazy = typelist <MfunBin<char, void>, MfunBin<int, void>, MfunBin<float, void>>;

      EXPECT_EQ (true, (std::is_same<r, transform<l1, QUn>>()));
      EXPECT_EQ (true, (std::is_same<r, transform<l1, l2, QBin>>()));

      EXPECT_EQ (true, (std::is_same<r_ulazy, transform_lazy<l1, QUn>>()));
      EXPECT_EQ (true, (std::is_same<r_blazy, transform_lazy<l1, l2, QBin>>()));
   }


   TEST(TmetaTypelist, Find) {
      using l1 = typelist <int, char, long, float>;
      using l2 = typelist <char, long, float>;
      using l3 = typelist <long, float>;
      using empty = typelist<>;

      EXPECT_EQ(true, (std::is_same<index_<1>, find_if<l1, same_as<char>>>()));
      EXPECT_EQ(true, (std::is_same<Npos, find_if<empty, same_as<char>>>()));
      EXPECT_EQ(true, (std::is_same<Npos, find_if<l1, same_as<double>>>()));

      EXPECT_EQ(true, (std::is_same<index_<2>, find<l1, long>>()));

      EXPECT_EQ(true, (std::is_same<l2, seek_if<l1, same_as<char>>>()));
      EXPECT_EQ(true, (std::is_same<empty, seek_if<empty, same_as<char>>>()));
      EXPECT_EQ(true, (std::is_same<empty, seek_if<l1, same_as<double>>>()));

      EXPECT_EQ(true, (std::is_same<l3, seek<l1, long>>()));
   }

   TEST(TmetaTypelist, Count) {
      using list = typelist<int, void*, char, int, long*, char, int, short>;
      using empty = typelist<>;

      EXPECT_EQ (true, (std::is_same<size_<3>, count_if<list, same_as<int>>>()));
      EXPECT_EQ (true, (std::is_same<size_<2>, count_if<list, same_as<char>>>()));
      EXPECT_EQ (true, (std::is_same<size_<0>, count_if<list, same_as<double>>>()));
      EXPECT_EQ (true, (std::is_same<size_<0>, count_if<empty, int>>()));

      EXPECT_EQ (true, (std::is_same<size_<1>, count<list, void*>>()));
   }

   TEST(TmetaTypelist, Filter) {
      using Q1 = quote<Pred_isInt>;
      using Q2 = quote<Pred_isVoid>;
      using list     = typelist<int, float, char, long*, short, double, void*>;
      using filtered = typelist<int, char, short>;

      EXPECT_EQ (true, (std::is_same<filtered,   filter<list, Q1>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, filter<typelist<>, Q1>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, filter<list, Q2>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, filter<typelist<>, Q1>>()));
   }

   TEST(TmetaTypelist, Replace) {
      using Q = quote<Pred_isInt>;
      using list = typelist<int, float, char, long*, short, double, void*>;
      using res  = typelist<void,float, void, long*, void,  double, void*>;
      using repl = typelist<int, float, void, long*, short, double, void*>;

      EXPECT_EQ (true, (std::is_same<res, replace_if<list, Q, void>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, replace_if<typelist<>, Q, void>>()));
      EXPECT_EQ (true, (std::is_same<res, replace_if<res, Q, void>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, replace_if<typelist<>, Q, void>>()));

      EXPECT_EQ (true, (std::is_same<repl, replace<list, char, void>>()));
      EXPECT_EQ (true, (std::is_same<typelist<>, replace<typelist<>, char, void>>()));
   }

   TEST (TmetaTypelist, AllAnyNone) {
      using l1 = typelist<int, float, char, long*, short, double, void*>;
      using l2 = typelist<int, char, long, short>;
      using l3 = typelist<>;

      EXPECT_EQ (true, (std::is_same<false_, all_of<l1, quote<Pred_isInt>>>()));
      EXPECT_EQ (true, (std::is_same<true_,  all_of<l2, quote<Pred_isInt>>>()));
      EXPECT_EQ (true, (std::is_same<false_, all_of<l3, quote<Pred_isVoid>>>()));

      EXPECT_EQ (true, (std::is_same<true_,  any_of<l1, quote<Pred_isInt>>>()));
      EXPECT_EQ (true, (std::is_same<false_, any_of<l2, quote<Pred_isVoid>>>()));
      EXPECT_EQ (true, (std::is_same<false_, any_of<l3, quote<Pred_isVoid>>>()));

      EXPECT_EQ (true, (std::is_same<true_,  none_of<l1, quote<Pred_isVoid>>>()));
      EXPECT_EQ (true, (std::is_same<false_, none_of<l1, quote<Pred_isInt>>>()));
      EXPECT_EQ (true, (std::is_same<true_,  none_of<l3, quote<Pred_isInt>>>()));
   }

}
